From 44605e21fcfa7dda709024884078d2eb936eaa0e Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 28 Sep 2026 15:05:53 +0000 Subject: [PATCH 1/7] fix: make daily work snapshots consistent with PR state and totals --- drizzle/0026_brainy_nitro.sql | 33 + drizzle/meta/0026_snapshot.json | 3821 ++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/app/api/cron/github-worker/route.ts | 7 +- src/app/api/github/webhook/route.ts | 6 +- src/components/codex-stats.tsx | 2 +- src/components/github-activity-days.tsx | 96 +- src/db/schema.ts | 24 +- src/lib/github-activity-feed-core.ts | 8 +- src/lib/github-activity-snapshots.ts | 78 +- src/lib/github-activity-store.ts | 45 +- src/lib/github-activity-types.ts | 21 +- src/lib/github-commits-core.ts | 53 +- src/lib/github-commits-store.ts | 19 +- src/lib/github-cron-config.ts | 2 +- src/lib/github-pull-request-store.ts | 22 +- src/lib/github-work-unit-core.ts | 59 +- src/lib/github-work-unit-projection-state.ts | 3 +- src/lib/github-work-unit-store.ts | 35 + tests/daily-history.test.ts | 56 +- tests/github-activity-days.test.tsx | 61 + tests/github-activity-feed-core.test.ts | 2 +- tests/github-commits.test.ts | 61 +- tests/github-work-unit-core.test.ts | 122 +- tests/github-work-unit-crosswalk.test.ts | 3 + tests/hourly-publication-resilience.test.ts | 18 +- 26 files changed, 4567 insertions(+), 97 deletions(-) create mode 100644 drizzle/0026_brainy_nitro.sql create mode 100644 drizzle/meta/0026_snapshot.json diff --git a/drizzle/0026_brainy_nitro.sql b/drizzle/0026_brainy_nitro.sql new file mode 100644 index 00000000..e281aa38 --- /dev/null +++ b/drizzle/0026_brainy_nitro.sql @@ -0,0 +1,33 @@ +ALTER TABLE "github_work_units" DROP CONSTRAINT "gh_work_units_activity_order";--> statement-breakpoint +ALTER TABLE "github_issues" ADD COLUMN "status" varchar(16) DEFAULT 'open' NOT NULL;--> statement-breakpoint +ALTER TABLE "github_issues" ADD COLUMN "activity_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "github_issues" ADD COLUMN "provider_updated_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "github_pull_requests" ADD COLUMN "status_changed_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "github_work_units" ADD COLUMN "pull_request" jsonb;--> statement-breakpoint +ALTER TABLE "github_issues" ADD CONSTRAINT "github_issues_status" CHECK ("github_issues"."status" IN ('open', 'closed', 'completed', 'not-planned'));--> statement-breakpoint +-- Rebuild direct-day groups using the display timezone; saved history stays intact. +DELETE FROM github_work_units WHERE kind = 'canonical_day'; +--> statement-breakpoint +UPDATE github_work_units SET activity_day = (activity_at AT TIME ZONE 'Asia/Kolkata')::date; +--> statement-breakpoint +ALTER TABLE "github_work_units" ADD CONSTRAINT "gh_work_units_activity_order" CHECK ("github_work_units"."first_activity_at" <= "github_work_units"."last_activity_at" AND "github_work_units"."activity_day" = ("github_work_units"."activity_at" AT TIME ZONE 'Asia/Kolkata')::date); +--> statement-breakpoint +CREATE OR REPLACE FUNCTION protect_github_activity_history() RETURNS trigger +LANGUAGE plpgsql SET search_path = public AS $$ +BEGIN + IF OLD.day < ((clock_timestamp() - interval '1 hour') AT TIME ZONE 'Asia/Kolkata')::date THEN + IF TG_OP = 'DELETE' THEN RAISE EXCEPTION 'Closed activity cannot be deleted'; END IF; + IF NEW.day <> OLD.day OR NEW.identity_key <> OLD.identity_key OR NEW.repository_id <> OLD.repository_id + OR (NEW.payload - 'headline' - 'summary' - 'summarizing') IS DISTINCT FROM (OLD.payload - 'headline' - 'summary' - 'summarizing') + OR (OLD.payload->>'headline' IS NOT NULL AND NEW.payload IS DISTINCT FROM OLD.payload) + THEN RAISE EXCEPTION 'Closed activity facts are immutable'; END IF; + END IF; + IF TG_OP = 'DELETE' THEN RETURN OLD; END IF; + RETURN NEW; +END; +$$; + +--> statement-breakpoint +UPDATE github_repositories SET projection_request_token = gen_random_uuid(); +--> statement-breakpoint +UPDATE github_public_feed_head SET projection_request_token = gen_random_uuid(); diff --git a/drizzle/meta/0026_snapshot.json b/drizzle/meta/0026_snapshot.json new file mode 100644 index 00000000..4f465812 --- /dev/null +++ b/drizzle/meta/0026_snapshot.json @@ -0,0 +1,3821 @@ +{ + "id": "33551e50-4d3b-4a14-9335-e4ea622e3907", + "prevId": "915b1359-e7dc-48fb-b27e-1b58468115f9", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.codex_accounts": { + "name": "codex_accounts", + "schema": "", + "columns": { + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sync_token": { + "name": "sync_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "sync_until": { + "name": "sync_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "snapshot_at": { + "name": "snapshot_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "codex_accounts_provider_account_id_unique": { + "name": "codex_accounts_provider_account_id_unique", + "nullsNotDistinct": false, + "columns": ["provider_account_id"] + } + }, + "policies": {}, + "checkConstraints": { + "codex_accounts_id_shape": { + "name": "codex_accounts_id_shape", + "value": "\"codex_accounts\".\"id\" ~ '^[a-z0-9][a-z0-9_-]{0,63}$'" + }, + "codex_accounts_snapshot_pair": { + "name": "codex_accounts_snapshot_pair", + "value": "(\"codex_accounts\".\"snapshot\" IS NULL) = (\"codex_accounts\".\"snapshot_at\" IS NULL)" + }, + "codex_accounts_snapshot_object": { + "name": "codex_accounts_snapshot_object", + "value": "\"codex_accounts\".\"snapshot\" IS NULL OR jsonb_typeof(\"codex_accounts\".\"snapshot\") = 'object'" + } + }, + "isRLSEnabled": true + }, + "public.codex_usage_days": { + "name": "codex_usage_days", + "schema": "", + "columns": { + "account_id": { + "name": "account_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "day": { + "name": "day", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "codex_usage_days_account_id_codex_accounts_id_fk": { + "name": "codex_usage_days_account_id_codex_accounts_id_fk", + "tableFrom": "codex_usage_days", + "tableTo": "codex_accounts", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "codex_usage_days_account_id_day_pk": { + "name": "codex_usage_days_account_id_day_pk", + "columns": ["account_id", "day"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.github_account_checkpoints": { + "name": "github_account_checkpoints", + "schema": "", + "columns": { + "account": { + "name": "account", + "type": "varchar(39)", + "primaryKey": true, + "notNull": true + }, + "events_etag": { + "name": "events_etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "events_last_attempted_at": { + "name": "events_last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "events_last_succeeded_at": { + "name": "events_last_succeeded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "events_next_poll_at": { + "name": "events_next_poll_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "gap_detected_at": { + "name": "gap_detected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "gap_expected_event_id": { + "name": "gap_expected_event_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "gap_oldest_available_event_id": { + "name": "gap_oldest_available_event_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "gap_state": { + "name": "gap_state", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true, + "default": "'clear'" + }, + "latest_event_id": { + "name": "latest_event_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "paused": { + "name": "paused", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pull_request_backfill_digest": { + "name": "pull_request_backfill_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "head_ref_cursor_repository_id": { + "name": "head_ref_cursor_repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "head_ref_cycle_started_at": { + "name": "head_ref_cycle_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "head_ref_last_attempted_at": { + "name": "head_ref_last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "head_ref_last_succeeded_at": { + "name": "head_ref_last_succeeded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "head_ref_lease_token": { + "name": "head_ref_lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "head_ref_lease_until": { + "name": "head_ref_lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "head_ref_next_page": { + "name": "head_ref_next_page", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "head_ref_scan_started_at": { + "name": "head_ref_scan_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ref_backfill_since_at": { + "name": "ref_backfill_since_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "tag_ref_cursor_repository_id": { + "name": "tag_ref_cursor_repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "tag_ref_cycle_started_at": { + "name": "tag_ref_cycle_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "tag_ref_last_attempted_at": { + "name": "tag_ref_last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "tag_ref_last_succeeded_at": { + "name": "tag_ref_last_succeeded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "tag_ref_lease_token": { + "name": "tag_ref_lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tag_ref_lease_until": { + "name": "tag_ref_lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "tag_ref_next_page": { + "name": "tag_ref_next_page", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tag_ref_scan_started_at": { + "name": "tag_ref_scan_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_account_checkpoints_tracked_account": { + "name": "github_account_checkpoints_tracked_account", + "value": "\"github_account_checkpoints\".\"account\" ~ '^[a-z0-9]([a-z0-9-]{0,37}[a-z0-9])?$'" + }, + "github_account_checkpoints_event_id_shape": { + "name": "github_account_checkpoints_event_id_shape", + "value": "(\"github_account_checkpoints\".\"latest_event_id\" IS NULL OR \"github_account_checkpoints\".\"latest_event_id\" ~ '^[0-9]{1,64}$') AND (\"github_account_checkpoints\".\"gap_expected_event_id\" IS NULL OR \"github_account_checkpoints\".\"gap_expected_event_id\" ~ '^[0-9]{1,64}$') AND (\"github_account_checkpoints\".\"gap_oldest_available_event_id\" IS NULL OR \"github_account_checkpoints\".\"gap_oldest_available_event_id\" ~ '^[0-9]{1,64}$')" + }, + "github_account_checkpoints_gap_state": { + "name": "github_account_checkpoints_gap_state", + "value": "\"github_account_checkpoints\".\"gap_state\" IN ('clear', 'detected')" + }, + "github_account_checkpoints_gap_details": { + "name": "github_account_checkpoints_gap_details", + "value": "(\"github_account_checkpoints\".\"gap_state\" = 'detected' AND \"github_account_checkpoints\".\"gap_detected_at\" IS NOT NULL) OR (\"github_account_checkpoints\".\"gap_state\" = 'clear' AND \"github_account_checkpoints\".\"gap_detected_at\" IS NULL AND \"github_account_checkpoints\".\"gap_expected_event_id\" IS NULL AND \"github_account_checkpoints\".\"gap_oldest_available_event_id\" IS NULL)" + }, + "github_account_checkpoints_ref_cursor_shape": { + "name": "github_account_checkpoints_ref_cursor_shape", + "value": "(\"github_account_checkpoints\".\"head_ref_cursor_repository_id\" IS NULL OR \"github_account_checkpoints\".\"head_ref_cursor_repository_id\" ~ '^[0-9]{1,32}$') AND (\"github_account_checkpoints\".\"tag_ref_cursor_repository_id\" IS NULL OR \"github_account_checkpoints\".\"tag_ref_cursor_repository_id\" ~ '^[0-9]{1,32}$')" + }, + "github_account_checkpoints_ref_leases": { + "name": "github_account_checkpoints_ref_leases", + "value": "(\"github_account_checkpoints\".\"head_ref_lease_token\" IS NULL) = (\"github_account_checkpoints\".\"head_ref_lease_until\" IS NULL) AND (\"github_account_checkpoints\".\"tag_ref_lease_token\" IS NULL) = (\"github_account_checkpoints\".\"tag_ref_lease_until\" IS NULL)" + }, + "github_account_checkpoints_ref_scans": { + "name": "github_account_checkpoints_ref_scans", + "value": "(\"github_account_checkpoints\".\"head_ref_next_page\" IS NULL AND \"github_account_checkpoints\".\"head_ref_scan_started_at\" IS NULL OR \"github_account_checkpoints\".\"head_ref_next_page\" >= 2 AND \"github_account_checkpoints\".\"head_ref_scan_started_at\" IS NOT NULL) AND (\"github_account_checkpoints\".\"tag_ref_next_page\" IS NULL AND \"github_account_checkpoints\".\"tag_ref_scan_started_at\" IS NULL OR \"github_account_checkpoints\".\"tag_ref_next_page\" >= 2 AND \"github_account_checkpoints\".\"tag_ref_scan_started_at\" IS NOT NULL)" + }, + "github_account_checkpoints_pr_backfill_digest": { + "name": "github_account_checkpoints_pr_backfill_digest", + "value": "\"github_account_checkpoints\".\"pull_request_backfill_digest\" IS NULL OR \"github_account_checkpoints\".\"pull_request_backfill_digest\" ~ '^[a-f0-9]{64}$'" + } + }, + "isRLSEnabled": true + }, + "public.github_account_repository_catalogs": { + "name": "github_account_repository_catalogs", + "schema": "", + "columns": { + "account_user_id": { + "name": "account_user_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "active_access": { + "name": "active_access", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inventory_generation": { + "name": "inventory_generation", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "gh_account_repo_catalogs_current_idx": { + "name": "gh_account_repo_catalogs_current_idx", + "columns": [ + { + "expression": "account_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "inventory_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "active_access", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "gh_account_repo_catalogs_account_fk": { + "name": "gh_account_repo_catalogs_account_fk", + "tableFrom": "github_account_repository_catalogs", + "tableTo": "github_repository_inventory_heads", + "columnsFrom": ["account_user_id"], + "columnsTo": ["account_user_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "gh_account_repo_catalogs_repository_fk": { + "name": "gh_account_repo_catalogs_repository_fk", + "tableFrom": "github_account_repository_catalogs", + "tableTo": "github_repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "gh_account_repo_catalogs_pk": { + "name": "gh_account_repo_catalogs_pk", + "columns": ["account_user_id", "repository_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "gh_account_repo_catalogs_generation": { + "name": "gh_account_repo_catalogs_generation", + "value": "\"github_account_repository_catalogs\".\"inventory_generation\" > 0" + } + }, + "isRLSEnabled": true + }, + "public.github_activity_snapshots": { + "name": "github_activity_snapshots", + "schema": "", + "columns": { + "day": { + "name": "day", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "identity_key": { + "name": "identity_key", + "type": "varchar(180)", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "work_unit_id": { + "name": "work_unit_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "work_unit_revision": { + "name": "work_unit_revision", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attribution_mode": { + "name": "attribution_mode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "facts_digest": { + "name": "facts_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "outcome_digest": { + "name": "outcome_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "summary_input_digest": { + "name": "summary_input_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_activity_snapshots_latest_idx": { + "name": "github_activity_snapshots_latest_idx", + "columns": [ + { + "expression": "identity_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "day", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_activity_snapshots_repository_idx": { + "name": "github_activity_snapshots_repository_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "github_activity_snapshots_day_identity_key_pk": { + "name": "github_activity_snapshots_day_identity_key_pk", + "columns": ["day", "identity_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.github_commit_pull_request_associations": { + "name": "github_commit_pull_request_associations", + "schema": "", + "columns": { + "commit_repository_id": { + "name": "commit_repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "commit_sha": { + "name": "commit_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "pull_request_node_id": { + "name": "pull_request_node_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gh_commit_pr_associations_commit_fk": { + "name": "gh_commit_pr_associations_commit_fk", + "tableFrom": "github_commit_pull_request_associations", + "tableTo": "github_commits", + "columnsFrom": ["commit_repository_id", "commit_sha"], + "columnsTo": ["repository_id", "sha"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "gh_commit_pr_associations_pr_fk": { + "name": "gh_commit_pr_associations_pr_fk", + "tableFrom": "github_commit_pull_request_associations", + "tableTo": "github_pull_requests", + "columnsFrom": ["pull_request_node_id"], + "columnsTo": ["node_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "gh_commit_pr_associations_pk": { + "name": "gh_commit_pr_associations_pk", + "columns": [ + "commit_repository_id", + "commit_sha", + "pull_request_node_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "gh_commit_pr_associations_sha_shape": { + "name": "gh_commit_pr_associations_sha_shape", + "value": "\"github_commit_pull_request_associations\".\"commit_sha\" ~ '^[a-f0-9]{40}$'" + } + }, + "isRLSEnabled": true + }, + "public.github_commits": { + "name": "github_commits", + "schema": "", + "columns": { + "additions": { + "name": "additions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "author_login": { + "name": "author_login", + "type": "varchar(39)", + "primaryKey": false, + "notNull": true + }, + "authored_at": { + "name": "authored_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "changed_files": { + "name": "changed_files", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "committed_at": { + "name": "committed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "committer_at": { + "name": "committer_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "committer_user_id": { + "name": "committer_user_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "deletions": { + "name": "deletions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enrichment_error": { + "name": "enrichment_error", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "enrichment_attempts": { + "name": "enrichment_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "enrichment_lease_token": { + "name": "enrichment_lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "enrichment_lease_until": { + "name": "enrichment_lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "enrichment_state": { + "name": "enrichment_state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "file_facts": { + "name": "file_facts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "file_facts_digest": { + "name": "file_facts_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "file_facts_pruned_at": { + "name": "file_facts_pruned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "file_facts_complete": { + "name": "file_facts_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "first_observed_at": { + "name": "first_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_shas": { + "name": "parent_shas", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "provider_file_cap_reached": { + "name": "provider_file_cap_reached", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pr_discovery_error": { + "name": "pr_discovery_error", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "pr_discovery_attempts": { + "name": "pr_discovery_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pr_discovery_lease_token": { + "name": "pr_discovery_lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pr_discovery_lease_until": { + "name": "pr_discovery_lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "pr_discovery_state": { + "name": "pr_discovery_state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "sha": { + "name": "sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "github_commits_committed_at_idx": { + "name": "github_commits_committed_at_idx", + "columns": [ + { + "expression": "committed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_commits_enrichment_pending_idx": { + "name": "github_commits_enrichment_pending_idx", + "columns": [ + { + "expression": "enrichment_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enrichment_lease_until", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "committed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_commits_pr_discovery_pending_idx": { + "name": "github_commits_pr_discovery_pending_idx", + "columns": [ + { + "expression": "pr_discovery_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_discovery_lease_until", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "first_observed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_commits_repository_fk": { + "name": "github_commits_repository_fk", + "tableFrom": "github_commits", + "tableTo": "github_repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "github_commits_repository_id_sha_pk": { + "name": "github_commits_repository_id_sha_pk", + "columns": ["repository_id", "sha"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_commits_sha_shape": { + "name": "github_commits_sha_shape", + "value": "\"github_commits\".\"sha\" ~ '^[a-f0-9]{40}$'" + }, + "github_commits_parent_shas_array": { + "name": "github_commits_parent_shas_array", + "value": "\"github_commits\".\"parent_shas\" IS NULL OR jsonb_typeof(\"github_commits\".\"parent_shas\") = 'array'" + }, + "github_commits_file_facts_array": { + "name": "github_commits_file_facts_array", + "value": "\"github_commits\".\"file_facts\" IS NULL OR jsonb_typeof(\"github_commits\".\"file_facts\") = 'array'" + }, + "github_commits_file_facts_completeness": { + "name": "github_commits_file_facts_completeness", + "value": "NOT \"github_commits\".\"file_facts_complete\" OR (\"github_commits\".\"file_facts\" IS NOT NULL AND NOT \"github_commits\".\"provider_file_cap_reached\")" + }, + "github_commits_enrichment_state": { + "name": "github_commits_enrichment_state", + "value": "\"github_commits\".\"enrichment_state\" IN ('pending', 'processing', 'complete', 'unavailable')" + }, + "github_commits_enrichment_lease": { + "name": "github_commits_enrichment_lease", + "value": "(\"github_commits\".\"enrichment_state\" = 'processing') = (\"github_commits\".\"enrichment_lease_token\" IS NOT NULL) AND (\"github_commits\".\"enrichment_state\" <> 'processing' OR \"github_commits\".\"enrichment_lease_until\" IS NOT NULL) AND (\"github_commits\".\"enrichment_state\" NOT IN ('complete', 'unavailable') OR \"github_commits\".\"enrichment_lease_until\" IS NULL)" + }, + "github_commits_pr_discovery_state": { + "name": "github_commits_pr_discovery_state", + "value": "\"github_commits\".\"pr_discovery_state\" IN ('pending', 'processing', 'complete', 'unavailable')" + }, + "github_commits_pr_discovery_lease": { + "name": "github_commits_pr_discovery_lease", + "value": "(\"github_commits\".\"pr_discovery_state\" = 'processing') = (\"github_commits\".\"pr_discovery_lease_token\" IS NOT NULL) AND (\"github_commits\".\"pr_discovery_state\" <> 'processing' OR \"github_commits\".\"pr_discovery_lease_until\" IS NOT NULL) AND (\"github_commits\".\"pr_discovery_state\" NOT IN ('complete', 'unavailable') OR \"github_commits\".\"pr_discovery_lease_until\" IS NULL)" + }, + "github_commits_tracked_author": { + "name": "github_commits_tracked_author", + "value": "\"github_commits\".\"author_login\" ~ '^[a-z0-9]([a-z0-9-]{0,37}[a-z0-9])?$'" + }, + "github_commits_nonnegative_activity_counts": { + "name": "github_commits_nonnegative_activity_counts", + "value": "(\"github_commits\".\"changed_files\" IS NULL OR \"github_commits\".\"changed_files\" >= 0) AND (\"github_commits\".\"additions\" IS NULL OR \"github_commits\".\"additions\" >= 0) AND (\"github_commits\".\"deletions\" IS NULL OR \"github_commits\".\"deletions\" >= 0)" + }, + "github_commits_nonnegative_attempts": { + "name": "github_commits_nonnegative_attempts", + "value": "\"github_commits\".\"enrichment_attempts\" >= 0 AND \"github_commits\".\"pr_discovery_attempts\" >= 0" + } + }, + "isRLSEnabled": true + }, + "public.github_issues": { + "name": "github_issues", + "schema": "", + "columns": { + "account": { + "name": "account", + "type": "varchar(39)", + "primaryKey": false, + "notNull": true + }, + "author_login": { + "name": "author_login", + "type": "varchar(39)", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "activity_at": { + "name": "activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "provider_updated_at": { + "name": "provider_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "first_observed_at": { + "name": "first_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "node_id": { + "name": "node_id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "title_snapshot": { + "name": "title_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url_snapshot": { + "name": "url_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "github_issues_repository_number_unique": { + "name": "github_issues_repository_number_unique", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_issues_author_idx": { + "name": "github_issues_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_issues_repository_id_github_repositories_id_fk": { + "name": "github_issues_repository_id_github_repositories_id_fk", + "tableFrom": "github_issues", + "tableTo": "github_repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_issues_tracked_account": { + "name": "github_issues_tracked_account", + "value": "\"github_issues\".\"account\" ~ '^[a-z0-9]([a-z0-9-]{0,37}[a-z0-9])?$'" + }, + "github_issues_status": { + "name": "github_issues_status", + "value": "\"github_issues\".\"status\" IN ('open', 'closed', 'completed', 'not-planned')" + }, + "github_issues_positive_number": { + "name": "github_issues_positive_number", + "value": "\"github_issues\".\"number\" > 0" + } + }, + "isRLSEnabled": true + }, + "public.github_public_feed_head": { + "name": "github_public_feed_head", + "schema": "", + "columns": { + "history_initialized_at": { + "name": "history_initialized_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feed_revision": { + "name": "feed_revision", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "head_content_revision": { + "name": "head_content_revision", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "id": { + "name": "id", + "type": "boolean", + "primaryKey": true, + "notNull": true, + "default": true + }, + "last_published_at": { + "name": "last_published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ordering_revision": { + "name": "ordering_revision", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "projection_request_token": { + "name": "projection_request_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "summary_policy_digest": { + "name": "summary_policy_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "summarizing": { + "name": "summarizing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "gh_public_feed_head_singleton": { + "name": "gh_public_feed_head_singleton", + "value": "\"github_public_feed_head\".\"id\"" + }, + "gh_public_feed_head_revisions": { + "name": "gh_public_feed_head_revisions", + "value": "\"github_public_feed_head\".\"feed_revision\" >= 0 AND \"github_public_feed_head\".\"head_content_revision\" >= 0 AND \"github_public_feed_head\".\"ordering_revision\" >= 0" + }, + "gh_public_feed_head_summary_policy_digest": { + "name": "gh_public_feed_head_summary_policy_digest", + "value": "\"github_public_feed_head\".\"summary_policy_digest\" IS NULL OR \"github_public_feed_head\".\"summary_policy_digest\" ~ '^[a-f0-9]{64}$'" + } + }, + "isRLSEnabled": true + }, + "public.github_pull_request_memberships": { + "name": "github_pull_request_memberships", + "schema": "", + "columns": { + "commit_repository_id": { + "name": "commit_repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "commit_sha": { + "name": "commit_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "is_head": { + "name": "is_head", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version_id": { + "name": "version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "github_pull_request_memberships_position_unique": { + "name": "github_pull_request_memberships_position_unique", + "columns": [ + { + "expression": "version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_request_memberships_commit_lookup_idx": { + "name": "github_pull_request_memberships_commit_lookup_idx", + "columns": [ + { + "expression": "commit_repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "commit_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "gh_pr_memberships_version_fk": { + "name": "gh_pr_memberships_version_fk", + "tableFrom": "github_pull_request_memberships", + "tableTo": "github_pull_requests", + "columnsFrom": ["version_id"], + "columnsTo": ["snapshot_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "gh_pr_memberships_pk": { + "name": "gh_pr_memberships_pk", + "columns": ["version_id", "commit_repository_id", "commit_sha"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_pull_request_memberships_sha_shape": { + "name": "github_pull_request_memberships_sha_shape", + "value": "\"github_pull_request_memberships\".\"commit_sha\" ~ '^[a-f0-9]{40}$'" + }, + "github_pull_request_memberships_nonnegative_position": { + "name": "github_pull_request_memberships_nonnegative_position", + "value": "\"github_pull_request_memberships\".\"position\" >= 0" + } + }, + "isRLSEnabled": true + }, + "public.github_pull_request_signals": { + "name": "github_pull_request_signals", + "schema": "", + "columns": { + "account": { + "name": "account", + "type": "varchar(39)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "event_id": { + "name": "event_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "repository_name_snapshot": { + "name": "repository_name_snapshot", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + } + }, + "indexes": { + "github_pull_request_signals_event_unique": { + "name": "github_pull_request_signals_event_unique", + "columns": [ + { + "expression": "account", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_request_signals_pending_idx": { + "name": "github_pull_request_signals_pending_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lease_until", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "observed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_pull_request_signals_account": { + "name": "github_pull_request_signals_account", + "value": "\"github_pull_request_signals\".\"account\" ~ '^[a-z0-9]([a-z0-9-]{0,37}[a-z0-9])?$'" + }, + "github_pull_request_signals_event_id": { + "name": "github_pull_request_signals_event_id", + "value": "\"github_pull_request_signals\".\"event_id\" ~ '^[0-9]{1,64}$'" + }, + "github_pull_request_signals_repository_id": { + "name": "github_pull_request_signals_repository_id", + "value": "\"github_pull_request_signals\".\"repository_id\" ~ '^[0-9]{1,32}$'" + }, + "github_pull_request_signals_state": { + "name": "github_pull_request_signals_state", + "value": "\"github_pull_request_signals\".\"state\" IN ('pending', 'processing', 'complete', 'unavailable')" + }, + "github_pull_request_signals_lease": { + "name": "github_pull_request_signals_lease", + "value": "(\"github_pull_request_signals\".\"state\" = 'processing') = (\"github_pull_request_signals\".\"lease_token\" IS NOT NULL) AND (\"github_pull_request_signals\".\"state\" <> 'processing' OR \"github_pull_request_signals\".\"lease_until\" IS NOT NULL)" + }, + "github_pull_request_signals_values": { + "name": "github_pull_request_signals_values", + "value": "\"github_pull_request_signals\".\"number\" > 0 AND \"github_pull_request_signals\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": true + }, + "public.github_pull_requests": { + "name": "github_pull_requests", + "schema": "", + "columns": { + "account": { + "name": "account", + "type": "varchar(39)", + "primaryKey": false, + "notNull": true + }, + "additions": { + "name": "additions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "author_login": { + "name": "author_login", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "base_ref_name": { + "name": "base_ref_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_repository_id": { + "name": "base_repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "base_sha": { + "name": "base_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_snapshot": { + "name": "body_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "changed_files": { + "name": "changed_files", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "commit_count": { + "name": "commit_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "deletions": { + "name": "deletions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "file_facts": { + "name": "file_facts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "file_facts_digest": { + "name": "file_facts_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "CASE WHEN \"file_facts\" IS NULL THEN NULL ELSE encode(sha256(jsonb_send(\"file_facts\")), 'hex') END", + "type": "stored" + } + }, + "file_facts_complete": { + "name": "file_facts_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "membership_complete": { + "name": "membership_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "merge_snapshot": { + "name": "merge_snapshot", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "snapshot_id": { + "name": "snapshot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "snapshot_observed_at": { + "name": "snapshot_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status_changed_at": { + "name": "status_changed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "draft": { + "name": "draft", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "first_observed_at": { + "name": "first_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "head_ref_name": { + "name": "head_ref_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "head_repository_id": { + "name": "head_repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "head_sha": { + "name": "head_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": false + }, + "last_reconciled_at": { + "name": "last_reconciled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "merged_at": { + "name": "merged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "merge_sha": { + "name": "merge_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": false + }, + "merge_sha_verified_at": { + "name": "merge_sha_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_reconcile_at": { + "name": "next_reconcile_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "node_id": { + "name": "node_id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_updated_at": { + "name": "provider_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "reconcile_attempts": { + "name": "reconcile_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reconcile_error": { + "name": "reconcile_error", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title_snapshot": { + "name": "title_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "github_pull_requests_snapshot_unique": { + "name": "github_pull_requests_snapshot_unique", + "columns": [ + { + "expression": "snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_repository_number_unique": { + "name": "github_pull_requests_repository_number_unique", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_reconciliation_idx": { + "name": "github_pull_requests_reconciliation_idx", + "columns": [ + { + "expression": "account", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_reconcile_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_author_idx": { + "name": "github_pull_requests_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_pull_requests_repository_id_github_repositories_id_fk": { + "name": "github_pull_requests_repository_id_github_repositories_id_fk", + "tableFrom": "github_pull_requests", + "tableTo": "github_repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_pull_requests_file_facts_array": { + "name": "github_pull_requests_file_facts_array", + "value": "\"github_pull_requests\".\"file_facts\" IS NULL OR jsonb_typeof(\"github_pull_requests\".\"file_facts\") = 'array'" + }, + "github_pull_requests_file_facts_complete": { + "name": "github_pull_requests_file_facts_complete", + "value": "NOT \"github_pull_requests\".\"file_facts_complete\" OR \"github_pull_requests\".\"file_facts\" IS NOT NULL" + }, + "github_pull_requests_snapshot": { + "name": "github_pull_requests_snapshot", + "value": "(\"github_pull_requests\".\"snapshot_id\" IS NULL) = (\"github_pull_requests\".\"snapshot_observed_at\" IS NULL) AND (\"github_pull_requests\".\"snapshot_id\" IS NULL OR (\"github_pull_requests\".\"base_sha\" IS NOT NULL AND \"github_pull_requests\".\"head_sha\" IS NOT NULL))" + }, + "github_pull_requests_tracked_account": { + "name": "github_pull_requests_tracked_account", + "value": "\"github_pull_requests\".\"account\" ~ '^[a-z0-9]([a-z0-9-]{0,37}[a-z0-9])?$'" + }, + "github_pull_requests_state": { + "name": "github_pull_requests_state", + "value": "\"github_pull_requests\".\"state\" IN ('open', 'closed', 'merged')" + }, + "github_pull_requests_terminal_state": { + "name": "github_pull_requests_terminal_state", + "value": "(\"github_pull_requests\".\"state\" = 'open' AND \"github_pull_requests\".\"terminal_at\" IS NULL) OR (\"github_pull_requests\".\"state\" IN ('closed', 'merged') AND \"github_pull_requests\".\"terminal_at\" IS NOT NULL)" + }, + "github_pull_requests_merged_state": { + "name": "github_pull_requests_merged_state", + "value": "(\"github_pull_requests\".\"state\" = 'merged') = (\"github_pull_requests\".\"merged_at\" IS NOT NULL)" + }, + "github_pull_requests_sha_shapes": { + "name": "github_pull_requests_sha_shapes", + "value": "(\"github_pull_requests\".\"base_sha\" IS NULL OR \"github_pull_requests\".\"base_sha\" ~ '^[a-f0-9]{40}$') AND (\"github_pull_requests\".\"head_sha\" IS NULL OR \"github_pull_requests\".\"head_sha\" ~ '^[a-f0-9]{40}$') AND (\"github_pull_requests\".\"merge_sha\" IS NULL OR \"github_pull_requests\".\"merge_sha\" ~ '^[a-f0-9]{40}$')" + }, + "github_pull_requests_verified_merge_sha": { + "name": "github_pull_requests_verified_merge_sha", + "value": "(\"github_pull_requests\".\"merge_sha\" IS NULL AND \"github_pull_requests\".\"merge_sha_verified_at\" IS NULL) OR (\"github_pull_requests\".\"state\" = 'merged' AND \"github_pull_requests\".\"merge_sha_verified_at\" IS NOT NULL)" + }, + "github_pull_requests_positive_number": { + "name": "github_pull_requests_positive_number", + "value": "\"github_pull_requests\".\"number\" > 0" + }, + "github_pull_requests_nonnegative_counts": { + "name": "github_pull_requests_nonnegative_counts", + "value": "(\"github_pull_requests\".\"changed_files\" IS NULL OR \"github_pull_requests\".\"changed_files\" >= 0) AND (\"github_pull_requests\".\"additions\" IS NULL OR \"github_pull_requests\".\"additions\" >= 0) AND (\"github_pull_requests\".\"deletions\" IS NULL OR \"github_pull_requests\".\"deletions\" >= 0) AND (\"github_pull_requests\".\"commit_count\" IS NULL OR \"github_pull_requests\".\"commit_count\" >= 0)" + }, + "github_pull_requests_nonnegative_attempts": { + "name": "github_pull_requests_nonnegative_attempts", + "value": "\"github_pull_requests\".\"reconcile_attempts\" >= 0" + } + }, + "isRLSEnabled": true + }, + "public.github_push_observations": { + "name": "github_push_observations", + "schema": "", + "columns": { + "account": { + "name": "account", + "type": "varchar(39)", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "after_sha": { + "name": "after_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "before_sha": { + "name": "before_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "known_shas": { + "name": "known_shas", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "expected_commit_count": { + "name": "expected_commit_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "history_since_at": { + "name": "history_since_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "history_until_at": { + "name": "history_until_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "provider_created_at": { + "name": "provider_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ref_name": { + "name": "ref_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "repository_name_snapshot": { + "name": "repository_name_snapshot", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + } + }, + "indexes": { + "github_push_observations_source_unique": { + "name": "github_push_observations_source_unique", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_push_observations_push_unique": { + "name": "github_push_observations_push_unique", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ref_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "before_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "after_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"github_push_observations\".\"source\" <> 'backfill'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_push_observations_pending_idx": { + "name": "github_push_observations_pending_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lease_until", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "observed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_push_observations_account_idx": { + "name": "github_push_observations_account_idx", + "columns": [ + { + "expression": "account", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "observed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "gh_push_observations_repository_fk": { + "name": "gh_push_observations_repository_fk", + "tableFrom": "github_push_observations", + "tableTo": "github_repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_push_observations_tracked_account": { + "name": "github_push_observations_tracked_account", + "value": "\"github_push_observations\".\"account\" ~ '^[a-z0-9]([a-z0-9-]{0,37}[a-z0-9])?$'" + }, + "github_push_observations_source": { + "name": "github_push_observations_source", + "value": "\"github_push_observations\".\"source\" IN ('webhook', 'events', 'refs', 'backfill')" + }, + "github_push_observations_sha_shape": { + "name": "github_push_observations_sha_shape", + "value": "\"github_push_observations\".\"before_sha\" ~ '^[a-f0-9]{40}$' AND \"github_push_observations\".\"after_sha\" ~ '^[a-f0-9]{40}$'" + }, + "github_push_observations_nonnegative_count": { + "name": "github_push_observations_nonnegative_count", + "value": "\"github_push_observations\".\"attempt_count\" >= 0 AND (\"github_push_observations\".\"expected_commit_count\" IS NULL OR \"github_push_observations\".\"expected_commit_count\" >= 0)" + }, + "github_push_observations_history_bounds": { + "name": "github_push_observations_history_bounds", + "value": "(\"github_push_observations\".\"source\" <> 'backfill' AND \"github_push_observations\".\"history_since_at\" IS NULL AND \"github_push_observations\".\"history_until_at\" IS NULL) OR (\"github_push_observations\".\"source\" = 'backfill' AND \"github_push_observations\".\"history_since_at\" IS NOT NULL AND \"github_push_observations\".\"history_until_at\" IS NOT NULL AND \"github_push_observations\".\"history_since_at\" <= \"github_push_observations\".\"history_until_at\" AND \"github_push_observations\".\"expected_commit_count\" IS NULL AND \"github_push_observations\".\"before_sha\" = repeat('0', 40))" + }, + "github_push_observations_state": { + "name": "github_push_observations_state", + "value": "\"github_push_observations\".\"state\" IN ('pending', 'processing', 'complete', 'deferred', 'unavailable')" + }, + "github_push_observations_lease": { + "name": "github_push_observations_lease", + "value": "(\"github_push_observations\".\"state\" = 'processing') = (\"github_push_observations\".\"lease_token\" IS NOT NULL) AND (\"github_push_observations\".\"state\" <> 'processing' OR \"github_push_observations\".\"lease_until\" IS NOT NULL)" + } + }, + "isRLSEnabled": true + }, + "public.github_ref_generations": { + "name": "github_ref_generations", + "schema": "", + "columns": { + "branch_lineage_id": { + "name": "branch_lineage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "coverage_since_at": { + "name": "coverage_since_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "ref_name": { + "name": "ref_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "gh_ref_generations_lineage_idx": { + "name": "gh_ref_generations_lineage_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_lineage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "gh_ref_generations_repository_fk": { + "name": "gh_ref_generations_repository_fk", + "tableFrom": "github_ref_generations", + "tableTo": "github_repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "gh_ref_generations_pk": { + "name": "gh_ref_generations_pk", + "columns": ["repository_id", "ref_name"] + } + }, + "uniqueConstraints": { + "gh_ref_generations_version_unique": { + "name": "gh_ref_generations_version_unique", + "nullsNotDistinct": false, + "columns": ["repository_id", "ref_name", "generation"] + } + }, + "policies": {}, + "checkConstraints": { + "gh_ref_generations_head_name": { + "name": "gh_ref_generations_head_name", + "value": "\"github_ref_generations\".\"ref_name\" LIKE 'refs/heads/%'" + }, + "gh_ref_generations_sha_shape": { + "name": "gh_ref_generations_sha_shape", + "value": "\"github_ref_generations\".\"head_sha\" ~ '^[a-f0-9]{40}$'" + }, + "gh_ref_generations_positive": { + "name": "gh_ref_generations_positive", + "value": "\"github_ref_generations\".\"generation\" > 0" + } + }, + "isRLSEnabled": true + }, + "public.github_ref_memberships": { + "name": "github_ref_memberships", + "schema": "", + "columns": { + "commit_repository_id": { + "name": "commit_repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "commit_sha": { + "name": "commit_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ref_name": { + "name": "ref_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "gh_ref_memberships_position_unique": { + "name": "gh_ref_memberships_position_unique", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ref_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "gh_ref_memberships_commit_idx": { + "name": "gh_ref_memberships_commit_idx", + "columns": [ + { + "expression": "commit_repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "commit_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "gh_ref_memberships_generation_fk": { + "name": "gh_ref_memberships_generation_fk", + "tableFrom": "github_ref_memberships", + "tableTo": "github_ref_generations", + "columnsFrom": ["repository_id", "ref_name", "generation"], + "columnsTo": ["repository_id", "ref_name", "generation"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "gh_ref_memberships_commit_fk": { + "name": "gh_ref_memberships_commit_fk", + "tableFrom": "github_ref_memberships", + "tableTo": "github_commits", + "columnsFrom": ["commit_repository_id", "commit_sha"], + "columnsTo": ["repository_id", "sha"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "gh_ref_memberships_pk": { + "name": "gh_ref_memberships_pk", + "columns": [ + "repository_id", + "ref_name", + "commit_repository_id", + "commit_sha" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "gh_ref_memberships_values": { + "name": "gh_ref_memberships_values", + "value": "\"github_ref_memberships\".\"generation\" > 0 AND \"github_ref_memberships\".\"position\" >= 0 AND \"github_ref_memberships\".\"commit_sha\" ~ '^[a-f0-9]{40}$'" + } + }, + "isRLSEnabled": true + }, + "public.github_repositories": { + "name": "github_repositories", + "schema": "", + "columns": { + "default_branch": { + "name": "default_branch", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_observed_at": { + "name": "first_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "facts_verified_at": { + "name": "facts_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "inventory_verified_at": { + "name": "inventory_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "full_name": { + "name": "full_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "homepage_url": { + "name": "homepage_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "heads_last_reconciled_at": { + "name": "heads_last_reconciled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "html_url": { + "name": "html_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "varchar(32)", + "primaryKey": true, + "notNull": true + }, + "projection_request_token": { + "name": "projection_request_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_observed_at": { + "name": "last_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "owner_avatar_url": { + "name": "owner_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "owner_login": { + "name": "owner_login", + "type": "varchar(39)", + "primaryKey": false, + "notNull": false + }, + "owner_type": { + "name": "owner_type", + "type": "varchar(12)", + "primaryKey": false, + "notNull": false + }, + "pushed_at": { + "name": "pushed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "topics": { + "name": "topics", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tags_last_reconciled_at": { + "name": "tags_last_reconciled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "varchar(12)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "github_repositories_full_name_idx": { + "name": "github_repositories_full_name_idx", + "columns": [ + { + "expression": "full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_repositories_full_name": { + "name": "github_repositories_full_name", + "value": "length(btrim(\"github_repositories\".\"full_name\")) > 0" + }, + "github_repositories_owner_type": { + "name": "github_repositories_owner_type", + "value": "\"github_repositories\".\"owner_type\" IS NULL OR \"github_repositories\".\"owner_type\" IN ('Organization', 'User')" + }, + "github_repositories_visibility": { + "name": "github_repositories_visibility", + "value": "\"github_repositories\".\"visibility\" IS NULL OR \"github_repositories\".\"visibility\" IN ('public', 'private', 'internal')" + }, + "github_repositories_topics_array": { + "name": "github_repositories_topics_array", + "value": "\"github_repositories\".\"topics\" IS NULL OR jsonb_typeof(\"github_repositories\".\"topics\") = 'array'" + }, + "github_repositories_observation_order": { + "name": "github_repositories_observation_order", + "value": "\"github_repositories\".\"last_observed_at\" >= \"github_repositories\".\"first_observed_at\"" + } + }, + "isRLSEnabled": true + }, + "public.github_repository_inventory_heads": { + "name": "github_repository_inventory_heads", + "schema": "", + "columns": { + "account_login": { + "name": "account_login", + "type": "varchar(39)", + "primaryKey": false, + "notNull": true + }, + "account_user_id": { + "name": "account_user_id", + "type": "varchar(32)", + "primaryKey": true, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "gh_repo_inventory_head_account_id": { + "name": "gh_repo_inventory_head_account_id", + "value": "\"github_repository_inventory_heads\".\"account_user_id\" ~ '^[0-9]{1,32}$'" + }, + "gh_repo_inventory_head_generation": { + "name": "gh_repo_inventory_head_generation", + "value": "(\"github_repository_inventory_heads\".\"generation\" = 0 AND \"github_repository_inventory_heads\".\"completed_at\" IS NULL) OR (\"github_repository_inventory_heads\".\"generation\" > 0 AND \"github_repository_inventory_heads\".\"completed_at\" IS NOT NULL)" + } + }, + "isRLSEnabled": true + }, + "public.github_repository_refs": { + "name": "github_repository_refs", + "schema": "", + "columns": { + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "branch_lineage_id": { + "name": "branch_lineage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "first_observed_at": { + "name": "first_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "head_sha": { + "name": "head_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true + }, + "last_observed_at": { + "name": "last_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "projection_relevant": { + "name": "projection_relevant", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ref_name": { + "name": "ref_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repair_attempts": { + "name": "repair_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "repair_error": { + "name": "repair_error", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "repair_lease_token": { + "name": "repair_lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repair_lease_until": { + "name": "repair_lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "github_repository_refs_active_idx": { + "name": "github_repository_refs_active_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_repository_refs_projection_idx": { + "name": "github_repository_refs_projection_idx", + "columns": [ + { + "expression": "projection_relevant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_observed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_repository_refs_repository_fk": { + "name": "github_repository_refs_repository_fk", + "tableFrom": "github_repository_refs", + "tableTo": "github_repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "github_repository_refs_repository_id_ref_name_pk": { + "name": "github_repository_refs_repository_id_ref_name_pk", + "columns": ["repository_id", "ref_name"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_repository_refs_kind": { + "name": "github_repository_refs_kind", + "value": "\"github_repository_refs\".\"kind\" IN ('head', 'tag')" + }, + "github_repository_refs_name": { + "name": "github_repository_refs_name", + "value": "(\"github_repository_refs\".\"kind\" = 'head' AND \"github_repository_refs\".\"ref_name\" LIKE 'refs/heads/%') OR (\"github_repository_refs\".\"kind\" = 'tag' AND \"github_repository_refs\".\"ref_name\" LIKE 'refs/tags/%')" + }, + "github_repository_refs_lineage": { + "name": "github_repository_refs_lineage", + "value": "(\"github_repository_refs\".\"kind\" = 'head') = (\"github_repository_refs\".\"branch_lineage_id\" IS NOT NULL)" + }, + "github_repository_refs_projection_relevance": { + "name": "github_repository_refs_projection_relevance", + "value": "NOT \"github_repository_refs\".\"projection_relevant\" OR \"github_repository_refs\".\"kind\" = 'head'" + }, + "github_repository_refs_sha_shape": { + "name": "github_repository_refs_sha_shape", + "value": "\"github_repository_refs\".\"head_sha\" ~ '^[a-f0-9]{40}$'" + }, + "github_repository_refs_observation_order": { + "name": "github_repository_refs_observation_order", + "value": "\"github_repository_refs\".\"last_observed_at\" >= \"github_repository_refs\".\"first_observed_at\"" + }, + "github_repository_refs_repair_lease": { + "name": "github_repository_refs_repair_lease", + "value": "(\"github_repository_refs\".\"repair_lease_token\" IS NULL) = (\"github_repository_refs\".\"repair_lease_until\" IS NULL) AND (\"github_repository_refs\".\"repair_lease_token\" IS NULL OR \"github_repository_refs\".\"kind\" = 'head')" + }, + "github_repository_refs_repair_attempts": { + "name": "github_repository_refs_repair_attempts", + "value": "\"github_repository_refs\".\"repair_attempts\" >= 0" + } + }, + "isRLSEnabled": true + }, + "public.github_webhook_deliveries": { + "name": "github_webhook_deliveries", + "schema": "", + "columns": { + "accepted": { + "name": "accepted", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "account": { + "name": "account", + "type": "varchar(39)", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "varchar(40)", + "primaryKey": false, + "notNull": false + }, + "delivery_id": { + "name": "delivery_id", + "type": "varchar(36)", + "primaryKey": true, + "notNull": true + }, + "event": { + "name": "event", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "github_webhook_deliveries_audit_idx": { + "name": "github_webhook_deliveries_audit_idx", + "columns": [ + { + "expression": "event", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "observed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_webhook_deliveries_id_shape": { + "name": "github_webhook_deliveries_id_shape", + "value": "\"github_webhook_deliveries\".\"delivery_id\" ~ '^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$'" + }, + "github_webhook_deliveries_event_shape": { + "name": "github_webhook_deliveries_event_shape", + "value": "\"github_webhook_deliveries\".\"event\" ~ '^[a-z][a-z0-9_]{0,39}$'" + }, + "github_webhook_deliveries_action_shape": { + "name": "github_webhook_deliveries_action_shape", + "value": "\"github_webhook_deliveries\".\"action\" IS NULL OR \"github_webhook_deliveries\".\"action\" ~ '^[a-z][a-z0-9_]{0,39}$'" + }, + "github_webhook_deliveries_repository_id_shape": { + "name": "github_webhook_deliveries_repository_id_shape", + "value": "\"github_webhook_deliveries\".\"repository_id\" IS NULL OR \"github_webhook_deliveries\".\"repository_id\" ~ '^[0-9]{1,32}$'" + }, + "github_webhook_deliveries_tracked_account": { + "name": "github_webhook_deliveries_tracked_account", + "value": "\"github_webhook_deliveries\".\"account\" IS NULL OR \"github_webhook_deliveries\".\"account\" ~ '^[a-z0-9]([a-z0-9-]{0,37}[a-z0-9])?$'" + } + }, + "isRLSEnabled": true + }, + "public.github_work_unit_memberships": { + "name": "github_work_unit_memberships", + "schema": "", + "columns": { + "logical_repository_id": { + "name": "logical_repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "logical_sha": { + "name": "logical_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "work_unit_id": { + "name": "work_unit_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "gh_work_unit_memberships_position_unique": { + "name": "gh_work_unit_memberships_position_unique", + "columns": [ + { + "expression": "work_unit_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "gh_work_unit_memberships_commit_unique": { + "name": "gh_work_unit_memberships_commit_unique", + "columns": [ + { + "expression": "logical_repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "logical_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "gh_work_unit_memberships_unit_fk": { + "name": "gh_work_unit_memberships_unit_fk", + "tableFrom": "github_work_unit_memberships", + "tableTo": "github_work_units", + "columnsFrom": ["work_unit_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "gh_work_unit_memberships_commit_fk": { + "name": "gh_work_unit_memberships_commit_fk", + "tableFrom": "github_work_unit_memberships", + "tableTo": "github_commits", + "columnsFrom": ["logical_repository_id", "logical_sha"], + "columnsTo": ["repository_id", "sha"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "gh_work_unit_memberships_pk": { + "name": "gh_work_unit_memberships_pk", + "columns": ["work_unit_id", "logical_repository_id", "logical_sha"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "gh_work_unit_memberships_values": { + "name": "gh_work_unit_memberships_values", + "value": "\"github_work_unit_memberships\".\"position\" >= 0 AND \"github_work_unit_memberships\".\"logical_sha\" ~ '^[a-f0-9]{40}$'" + } + }, + "isRLSEnabled": true + }, + "public.github_work_unit_summary_attempts": { + "name": "github_work_unit_summary_attempts", + "schema": "", + "columns": { + "identity_key": { + "name": "identity_key", + "type": "varchar(180)", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attribution_mode": { + "name": "attribution_mode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "debounce_until": { + "name": "debounce_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_started_at": { + "name": "request_started_at", + "type": "timestamp with time zone[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::timestamptz[]" + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome_digest": { + "name": "outcome_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "recipe": { + "name": "recipe", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "request_payload": { + "name": "request_payload", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "started_requests": { + "name": "started_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "state": { + "name": "state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "summary_input_digest": { + "name": "summary_input_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "work_unit_id": { + "name": "work_unit_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "gh_work_unit_summary_input_unique": { + "name": "gh_work_unit_summary_input_unique", + "columns": [ + { + "expression": "work_unit_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "summary_input_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recipe", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "gh_work_unit_summary_claim_idx": { + "name": "gh_work_unit_summary_claim_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "debounce_until", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "gh_work_unit_summary_accepted_outcome_idx": { + "name": "gh_work_unit_summary_accepted_outcome_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "outcome_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attribution_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recipe", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"github_work_unit_summary_attempts\".\"state\" = 'accepted'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "gh_work_unit_summary_attempts_identity_idx": { + "name": "gh_work_unit_summary_attempts_identity_idx", + "columns": [ + { + "expression": "identity_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "gh_work_unit_summary_attempts_pk": { + "name": "gh_work_unit_summary_attempts_pk", + "columns": ["work_unit_id", "revision"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "gh_work_unit_summary_state": { + "name": "gh_work_unit_summary_state", + "value": "\"github_work_unit_summary_attempts\".\"state\" IN ('pending', 'processing', 'retryable', 'accepted', 'terminal')" + }, + "gh_work_unit_summary_digests": { + "name": "gh_work_unit_summary_digests", + "value": "\"github_work_unit_summary_attempts\".\"outcome_digest\" ~ '^[a-f0-9]{64}$' AND \"github_work_unit_summary_attempts\".\"summary_input_digest\" ~ '^[a-f0-9]{64}$'" + }, + "gh_work_unit_summary_attribution": { + "name": "gh_work_unit_summary_attribution", + "value": "\"github_work_unit_summary_attempts\".\"attribution_mode\" IN ('tracked_authored_pr', 'foreign_pr_contribution', 'canonical_owned_composite', 'branch_owned_composite')" + }, + "gh_work_unit_summary_revisions": { + "name": "gh_work_unit_summary_revisions", + "value": "\"github_work_unit_summary_attempts\".\"revision\" > 0 AND \"github_work_unit_summary_attempts\".\"started_requests\" BETWEEN 0 AND 2" + }, + "gh_work_unit_summary_lease": { + "name": "gh_work_unit_summary_lease", + "value": "(\"github_work_unit_summary_attempts\".\"lease_token\" IS NULL) = (\"github_work_unit_summary_attempts\".\"lease_until\" IS NULL) AND (\"github_work_unit_summary_attempts\".\"state\" = 'processing') = (\"github_work_unit_summary_attempts\".\"lease_token\" IS NOT NULL) AND (\"github_work_unit_summary_attempts\".\"state\" <> 'processing' OR (\"github_work_unit_summary_attempts\".\"started_requests\" > 0 AND \"github_work_unit_summary_attempts\".\"request_payload\" IS NOT NULL))" + }, + "gh_work_unit_summary_terminal_payload": { + "name": "gh_work_unit_summary_terminal_payload", + "value": "\"github_work_unit_summary_attempts\".\"state\" NOT IN ('accepted', 'terminal') OR \"github_work_unit_summary_attempts\".\"request_payload\" IS NULL" + }, + "gh_work_unit_summary_accepted_output": { + "name": "gh_work_unit_summary_accepted_output", + "value": "(\"github_work_unit_summary_attempts\".\"state\" = 'accepted' AND \"github_work_unit_summary_attempts\".\"outcome\" IS NOT NULL AND \"github_work_unit_summary_attempts\".\"accepted_at\" IS NOT NULL AND \"github_work_unit_summary_attempts\".\"completed_at\" IS NOT NULL) OR (\"github_work_unit_summary_attempts\".\"state\" <> 'accepted' AND \"github_work_unit_summary_attempts\".\"outcome\" IS NULL AND \"github_work_unit_summary_attempts\".\"accepted_at\" IS NULL AND (\"github_work_unit_summary_attempts\".\"state\" <> 'terminal' OR \"github_work_unit_summary_attempts\".\"completed_at\" IS NOT NULL))" + }, + "gh_work_unit_summary_started": { + "name": "gh_work_unit_summary_started", + "value": "(\"github_work_unit_summary_attempts\".\"started_requests\" = 0) = (\"github_work_unit_summary_attempts\".\"last_started_at\" IS NULL) AND (\"github_work_unit_summary_attempts\".\"state\" <> 'pending' OR \"github_work_unit_summary_attempts\".\"request_payload\" IS NOT NULL)" + }, + "gh_work_unit_summary_request_cap": { + "name": "gh_work_unit_summary_request_cap", + "value": "\"github_work_unit_summary_attempts\".\"request_payload\" IS NULL OR octet_length(\"github_work_unit_summary_attempts\".\"request_payload\") <= 393216" + }, + "gh_work_unit_summary_request_times": { + "name": "gh_work_unit_summary_request_times", + "value": "cardinality(\"github_work_unit_summary_attempts\".\"request_started_at\") = \"github_work_unit_summary_attempts\".\"started_requests\"" + }, + "gh_work_unit_summary_metrics": { + "name": "gh_work_unit_summary_metrics", + "value": "(\"github_work_unit_summary_attempts\".\"input_tokens\" IS NULL OR \"github_work_unit_summary_attempts\".\"input_tokens\" >= 0) AND (\"github_work_unit_summary_attempts\".\"output_tokens\" IS NULL OR \"github_work_unit_summary_attempts\".\"output_tokens\" >= 0) AND (\"github_work_unit_summary_attempts\".\"latency_ms\" IS NULL OR \"github_work_unit_summary_attempts\".\"latency_ms\" >= 0)" + } + }, + "isRLSEnabled": true + }, + "public.github_work_unit_summary_daily_usage": { + "name": "github_work_unit_summary_daily_usage", + "schema": "", + "columns": { + "day": { + "name": "day", + "type": "date", + "primaryKey": true, + "notNull": true + }, + "started_requests": { + "name": "started_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.github_work_units": { + "name": "github_work_units", + "schema": "", + "columns": { + "activity_anchor_at": { + "name": "activity_anchor_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "activity_at": { + "name": "activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "activity_day": { + "name": "activity_day", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "additions": { + "name": "additions", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "attribution_mode": { + "name": "attribution_mode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "branch_lineage_id": { + "name": "branch_lineage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content_observed_at": { + "name": "content_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "deletions": { + "name": "deletions", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "facts_digest": { + "name": "facts_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "first_activity_at": { + "name": "first_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "identity_key": { + "name": "identity_key", + "type": "varchar(180)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "languages": { + "name": "languages", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_activity_at": { + "name": "last_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "member_count": { + "name": "member_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "membership_digest": { + "name": "membership_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "newest_commit_repository_id": { + "name": "newest_commit_repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "newest_commit_sha": { + "name": "newest_commit_sha", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "outcome_digest": { + "name": "outcome_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "pull_request": { + "name": "pull_request", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pull_request_node_id": { + "name": "pull_request_node_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "summary_evaluation_digest": { + "name": "summary_evaluation_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "summary_evaluated_digest": { + "name": "summary_evaluated_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "summary_input_digest": { + "name": "summary_input_digest", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "gh_work_units_identity_unique": { + "name": "gh_work_units_identity_unique", + "columns": [ + { + "expression": "identity_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "gh_work_units_pr_unique": { + "name": "gh_work_units_pr_unique", + "columns": [ + { + "expression": "pull_request_node_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"github_work_units\".\"kind\" = 'pull_request'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "gh_work_units_canonical_day_unique": { + "name": "gh_work_units_canonical_day_unique", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "activity_day", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"github_work_units\".\"kind\" = 'canonical_day'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "gh_work_units_branch_unique": { + "name": "gh_work_units_branch_unique", + "columns": [ + { + "expression": "branch_lineage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"github_work_units\".\"kind\" = 'branch'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "gh_work_units_feed_idx": { + "name": "gh_work_units_feed_idx", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "activity_day", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "activity_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "gh_work_units_repository_fk": { + "name": "gh_work_units_repository_fk", + "tableFrom": "github_work_units", + "tableTo": "github_repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "gh_work_units_pull_request_fk": { + "name": "gh_work_units_pull_request_fk", + "tableFrom": "github_work_units", + "tableTo": "github_pull_requests", + "columnsFrom": ["pull_request_node_id"], + "columnsTo": ["node_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "gh_work_units_newest_commit_fk": { + "name": "gh_work_units_newest_commit_fk", + "tableFrom": "github_work_units", + "tableTo": "github_commits", + "columnsFrom": ["newest_commit_repository_id", "newest_commit_sha"], + "columnsTo": ["repository_id", "sha"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "gh_work_units_kind": { + "name": "gh_work_units_kind", + "value": "\"github_work_units\".\"kind\" IN ('pull_request', 'canonical_day', 'branch')" + }, + "gh_work_units_owner_shape": { + "name": "gh_work_units_owner_shape", + "value": "(\"github_work_units\".\"kind\" = 'pull_request' AND \"github_work_units\".\"pull_request_node_id\" IS NOT NULL AND \"github_work_units\".\"branch_lineage_id\" IS NULL) OR (\"github_work_units\".\"kind\" = 'canonical_day' AND \"github_work_units\".\"pull_request_node_id\" IS NULL AND \"github_work_units\".\"branch_lineage_id\" IS NULL) OR (\"github_work_units\".\"kind\" = 'branch' AND \"github_work_units\".\"pull_request_node_id\" IS NULL AND \"github_work_units\".\"branch_lineage_id\" IS NOT NULL)" + }, + "gh_work_units_identity": { + "name": "gh_work_units_identity", + "value": "(\"github_work_units\".\"kind\" = 'pull_request' AND \"github_work_units\".\"identity_key\" = 'pr:' || \"github_work_units\".\"pull_request_node_id\") OR (\"github_work_units\".\"kind\" = 'canonical_day' AND \"github_work_units\".\"identity_key\" = 'canonical:' || \"github_work_units\".\"repository_id\" || ':' || \"github_work_units\".\"activity_day\"::text) OR (\"github_work_units\".\"kind\" = 'branch' AND \"github_work_units\".\"identity_key\" = 'branch:' || \"github_work_units\".\"branch_lineage_id\"::text)" + }, + "gh_work_units_attribution_mode": { + "name": "gh_work_units_attribution_mode", + "value": "\"github_work_units\".\"attribution_mode\" IN ('tracked_authored_pr', 'foreign_pr_contribution', 'canonical_owned_composite', 'branch_owned_composite')" + }, + "gh_work_units_kind_attribution": { + "name": "gh_work_units_kind_attribution", + "value": "(\"github_work_units\".\"kind\" = 'pull_request' AND \"github_work_units\".\"attribution_mode\" IN ('tracked_authored_pr', 'foreign_pr_contribution')) OR (\"github_work_units\".\"kind\" = 'canonical_day' AND \"github_work_units\".\"attribution_mode\" = 'canonical_owned_composite') OR (\"github_work_units\".\"kind\" = 'branch' AND \"github_work_units\".\"attribution_mode\" = 'branch_owned_composite')" + }, + "gh_work_units_visibility": { + "name": "gh_work_units_visibility", + "value": "\"github_work_units\".\"visibility\" IN ('public', 'private')" + }, + "gh_work_units_nonnegative_facts": { + "name": "gh_work_units_nonnegative_facts", + "value": "\"github_work_units\".\"member_count\" > 0 AND \"github_work_units\".\"file_count\" >= 0 AND \"github_work_units\".\"additions\" >= 0 AND \"github_work_units\".\"deletions\" >= 0 AND \"github_work_units\".\"revision\" > 0" + }, + "gh_work_units_activity_order": { + "name": "gh_work_units_activity_order", + "value": "\"github_work_units\".\"first_activity_at\" <= \"github_work_units\".\"last_activity_at\" AND \"github_work_units\".\"activity_day\" = (\"github_work_units\".\"activity_at\" AT TIME ZONE 'Asia/Kolkata')::date" + }, + "gh_work_units_digest_shapes": { + "name": "gh_work_units_digest_shapes", + "value": "\"github_work_units\".\"facts_digest\" ~ '^[a-f0-9]{64}$' AND \"github_work_units\".\"membership_digest\" ~ '^[a-f0-9]{64}$' AND (\"github_work_units\".\"outcome_digest\" IS NULL OR \"github_work_units\".\"outcome_digest\" ~ '^[a-f0-9]{64}$') AND (\"github_work_units\".\"summary_evaluation_digest\" IS NULL OR \"github_work_units\".\"summary_evaluation_digest\" ~ '^[a-f0-9]{64}$') AND (\"github_work_units\".\"summary_evaluated_digest\" IS NULL OR \"github_work_units\".\"summary_evaluated_digest\" ~ '^[a-f0-9]{64}$') AND (\"github_work_units\".\"summary_input_digest\" IS NULL OR \"github_work_units\".\"summary_input_digest\" ~ '^[a-f0-9]{64}$')" + }, + "gh_work_units_languages_array": { + "name": "gh_work_units_languages_array", + "value": "\"github_work_units\".\"languages\" IS NULL OR jsonb_typeof(\"github_work_units\".\"languages\") = 'array'" + } + }, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 15343239..acb5a538 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -183,6 +183,13 @@ "when": 1790602380374, "tag": "0025_noisy_genesis", "breakpoints": true + }, + { + "idx": 26, + "version": "7", + "when": 1790606962661, + "tag": "0026_brainy_nitro", + "breakpoints": true } ] } diff --git a/src/app/api/cron/github-worker/route.ts b/src/app/api/cron/github-worker/route.ts index f7864afd..7306d72a 100644 --- a/src/app/api/cron/github-worker/route.ts +++ b/src/app/api/cron/github-worker/route.ts @@ -21,15 +21,14 @@ export async function POST(request: Request) { return Response.json({ ok: false }, { status: 401 }); } const params = new URL(request.url).searchParams; - const publish = params.get("publish"); const batchSize = workerBatchSizeFrom(params.get("batch")); - if (batchSize === null || (publish !== null && publish !== "1")) { + if (batchSize === null) { return Response.json({ ok: false }, { status: 400 }); } try { const activity = await runGitHubActivityWorker({ - includeProjection: publish === "1", + includeProjection: true, maximumDurationMs: GITHUB_WORKER_EXECUTION_DURATION_MS, ...(batchSize === undefined ? {} @@ -43,7 +42,7 @@ export async function POST(request: Request) { }), }); if (activity.projection?.feedRevisionChanged === true) { - revalidateTag("public-github-activity", "max"); + revalidateTag("public-github-activity", { expire: 0 }); after(async () => { await getInitialGitHubActivity(); }); diff --git a/src/app/api/github/webhook/route.ts b/src/app/api/github/webhook/route.ts index 6c9eaf49..7a643353 100644 --- a/src/app/api/github/webhook/route.ts +++ b/src/app/api/github/webhook/route.ts @@ -140,7 +140,11 @@ const persistSupportedWebhook = async ( } else { const issue = issueFromWebhook(payload); if (issue !== null) { - return await persistGitHubWebhookIssue(deliveryId, issue); + return await persistGitHubWebhookIssue( + deliveryId, + issue, + issueActionFromWebhook(payload) ?? "opened" + ); } if (!intentionallyIgnoredIssue(payload)) { throw new InvalidGitHubWebhookPayloadError(); diff --git a/src/components/codex-stats.tsx b/src/components/codex-stats.tsx index 11b9c68e..354b3714 100644 --- a/src/components/codex-stats.tsx +++ b/src/components/codex-stats.tsx @@ -106,7 +106,7 @@ export function CodexTotals({ stats }: { stats: PublicCodexStats }) { return ( - + diff --git a/src/components/github-activity-days.tsx b/src/components/github-activity-days.tsx index dfb9cf5e..08ce05b8 100644 --- a/src/components/github-activity-days.tsx +++ b/src/components/github-activity-days.tsx @@ -1,6 +1,12 @@ "use client"; -import { CircleDot, FolderGit2, LockKeyhole } from "lucide-react"; +import { + CircleDot, + CircleCheck, + CircleSlash, + FolderGit2, + LockKeyhole, +} from "lucide-react"; import Image from "next/image"; import { DateTime } from "@/components/date-time"; @@ -26,14 +32,28 @@ import type { PublicGitHubWorkUnitActivity, PublicGitHubWorkUnitFacts, } from "@/lib/github-activity-types"; +import { githubIconPaths } from "@/lib/github-icons"; const countFormatter = new Intl.NumberFormat("en-US"); const workUnitLabels = { - branch: "Active branch work", - "canonical-day": "Direct canonical-branch work", + branch: "Branch work", + "canonical-day": "Repository updates", "pull-request": "Pull request", } as const; +const statusColor = (status: string) => { + if (status === "merged" || status === "completed") { + return "text-[light-dark(oklch(0.5_0.18_300),oklch(0.76_0.13_300))]"; + } + if (status === "open") { + return "text-[light-dark(oklch(0.48_0.12_155),oklch(0.75_0.13_155))]"; + } + if (status === "closed") { + return "text-[light-dark(oklch(0.52_0.16_25),oklch(0.76_0.13_25))]"; + } + return "text-muted-foreground"; +}; + function RepositoryIdentity({ repository, }: Readonly<{ repository: PublicGitHubActivityRepository }>) { @@ -87,6 +107,9 @@ function DiffCounters({ }: Readonly<{ facts: Pick; }>) { + if (facts.additions === null || facts.deletions === null) { + return null; + } return ( @@ -102,17 +125,21 @@ function DiffCounters({ } function WorkUnitFacts({ - facts, -}: Readonly<{ facts: PublicGitHubWorkUnitFacts }>) { + item, +}: Readonly<{ item: PublicGitHubWorkUnitActivity }>) { + const { facts } = item; const commits = `${countFormatter.format(facts.ownedCommitCount)} ${facts.ownedCommitCount === 1 ? "commit" : "commits"}`; const files = `${countFormatter.format(facts.uniqueFileCount)} ${facts.uniqueFileCount === 1 ? "file" : "files"}`; return (
+ {item.pullRequest?.diff ? "PR total · " : ""} {commits} · {files} - + {item.pullRequest?.diff ? ( + + ) : null} {facts.languages?.map((language) => ( @@ -124,7 +151,8 @@ function WorkUnitFacts({ function WorkUnitRow({ item, }: Readonly<{ item: PublicGitHubWorkUnitActivity }>) { - const headline = item.headline ?? workUnitLabels[item.kind]; + const headline = + item.headline ?? item.pullRequest?.title ?? workUnitLabels[item.kind]; return ( {item.summary}

)} - + } render={ @@ -156,8 +184,28 @@ function WorkUnitRow({ - + {item.pullRequest?.diff ? ( + + ) : null} + {item.kind === "pull-request" && item.pullRequest ? ( + + + + Pull request {item.pullRequest.status} + + + ) : null} {item.summary}

)} - + {item.destination === null ? null : ( - View on GitHub ↗ + {item.kind === "pull-request" + ? "View pull request ↗" + : "View latest commit ↗"} )}
@@ -193,9 +243,20 @@ function WorkUnitRow({ function IssueRow({ item, }: Readonly<{ - item: Extract; + item: Extract; }>) { const Row = item.destination === null ? "div" : "a"; + const status = item.status ?? "open"; + const Icon = + status === "open" + ? CircleDot + : status === "completed" + ? CircleCheck + : CircleSlash; + const label = + status === "not-planned" + ? "Issue closed as not planned" + : `Issue ${status}`; return (
  • + + -
  • @@ -223,7 +287,7 @@ function IssueRow({ } function ActivityItem({ item }: Readonly<{ item: PublicGitHubActivityItem }>) { - return item.kind === "issue-opened" ? ( + return item.kind === "issue" ? ( ) : ( @@ -283,9 +347,7 @@ function GitHubActivityDay({ itemLimit, }: Readonly<{ day: PublicGitHubActivityDay; itemLimit?: number }>) { const repositoryCount = day.repositories.length; - const workUnits = day.repositories - .flatMap(({ items }) => items) - .filter((item) => item.kind !== "issue-opened"); + const workUnits = day.repositories.flatMap(({ items }) => items); return (
    diff --git a/src/db/schema.ts b/src/db/schema.ts index 21d4dc76..f9dbe718 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -22,6 +22,10 @@ import type { PublicGitHubIssueRow, PublicGitHubWorkUnitRow, } from "@/lib/github-activity-feed-core"; +import type { + GitHubPullRequestDisplay, + GitHubIssueStatus, +} from "@/lib/github-activity-types"; import type { GitHubLanguageFact, GitHubWorkUnitFileFact, @@ -653,6 +657,10 @@ export const githubPullRequests = pgTable( mode: "date", withTimezone: true, }), + statusChangedAt: timestamp("status_changed_at", { + mode: "date", + withTimezone: true, + }), draft: boolean("draft").default(false).notNull(), firstObservedAt: timestamp("first_observed_at", { mode: "date", @@ -922,6 +930,15 @@ export const githubIssues = pgTable( account: varchar("account", { length: 39 }).notNull(), authorLogin: varchar("author_login", { length: 39 }), authorUserId: varchar("author_user_id", { length: 32 }).notNull(), + status: varchar("status", { length: 16 }) + .$type() + .default("open") + .notNull(), + activityAt: timestamp("activity_at", { mode: "date", withTimezone: true }), + providerUpdatedAt: timestamp("provider_updated_at", { + mode: "date", + withTimezone: true, + }), createdAt: timestamp("created_at", { mode: "date", withTimezone: true, @@ -950,6 +967,10 @@ export const githubIssues = pgTable( "github_issues_tracked_account", sql`${table.account} ~ ${githubLoginPattern}` ), + check( + "github_issues_status", + sql`${table.status} IN ('open', 'closed', 'completed', 'not-planned')` + ), check("github_issues_positive_number", sql`${table.number} > 0`), ] ).enableRLS(); @@ -1169,6 +1190,7 @@ export const githubWorkUnits = pgTable( }).notNull(), newestCommitSha: varchar("newest_commit_sha", { length: 40 }).notNull(), outcomeDigest: varchar("outcome_digest", { length: 64 }), + pullRequest: jsonb("pull_request").$type(), pullRequestNodeId: varchar("pull_request_node_id", { length: 128 }), repositoryId: varchar("repository_id", { length: 32 }).notNull(), revision: integer("revision").default(1).notNull(), @@ -1243,7 +1265,7 @@ export const githubWorkUnits = pgTable( ), check( "gh_work_units_activity_order", - sql`${table.firstActivityAt} <= ${table.lastActivityAt} AND ${table.activityDay} = (${table.activityAt} AT TIME ZONE 'UTC')::date` + sql`${table.firstActivityAt} <= ${table.lastActivityAt} AND ${table.activityDay} = (${table.activityAt} AT TIME ZONE 'Asia/Kolkata')::date` ), check( "gh_work_units_digest_shapes", diff --git a/src/lib/github-activity-feed-core.ts b/src/lib/github-activity-feed-core.ts index 7265f66a..973d8c4b 100644 --- a/src/lib/github-activity-feed-core.ts +++ b/src/lib/github-activity-feed-core.ts @@ -5,6 +5,8 @@ import type { PublicGitHubActivityRepository, PublicGitHubWorkUnitFacts, PublicGitHubWorkUnitKind, + GitHubPullRequestDisplay, + GitHubIssueStatus, } from "@/lib/github-activity-types"; interface PublicRepositoryActivityRow { @@ -18,6 +20,7 @@ export interface PublicGitHubWorkUnitRow extends PublicRepositoryActivityRow { destination: PublicGitHubActivityDestination | null; facts: PublicGitHubWorkUnitFacts; headline: string | null; + pullRequest?: GitHubPullRequestDisplay | null; kind: PublicGitHubWorkUnitKind; summarizing: boolean; summary: string | null; @@ -27,6 +30,7 @@ export interface PublicGitHubIssueRow extends PublicRepositoryActivityRow { day: string; destination: PublicGitHubActivityDestination | null; title: string; + status?: GitHubIssueStatus; } export interface BuildPublicGitHubActivityDaysInput { @@ -142,6 +146,7 @@ export const buildPublicGitHubActivityDays = ( destination: row.destination, facts: row.facts, headline: row.headline, + pullRequest: row.pullRequest, id: row.id, kind: row.kind, summarizing: row.summarizing, @@ -153,8 +158,9 @@ export const buildPublicGitHubActivityDays = ( activityAt: row.activityAt, destination: row.destination, id: row.id, - kind: "issue-opened", + kind: "issue", title: row.title, + status: row.status, }); } const repositories = [...groups.values()] diff --git a/src/lib/github-activity-snapshots.ts b/src/lib/github-activity-snapshots.ts index 5ec2ec44..3c757f45 100644 --- a/src/lib/github-activity-snapshots.ts +++ b/src/lib/github-activity-snapshots.ts @@ -1,6 +1,6 @@ import { isDeepStrictEqual } from "node:util"; -import { and, desc, eq, inArray, sql } from "drizzle-orm"; +import { and, desc, eq, gte, inArray, sql } from "drizzle-orm"; import { getDatabase } from "@/db/client"; import { @@ -19,7 +19,26 @@ type Transaction = Parameters< Parameters["transaction"]>[0] >[0]; +// oxlint-disable-next-line eslint/complexity -- Compare status and code independently so metadata cannot masquerade as another day of work. export const githubSnapshotChanged = (previous: Saved, next: Saved) => { + if (!("facts" in previous.payload) && !("facts" in next.payload)) { + return ( + (previous.payload.status ?? "open") !== (next.payload.status ?? "open") + ); + } + const previousStatus = + "facts" in previous.payload + ? previous.payload.pullRequest?.status + : undefined; + const nextStatus = + "facts" in next.payload ? next.payload.pullRequest?.status : undefined; + if ( + previousStatus !== undefined && + nextStatus !== undefined && + previousStatus !== nextStatus + ) { + return true; + } if (previous.attributionMode !== next.attributionMode) { return true; } @@ -53,6 +72,10 @@ export const githubSnapshotChanged = (previous: Saved, next: Saved) => { ); }; +// One hour for delayed ingestion. Only source activity from that day may fill it. +export const mutableGitHubDay = (now: Date) => + dateKey(new Date(now.getTime() - 60 * 60 * 1000), WORK_LOG_TIME_ZONE); + export const fillSavedGitHubSummaries = async (transaction: Transaction) => { // The queue owns the exact immutable input. Fill empty prose once, never use // an older same-identity summary to describe a newer snapshot. @@ -129,6 +152,7 @@ export const publishGitHubActivitySnapshots = async ( const publicationRepositoryIds = bootstrap ? undefined : repositoryIds; const now = new Date(); const today = dateKey(now, WORK_LOG_TIME_ZONE); + const mutableStart = mutableGitHubDay(now); const scope = publicationRepositoryIds ? inArray(githubWorkUnits.repositoryId, [...publicationRepositoryIds]) : undefined; @@ -144,37 +168,34 @@ export const publishGitHubActivitySnapshots = async ( attributionMode: githubWorkUnits.attributionMode, }) .from(githubWorkUnits) - .where(scope); + .where( + and( + scope, + bootstrap ? undefined : gte(githubWorkUnits.activityDay, mutableStart) + ) + ); const byIdentity = new Map(units.map((unit) => [unit.identityKey, unit])); + const rows = await readCurrentPublicGitHubRows(transaction, { + repositoryIds: publicationRepositoryIds, + exactSummary: !bootstrap, + sinceDay: bootstrap ? undefined : mutableStart, + }); + const identities = [...rows.workUnits, ...rows.issues].map((row) => row.id); const latest = await transaction .selectDistinctOn([githubActivitySnapshots.identityKey]) .from(githubActivitySnapshots) - .where( - publicationRepositoryIds - ? inArray(githubActivitySnapshots.repositoryId, [ - ...publicationRepositoryIds, - ]) - : undefined - ) + .where(inArray(githubActivitySnapshots.identityKey, identities)) .orderBy( githubActivitySnapshots.identityKey, desc(githubActivitySnapshots.day) ); const previous = new Map(latest.map((row) => [row.identityKey, row])); - const rows = await readCurrentPublicGitHubRows(transaction, { - repositoryIds: publicationRepositoryIds, - exactSummary: !bootstrap, - }); let changed = false; for (const row of [...rows.workUnits, ...rows.issues]) { const old = previous.get(row.id); const unit = byIdentity.get(row.id); - const isIssue = !("facts" in row); - if (isIssue && old) { - continue; - } const day = dateKey(row.activityAt, WORK_LOG_TIME_ZONE); - if (day > today) { + if (day > today || (!bootstrap && day < mutableStart)) { continue; } const next: Saved = { @@ -194,7 +215,21 @@ export const publishGitHubActivitySnapshots = async ( }, recordedAt: now, }; - if (old && !githubSnapshotChanged(old, next)) { + if ( + typeof old?.outcomeDigest === "string" && + typeof next.outcomeDigest !== "string" + ) { + continue; + } + if ( + old && + !githubSnapshotChanged(old, next) && + !( + old.day === day && + day >= mutableStart && + !isDeepStrictEqual(old.payload, next.payload) + ) + ) { // Seed/advance the comparison digest without changing historical display. // Bind late prose only to the exact revision originally saved. const metadata = { @@ -236,7 +271,10 @@ export const publishGitHubActivitySnapshots = async ( githubActivitySnapshots.identityKey, ], set: next, - setWhere: eq(githubActivitySnapshots.day, today), + setWhere: and( + gte(githubActivitySnapshots.day, mutableStart), + sql`(${githubActivitySnapshots.payload}->>'activityAt')::timestamptz <= ${row.activityAt}::timestamptz` + ), }) .returning({ day: githubActivitySnapshots.day }); changed ||= written.length > 0; diff --git a/src/lib/github-activity-store.ts b/src/lib/github-activity-store.ts index 987a4516..9f173bf9 100644 --- a/src/lib/github-activity-store.ts +++ b/src/lib/github-activity-store.ts @@ -1,4 +1,4 @@ -import { and, desc, eq, inArray, lt, or, sql } from "drizzle-orm"; +import { and, desc, eq, gte, inArray, lt, or, sql } from "drizzle-orm"; import { getDatabase } from "@/db/client"; import { @@ -10,6 +10,7 @@ import { githubWorkUnitSummaryAttempts, githubWorkUnits, } from "@/db/schema"; +import { dateKey, WORK_LOG_TIME_ZONE } from "@/lib/date"; import { encodeGitHubActivityCursor } from "@/lib/github-activity-cursor"; import type { GitHubActivityCursor } from "@/lib/github-activity-cursor"; import { buildPublicGitHubActivityDays } from "@/lib/github-activity-feed-core"; @@ -203,7 +204,11 @@ const publicWorkUnitKind = (value: string): PublicGitHubWorkUnitKind => { throw new Error("A public GitHub work-unit kind is invalid."); }; -const issueDay = sql`to_char(${githubIssues.createdAt} AT TIME ZONE 'UTC', 'YYYY-MM-DD')`; +const issueActivityAt = + sql`coalesce(${githubIssues.activityAt}, ${githubIssues.createdAt})`.mapWith( + githubIssues.createdAt + ); +const issueDay = sql`to_char(${issueActivityAt} AT TIME ZONE 'Asia/Kolkata', 'YYYY-MM-DD')`; interface AvailableDays { hasNextPage: boolean; @@ -243,7 +248,11 @@ const readAvailableDays = async ( // oxlint-disable-next-line eslint/complexity -- Keep validation and exact-summary selection in the shared legacy-to-snapshot mapper. export const readCurrentPublicGitHubRows = async ( database: GitHubActivityDatabase, - options: { repositoryIds?: readonly string[]; exactSummary?: boolean } = {} + options: { + repositoryIds?: readonly string[]; + exactSummary?: boolean; + sinceDay?: string; + } = {} ): Promise<{ issues: readonly PublicGitHubIssueRow[]; workUnits: readonly PublicGitHubWorkUnitRow[]; @@ -268,6 +277,7 @@ export const readCurrentPublicGitHubRows = async ( newestCommitRepositoryId: githubWorkUnits.newestCommitRepositoryId, newestCommitSha: githubWorkUnits.newestCommitSha, ownerAvatarUrl: githubRepositories.ownerAvatarUrl, + pullRequest: githubWorkUnits.pullRequest, pullRequestNumber: githubPullRequests.number, repositoryId: githubRepositories.id, visibility: githubRepositories.visibility, @@ -286,6 +296,9 @@ export const readCurrentPublicGitHubRows = async ( ) .where( and( + options.sinceDay === undefined + ? undefined + : gte(githubWorkUnits.activityDay, options.sinceDay), options.repositoryIds ? inArray(githubWorkUnits.repositoryId, [...options.repositoryIds]) : undefined, @@ -301,7 +314,8 @@ export const readCurrentPublicGitHubRows = async ( .limit(maximumRows + 1), database .select({ - activityAt: githubIssues.createdAt, + activityAt: issueActivityAt, + status: githubIssues.status, day: issueDay, fullName: githubRepositories.fullName, nodeId: githubIssues.nodeId, @@ -318,6 +332,9 @@ export const readCurrentPublicGitHubRows = async ( ) .where( and( + options.sinceDay === undefined + ? undefined + : gte(issueDay, options.sinceDay), options.repositoryIds ? inArray(githubIssues.repositoryId, [...options.repositoryIds]) : undefined, @@ -332,7 +349,7 @@ export const readCurrentPublicGitHubRows = async ( ]) ) ) - .orderBy(desc(githubIssues.createdAt), githubIssues.nodeId) + .orderBy(desc(issueActivityAt), githubIssues.nodeId) .limit(maximumRows + 1), ]); if (unitRows.length > maximumRows || issueRows.length > maximumRows) { @@ -474,6 +491,7 @@ export const readCurrentPublicGitHubRows = async ( } } } + // oxlint-disable-next-line eslint/complexity -- Keep destination validation and the saved public shape together. const workUnits = unitRows.map((row): PublicGitHubWorkUnitRow => { const repository = checkedPublicRepository({ fullName: row.fullName, @@ -509,8 +527,8 @@ export const readCurrentPublicGitHubRows = async ( url: `${repository.baseUrl}/commit/${row.newestCommitSha}`, }; } - const firstDay = row.firstActivityAt.toISOString().slice(0, 10); - const lastDay = row.lastActivityAt.toISOString().slice(0, 10); + const firstDay = dateKey(row.firstActivityAt, WORK_LOG_TIME_ZONE); + const lastDay = dateKey(row.lastActivityAt, WORK_LOG_TIME_ZONE); const summary = currentSummaries.get(row.id) ?? fallbackSummaries.get(row.id); return { @@ -518,16 +536,20 @@ export const readCurrentPublicGitHubRows = async ( day: row.activityDay, destination, facts: { - additions: row.additions, + additions: row.pullRequest?.diff?.additions ?? null, dateRange: firstDay === lastDay ? null : { end: lastDay, start: firstDay }, - deletions: row.deletions, - languages: row.languages?.map(({ label }) => label) ?? null, + deletions: row.pullRequest?.diff?.deletions ?? null, + languages: + kind === "pull-request" + ? null + : (row.languages?.map(({ label }) => label) ?? null), ownedCommitCount: row.memberCount, - uniqueFileCount: row.fileCount, + uniqueFileCount: row.pullRequest?.diff?.files ?? row.fileCount, }, id: row.identityKey, headline: summary?.headline ?? null, + pullRequest: row.pullRequest, kind, repository: repository.projection, summarizing: false, @@ -557,6 +579,7 @@ export const readCurrentPublicGitHubRows = async ( id: `issue:${row.nodeId}`, repository: repository.projection, title: row.title, + status: row.status, }; }); return { issues, workUnits }; diff --git a/src/lib/github-activity-types.ts b/src/lib/github-activity-types.ts index e3f2ae3a..fb734e20 100644 --- a/src/lib/github-activity-types.ts +++ b/src/lib/github-activity-types.ts @@ -21,14 +21,23 @@ export interface PublicGitHubActivityDateRange { } export interface PublicGitHubWorkUnitFacts { - additions: number; + additions: number | null; dateRange: PublicGitHubActivityDateRange | null; - deletions: number; + deletions: number | null; languages: readonly string[] | null; ownedCommitCount: number; uniqueFileCount: number; } +export interface GitHubPullRequestDisplay { + title: string; + status: "open" | "draft" | "merged" | "closed"; + // Null for a contribution to someone else's PR or incomplete evidence. + diff: { additions: number; deletions: number; files: number } | null; +} + +export type GitHubIssueStatus = "open" | "completed" | "not-planned" | "closed"; + export interface PublicGitHubWorkUnitActivity { activityAt: string; destination: PublicGitHubActivityDestination | null; @@ -36,20 +45,22 @@ export interface PublicGitHubWorkUnitActivity { id: string; kind: PublicGitHubWorkUnitKind; headline: string | null; + pullRequest?: GitHubPullRequestDisplay | null; summarizing: boolean; summary: string | null; } -export interface PublicGitHubIssueOpenedActivity { +export interface PublicGitHubIssueActivity { activityAt: string; destination: PublicGitHubActivityDestination | null; id: string; - kind: "issue-opened"; + kind: "issue"; title: string; + status?: GitHubIssueStatus; } export type PublicGitHubActivityItem = - | PublicGitHubIssueOpenedActivity + | PublicGitHubIssueActivity | PublicGitHubWorkUnitActivity; export interface PublicGitHubActivityRepositoryGroup { diff --git a/src/lib/github-commits-core.ts b/src/lib/github-commits-core.ts index b55d61d3..2f350a7c 100644 --- a/src/lib/github-commits-core.ts +++ b/src/lib/github-commits-core.ts @@ -134,6 +134,10 @@ export interface GitHubIssue { authorLogin: string; authorUserId: string; createdAt: string; + activityAt: string; + providerUpdatedAt: string; + closedAt: string | null; + status: "open" | "closed" | "completed" | "not-planned"; nodeId: string; number: number; repository: GitHubRepositoryFacts; @@ -621,9 +625,11 @@ const repositoryFactsFromEvent = (event: JsonObject) => { }); }; +// oxlint-disable-next-line eslint/complexity -- Validate the complete provider issue at the ingestion boundary. export const issueFromGitHub = ( value: unknown, - repository: GitHubRepositoryFacts + repository: GitHubRepositoryFacts, + action = "opened" ): GitHubIssue | null => { if (!isObject(value) || !isObject(value.user) || "pull_request" in value) { return null; @@ -632,6 +638,18 @@ export const issueFromGitHub = ( const authorUserId = repositoryIdFrom(value.user.id); const account = trackedGitHubAccountFromUserId(authorUserId); const createdAt = normalizedDate(value.created_at); + const providerUpdatedAt = normalizedDate(value.updated_at); + const closedAt = optionalDate(value.closed_at); + const { state } = value; + const reason = value.state_reason; + const status = + state === "open" + ? "open" + : reason === "completed" + ? "completed" + : reason === "not_planned" + ? "not-planned" + : "closed"; const nodeId = normalizedText(value.node_id, 128); const number = positiveInteger(value.number); const title = @@ -643,6 +661,15 @@ export const issueFromGitHub = ( authorLogin === null || authorUserId === null || createdAt === null || + providerUpdatedAt === null || + !closedAt.valid || + (state !== "open" && state !== "closed") || + (state === "closed") !== (closedAt.value !== null) || + (reason !== null && + reason !== undefined && + reason !== "completed" && + reason !== "not_planned" && + reason !== "reopened") || nodeId === null || number === null || title === null @@ -658,6 +685,11 @@ export const issueFromGitHub = ( authorLogin, authorUserId, createdAt, + activityAt: + closedAt.value ?? (action === "reopened" ? providerUpdatedAt : createdAt), + providerUpdatedAt, + closedAt: closedAt.value, + status, nodeId, number, repository, @@ -922,7 +954,7 @@ const issueEventFrom = ( if (action === undefined || !PULL_REQUEST_ACTION.test(action)) { return null; } - if (action !== "opened") { + if (!["opened", "closed", "reopened", "edited"].includes(action)) { return { id, issue: null, occurredAt, pullRequest: null, push: null }; } const rawIssue = value.payload.issue; @@ -936,7 +968,7 @@ const issueEventFrom = ( if (trackedGitHubAccountFromUserId(rawIssue.user.id) !== account) { return { id, issue: null, occurredAt, pullRequest: null, push: null }; } - const issue = issueFromGitHub(rawIssue, repository); + const issue = issueFromGitHub(rawIssue, repository, action); if (issue === null) { return null; } @@ -1247,11 +1279,22 @@ export const issueActionFromWebhook = (value: unknown) => { }; export const issueFromWebhook = (value: unknown): GitHubIssue | null => { - if (!isObject(value) || issueActionFromWebhook(value) !== "opened") { + if ( + !isObject(value) || + !["opened", "closed", "reopened", "edited"].includes( + issueActionFromWebhook(value) ?? "" + ) + ) { return null; } const repository = repositoryFactsFrom(value.repository); - return repository === null ? null : issueFromGitHub(value.issue, repository); + return repository === null + ? null + : issueFromGitHub( + value.issue, + repository, + issueActionFromWebhook(value) ?? "opened" + ); }; export const pullRequestObservationFromWebhook = ( diff --git a/src/lib/github-commits-store.ts b/src/lib/github-commits-store.ts index 19538e22..d39bc949 100644 --- a/src/lib/github-commits-store.ts +++ b/src/lib/github-commits-store.ts @@ -190,6 +190,9 @@ const persistIssue = async ( authorLogin: issue.authorLogin, authorUserId: issue.authorUserId, createdAt: new Date(issue.createdAt), + providerUpdatedAt: new Date(issue.providerUpdatedAt), + activityAt: new Date(issue.activityAt), + status: issue.status, firstObservedAt: observedAt, nodeId: issue.nodeId, number: issue.number, @@ -197,7 +200,16 @@ const persistIssue = async ( titleSnapshot: issue.title, urlSnapshot: issue.url, }) - .onConflictDoNothing({ target: githubIssues.nodeId }) + .onConflictDoUpdate({ + target: githubIssues.nodeId, + set: { + titleSnapshot: issue.title, + providerUpdatedAt: new Date(issue.providerUpdatedAt), + status: issue.status, + activityAt: sql`case when ${githubIssues.status} <> ${issue.status} then ${issue.closedAt ?? issue.providerUpdatedAt}::timestamptz else coalesce(${githubIssues.activityAt}, ${githubIssues.createdAt}) end`, + }, + setWhere: sql`${githubIssues.providerUpdatedAt} is null or ${githubIssues.providerUpdatedAt} < ${issue.providerUpdatedAt}::timestamptz`, + }) .returning({ nodeId: githubIssues.nodeId }); if (insertedIssue === undefined) { return false; @@ -1466,12 +1478,13 @@ export const persistGitHubWebhookHeadSignal = async ( export const persistGitHubWebhookIssue = async ( deliveryId: string, - issue: GitHubIssue + issue: GitHubIssue, + action: string ): Promise => await getDatabase().transaction(async (transaction) => { const delivery = { account: issue.account, - action: "opened", + action, deliveryId, event: "issues", repositoryId: issue.repository.id, diff --git a/src/lib/github-cron-config.ts b/src/lib/github-cron-config.ts index 419853f6..a22eda78 100644 --- a/src/lib/github-cron-config.ts +++ b/src/lib/github-cron-config.ts @@ -5,7 +5,7 @@ export const GITHUB_EVENTS_CRON_JOB = { export const GITHUB_WORKER_CRON_JOB = { name: "github-activity-worker-every-five-minutes", - // The first worker run also publishes; the remaining runs only ingest. + // All workers publish dirty repositories after ingestion. schedule: "7-57/5 * * * *", } as const; diff --git a/src/lib/github-pull-request-store.ts b/src/lib/github-pull-request-store.ts index 81e4f0f6..3afb676d 100644 --- a/src/lib/github-pull-request-store.ts +++ b/src/lib/github-pull-request-store.ts @@ -77,6 +77,9 @@ export const persistPullRequestSnapshotInTransaction = async ( changedFiles: githubPullRequests.changedFiles, commitCount: githubPullRequests.commitCount, deletions: githubPullRequests.deletions, + draft: githubPullRequests.draft, + title: githubPullRequests.title, + statusChangedAt: githubPullRequests.statusChangedAt, headRepositoryId: githubPullRequests.headRepositoryId, headSha: githubPullRequests.headSha, mergeSha: githubPullRequests.mergeSha, @@ -156,6 +159,15 @@ export const persistPullRequestSnapshotInTransaction = async ( ? now : (existing?.mergeShaVerifiedAt ?? null) : null; + const statusChanged = + existing !== undefined && + (existing.state !== state || + (state === "open" && existing.draft !== pullRequest.draft)); + const statusChangedAt = + terminalAt ?? + (statusChanged + ? providerUpdatedAt + : (existing?.statusChangedAt ?? new Date(pullRequest.createdAt))); const projectionEvidenceChanged = existing !== undefined && (existing.headSha !== pullRequest.headSha || @@ -164,7 +176,9 @@ export const persistPullRequestSnapshotInTransaction = async ( (pullRequest.headRepository !== null && existing.headRepositoryId !== pullRequest.headRepository.id) || existing.repositoryId !== pullRequest.repository.id || - existing.state !== state); + existing.state !== state || + existing.draft !== pullRequest.draft || + existing.title !== pullRequest.title); const mergeLandingChanged = existing !== undefined && (existing.mergeSha !== mergeSha || @@ -203,6 +217,7 @@ export const persistPullRequestSnapshotInTransaction = async ( providerUpdatedAt, state, terminalAt, + statusChangedAt, title: pullRequest.title, url: pullRequest.url, } as const; @@ -271,6 +286,7 @@ export const persistPullRequestSnapshotInTransaction = async ( mergeShaVerifiedAt, state, terminalAt, + statusChangedAt, } : {}), ...retryLifecycleUpdate, @@ -312,7 +328,9 @@ export const persistPullRequestSnapshotInTransaction = async ( (existing.headRepositoryId ?? existing.repositoryId) !== commitRepositoryId || existing.commitCount !== expectedMembershipCount || - existing.changedFiles !== expectedChangedFiles; + existing.changedFiles !== expectedChangedFiles || + existing.additions !== mutableUpdate.additions || + existing.deletions !== mutableUpdate.deletions; const versionId = evidenceChanged ? randomUUID() : existing.snapshotId; if (versionId === null || versionId === undefined) { throw new Error("Missing PR snapshot identity."); diff --git a/src/lib/github-work-unit-core.ts b/src/lib/github-work-unit-core.ts index 106f63ce..bceedf80 100644 --- a/src/lib/github-work-unit-core.ts +++ b/src/lib/github-work-unit-core.ts @@ -1,5 +1,7 @@ import { createHash } from "node:crypto"; +import { dateKey, WORK_LOG_TIME_ZONE } from "@/lib/date"; +import type { GitHubPullRequestDisplay } from "@/lib/github-activity-types"; import { aggregateGitHubLanguages } from "@/lib/github-change-evidence"; import type { GitHubFileChangeStat, @@ -69,6 +71,8 @@ export interface GitHubPullRequestProjectionEvidence { netOutcome: GitHubNormalizedOutcomeEvidence | null; netOutcomeOwnedCompletely: boolean; nodeId: string; + display: GitHubPullRequestDisplay; + statusChangedAt: string; snapshotKind: "current" | "final"; state: "closed" | "merged" | "open"; } @@ -102,6 +106,7 @@ interface GitHubWorkUnitProjectionOptions { } export interface GitHubWorkUnitOwnershipIndex { + pendingPullRequestNodeIds: ReadonlySet; pullRequestsByLogicalKey: ReadonlyMap< string, readonly GitHubPullRequestProjectionEvidence[] @@ -143,6 +148,7 @@ export interface GitHubProjectedWorkUnit { newestCommitRepositoryId: string; newestCommitSha: string; outcomeDigest: string | null; + pullRequest: GitHubPullRequestDisplay | null; pullRequestNodeId: string | null; repositoryId: string; visibility: "private" | "public"; @@ -184,9 +190,6 @@ const normalizedInstant = (value: string): string => { return instant.toISOString(); }; -const utcDayFrom = (value: string): string => - normalizedInstant(value).slice(0, 10); - const digestJson = (value: unknown): string => createHash("sha256").update(JSON.stringify(value), "utf-8").digest("hex"); @@ -589,12 +592,30 @@ export const indexGitHubWorkUnitOwnershipEvidence = ( } return { + pendingPullRequestNodeIds: new Set( + input.pullRequests + .filter((pr) => !pr.membershipComplete) + .map((pr) => pr.nodeId) + ), pullRequestsByLogicalKey, refsByLogicalKey, repositoriesById, }; }; +export const hasPendingGitHubPullRequest = ( + change: GitHubLogicalChange, + ownership: GitHubWorkUnitOwnershipIndex +) => + [ + ...change.associatedPullRequestNodeIds, + ...( + ownership.pullRequestsByLogicalKey.get( + githubLogicalChangeKey(change.logicalRepositoryId, change.logicalSha) + ) ?? [] + ).map((pr) => pr.nodeId), + ].some((id) => ownership.pendingPullRequestNodeIds.has(id)); + const chooseOwnerFor = ( change: GitHubLogicalChange, ownership: GitHubWorkUnitOwnershipIndex, @@ -604,6 +625,10 @@ const chooseOwnerFor = ( change.logicalRepositoryId, change.logicalSha ); + // Wait for known PRs before assigning these commits to another owner. + if (hasPendingGitHubPullRequest(change, ownership)) { + return null; + } const pullRequest = chooseEffectivePullRequest( ownership.pullRequestsByLogicalKey.get(logicalKey) ?? [], trackedAuthorUserIds @@ -634,7 +659,7 @@ const chooseOwnerFor = ( ); if (canonicalRef !== undefined) { return { - activityDay: utcDayFrom(change.logicalActivityAt), + activityDay: dateKey(change.logicalActivityAt, WORK_LOG_TIME_ZONE), kind: "canonical_day", ref: canonicalRef, repository, @@ -758,7 +783,26 @@ const projectedUnitFrom = ( ? owner.pullRequest.contentObservedAt : owner.ref.contentObservedAt, ]); - const activityDay = utcDayFrom(activityAnchorAt); + const pullRequest = + owner.kind === "pull_request" + ? { + ...owner.pullRequest.display, + diff: + attributionMode === "tracked_authored_pr" && + owner.pullRequest.netOutcomeOwnedCompletely + ? owner.pullRequest.display.diff + : null, + } + : null; + const activityAt = + owner.kind === "pull_request" + ? maxInstant([ + activityAnchorAt, + owner.pullRequest.createdAt, + owner.pullRequest.statusChangedAt, + ]) + : activityAnchorAt; + const activityDay = dateKey(activityAt, WORK_LOG_TIME_ZONE); const factsDigest = digestJson({ activityAnchorAt, activityDay, @@ -772,6 +816,8 @@ const projectedUnitFrom = ( newestChange.logicalRepositoryId, newestChange.logicalSha ), + pullRequest, + activityAt, recipe: "github_work_unit_facts_v1", repositoryId: repository.id, visibility, @@ -779,7 +825,7 @@ const projectedUnitFrom = ( return { activityAnchorAt, - activityAt: activityAnchorAt, + activityAt, activityDay, attributionMode, branchLineageId: owner.kind === "branch" ? owner.ref.branchLineageId : null, @@ -795,6 +841,7 @@ const projectedUnitFrom = ( newestCommitRepositoryId: newestChange.repositoryId, newestCommitSha: newestChange.sha, outcomeDigest, + pullRequest, pullRequestNodeId: owner.kind === "pull_request" ? owner.pullRequest.nodeId : null, repositoryId: repository.id, diff --git a/src/lib/github-work-unit-projection-state.ts b/src/lib/github-work-unit-projection-state.ts index 3e3ed18c..7308a95c 100644 --- a/src/lib/github-work-unit-projection-state.ts +++ b/src/lib/github-work-unit-projection-state.ts @@ -19,8 +19,7 @@ type DatabaseTransaction = Parameters< const PROJECTION_LOCK = "github-work-unit-projection-v1"; // Bump whenever durable evidence maps to different work-unit ownership. -const PROJECTION_POLICY = - "github-work-unit-projection-v4-merged-pr-file-equivalence"; +const PROJECTION_POLICY = "github-work-unit-projection-v5-daily-pr-snapshots"; const PIPELINE_POLICY_DIGEST = createHash("sha256") .update( JSON.stringify({ diff --git a/src/lib/github-work-unit-store.ts b/src/lib/github-work-unit-store.ts index 28676039..4efffe9a 100644 --- a/src/lib/github-work-unit-store.ts +++ b/src/lib/github-work-unit-store.ts @@ -28,6 +28,7 @@ import type { import { TRACKED_GITHUB_USER_IDS } from "@/lib/github-commits-core"; import { chooseEffectivePullRequest, + hasPendingGitHubPullRequest, githubLogicalChangeKey, githubWorkUnitSummaryDiffEvidenceFrom, indexGitHubWorkUnitOwnershipEvidence, @@ -133,6 +134,7 @@ interface CurrentWorkUnitRow { membershipDigest: string; newestCommitSha: string; outcomeDigest: string | null; + pullRequest: GitHubProjectedWorkUnit["pullRequest"]; pullRequestNodeId: string | null; repositoryId: string; revision: number; @@ -358,6 +360,7 @@ const currentWorkUnitSelection = { membershipDigest: githubWorkUnits.membershipDigest, newestCommitSha: githubWorkUnits.newestCommitSha, outcomeDigest: githubWorkUnits.outcomeDigest, + pullRequest: githubWorkUnits.pullRequest, pullRequestNodeId: githubWorkUnits.pullRequestNodeId, repositoryId: githubWorkUnits.repositoryId, revision: githubWorkUnits.revision, @@ -471,6 +474,8 @@ const excludedChangesFrom = ( reason = "merged_pr_landing"; } else if (repository === undefined || repository.visibility === null) { reason = "repository_visibility_unknown"; + } else if (hasPendingGitHubPullRequest(change, ownership)) { + reason = "pull_request_coverage_incomplete"; } else if (effectivePullRequest !== null) { throw new Error( `Effective pull-request member was not projected: ${logicalKey}` @@ -976,6 +981,13 @@ const loadProjectionSnapshot = async ( authorUserId: githubPullRequests.authorUserId, baseRepositoryId: githubPullRequests.baseRepositoryId, baseSha: githubPullRequests.baseSha, + additions: githubPullRequests.additions, + deletions: githubPullRequests.deletions, + changedFiles: githubPullRequests.changedFiles, + draft: githubPullRequests.draft, + title: githubPullRequests.title, + statusChangedAt: githubPullRequests.statusChangedAt, + terminalAt: githubPullRequests.terminalAt, commitCount: githubPullRequests.commitCount, createdAt: githubPullRequests.createdAt, fileFactsComplete: githubPullRequests.fileFactsComplete, @@ -1062,6 +1074,26 @@ const loadProjectionSnapshot = async ( memberLogicalKeys.length > 0 && memberLogicalKeys.every((key) => eligibleChanges.has(key)), nodeId: row.nodeId, + display: { + title: row.title, + status: row.state === "open" && row.draft ? "draft" : row.state, + diff: + row.fileFactsComplete && + row.additions !== null && + row.deletions !== null && + row.changedFiles !== null + ? { + additions: row.additions, + deletions: row.deletions, + files: row.changedFiles, + } + : null, + }, + statusChangedAt: ( + row.statusChangedAt ?? + row.terminalAt ?? + row.createdAt + ).toISOString(), snapshotKind: row.state === "open" ? "current" : "final", state: row.state, }); @@ -1422,6 +1454,8 @@ const publicPayloadChanged = ( current.memberCount !== projected.facts.memberCount || current.newestCommitSha !== projected.newestCommitSha || current.outcomeDigest !== projected.outcomeDigest || + JSON.stringify(current.pullRequest) !== + JSON.stringify(projected.pullRequest) || current.pullRequestNodeId !== projected.pullRequestNodeId || current.repositoryId !== projected.repositoryId || current.visibility !== projected.visibility; @@ -1453,6 +1487,7 @@ const projectedValues = ( newestCommitRepositoryId: projected.newestCommitRepositoryId, newestCommitSha: projected.newestCommitSha, outcomeDigest: projected.outcomeDigest, + pullRequest: projected.pullRequest, pullRequestNodeId: projected.pullRequestNodeId, repositoryId: projected.repositoryId, revision, diff --git a/tests/daily-history.test.ts b/tests/daily-history.test.ts index b6b470c0..f999512c 100644 --- a/tests/daily-history.test.ts +++ b/tests/daily-history.test.ts @@ -13,7 +13,10 @@ import { buildPublicCodexStats, } from "../src/lib/codex/stats"; import { buildPublicGitHubActivityDays } from "../src/lib/github-activity-feed-core"; -import { githubSnapshotChanged } from "../src/lib/github-activity-snapshots"; +import { + githubSnapshotChanged, + mutableGitHubDay, +} from "../src/lib/github-activity-snapshots"; test("daily storage round-trips explicit zeros, gaps, source units and archived units", () => { const snapshot = createCodexAccountSnapshot({ stats: {} }, {}); @@ -196,3 +199,54 @@ test("saved display days keep both sides of IST midnight in their own page", () ["2026-09-27", 1], ]); }); + +test("work freezes one hour after IST midnight", () => { + expect(mutableGitHubDay(new Date("2026-09-28T18:29:59Z"))).toBe("2026-09-28"); + expect(mutableGitHubDay(new Date("2026-09-28T18:30:00Z"))).toBe("2026-09-28"); + expect(mutableGitHubDay(new Date("2026-09-28T19:29:59Z"))).toBe("2026-09-28"); + expect(mutableGitHubDay(new Date("2026-09-28T19:30:00Z"))).toBe("2026-09-29"); +}); + +test("status transitions publish without a new code digest; title edits and status hydration do not repost", () => { + const before = saved("same-code"); + const after = saved("same-code"); + if (!("facts" in before.payload) || !("facts" in after.payload)) { + throw new Error("Expected work"); + } + before.payload.pullRequest = { title: "Search", status: "open", diff: null }; + after.payload.pullRequest = { + ...before.payload.pullRequest, + status: "merged", + }; + expect(githubSnapshotChanged(before, after)).toBe(true); + after.payload.pullRequest = { + ...before.payload.pullRequest, + title: "Search and filters", + }; + expect(githubSnapshotChanged(before, after)).toBe(false); + expect(githubSnapshotChanged(saved("same-code"), after)).toBe(false); + const issue = { + ...before, + payload: { + day: before.day, + id: "issue:1", + activityAt: "2026-09-28T10:00:00Z", + repository: before.payload.repository, + destination: null, + title: "Search", + status: "open" as const, + }, + }; + expect( + githubSnapshotChanged(issue, { + ...issue, + payload: { ...issue.payload, status: "completed" }, + }) + ).toBe(true); + expect( + githubSnapshotChanged(issue, { + ...issue, + payload: { ...issue.payload, title: "New title" }, + }) + ).toBe(false); +}); diff --git a/tests/github-activity-days.test.tsx b/tests/github-activity-days.test.tsx index 69e5cc69..00787ffb 100644 --- a/tests/github-activity-days.test.tsx +++ b/tests/github-activity-days.test.tsx @@ -59,3 +59,64 @@ test("the initial homepage HTML includes saved IST work across UTC midnight with expect(html).toContain("5:08 AM"); expect(html).not.toContain("yesterday"); }); + +test("work uses accessible status icons, PR totals and useful title fallbacks; issues count as updates", () => { + const repository = { key: "1", label: "Private", url: null, avatarUrl: null }; + const days = buildPublicGitHubActivityDays({ + days: ["2026-09-28"], + workUnits: ["open", "draft", "merged", "closed"].map((status) => ({ + activityAt: "2026-09-28T12:00:00Z", + day: "2026-09-28", + destination: null, + id: status, + kind: "pull-request" as const, + headline: null, + summary: null, + summarizing: false, + repository, + facts: { + additions: 100, + deletions: 80, + ownedCommitCount: 2, + uniqueFileCount: 1, + languages: null, + dateRange: null, + }, + pullRequest: { + title: "Add filtering", + status: status as "open" | "draft" | "merged" | "closed", + diff: { additions: 20, deletions: 0, files: 1 }, + }, + })), + issues: ["open", "completed", "not-planned", "closed"].map((status) => ({ + activityAt: "2026-09-28T12:00:00Z", + day: "2026-09-28", + destination: null, + id: `issue-${status}`, + title: "Improve filtering", + repository, + status: status as "open" | "completed" | "not-planned" | "closed", + })), + }); + const html = renderToStaticMarkup( + + ); + for (const label of [ + "Pull request open", + "Pull request draft", + "Pull request merged", + "Pull request closed", + "Issue open", + "Issue completed", + "Issue closed as not planned", + "Issue closed", + "Add filtering", + "PR total", + "8 updates across 1 repo", + ]) { + expect(html).toContain(label); + } + expect(html).not.toContain("+100"); + expect(html).not.toContain("−80"); + expect(html).not.toContain("Direct canonical"); +}); diff --git a/tests/github-activity-feed-core.test.ts b/tests/github-activity-feed-core.test.ts index 0d979da2..59079263 100644 --- a/tests/github-activity-feed-core.test.ts +++ b/tests/github-activity-feed-core.test.ts @@ -55,7 +55,7 @@ describe("public GitHub activity day projection", () => { repository: repository("42"), items: [ { - kind: "issue-opened", + kind: "issue", activityAt: "2026-09-04T12:00:00.000Z", id: "issue", title: "Investigate", diff --git a/tests/github-commits.test.ts b/tests/github-commits.test.ts index b0fe75f1..c99cefb3 100644 --- a/tests/github-commits.test.ts +++ b/tests/github-commits.test.ts @@ -127,6 +127,10 @@ const sparsePullRequestEventFrom = ( "f0rr0" ); const issue = { + state: "open", + state_reason: null, + closed_at: null, + updated_at: "2026-08-26T12:00:00Z", created_at: "2026-08-26T10:00:00Z", html_url: "https://github.com/another-org/private-repo/issues/91", id: 654, @@ -270,7 +274,7 @@ describe("authenticated user events", () => { }); }); - test("normalizes only opened issues authored by the authenticated account", () => { + test("normalizes issue activity authored by the authenticated account", () => { expect( githubEventFrom( { @@ -521,7 +525,7 @@ describe("issue observation normalization", () => { ).toBeNull(); }); - test("preserves the immutable issue-creation snapshot", () => { + test("normalizes issue state and provider timestamps", () => { const facts = repositoryFactsFrom(webhookRepository); expect(facts).not.toBeNull(); assert.ok(facts); @@ -535,6 +539,10 @@ describe("issue observation normalization", () => { repository: facts, title: "Make event intake durable", url: "https://github.com/another-org/private-repo/issues/91", + activityAt: "2026-08-26T10:00:00.000Z", + providerUpdatedAt: "2026-08-26T12:00:00.000Z", + closedAt: null, + status: "open", }); expect( issueFromGitHub( @@ -548,7 +556,7 @@ describe("issue observation normalization", () => { ).toBeNull(); }); - test("accepts only an opened issues webhook and attributes the author", () => { + test("accepts issue changes and attributes the author", () => { expect( issueFromWebhook({ action: "opened", @@ -559,7 +567,7 @@ describe("issue observation normalization", () => { ).toMatchObject({ account: "f0rr0", nodeId: "I_kwDOExample" }); expect( issueFromWebhook({ - action: "edited", + action: "labeled", issue, repository: webhookRepository, }) @@ -1363,3 +1371,48 @@ describe("GitHub request deferral", () => { ); }); }); + +test("issue webhooks preserve completed, not-planned and reopened states and reject malformed dates", () => { + for (const [action, state, state_reason, status] of [ + ["closed", "closed", "completed", "completed"], + ["closed", "closed", "not_planned", "not-planned"], + ["closed", "closed", null, "closed"], + ["reopened", "open", "reopened", "open"], + ["edited", "open", null, "open"], + ] as const) { + expect( + issueFromWebhook({ + action, + repository: webhookRepository, + issue: { + ...issue, + state, + state_reason, + closed_at: state === "closed" ? "2026-08-26T12:00:00Z" : null, + }, + })?.status + ).toBe(status); + } + for (const changes of [ + { updated_at: "bad" }, + { state: "closed", closed_at: null }, + { state: "open", closed_at: "2026-08-26T12:00:00Z" }, + ]) { + expect( + issueFromWebhook({ + action: "closed", + repository: webhookRepository, + issue: { ...issue, ...changes }, + }) + ).toBeNull(); + } +}); + +test("a newly discovered reopened issue uses its reopening time", () => { + const result = issueFromWebhook({ + action: "reopened", + repository: webhookRepository, + issue, + }); + expect(result?.activityAt).toBe("2026-08-26T12:00:00.000Z"); +}); diff --git a/tests/github-work-unit-core.test.ts b/tests/github-work-unit-core.test.ts index d4f0d6d3..4290389a 100644 --- a/tests/github-work-unit-core.test.ts +++ b/tests/github-work-unit-core.test.ts @@ -122,6 +122,12 @@ const pullRequest = ( netOutcome: null, netOutcomeOwnedCompletely: true, nodeId, + display: { + title: "Example work", + status: overrides.state ?? "open", + diff: null, + }, + statusChangedAt: "2026-08-29T12:00:00.000Z", snapshotKind: "current", state: "open", ...overrides, @@ -141,7 +147,7 @@ const logicalKeyFrom = (item: GitHubLogicalChange) => githubLogicalChangeKey(item.logicalRepositoryId, item.logicalSha); describe("deterministic GitHub work ownership", () => { - test("moving heads and closing a PR do not revise unchanged work", () => { + test("moving heads do not revise work; PR status changes do", () => { const work = change("c"); const key = logicalKeyFrom(work); const foreign = change("d", { authorUserId: "200" }); @@ -174,7 +180,8 @@ describe("deterministic GitHub work ownership", () => { }), ], }); - expect(merged.factsDigest).toBe(open.factsDigest); + expect(merged.factsDigest).not.toBe(open.factsDigest); + expect(merged.pullRequest?.status).toBe("merged"); const [changed] = projection({ changes: [{ ...work, additions: work.additions + 1 }], pullRequests: [pullRequest("PR_stable", [key])], @@ -745,3 +752,114 @@ describe("deterministic GitHub work ownership", () => { ).toThrow("cycle"); }); }); + +test("PR display uses one combined diff, preserves private access, and withholds totals for contributions", () => { + const first = change("a", { fileFacts: [file("src/file.ts", 100, 0)] }); + const second = change("b", { fileFacts: [file("src/file.ts", 0, 80)] }); + const pr = pullRequest("PR_net", [first, second].map(logicalKeyFrom), { + display: { + title: "Add search", + status: "open", + diff: { additions: 20, deletions: 0, files: 1 }, + }, + }); + const input = { + changes: [first, second], + pullRequests: [pr], + repositories: [repository("1", { visibility: "private" })], + }; + const [own] = projection(input); + expect(own.pullRequest?.diff).toEqual({ + additions: 20, + deletions: 0, + files: 1, + }); + expect(own.visibility).toBe("private"); + for (const override of [ + { authorUserId: "200" }, + { netOutcomeOwnedCompletely: false }, + ]) { + const [contribution] = projection({ + ...input, + pullRequests: [{ ...pr, ...override }], + }); + expect(contribution.pullRequest?.diff).toBeNull(); + } +}); + +test("a merge, close, reopen, and ready-for-review change the activity day without changing the code outcome", () => { + const work = change("a"); + const pr = pullRequest("PR_status", [logicalKeyFrom(work)], { + netOutcome: { + complete: true, + providerFileCapReached: false, + files: work.fileFacts, + }, + }); + const [open] = projection({ changes: [work], pullRequests: [pr] }); + for (const status of ["merged", "closed", "open", "draft"] as const) { + const [updated] = projection({ + changes: [work], + pullRequests: [ + { + ...pr, + state: status === "draft" ? "open" : status, + snapshotKind: + status === "merged" || status === "closed" ? "final" : "current", + display: { ...pr.display, status }, + statusChangedAt: "2026-08-30T19:00:00.000Z", + }, + ], + }); + expect(updated.activityDay).toBe("2026-08-31"); + expect(updated.activityAt).toBe("2026-08-30T19:00:00.000Z"); + expect(updated.outcomeDigest).toBe(open.outcomeDigest); + expect(updated.activityAnchorAt).toBe(open.activityAnchorAt); + } +}); + +test("direct work groups on IST midnight, including across UTC midnight", () => { + for (const [times, days] of [ + [ + ["2026-09-28T18:29:59Z", "2026-09-28T18:30:00Z"], + ["2026-09-28", "2026-09-29"], + ], + [["2026-09-28T20:00:00Z", "2026-09-29T02:00:00Z"], ["2026-09-29"]], + ]) { + const changes = times.map((logicalActivityAt, index) => + change(index ? "b" : "a", { logicalActivityAt }) + ); + const units = projection({ + changes, + refs: [ref("refs/heads/main", changes.map(logicalKeyFrom))], + }); + expect(units.map((unit) => unit.activityDay).toSorted()).toEqual(days); + expect(units.reduce((count, unit) => count + unit.members.length, 0)).toBe( + 2 + ); + } +}); + +test("a PR refresh cannot temporarily relabel its commits as direct work", () => { + const work = change("a", { associatedPullRequestNodeIds: ["PR_pending"] }); + const pr = pullRequest("PR_pending", [], { membershipComplete: false }); + const input = { + changes: [work], + pullRequests: [pr], + refs: [ref("refs/heads/main", [logicalKeyFrom(work)])], + }; + expect(projection(input)).toEqual([]); + expect( + projection({ + ...input, + pullRequests: [pr, pullRequest("PR_other", [logicalKeyFrom(work)])], + }) + ).toEqual([]); + // Complete evidence that the commit was removed permits ordinary ownership again. + expect( + projection({ + ...input, + pullRequests: [{ ...pr, membershipComplete: true }], + })[0].kind + ).toBe("canonical_day"); +}); diff --git a/tests/github-work-unit-crosswalk.test.ts b/tests/github-work-unit-crosswalk.test.ts index fd8d5033..c5b18c60 100644 --- a/tests/github-work-unit-crosswalk.test.ts +++ b/tests/github-work-unit-crosswalk.test.ts @@ -92,6 +92,8 @@ const pullRequest = ({ netOutcome: null, netOutcomeOwnedCompletely: false, nodeId, + display: { title: "Example work", status: "open", diff: null }, + statusChangedAt: "2026-08-29T12:00:00.000Z", snapshotKind: "current", state: "open", }); @@ -141,6 +143,7 @@ const unit = ({ newestCommitRepositoryId: repositoryId, newestCommitSha: members.at(-1)?.split("/")[1] ?? sha("0"), outcomeDigest: null, + pullRequest: null, pullRequestNodeId: kind === "pull_request" ? identityKey.slice(3) : null, repositoryId, visibility: repositoryId === "3" ? "private" : "public", diff --git a/tests/hourly-publication-resilience.test.ts b/tests/hourly-publication-resilience.test.ts index 96331081..6abf128c 100644 --- a/tests/hourly-publication-resilience.test.ts +++ b/tests/hourly-publication-resilience.test.ts @@ -51,8 +51,9 @@ test("publication keeps source dates through delayed syncs, metadata revisions a insert: table => ({ values: value => ({ onConflictDoUpdate: options => ({ returning: async () => { assert.equal(table, githubActivitySnapshots); const condition = new PgDialect().sqlToQuery(options.setWhere); - assert.deepEqual(condition.params, ["2026-09-29"]); - assert.equal(condition.sql, '"github_activity_snapshots"."day" = $1'); + assert.deepEqual(condition.params, ["2026-09-28", value.payload.activityAt]); + assert.ok(condition.sql.includes('"github_activity_snapshots"."day" >= $1')); + assert.ok(condition.sql.includes("::timestamptz <= $2::timestamptz")); writes.push(value); return []; } }) }) }), @@ -63,16 +64,15 @@ test("publication keeps source dates through delayed syncs, metadata revisions a const { publishGitHubActivitySnapshots } = await import("./src/lib/github-activity-snapshots.ts"); await publishGitHubActivitySnapshots(); assert.deepEqual(writes.map(row => [row.identityKey, row.day, row.payload.activityAt]), [ - ["canonical:1:2026-08-04", "2026-08-04", "2026-08-04T09:05:36Z"], ["late", "2026-09-28", "2026-09-28T18:29:59Z"], ["changed-pr", "2026-09-29", "2026-09-28T18:30:00Z"], ["late-issue", "2026-09-28", "2026-09-28T18:29:59Z"], ]); - assert.equal(writes[2].payload.facts.additions, 240); + assert.equal(writes[1].payload.facts.additions, 240); `); }); -test("only the hourly invocation publishes and warms changed activity, without changing ingestion limits or auth", () => { +test("every worker publishes and warms changed activity without changing ingestion limits or auth", () => { check(` import assert from "node:assert/strict"; import { mock } from "bun:test"; @@ -99,14 +99,14 @@ test("only the hourly invocation publishes and warms changed activity, without c { method: "POST", headers: authorized ? { authorization: "Bearer hourly-publication-test-secret-32-characters" } : {} } )); assert.equal((await request("?publish=1", false)).status, 401); - for (const query of ["?publish=0", "?publish=yes", "?publish=", "?batch=0&publish=1"]) { + for (const query of ["?batch=0", "?batch=no", "?batch="]) { assert.equal((await request(query)).status, 400); } assert.equal(calls.length, 0); assert.equal((await request("")).status, 200); assert.equal((await request("?publish=1")).status, 200); assert.equal((await request("?publish=1&batch=2")).status, 200); - assert.equal(calls[0].includeProjection, false); + assert.equal(calls[0].includeProjection, true); assert.equal(calls[1].includeProjection, true); assert.deepEqual({ ...calls[0], includeProjection: true }, calls[1]); assert.equal(calls[2].includeProjection, true); @@ -116,10 +116,10 @@ test("only the hourly invocation publishes and warms changed activity, without c assert.equal(callbacks.length, 0); changed = true; assert.equal((await request("?publish=1")).status, 200); - assert.deepEqual(events, [["public-github-activity", "max"]]); + assert.deepEqual(events, [["public-github-activity", { expire: 0 }]]); assert.equal(callbacks.length, 1); await callbacks.shift()(); - assert.deepEqual(events, [["public-github-activity", "max"], "warmed"]); + assert.deepEqual(events, [["public-github-activity", { expire: 0 }], "warmed"]); events.length = 0; fail = true; assert.equal((await request("?publish=1")).status, 503); From 3f2b411d07fce3e9075aaed7cee4b8561ca9d029 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 28 Sep 2026 15:59:00 +0000 Subject: [PATCH 2/7] fix: use one site timezone and preserve provider dates --- docs/codex-stats.md | 5 ++- src/components/codex-stats.tsx | 9 +---- src/components/date-time.tsx | 8 ++-- src/components/github-activity-days.tsx | 6 +-- src/components/token-details.tsx | 2 +- src/components/token-history.tsx | 26 ++++++++----- src/content/site.ts | 2 +- src/content/tokens.ts | 4 +- src/lib/codex/history.ts | 7 +++- src/lib/codex/stats.ts | 6 +-- src/lib/date.ts | 26 ++++++------- src/lib/github-activity-snapshots.ts | 8 ++-- src/lib/github-activity-store.ts | 13 ++++--- src/lib/github-work-unit-core.ts | 6 +-- src/lib/github-work-unit-store.ts | 14 +++---- src/lib/github-work-unit-summary-store.ts | 10 +++-- src/lib/remark-embed-github.mjs | 5 ++- tests/codex-stats.test.ts | 47 +++++++++++++++++++++-- tests/daily-history.test.ts | 5 ++- tests/date.test.ts | 14 +++++++ tests/github-activity-days.test.tsx | 4 +- 21 files changed, 143 insertions(+), 84 deletions(-) diff --git a/docs/codex-stats.md b/docs/codex-stats.md index 0a926385..d61141d8 100644 --- a/docs/codex-stats.md +++ b/docs/codex-stats.md @@ -106,7 +106,6 @@ Configure public presentation in `src/content/tokens.ts`: - `rankingLimit`: number of ranked tools and skills displayed. - `accountLabels`: optional public labels keyed by registered account ID; defaults to numbered accounts. Credentials and upstream account identities stay private. -- `timeZone`: timezone for displaying allowance reset dates. - `excludedTools`: exact plugin/skill names to exclude from public data, including older snapshots. Stored data is not deleted. @@ -118,7 +117,9 @@ The detailed breakdowns use internal Codex endpoints. History means available records within `historyDays`, not complete lifetime coverage. Changing the window triggers a fresh backfill. Requests fetch up to 100 named tools/skills; upstream may group additional entries. These sources may lag behind profile totals. Failed -requests retain previous data; empty sections are hidden. Day boundaries are UTC. +requests retain previous data; empty sections are hidden. Provider daily totals keep +their reported dates. Timestamps use the site's IST timezone; daily aggregates are +not relabeled as an IST “today” total. Token and invocation counts sum across accounts. Cache hit rate is weighted by input tokens from rows with all components reported; missing components are not diff --git a/src/components/codex-stats.tsx b/src/components/codex-stats.tsx index 354b3714..8c6e514e 100644 --- a/src/components/codex-stats.tsx +++ b/src/components/codex-stats.tsx @@ -104,9 +104,8 @@ const LimitBar = ({ export function CodexTotals({ stats }: { stats: PublicCodexStats }) { return ( - + - @@ -264,11 +263,7 @@ export function CodexUsageLimit({ stats }: { stats: PublicCodexStats }) {

    Resets{" "}

    )} diff --git a/src/components/date-time.tsx b/src/components/date-time.tsx index 5daf9259..400b7dd6 100644 --- a/src/components/date-time.tsx +++ b/src/components/date-time.tsx @@ -6,20 +6,18 @@ import type { dateFormats } from "@/lib/date"; export function DateTime({ dateTime, format = "date", - timeZone = "UTC", ...props }: Omit, "dateTime" | "children"> & { dateTime: string; format?: keyof typeof dateFormats; - timeZone?: string; }) { return ( ); } diff --git a/src/components/github-activity-days.tsx b/src/components/github-activity-days.tsx index 08ce05b8..5caa851e 100644 --- a/src/components/github-activity-days.tsx +++ b/src/components/github-activity-days.tsx @@ -22,7 +22,7 @@ import { TooltipGroup, TooltipTrigger, } from "@/components/site-tooltip"; -import { dateKey, formatDate, WORK_LOG_TIME_ZONE } from "@/lib/date"; +import { dateKey, formatDate } from "@/lib/date"; import { getVisibleGitHubActivityDays } from "@/lib/github-activity-feed-core"; import type { PublicGitHubActivityDay, @@ -210,7 +210,6 @@ function WorkUnitRow({ className="whitespace-nowrap" dateTime={item.activityAt} format="time" - timeZone={WORK_LOG_TIME_ZONE} /> @@ -278,7 +277,6 @@ function IssueRow({ className="whitespace-nowrap" dateTime={item.activityAt} format="time" - timeZone={WORK_LOG_TIME_ZONE} /> @@ -399,7 +397,7 @@ export function GitHubActivityDays({ preview?: boolean; now: string; }>) { - const today = dateKey(now, WORK_LOG_TIME_ZONE); + const today = dateKey(now); const activeDays = getVisibleGitHubActivityDays(days, today); const visibleDays = preview ? activeDays.slice(0, 1) : activeDays; return ( diff --git a/src/components/token-details.tsx b/src/components/token-details.tsx index 2e288c13..8d317215 100644 --- a/src/components/token-details.tsx +++ b/src/components/token-details.tsx @@ -373,7 +373,7 @@ export function TokenUsageDetails({
    ) : null} diff --git a/src/components/token-history.tsx b/src/components/token-history.tsx index cfd09812..ae58d23f 100644 --- a/src/components/token-history.tsx +++ b/src/components/token-history.tsx @@ -45,14 +45,14 @@ function historyAxis( function HistoryPlot({ history, - today, + reportingDay, mode, }: { history: TokenHistory; - today: string; + reportingDay: string; mode: "daily" | "cumulative"; }) { - const summary = summarizeTokenHistory(history, today); + const summary = summarizeTokenHistory(history, reportingDay); const rows = mode === "cumulative" ? summary.cumulativeRows : summary.rows; const { peak } = summary; const peakLabel = @@ -121,8 +121,8 @@ function HistoryPlot({ ? "Usage unavailable" : `${number.format(row.tokens)} ${mode === "cumulative" && summary.partial ? "recorded " : ""}tokens`}

    - {row.day === today ? ( -

    Today is incomplete

    + {row.day === reportingDay ? ( +

    Still updating

    ) : null} ) : null; @@ -203,12 +203,16 @@ function HistoryPlot({ export function TokenHistoryChart({ history, - today, + reportingDay, }: { history: TokenHistory; - today: string; + reportingDay: string; }) { - if (!history.values.some((row) => row.day <= today && row.tokens !== null)) { + if ( + !history.values.some( + (row) => row.day <= reportingDay && row.tokens !== null + ) + ) { return null; } return ( @@ -236,7 +240,11 @@ export function TokenHistoryChart({ > {(["cumulative", "daily"] as const).map((mode) => ( - + ))} diff --git a/src/content/site.ts b/src/content/site.ts index 79b86eed..4b2094ce 100644 --- a/src/content/site.ts +++ b/src/content/site.ts @@ -4,7 +4,7 @@ export const sitePreferences = { description: "I build software, from the interfaces people use to the systems that power them. These days, that includes AI. I write about what I learn along the way.", language: "en-US", - workLogTimeZone: "Asia/Kolkata", + timeZone: "Asia/Kolkata", }; // Public authors to include in the work log. Credentials never select authors. diff --git a/src/content/tokens.ts b/src/content/tokens.ts index 287c96e0..35788ad0 100644 --- a/src/content/tokens.ts +++ b/src/content/tokens.ts @@ -1,4 +1,4 @@ -import { siteNavigation, sitePreferences } from "@/content/site"; +import { siteNavigation } from "@/content/site"; // Public presentation only. Account credentials stay in the existing server store. export interface TokenPreferences { @@ -14,7 +14,6 @@ export interface TokenPreferences { delegation: boolean; limits: boolean; }; - timeZone: string; historyDays: number; rankingLimit: number; accountLabels: Readonly>; @@ -36,7 +35,6 @@ export const tokenPreferences: TokenPreferences = { delegation: true, limits: true, }, - timeZone: sitePreferences.workLogTimeZone, historyDays: 365, rankingLimit: 5, accountLabels: {}, diff --git a/src/lib/codex/history.ts b/src/lib/codex/history.ts index c9f5f31c..5b70d38f 100644 --- a/src/lib/codex/history.ts +++ b/src/lib/codex/history.ts @@ -3,8 +3,11 @@ export interface TokenHistory { values: { day: string; tokens: number | null }[]; } -export function summarizeTokenHistory(history: TokenHistory, today: string) { - const reported = history.values.filter((row) => row.day <= today); +export function summarizeTokenHistory( + history: TokenHistory, + reportingDay: string +) { + const reported = history.values.filter((row) => row.day <= reportingDay); const rows = reported.map((row) => ({ ...row, tokens: row.tokens ?? 0 })); let total = 0; const cumulativeRows: TokenHistory["values"] = []; diff --git a/src/lib/codex/stats.ts b/src/lib/codex/stats.ts index d2adc778..397c2176 100644 --- a/src/lib/codex/stats.ts +++ b/src/lib/codex/stats.ts @@ -175,7 +175,6 @@ export interface PublicCodexStats { longestRunningTurnSec: PublicCodexMetric; totalSkillsUsed: PublicCodexMetric; totalThreads: PublicCodexMetric; - todayTokens: PublicCodexMetric; }; } @@ -499,6 +498,7 @@ export const buildPublicCodexStats = ( return null; } + // Codex supplies whole-day buckets, not timestamps we can regroup into IST. const today = now.toISOString().slice(0, 10); const missingAccountCount = Math.max( 0, @@ -705,10 +705,6 @@ export const buildPublicCodexStats = ( ], sum ), - todayTokens: { - partial: dailyPartial, - value: hasKnownDaily ? (combinedDaily.get(today) ?? 0) : null, - }, }, }; }; diff --git a/src/lib/date.ts b/src/lib/date.ts index eacd7350..88e17351 100644 --- a/src/lib/date.ts +++ b/src/lib/date.ts @@ -1,6 +1,6 @@ import { sitePreferences } from "@/content/site"; -export const WORK_LOG_TIME_ZONE = sitePreferences.workLogTimeZone; +const calendarDate = /^\d{4}-\d{2}-\d{2}$/u; export const dateFormats = { date: { dateStyle: "medium" }, @@ -13,13 +13,6 @@ export const dateFormats = { day: { dateStyle: "full" }, month: { month: "short" }, time: { hour: "numeric", minute: "2-digit", hour12: true }, - clock: { - hour: "numeric", - minute: "2-digit", - second: "2-digit", - hour12: true, - timeZoneName: "short", - }, dateTime: { dateStyle: "medium", timeStyle: "short", hour12: true }, } satisfies Record; @@ -28,22 +21,29 @@ const dateFormatters = new Map(); export const formatDate = ( value: Date | string, format: keyof typeof dateFormats = "date", - timeZone = "UTC" + timeZone = sitePreferences.timeZone ) => { - const key = `${format}:${timeZone}`; + // A provider's calendar date has no clock to convert. + const zone = + typeof value === "string" && calendarDate.test(value) ? "UTC" : timeZone; + const key = `${format}:${zone}`; const formatter = dateFormatters.get(key) ?? new Intl.DateTimeFormat(sitePreferences.language, { ...dateFormats[format], - timeZone, + timeZone: zone, }); dateFormatters.set(key, formatter); return formatter.format(new Date(value)); }; -export const dateKey = (value: Date | string, timeZone = "UTC") => +export const dateKey = ( + value: Date | string, + timeZone = sitePreferences.timeZone +) => new Intl.DateTimeFormat("en-CA", { - timeZone, + timeZone: + typeof value === "string" && calendarDate.test(value) ? "UTC" : timeZone, year: "numeric", month: "2-digit", day: "2-digit", diff --git a/src/lib/github-activity-snapshots.ts b/src/lib/github-activity-snapshots.ts index 3c757f45..48460e6f 100644 --- a/src/lib/github-activity-snapshots.ts +++ b/src/lib/github-activity-snapshots.ts @@ -9,7 +9,7 @@ import { githubWorkUnits, githubWorkUnitSummaryAttempts, } from "@/db/schema"; -import { dateKey, WORK_LOG_TIME_ZONE } from "@/lib/date"; +import { dateKey } from "@/lib/date"; import { readCurrentPublicGitHubRows } from "@/lib/github-activity-store"; import { acquireGitHubWorkUnitProjectionLock } from "@/lib/github-work-unit-projection-state"; import { decodeGitHubWorkUnitSummary } from "@/lib/github-work-unit-summary"; @@ -74,7 +74,7 @@ export const githubSnapshotChanged = (previous: Saved, next: Saved) => { // One hour for delayed ingestion. Only source activity from that day may fill it. export const mutableGitHubDay = (now: Date) => - dateKey(new Date(now.getTime() - 60 * 60 * 1000), WORK_LOG_TIME_ZONE); + dateKey(new Date(now.getTime() - 60 * 60 * 1000)); export const fillSavedGitHubSummaries = async (transaction: Transaction) => { // The queue owns the exact immutable input. Fill empty prose once, never use @@ -151,7 +151,7 @@ export const publishGitHubActivitySnapshots = async ( const bootstrap = !head?.initialized; const publicationRepositoryIds = bootstrap ? undefined : repositoryIds; const now = new Date(); - const today = dateKey(now, WORK_LOG_TIME_ZONE); + const today = dateKey(now); const mutableStart = mutableGitHubDay(now); const scope = publicationRepositoryIds ? inArray(githubWorkUnits.repositoryId, [...publicationRepositoryIds]) @@ -194,7 +194,7 @@ export const publishGitHubActivitySnapshots = async ( for (const row of [...rows.workUnits, ...rows.issues]) { const old = previous.get(row.id); const unit = byIdentity.get(row.id); - const day = dateKey(row.activityAt, WORK_LOG_TIME_ZONE); + const day = dateKey(row.activityAt); if (day > today || (!bootstrap && day < mutableStart)) { continue; } diff --git a/src/lib/github-activity-store.ts b/src/lib/github-activity-store.ts index 9f173bf9..f424d51a 100644 --- a/src/lib/github-activity-store.ts +++ b/src/lib/github-activity-store.ts @@ -1,5 +1,6 @@ import { and, desc, eq, gte, inArray, lt, or, sql } from "drizzle-orm"; +import { sitePreferences } from "@/content/site"; import { getDatabase } from "@/db/client"; import { githubActivitySnapshots, @@ -10,7 +11,7 @@ import { githubWorkUnitSummaryAttempts, githubWorkUnits, } from "@/db/schema"; -import { dateKey, WORK_LOG_TIME_ZONE } from "@/lib/date"; +import { dateKey } from "@/lib/date"; import { encodeGitHubActivityCursor } from "@/lib/github-activity-cursor"; import type { GitHubActivityCursor } from "@/lib/github-activity-cursor"; import { buildPublicGitHubActivityDays } from "@/lib/github-activity-feed-core"; @@ -208,7 +209,7 @@ const issueActivityAt = sql`coalesce(${githubIssues.activityAt}, ${githubIssues.createdAt})`.mapWith( githubIssues.createdAt ); -const issueDay = sql`to_char(${issueActivityAt} AT TIME ZONE 'Asia/Kolkata', 'YYYY-MM-DD')`; +export const githubIssueActivityDay = sql`to_char(${issueActivityAt} AT TIME ZONE ${sitePreferences.timeZone}, 'YYYY-MM-DD')`; interface AvailableDays { hasNextPage: boolean; @@ -316,7 +317,7 @@ export const readCurrentPublicGitHubRows = async ( .select({ activityAt: issueActivityAt, status: githubIssues.status, - day: issueDay, + day: githubIssueActivityDay, fullName: githubRepositories.fullName, nodeId: githubIssues.nodeId, number: githubIssues.number, @@ -334,7 +335,7 @@ export const readCurrentPublicGitHubRows = async ( and( options.sinceDay === undefined ? undefined - : gte(issueDay, options.sinceDay), + : gte(githubIssueActivityDay, options.sinceDay), options.repositoryIds ? inArray(githubIssues.repositoryId, [...options.repositoryIds]) : undefined, @@ -527,8 +528,8 @@ export const readCurrentPublicGitHubRows = async ( url: `${repository.baseUrl}/commit/${row.newestCommitSha}`, }; } - const firstDay = dateKey(row.firstActivityAt, WORK_LOG_TIME_ZONE); - const lastDay = dateKey(row.lastActivityAt, WORK_LOG_TIME_ZONE); + const firstDay = dateKey(row.firstActivityAt); + const lastDay = dateKey(row.lastActivityAt); const summary = currentSummaries.get(row.id) ?? fallbackSummaries.get(row.id); return { diff --git a/src/lib/github-work-unit-core.ts b/src/lib/github-work-unit-core.ts index bceedf80..58d69284 100644 --- a/src/lib/github-work-unit-core.ts +++ b/src/lib/github-work-unit-core.ts @@ -1,6 +1,6 @@ import { createHash } from "node:crypto"; -import { dateKey, WORK_LOG_TIME_ZONE } from "@/lib/date"; +import { dateKey } from "@/lib/date"; import type { GitHubPullRequestDisplay } from "@/lib/github-activity-types"; import { aggregateGitHubLanguages } from "@/lib/github-change-evidence"; import type { @@ -659,7 +659,7 @@ const chooseOwnerFor = ( ); if (canonicalRef !== undefined) { return { - activityDay: dateKey(change.logicalActivityAt, WORK_LOG_TIME_ZONE), + activityDay: dateKey(change.logicalActivityAt), kind: "canonical_day", ref: canonicalRef, repository, @@ -802,7 +802,7 @@ const projectedUnitFrom = ( owner.pullRequest.statusChangedAt, ]) : activityAnchorAt; - const activityDay = dateKey(activityAt, WORK_LOG_TIME_ZONE); + const activityDay = dateKey(activityAt); const factsDigest = digestJson({ activityAnchorAt, activityDay, diff --git a/src/lib/github-work-unit-store.ts b/src/lib/github-work-unit-store.ts index 4efffe9a..28376da5 100644 --- a/src/lib/github-work-unit-store.ts +++ b/src/lib/github-work-unit-store.ts @@ -19,7 +19,10 @@ import { githubWorkUnits, } from "@/db/schema"; import { publishGitHubActivitySnapshots } from "@/lib/github-activity-snapshots"; -import { PUBLIC_GITHUB_ACTIVITY_DAY_PAGE_SIZE } from "@/lib/github-activity-store"; +import { + githubIssueActivityDay, + PUBLIC_GITHUB_ACTIVITY_DAY_PAGE_SIZE, +} from "@/lib/github-activity-store"; import type { GitHubFileChangeStat, GitHubLanguageFact, @@ -387,8 +390,6 @@ const readCurrentUnits = async ( return lock ? await query.for("update") : await query; }; -const issueDayFrom = (createdAt: Date) => createdAt.toISOString().slice(0, 10); - const sortedUniqueDays = (days: readonly string[]) => [...new Set(days)].toSorted((left, right) => bytewiseCompare(right, left)); @@ -1275,9 +1276,8 @@ const loadProjectionSnapshot = async ( : projectGitHubWorkUnits(input, ownership, { outcomeDigests }); const excludedChanges = excludedChangesFrom(input, units, ownership); const issueRows = await transaction - .select({ - createdAt: githubIssues.createdAt, - visibility: githubRepositories.visibility, + .selectDistinct({ + day: githubIssueActivityDay, }) .from(githubIssues) .innerJoin( @@ -1294,7 +1294,7 @@ const loadProjectionSnapshot = async ( ]) ) ); - const issueDays = issueRows.map((issue) => issueDayFrom(issue.createdAt)); + const issueDays = issueRows.map((issue) => issue.day); return { currentUnits, input, diff --git a/src/lib/github-work-unit-summary-store.ts b/src/lib/github-work-unit-summary-store.ts index c3bcce63..4aa9c2f1 100644 --- a/src/lib/github-work-unit-summary-store.ts +++ b/src/lib/github-work-unit-summary-store.ts @@ -26,7 +26,10 @@ import { } from "@/db/schema"; import { env } from "@/env"; import { fillSavedGitHubSummaries } from "@/lib/github-activity-snapshots"; -import { PUBLIC_GITHUB_ACTIVITY_DAY_PAGE_SIZE } from "@/lib/github-activity-store"; +import { + githubIssueActivityDay, + PUBLIC_GITHUB_ACTIVITY_DAY_PAGE_SIZE, +} from "@/lib/github-activity-store"; import { TRACKED_GITHUB_USER_IDS } from "@/lib/github-commits-core"; import { GITHUB_SUMMARY_REQUEST_BUDGET } from "@/lib/github-cron-config"; import { acquireGitHubWorkUnitProjectionLock } from "@/lib/github-work-unit-projection-state"; @@ -688,7 +691,6 @@ async function readInitialPageDays(transaction: SummaryTransaction) { inArray(githubWorkUnits.visibility, ["public", "private"]), inArray(githubRepositories.visibility, ["public", "private", "internal"]) ); - const issueDay = sql`to_char(${githubIssues.createdAt} AT TIME ZONE 'UTC', 'YYYY-MM-DD')`; const [workDays, issueDays] = await Promise.all([ transaction .selectDistinct({ day: githubWorkUnits.activityDay }) @@ -701,7 +703,7 @@ async function readInitialPageDays(transaction: SummaryTransaction) { .orderBy(desc(githubWorkUnits.activityDay)) .limit(PUBLIC_GITHUB_ACTIVITY_DAY_PAGE_SIZE), transaction - .selectDistinct({ day: issueDay }) + .selectDistinct({ day: githubIssueActivityDay.as("day") }) .from(githubIssues) .innerJoin( githubRepositories, @@ -720,7 +722,7 @@ async function readInitialPageDays(transaction: SummaryTransaction) { ]) ) ) - .orderBy(desc(issueDay)) + .orderBy(({ day }) => desc(day)) .limit(PUBLIC_GITHUB_ACTIVITY_DAY_PAGE_SIZE), ]); return new Set( diff --git a/src/lib/remark-embed-github.mjs b/src/lib/remark-embed-github.mjs index 39a4a6a7..afec4888 100644 --- a/src/lib/remark-embed-github.mjs +++ b/src/lib/remark-embed-github.mjs @@ -3,6 +3,7 @@ import { Buffer } from "node:buffer"; import remarkEmbedderModule from "@remark-embedder/core"; import { codeToHtml } from "shiki"; +import { sitePreferences } from "../content/site.ts"; import { env } from "../env.ts"; import { githubIconPaths } from "./github-icons.ts"; @@ -69,10 +70,10 @@ const languageByExtension = { zsh: "bash", }; -const dateFormatter = new Intl.DateTimeFormat("en", { +const dateFormatter = new Intl.DateTimeFormat(sitePreferences.language, { day: "numeric", month: "short", - timeZone: "UTC", + timeZone: sitePreferences.timeZone, year: "numeric", }); diff --git a/tests/codex-stats.test.ts b/tests/codex-stats.test.ts index 31b38bd3..49083699 100644 --- a/tests/codex-stats.test.ts +++ b/tests/codex-stats.test.ts @@ -7,6 +7,7 @@ import { activityThresholds, CodexActivity, } from "../src/components/codex-activity.tsx"; +import { CodexTotals, CodexUsageLimit } from "../src/components/codex-stats"; import type { CodexAccountSnapshot } from "../src/lib/codex/stats"; import { buildPublicCodexStats, @@ -90,6 +91,47 @@ const requireStats = (stats: T | null): T => { }; describe("public Codex statistics", () => { + test("provider dates survive IST midnight without claiming an IST today total", () => { + const snapshot = createCodexAccountSnapshot( + profile(120, [{ start_date: "2026-09-28", tokens: 120 }]), + { + rate_limit: { + primary_window: { + used_percent: 25, + limit_window_seconds: 18_000, + reset_at: Date.parse("2026-09-28T18:30:00Z") / 1000, + }, + }, + } + ); + for (const instant of [ + "2026-09-28T18:29:59Z", + "2026-09-28T18:30:00Z", + "2026-09-28T23:59:59Z", + "2026-09-29T00:00:00Z", + ]) { + const stats = requireStats( + buildPublicCodexStats([{ snapshot }], new Date(instant)) + ); + expect( + stats.history.values.find(({ day }) => day === "2026-09-28") + ).toEqual({ + day: "2026-09-28", + tokens: 120, + }); + expect(stats.totals.last7Days.value).toBe(120); + const html = renderToStaticMarkup(createElement(CodexTotals, { stats })); + expect(html).toContain("Last 7 days"); + expect(html).not.toContain("Today"); + expect(html).not.toContain("UTC"); + const limits = renderToStaticMarkup( + createElement(CodexUsageLimit, { stats }) + ); + expect(limits).toContain("Sep 29, 2026"); + expect(limits).not.toContain("UTC"); + } + }); + test("calendar bands follow nonzero usage quartiles as counts grow", () => { const counts = [0, 0, 1, 2, 3, 4, 5, 6, 7, 1000]; expect(activityThresholds(counts)).toEqual([2, 4, 6]); @@ -185,7 +227,6 @@ describe("public Codex statistics", () => { partial: false, value: 160, }); - expect(stats.totals.todayTokens.value).toBe(120); expect(stats.reportingDay).toBe("2026-01-30"); expect(stats.totals.totalSkillsUsed).toEqual({ partial: false, @@ -296,8 +337,8 @@ describe("public Codex statistics", () => { ], new Date("2026-01-30T12:00:00Z") ) - ).totals.todayTokens.value - ).toBe(0); + ).history.values.at(-1) + ).toEqual({ day: "2026-01-30", tokens: 0 }); expect(() => { validateCodexAuthJson('{"OPENAI_API_KEY":"secret"}'); diff --git a/tests/daily-history.test.ts b/tests/daily-history.test.ts index f999512c..ffca08e1 100644 --- a/tests/daily-history.test.ts +++ b/tests/daily-history.test.ts @@ -85,7 +85,10 @@ test("daily storage round-trips explicit zeros, gaps, source units and archived [{ snapshot: restored }], new Date("2026-09-27T12:00:00Z") ); - expect(stats?.totals.todayTokens).toEqual({ value: 0, partial: false }); + expect(stats?.history.values.at(-1)).toEqual({ + day: "2026-09-27", + tokens: 0, + }); expect(stats?.totals.last7Days).toEqual({ value: 200, partial: true }); }); diff --git a/tests/date.test.ts b/tests/date.test.ts index bd7f7227..6dcbb3f8 100644 --- a/tests/date.test.ts +++ b/tests/date.test.ts @@ -19,4 +19,18 @@ test("logical token dates have no time, timezone, or end date", () => { expect(formatDate("2026-08-28")).toBe("Aug 28, 2026"); expect(formatDate("2026-04-27")).toBe("Apr 27, 2026"); expect(formatDate("2026-09-07", "weekday")).toBe("Mon, Sep 7, 2026"); + expect(formatDate("2026-09-07", "date", "America/Los_Angeles")).toBe( + "Sep 7, 2026" + ); + expect(dateKey("2026-09-07", "America/Los_Angeles")).toBe("2026-09-07"); +}); + +test("the site uses IST at midnight and year boundaries for every timestamp", () => { + expect(dateKey("2026-12-31T18:29:59Z")).toBe("2026-12-31"); + expect(dateKey("2026-12-31T18:30:00Z")).toBe("2027-01-01"); + expect(dateKey("2027-01-01T00:00:00+05:30")).toBe("2027-01-01"); + expect(formatDate("2026-12-31T18:30:00Z", "dateTime")).toBe( + "Jan 1, 2027, 12:00 AM" + ); + expect(formatDate(new Date("2026-12-31T18:30:00Z"), "time")).toBe("12:00 AM"); }); diff --git a/tests/github-activity-days.test.tsx b/tests/github-activity-days.test.tsx index 00787ffb..c1afb0bd 100644 --- a/tests/github-activity-days.test.tsx +++ b/tests/github-activity-days.test.tsx @@ -3,7 +3,7 @@ import { expect, test } from "bun:test"; import { renderToStaticMarkup } from "react-dom/server"; import { GitHubActivityDays } from "../src/components/github-activity-days"; -import { dateKey, WORK_LOG_TIME_ZONE } from "../src/lib/date"; +import { dateKey } from "../src/lib/date"; import { buildPublicGitHubActivityDays } from "../src/lib/github-activity-feed-core"; test("the initial homepage HTML includes saved IST work across UTC midnight without adding PR totals", () => { @@ -17,7 +17,7 @@ test("the initial homepage HTML includes saved IST work across UTC midnight with ].map(([id, activityAt, count, additions, deletions]) => ({ id: String(id), activityAt: String(activityAt), - day: dateKey(String(activityAt), WORK_LOG_TIME_ZONE), + day: dateKey(String(activityAt)), destination: id === "site" ? { From 127de0e9784ec88eb132d5a216b0396963a16b17 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 28 Sep 2026 16:03:50 +0000 Subject: [PATCH 3/7] fix: keep token timing presentation simple --- docs/codex-stats.md | 5 ++--- src/components/codex-stats.tsx | 3 ++- src/components/token-history.tsx | 3 --- src/lib/codex/public-stats.ts | 2 +- src/lib/codex/stats.ts | 7 ++++++- tests/codex-stats.test.ts | 17 +++++++++-------- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/codex-stats.md b/docs/codex-stats.md index d61141d8..49604949 100644 --- a/docs/codex-stats.md +++ b/docs/codex-stats.md @@ -117,9 +117,8 @@ The detailed breakdowns use internal Codex endpoints. History means available records within `historyDays`, not complete lifetime coverage. Changing the window triggers a fresh backfill. Requests fetch up to 100 named tools/skills; upstream may group additional entries. These sources may lag behind profile totals. Failed -requests retain previous data; empty sections are hidden. Provider daily totals keep -their reported dates. Timestamps use the site's IST timezone; daily aggregates are -not relabeled as an IST “today” total. +requests retain previous data; empty sections are hidden. Display timestamps in IST +and keep provider dates for daily totals. Token and invocation counts sum across accounts. Cache hit rate is weighted by input tokens from rows with all components reported; missing components are not diff --git a/src/components/codex-stats.tsx b/src/components/codex-stats.tsx index 8c6e514e..a31e85e1 100644 --- a/src/components/codex-stats.tsx +++ b/src/components/codex-stats.tsx @@ -104,8 +104,9 @@ const LimitBar = ({ export function CodexTotals({ stats }: { stats: PublicCodexStats }) { return ( - + + diff --git a/src/components/token-history.tsx b/src/components/token-history.tsx index ae58d23f..8ae01f4a 100644 --- a/src/components/token-history.tsx +++ b/src/components/token-history.tsx @@ -121,9 +121,6 @@ function HistoryPlot({ ? "Usage unavailable" : `${number.format(row.tokens)} ${mode === "cumulative" && summary.partial ? "recorded " : ""}tokens`}

    - {row.day === reportingDay ? ( -

    Still updating

    - ) : null} ) : null; }} diff --git a/src/lib/codex/public-stats.ts b/src/lib/codex/public-stats.ts index b48ab7da..7a5011fc 100644 --- a/src/lib/codex/public-stats.ts +++ b/src/lib/codex/public-stats.ts @@ -81,7 +81,7 @@ const readPublicViews = unstable_cache( ), }; }, - ["public-codex-views-v1", JSON.stringify(tokenPreferences)], + ["public-codex-views-v2", JSON.stringify(tokenPreferences)], { revalidate: 900, tags: ["public-codex-stats"] } ); diff --git a/src/lib/codex/stats.ts b/src/lib/codex/stats.ts index 397c2176..6986915c 100644 --- a/src/lib/codex/stats.ts +++ b/src/lib/codex/stats.ts @@ -175,6 +175,7 @@ export interface PublicCodexStats { longestRunningTurnSec: PublicCodexMetric; totalSkillsUsed: PublicCodexMetric; totalThreads: PublicCodexMetric; + todayTokens: PublicCodexMetric; }; } @@ -498,7 +499,7 @@ export const buildPublicCodexStats = ( return null; } - // Codex supplies whole-day buckets, not timestamps we can regroup into IST. + // Daily totals follow Codex's reporting dates. const today = now.toISOString().slice(0, 10); const missingAccountCount = Math.max( 0, @@ -705,6 +706,10 @@ export const buildPublicCodexStats = ( ], sum ), + todayTokens: { + partial: dailyPartial, + value: hasKnownDaily ? (combinedDaily.get(today) ?? 0) : null, + }, }, }; }; diff --git a/tests/codex-stats.test.ts b/tests/codex-stats.test.ts index 49083699..fb6a1395 100644 --- a/tests/codex-stats.test.ts +++ b/tests/codex-stats.test.ts @@ -91,7 +91,7 @@ const requireStats = (stats: T | null): T => { }; describe("public Codex statistics", () => { - test("provider dates survive IST midnight without claiming an IST today total", () => { + test("daily totals use provider dates and reset timestamps display in IST", () => { const snapshot = createCodexAccountSnapshot( profile(120, [{ start_date: "2026-09-28", tokens: 120 }]), { @@ -104,12 +104,12 @@ describe("public Codex statistics", () => { }, } ); - for (const instant of [ - "2026-09-28T18:29:59Z", - "2026-09-28T18:30:00Z", - "2026-09-28T23:59:59Z", - "2026-09-29T00:00:00Z", - ]) { + for (const [instant, expectedToday] of [ + ["2026-09-28T18:29:59Z", 120], + ["2026-09-28T18:30:00Z", 120], + ["2026-09-28T23:59:59Z", 120], + ["2026-09-29T00:00:00Z", 0], + ] as const) { const stats = requireStats( buildPublicCodexStats([{ snapshot }], new Date(instant)) ); @@ -120,9 +120,10 @@ describe("public Codex statistics", () => { tokens: 120, }); expect(stats.totals.last7Days.value).toBe(120); + expect(stats.totals.todayTokens.value).toBe(expectedToday); const html = renderToStaticMarkup(createElement(CodexTotals, { stats })); expect(html).toContain("Last 7 days"); - expect(html).not.toContain("Today"); + expect(html).toContain(">Today"); expect(html).not.toContain("UTC"); const limits = renderToStaticMarkup( createElement(CodexUsageLimit, { stats }) From 5e9da67e465588045d6e5fc10dc2292f380778cd Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 28 Sep 2026 16:19:55 +0000 Subject: [PATCH 4/7] fix: localize standalone dates in the browser --- docs/codex-stats.md | 4 ++-- src/components/codex-stats.tsx | 8 ++++---- src/components/date-time.tsx | 27 +++++++++++++++++++++++-- src/components/github-activity-days.tsx | 3 +++ src/components/writing-list.tsx | 8 +++++--- tests/date.test.ts | 20 ++++++++++++++++++ 6 files changed, 59 insertions(+), 11 deletions(-) diff --git a/docs/codex-stats.md b/docs/codex-stats.md index 49604949..13914f94 100644 --- a/docs/codex-stats.md +++ b/docs/codex-stats.md @@ -117,8 +117,8 @@ The detailed breakdowns use internal Codex endpoints. History means available records within `historyDays`, not complete lifetime coverage. Changing the window triggers a fresh backfill. Requests fetch up to 100 named tools/skills; upstream may group additional entries. These sources may lag behind profile totals. Failed -requests retain previous data; empty sections are hidden. Display timestamps in IST -and keep provider dates for daily totals. +requests retain previous data; empty sections are hidden. Display reset timestamps +in the visitor's timezone and keep provider dates for daily totals. Token and invocation counts sum across accounts. Cache hit rate is weighted by input tokens from rows with all components reported; missing components are not diff --git a/src/components/codex-stats.tsx b/src/components/codex-stats.tsx index a31e85e1..6b7074d9 100644 --- a/src/components/codex-stats.tsx +++ b/src/components/codex-stats.tsx @@ -1,6 +1,7 @@ import type { ReactNode } from "react"; import { CodexActivity } from "@/components/codex-activity"; +import { DateTime } from "@/components/date-time"; import { InfoLabel } from "@/components/info-label"; import { SiteSection } from "@/components/site-page"; import { TokenStatGrid } from "@/components/token-stat-grid"; @@ -11,7 +12,6 @@ import type { PublicCodexRange, PublicCodexStats, } from "@/lib/codex/stats"; -import { formatDate } from "@/lib/date"; const compactNumber = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1, @@ -263,9 +263,9 @@ export function CodexUsageLimit({ stats }: { stats: PublicCodexStats }) { {limit.resetAt === null ? null : (

    Resets{" "} - +

    )} diff --git a/src/components/date-time.tsx b/src/components/date-time.tsx index 400b7dd6..169df326 100644 --- a/src/components/date-time.tsx +++ b/src/components/date-time.tsx @@ -1,23 +1,46 @@ +"use client"; + +import { useSyncExternalStore } from "react"; import type { ComponentProps } from "react"; +import { sitePreferences } from "@/content/site"; import { formatDate } from "@/lib/date"; import type { dateFormats } from "@/lib/date"; +const subscribe = () => () => { + // The browser timezone is fixed for this page view; no subscription is needed. +}; +const visitorTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; +const browserTimeZone = () => visitorTimeZone; +const serverTimeZone = () => sitePreferences.timeZone; + export function DateTime({ dateTime, format = "date", + timeZone, ...props }: Omit, "dateTime" | "children"> & { dateTime: string; format?: keyof typeof dateFormats; + timeZone?: string; }) { + const localTimeZone = useSyncExternalStore( + subscribe, + browserTimeZone, + serverTimeZone + ); + const zone = timeZone ?? localTimeZone; return ( ); } diff --git a/src/components/github-activity-days.tsx b/src/components/github-activity-days.tsx index 5caa851e..869f4d82 100644 --- a/src/components/github-activity-days.tsx +++ b/src/components/github-activity-days.tsx @@ -22,6 +22,7 @@ import { TooltipGroup, TooltipTrigger, } from "@/components/site-tooltip"; +import { sitePreferences } from "@/content/site"; import { dateKey, formatDate } from "@/lib/date"; import { getVisibleGitHubActivityDays } from "@/lib/github-activity-feed-core"; import type { @@ -210,6 +211,7 @@ function WorkUnitRow({ className="whitespace-nowrap" dateTime={item.activityAt} format="time" + timeZone={sitePreferences.timeZone} />
    @@ -277,6 +279,7 @@ function IssueRow({ className="whitespace-nowrap" dateTime={item.activityAt} format="time" + timeZone={sitePreferences.timeZone} />
    diff --git a/src/components/writing-list.tsx b/src/components/writing-list.tsx index dc361c58..caa5df01 100644 --- a/src/components/writing-list.tsx +++ b/src/components/writing-list.tsx @@ -56,8 +56,10 @@ export async function WritingList({ posts }: Readonly<{ posts: BlogPost[] }>) { {post.metadata.summary}

    - ·{" "} - {post.readingTime} + {" "} + · {post.readingTime} {post.metadata.draft === true ? " · Draft preview" : ""}

    @@ -71,7 +73,7 @@ export async function WritingList({ posts }: Readonly<{ posts: BlogPost[] }>) { diff --git a/tests/date.test.ts b/tests/date.test.ts index 6dcbb3f8..db43a454 100644 --- a/tests/date.test.ts +++ b/tests/date.test.ts @@ -1,5 +1,9 @@ import { expect, test } from "bun:test"; +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; + +import { DateTime } from "../src/components/date-time"; import { dateKey, formatDate } from "../src/lib/date.ts"; test("date formatting handles UTC, local midnight, and daylight saving transitions", () => { @@ -34,3 +38,19 @@ test("the site uses IST at midnight and year boundaries for every timestamp", () ); expect(formatDate(new Date("2026-12-31T18:30:00Z"), "time")).toBe("12:00 AM"); }); + +test("timestamps have a stable server fallback and calendar dates stay date-only", () => { + const timestamp = renderToStaticMarkup( + createElement(DateTime, { + dateTime: "2026-12-31T18:30:00Z", + format: "dateTime", + }) + ); + expect(timestamp).toContain("Jan 1, 2027, 12:00 AM"); + const calendar = renderToStaticMarkup( + createElement(DateTime, { dateTime: "2026-12-31" }) + ); + expect(calendar).toBe( + '' + ); +}); From ec487d0ea855c2a5e7816253ec4b229c6bd8e521 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 28 Sep 2026 16:22:11 +0000 Subject: [PATCH 5/7] fix: keep dates stable on initial render --- docs/codex-stats.md | 4 ++-- src/components/codex-stats.tsx | 8 ++++---- src/components/date-time.tsx | 27 ++----------------------- src/components/github-activity-days.tsx | 3 --- src/components/writing-list.tsx | 8 +++----- tests/date.test.ts | 20 ------------------ 6 files changed, 11 insertions(+), 59 deletions(-) diff --git a/docs/codex-stats.md b/docs/codex-stats.md index 13914f94..49604949 100644 --- a/docs/codex-stats.md +++ b/docs/codex-stats.md @@ -117,8 +117,8 @@ The detailed breakdowns use internal Codex endpoints. History means available records within `historyDays`, not complete lifetime coverage. Changing the window triggers a fresh backfill. Requests fetch up to 100 named tools/skills; upstream may group additional entries. These sources may lag behind profile totals. Failed -requests retain previous data; empty sections are hidden. Display reset timestamps -in the visitor's timezone and keep provider dates for daily totals. +requests retain previous data; empty sections are hidden. Display timestamps in IST +and keep provider dates for daily totals. Token and invocation counts sum across accounts. Cache hit rate is weighted by input tokens from rows with all components reported; missing components are not diff --git a/src/components/codex-stats.tsx b/src/components/codex-stats.tsx index 6b7074d9..a31e85e1 100644 --- a/src/components/codex-stats.tsx +++ b/src/components/codex-stats.tsx @@ -1,7 +1,6 @@ import type { ReactNode } from "react"; import { CodexActivity } from "@/components/codex-activity"; -import { DateTime } from "@/components/date-time"; import { InfoLabel } from "@/components/info-label"; import { SiteSection } from "@/components/site-page"; import { TokenStatGrid } from "@/components/token-stat-grid"; @@ -12,6 +11,7 @@ import type { PublicCodexRange, PublicCodexStats, } from "@/lib/codex/stats"; +import { formatDate } from "@/lib/date"; const compactNumber = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1, @@ -263,9 +263,9 @@ export function CodexUsageLimit({ stats }: { stats: PublicCodexStats }) { {limit.resetAt === null ? null : (

    Resets{" "} - +

    )} diff --git a/src/components/date-time.tsx b/src/components/date-time.tsx index 169df326..400b7dd6 100644 --- a/src/components/date-time.tsx +++ b/src/components/date-time.tsx @@ -1,46 +1,23 @@ -"use client"; - -import { useSyncExternalStore } from "react"; import type { ComponentProps } from "react"; -import { sitePreferences } from "@/content/site"; import { formatDate } from "@/lib/date"; import type { dateFormats } from "@/lib/date"; -const subscribe = () => () => { - // The browser timezone is fixed for this page view; no subscription is needed. -}; -const visitorTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; -const browserTimeZone = () => visitorTimeZone; -const serverTimeZone = () => sitePreferences.timeZone; - export function DateTime({ dateTime, format = "date", - timeZone, ...props }: Omit, "dateTime" | "children"> & { dateTime: string; format?: keyof typeof dateFormats; - timeZone?: string; }) { - const localTimeZone = useSyncExternalStore( - subscribe, - browserTimeZone, - serverTimeZone - ); - const zone = timeZone ?? localTimeZone; return ( ); } diff --git a/src/components/github-activity-days.tsx b/src/components/github-activity-days.tsx index 869f4d82..5caa851e 100644 --- a/src/components/github-activity-days.tsx +++ b/src/components/github-activity-days.tsx @@ -22,7 +22,6 @@ import { TooltipGroup, TooltipTrigger, } from "@/components/site-tooltip"; -import { sitePreferences } from "@/content/site"; import { dateKey, formatDate } from "@/lib/date"; import { getVisibleGitHubActivityDays } from "@/lib/github-activity-feed-core"; import type { @@ -211,7 +210,6 @@ function WorkUnitRow({ className="whitespace-nowrap" dateTime={item.activityAt} format="time" - timeZone={sitePreferences.timeZone} /> @@ -279,7 +277,6 @@ function IssueRow({ className="whitespace-nowrap" dateTime={item.activityAt} format="time" - timeZone={sitePreferences.timeZone} /> diff --git a/src/components/writing-list.tsx b/src/components/writing-list.tsx index caa5df01..dc361c58 100644 --- a/src/components/writing-list.tsx +++ b/src/components/writing-list.tsx @@ -56,10 +56,8 @@ export async function WritingList({ posts }: Readonly<{ posts: BlogPost[] }>) { {post.metadata.summary}

    - {" "} - · {post.readingTime} + ·{" "} + {post.readingTime} {post.metadata.draft === true ? " · Draft preview" : ""}

    @@ -73,7 +71,7 @@ export async function WritingList({ posts }: Readonly<{ posts: BlogPost[] }>) { diff --git a/tests/date.test.ts b/tests/date.test.ts index db43a454..6dcbb3f8 100644 --- a/tests/date.test.ts +++ b/tests/date.test.ts @@ -1,9 +1,5 @@ import { expect, test } from "bun:test"; -import { createElement } from "react"; -import { renderToStaticMarkup } from "react-dom/server"; - -import { DateTime } from "../src/components/date-time"; import { dateKey, formatDate } from "../src/lib/date.ts"; test("date formatting handles UTC, local midnight, and daylight saving transitions", () => { @@ -38,19 +34,3 @@ test("the site uses IST at midnight and year boundaries for every timestamp", () ); expect(formatDate(new Date("2026-12-31T18:30:00Z"), "time")).toBe("12:00 AM"); }); - -test("timestamps have a stable server fallback and calendar dates stay date-only", () => { - const timestamp = renderToStaticMarkup( - createElement(DateTime, { - dateTime: "2026-12-31T18:30:00Z", - format: "dateTime", - }) - ); - expect(timestamp).toContain("Jan 1, 2027, 12:00 AM"); - const calendar = renderToStaticMarkup( - createElement(DateTime, { dateTime: "2026-12-31" }) - ); - expect(calendar).toBe( - '' - ); -}); From 9bb9440101958975fcf541230522ea2ec4919e4b Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 28 Sep 2026 16:31:54 +0000 Subject: [PATCH 6/7] fix: localize timestamps before first paint --- docs/codex-stats.md | 4 +-- src/components/codex-stats.tsx | 8 ++--- src/components/date-time.tsx | 44 ++++++++++++++++++----- src/components/github-activity-days.tsx | 3 ++ src/components/writing-list.tsx | 8 +++-- tests/date.test.ts | 46 ++++++++++++++++++++++++- 6 files changed, 94 insertions(+), 19 deletions(-) diff --git a/docs/codex-stats.md b/docs/codex-stats.md index 49604949..13914f94 100644 --- a/docs/codex-stats.md +++ b/docs/codex-stats.md @@ -117,8 +117,8 @@ The detailed breakdowns use internal Codex endpoints. History means available records within `historyDays`, not complete lifetime coverage. Changing the window triggers a fresh backfill. Requests fetch up to 100 named tools/skills; upstream may group additional entries. These sources may lag behind profile totals. Failed -requests retain previous data; empty sections are hidden. Display timestamps in IST -and keep provider dates for daily totals. +requests retain previous data; empty sections are hidden. Display reset timestamps +in the visitor's timezone and keep provider dates for daily totals. Token and invocation counts sum across accounts. Cache hit rate is weighted by input tokens from rows with all components reported; missing components are not diff --git a/src/components/codex-stats.tsx b/src/components/codex-stats.tsx index a31e85e1..6b7074d9 100644 --- a/src/components/codex-stats.tsx +++ b/src/components/codex-stats.tsx @@ -1,6 +1,7 @@ import type { ReactNode } from "react"; import { CodexActivity } from "@/components/codex-activity"; +import { DateTime } from "@/components/date-time"; import { InfoLabel } from "@/components/info-label"; import { SiteSection } from "@/components/site-page"; import { TokenStatGrid } from "@/components/token-stat-grid"; @@ -11,7 +12,6 @@ import type { PublicCodexRange, PublicCodexStats, } from "@/lib/codex/stats"; -import { formatDate } from "@/lib/date"; const compactNumber = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1, @@ -263,9 +263,9 @@ export function CodexUsageLimit({ stats }: { stats: PublicCodexStats }) { {limit.resetAt === null ? null : (

    Resets{" "} - +

    )} diff --git a/src/components/date-time.tsx b/src/components/date-time.tsx index 400b7dd6..2afee8a4 100644 --- a/src/components/date-time.tsx +++ b/src/components/date-time.tsx @@ -1,23 +1,49 @@ +"use client"; + import type { ComponentProps } from "react"; -import { formatDate } from "@/lib/date"; -import type { dateFormats } from "@/lib/date"; +import { sitePreferences } from "@/content/site"; +import { dateFormats, formatDate } from "@/lib/date"; + +const browser = typeof window !== "undefined"; +const visitorTimeZone = browser + ? Intl.DateTimeFormat().resolvedOptions().timeZone + : sitePreferences.timeZone; export function DateTime({ dateTime, format = "date", + timeZone, ...props }: Omit, "dateTime" | "children"> & { dateTime: string; format?: keyof typeof dateFormats; + timeZone?: string; }) { + const localize = + timeZone === undefined && !/^\d{4}-\d{2}-\d{2}$/u.test(dateTime); + const zone = timeZone ?? visitorTimeZone; + const titleFormat = format === "time" ? "dateTime" : format; return ( - + <> + + {localize ? ( + // Initial HTML is localized during parsing; navigation formats during render. +