@catalyst-cloud/schema 0.1.16 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +15 -0
- package/src/migrations.generated.ts +9 -0
- package/src/mirror.ts +166 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catalyst-cloud/schema",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed Drizzle schema = single source of truth for the per-tenant Mirror DO SQLite store (CTC-13 / ADR-0002). Shared by the mirror Worker, the host-sync replica, and the browser OPFS replica.",
|
|
6
6
|
"license": "MIT",
|
package/src/index.ts
CHANGED
|
@@ -45,9 +45,11 @@ import {
|
|
|
45
45
|
pr_ancillary_backfill,
|
|
46
46
|
push_subscriptions,
|
|
47
47
|
pushes,
|
|
48
|
+
push_events,
|
|
48
49
|
deployments,
|
|
49
50
|
deployment_statuses,
|
|
50
51
|
pr_review_threads,
|
|
52
|
+
check_suites,
|
|
51
53
|
} from "./mirror.js";
|
|
52
54
|
|
|
53
55
|
export * from "./mirror.js";
|
|
@@ -126,11 +128,17 @@ export const mirrorSchema = {
|
|
|
126
128
|
push_subscriptions,
|
|
127
129
|
// CTC-667: the current head of a pushed git ref — the orchestrator's rebase-detection signal.
|
|
128
130
|
pushes,
|
|
131
|
+
// CTC-704: one row per push DELIVERY, append-only next to the per-ref row above — what lets the
|
|
132
|
+
// feed emit an edge per push instead of collapsing two pushes to one ref into one.
|
|
133
|
+
push_events,
|
|
129
134
|
// CTC-667: a GitHub deployment, its status transitions, and a PR review thread's resolution state
|
|
130
135
|
// — the remaining payload types host dispatch consumes (CTL-1929's last smee tunnel).
|
|
131
136
|
deployments,
|
|
132
137
|
deployment_statuses,
|
|
133
138
|
pr_review_threads,
|
|
139
|
+
// CTC-667 item 4: the check SUITE's own rollup — the highest-volume signal in CTL's census and the
|
|
140
|
+
// one every phase agent's CI wait blocks on.
|
|
141
|
+
check_suites,
|
|
134
142
|
} as const;
|
|
135
143
|
|
|
136
144
|
/**
|
|
@@ -190,6 +198,10 @@ export const feedEntityTables = {
|
|
|
190
198
|
// rebase-detection signal, and the first of the four payload types CTL's dispatch consumes that
|
|
191
199
|
// the mirror did not carry (CTL-1929's last smee tunnel).
|
|
192
200
|
pushes,
|
|
201
|
+
// CTC-704: the per-DELIVERY push row. ⛔ A feed entity precisely because `pushes` cannot be one
|
|
202
|
+
// faithfully — a producer emits one edge per row, so a per-ref row caps the feed at one edge per
|
|
203
|
+
// ref per tick (CTL-48: 101 of 138 unmatched smee events were pushes).
|
|
204
|
+
push_events,
|
|
193
205
|
// CTC-667: a GitHub deployment and its status history — what `phase-monitor-deploy` and the deploy
|
|
194
206
|
// state machine key on (`environment`, `state`, `target_url`, `environment_url`).
|
|
195
207
|
deployments,
|
|
@@ -197,6 +209,9 @@ export const feedEntityTables = {
|
|
|
197
209
|
// CTC-667: a PR review thread's RESOLUTION state — the merge gate AGENTS.md names, and the one
|
|
198
210
|
// fact `pr_review_comments` cannot express (it has no resolution column).
|
|
199
211
|
pr_review_threads,
|
|
212
|
+
// CTC-667 item 4: GitHub's OWN check-suite rollup. ⛔ The row is contractual; a derivation from
|
|
213
|
+
// `check_runs` is NOT — see the table's own header for the two reasons it cannot agree with GitHub.
|
|
214
|
+
check_suites,
|
|
200
215
|
} as const;
|
|
201
216
|
|
|
202
217
|
/**
|
|
@@ -200,6 +200,13 @@ export const MIRROR_MIGRATIONS = {
|
|
|
200
200
|
tag: "0026_curvy_micromacro",
|
|
201
201
|
breakpoints: true,
|
|
202
202
|
},
|
|
203
|
+
{
|
|
204
|
+
idx: 27,
|
|
205
|
+
version: "6",
|
|
206
|
+
when: 1787034085255,
|
|
207
|
+
tag: "0027_closed_miek",
|
|
208
|
+
breakpoints: true,
|
|
209
|
+
},
|
|
203
210
|
],
|
|
204
211
|
},
|
|
205
212
|
migrations: {
|
|
@@ -255,5 +262,7 @@ export const MIRROR_MIGRATIONS = {
|
|
|
255
262
|
"CREATE TABLE `pushes` (\n\t`repo_id` text NOT NULL,\n\t`ref` text NOT NULL,\n\t`before` text,\n\t`after` text,\n\t`forced` integer,\n\t`created` integer,\n\t`deleted` integer,\n\t`base_ref` text,\n\t`pusher_id` text,\n\t`head_commit_sha` text,\n\t`updated_at` integer,\n\tPRIMARY KEY(`repo_id`, `ref`)\n);\n",
|
|
256
263
|
"0026_curvy_micromacro":
|
|
257
264
|
"CREATE TABLE `deployment_statuses` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`repo_id` text NOT NULL,\n\t`deployment_id` text NOT NULL,\n\t`state` text,\n\t`environment` text,\n\t`target_url` text,\n\t`environment_url` text,\n\t`description` text,\n\t`creator_id` text,\n\t`created_at` integer,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_deployment_statuses_deployment` ON `deployment_statuses` (`deployment_id`,`created_at`);--> statement-breakpoint\nCREATE TABLE `deployments` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`repo_id` text NOT NULL,\n\t`ref` text,\n\t`sha` text,\n\t`task` text,\n\t`environment` text,\n\t`production_environment` integer,\n\t`transient_environment` integer,\n\t`description` text,\n\t`creator_id` text,\n\t`created_at` integer,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_deployments_repo_env` ON `deployments` (`repo_id`,`environment`,`created_at`);--> statement-breakpoint\nCREATE TABLE `pr_review_threads` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`resolved` integer,\n\t`resolved_at` integer,\n\t`resolver_id` text,\n\t`first_comment_id` text,\n\t`comment_count` integer,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_pr_review_threads_pr` ON `pr_review_threads` (`repo_id`,`pr_number`);",
|
|
265
|
+
"0027_closed_miek":
|
|
266
|
+
"CREATE TABLE `check_suites` (\n\t`repo_id` text NOT NULL,\n\t`check_suite_id` text PRIMARY KEY NOT NULL,\n\t`head_sha` text,\n\t`head_branch` text,\n\t`status` text,\n\t`conclusion` text,\n\t`app_slug` text,\n\t`latest_check_runs_count` integer,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_check_suites_sha` ON `check_suites` (`head_sha`);--> statement-breakpoint\nCREATE TABLE `push_events` (\n\t`delivery_id` text PRIMARY KEY NOT NULL,\n\t`repo_id` text NOT NULL,\n\t`ref` text NOT NULL,\n\t`before` text,\n\t`after` text,\n\t`forced` integer,\n\t`created` integer,\n\t`deleted` integer,\n\t`base_ref` text,\n\t`pusher_id` text,\n\t`head_commit_sha` text,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_push_events_repo_ref` ON `push_events` (`repo_id`,`ref`,`updated_at`);--> statement-breakpoint\nALTER TABLE `pull_requests` ADD `merge_commit_sha` text;",
|
|
258
267
|
},
|
|
259
268
|
} as const;
|
package/src/mirror.ts
CHANGED
|
@@ -465,6 +465,11 @@ export const pull_requests = sqliteTable(
|
|
|
465
465
|
// is gated at READ time (a false-positive parse never surfaces because no issue matches it).
|
|
466
466
|
head_ref: text("head_ref"),
|
|
467
467
|
linear_issue_identifier: text("linear_issue_identifier"),
|
|
468
|
+
// CTC-691: the merge commit SHA GitHub assigns when a PR merges — the join key the host uses
|
|
469
|
+
// to match a github.pr.merged event to its deployment (setFilterStateMerged). Nullable: GitHub
|
|
470
|
+
// also sends a throwaway test-merge SHA on UNMERGED PRs, and pre-backfill rows lack it; stored
|
|
471
|
+
// verbatim because the host discriminates on `merged`, not on SHA presence.
|
|
472
|
+
merge_commit_sha: text("merge_commit_sha"),
|
|
468
473
|
},
|
|
469
474
|
(t) => [
|
|
470
475
|
primaryKey({ columns: [t.repo_id, t.number] }),
|
|
@@ -666,6 +671,14 @@ export const pr_events = sqliteTable(
|
|
|
666
671
|
* distinguishes a fast-forward from a force-push when read alongside `forced`, and a consumer that
|
|
667
672
|
* only ever sees the latest row would otherwise have to infer it.
|
|
668
673
|
*
|
|
674
|
+
* ⚠️ WEBHOOK-ONLY, NO BOOTSTRAP — a ref whose last push predates this rollout has NO row until its
|
|
675
|
+
* next push, and on a quiet branch that is never. Raised on #758 (Codex P2). Adding this table to
|
|
676
|
+
* `SNAPSHOT_TABLES` seeds a fresh replica from the DO, which is right and necessary, but it cannot
|
|
677
|
+
* invent rows the DO never received: an unpushed-since-rollout ref is currently indistinguishable
|
|
678
|
+
* from one that never existed — the exact ambiguity the `deleted = 1` rule below exists to avoid for
|
|
679
|
+
* the OTHER case. The fix is the same backfill as `pr_review_threads` above (CTC-677): seed from
|
|
680
|
+
* GitHub's current refs rather than only from future webhooks.
|
|
681
|
+
*
|
|
669
682
|
* ⚠️ `created` / `deleted` are the webhook's own booleans for branch creation and deletion. A DELETED
|
|
670
683
|
* ref keeps its row with `deleted = 1` rather than being removed: "this branch is gone" is a fact a
|
|
671
684
|
* consumer needs, and a vanished row is indistinguishable from one that never existed.
|
|
@@ -697,6 +710,144 @@ export const pushes = sqliteTable(
|
|
|
697
710
|
(t) => [primaryKey({ columns: [t.repo_id, t.ref] })],
|
|
698
711
|
);
|
|
699
712
|
|
|
713
|
+
/**
|
|
714
|
+
* CTC-704 — ONE ROW PER PUSH DELIVERY, append-only, alongside the per-ref `pushes` row above.
|
|
715
|
+
*
|
|
716
|
+
* ⛔ WHY THIS EXISTS, and it is a parity blocker rather than a nicety. `pushes` is keyed
|
|
717
|
+
* `(repo_id, ref)` and carries that ref's CURRENT head — deliberately, and that choice is still
|
|
718
|
+
* right for the question it answers. But a feed producer emits one edge per ROW, so it can never
|
|
719
|
+
* emit more than one edge per ref: **two pushes to the same ref between two producer ticks collapse
|
|
720
|
+
* to one edge**, and the second is unrecoverable from the mirror. CTL-48 measured the consequence
|
|
721
|
+
* against real traffic — **101 of 138** unmatched smee events were `github.push`, which makes GitHub
|
|
722
|
+
* parity structurally INCONCLUSIVE and the last smee tunnel un-retirable (CTL-1929).
|
|
723
|
+
*
|
|
724
|
+
* ⚠️ The 101 is an UPPER BOUND from a replay, not the live rate: a replay sees one row per ref, while
|
|
725
|
+
* live only pushes inside a single tick collapse. The number that matters is the shadow window's.
|
|
726
|
+
*
|
|
727
|
+
* ⭐ WHY A SECOND TABLE RATHER THAN RE-KEYING `pushes`. This is the same shape `deployment_statuses`
|
|
728
|
+
* takes next to `deployments`, and for the same stated reason: a consumer that acts on the ARRIVAL of
|
|
729
|
+
* a transition needs every arrival, while a consumer asking "where is this ref now" needs exactly one
|
|
730
|
+
* row. Re-keying `pushes` per push would serve the first and break the second — rebase detection
|
|
731
|
+
* (broker/router.mjs:1582) reads the per-ref head, `SNAPSHOT_TABLES` seeds it as CURRENT STATE, and
|
|
732
|
+
* both would then have to page an unbounded log to answer a one-row question. Splitting keeps
|
|
733
|
+
* ⭐ ref-latest EXACTLY derivable — it is the untouched `pushes` row — and costs one additive table.
|
|
734
|
+
*
|
|
735
|
+
* ⚠️ NOT SEEDED (absent from `SNAPSHOT_TABLES`), and that is the deliberate half of the trade. This
|
|
736
|
+
* table grows with every push — 3,746/month on mini-2 — and it is the unboundedness argument in
|
|
737
|
+
* `pushes`' own doc that makes it wrong to hand a fresh replica the entire history. A new host reads
|
|
738
|
+
* ref-latest from the seeded `pushes` row and per-push edges from its cursor forward, which is what a
|
|
739
|
+
* feed CONSUMER needs; nobody in the census asks a fresh replica for last month's push sequence.
|
|
740
|
+
*
|
|
741
|
+
* ⭐ THE PK IS THE WEBHOOK DELIVERY ID, which makes a redelivery idempotent for free. GitHub resends
|
|
742
|
+
* with the same `x-github-delivery`, and `processed_events.delivery_id` already drops a redelivery
|
|
743
|
+
* BEFORE the normalizer runs (MirrorDO.ts:2276) — so this table cannot double-count even if that
|
|
744
|
+
* upstream guard were bypassed. ⛔ A synthetic/random id would NOT have this property, and
|
|
745
|
+
* `(repo_id, ref, after)` would not either: the `pushes` doc records that two separate pushes can
|
|
746
|
+
* carry the SAME head sha (a revert-and-repush, a branch reset onto an existing sha), so keying on
|
|
747
|
+
* `after` silently collapses exactly the force-push case this table exists to preserve.
|
|
748
|
+
*
|
|
749
|
+
* ⚠️ WEBHOOK-ONLY, NO BACKFILL — like `pushes`, and here it is honest rather than a gap: an event log
|
|
750
|
+
* cannot invent deliveries that were never received. Parity is measured from the cutover forward.
|
|
751
|
+
*/
|
|
752
|
+
export const push_events = sqliteTable(
|
|
753
|
+
"push_events",
|
|
754
|
+
{
|
|
755
|
+
/** GitHub's `x-github-delivery` for the push webhook — see the header on why this, not a uuid. */
|
|
756
|
+
delivery_id: text("delivery_id").primaryKey(),
|
|
757
|
+
repo_id: text("repo_id").notNull(),
|
|
758
|
+
/** Full git ref as GitHub sends it, e.g. `refs/heads/main` — NOT shortened, so tags are unambiguous. */
|
|
759
|
+
ref: text("ref").notNull(),
|
|
760
|
+
/** The ref's head BEFORE this push (all-zero sha when the ref was just created). */
|
|
761
|
+
before: text("before"),
|
|
762
|
+
/** The ref's head AFTER this push (all-zero sha when the ref was deleted). */
|
|
763
|
+
after: text("after"),
|
|
764
|
+
/** True when the push was a force-push — the discriminator rebase detection actually keys on. */
|
|
765
|
+
forced: integer("forced"),
|
|
766
|
+
/** True when this push created the ref. */
|
|
767
|
+
created: integer("created"),
|
|
768
|
+
/** True when this push deleted the ref. */
|
|
769
|
+
deleted: integer("deleted"),
|
|
770
|
+
/** The ref this branch was created FROM, when GitHub reports one (`base_ref`); usually null. */
|
|
771
|
+
base_ref: text("base_ref"),
|
|
772
|
+
/** Who pushed — the resolvable `github:<login>` key, same convention as pr_events.actor_id. */
|
|
773
|
+
pusher_id: text("pusher_id"),
|
|
774
|
+
/** Head commit sha of the pushed head, when the payload carried a head_commit. */
|
|
775
|
+
head_commit_sha: text("head_commit_sha"),
|
|
776
|
+
/**
|
|
777
|
+
* ⛔ DELIVERY TIME, not the head commit's timestamp — the same rule `pushes.updated_at` follows
|
|
778
|
+
* and for the same reason (a force-push to an older commit moves a commit clock BACKWARD). Here
|
|
779
|
+
* it is also the producer's ORDERING key, so it must be monotonic in arrival order.
|
|
780
|
+
*/
|
|
781
|
+
updated_at: integer("updated_at"),
|
|
782
|
+
},
|
|
783
|
+
(t) => [index("idx_push_events_repo_ref").on(t.repo_id, t.ref, t.updated_at)],
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* CTC-667 item 4 — a GitHub CHECK SUITE's completion, one row per suite id.
|
|
788
|
+
*
|
|
789
|
+
* ⛔ WHY THIS EXISTS. `github.check_suite.completed` is the orchestrator's CI wait
|
|
790
|
+
* (broker/router.mjs:1497) and the HIGHEST-VOLUME signal in CTL's census — 20,400 events on mini-2 in
|
|
791
|
+
* 2026-08 — and the mirror stored NO row for it: `normalizeGithub`'s `check_suite` case was a
|
|
792
|
+
* deliberate `return []`. So the one thing every phase agent blocks on had nothing to read, and the
|
|
793
|
+
* last GitHub smee tunnel could not be retired.
|
|
794
|
+
*
|
|
795
|
+
* ⭐⭐ THE ANSWER TO CTL'S QUESTION — "a suite row or an agreed derivation? just say which is
|
|
796
|
+
* contractual" — IS **THIS ROW**, AND A DERIVATION FROM `check_runs` IS EXPLICITLY *NOT*
|
|
797
|
+
* CONTRACTUAL. GitHub computes the rollup itself, and its answer is not reproducible from the
|
|
798
|
+
* constituent runs without replicating rules we do not have:
|
|
799
|
+
*
|
|
800
|
+
* • ⛔ A DERIVATION MUST NOT BE "every run concluded `success`". `neutral` and `skipped` are
|
|
801
|
+
* NON-FAILING under GitHub's own rollup, and this fleet has real rows of both — check run
|
|
802
|
+
* `84696876797` is a genuine `neutral` (measured while fixing CTC-673). The naive rule reports a
|
|
803
|
+
* GREEN suite as not-green, which for a CI wait means a phase agent blocking forever on a build
|
|
804
|
+
* that already passed.
|
|
805
|
+
* • ⛔ REQUIRED vs NON-REQUIRED checks are a BRANCH-PROTECTION fact, not a check_runs fact. A suite
|
|
806
|
+
* can conclude `success` with a failing non-required run in it. Nothing in `check_runs` carries
|
|
807
|
+
* the required-ness, so no derivation over that table can agree with GitHub in general.
|
|
808
|
+
*
|
|
809
|
+
* Storing GitHub's own `conclusion` sidesteps both. ⚠️ IF a consumer ever must derive one anyway
|
|
810
|
+
* (e.g. for suites predating this table), the ONLY defensible rule is **"no run failed, and all runs
|
|
811
|
+
* completed"** — never "all concluded success". Written down here so the second definition CTL was
|
|
812
|
+
* right to fear does not get invented independently.
|
|
813
|
+
*
|
|
814
|
+
* ⭐ CURRENT STATE, one row per suite id, same as every table here. A suite's interesting movement is
|
|
815
|
+
* `status`/`conclusion` converging, and the ORDERED history is the raw passthrough feed's job
|
|
816
|
+
* (ADR-0017) — exactly the argument `pushes` above makes.
|
|
817
|
+
*
|
|
818
|
+
* ⚠️ `conclusion` IS MUTABLE AFTER COMPLETION and this is the CTC-673 trap in a new place: a re-run
|
|
819
|
+
* or a late correction changes `conclusion` while `updated_at` (derived from an EVENT time) can stay
|
|
820
|
+
* put. CTC-673 fixed the upsert guard to accept an equal-timestamp write whose CONTENT differs, which
|
|
821
|
+
* is what lets this row converge at all — do not re-narrow that guard.
|
|
822
|
+
*
|
|
823
|
+
* ⚠️ `app_slug` distinguishes suites from different CI providers on one SHA (GitHub Actions, CodeQL,
|
|
824
|
+
* a third-party app). A consumer that waits on "the" suite for a SHA without it would pick one
|
|
825
|
+
* arbitrarily and report the wrong verdict on any repo with two providers — which this one has.
|
|
826
|
+
*/
|
|
827
|
+
export const check_suites = sqliteTable(
|
|
828
|
+
"check_suites",
|
|
829
|
+
{
|
|
830
|
+
repo_id: text("repo_id").notNull(),
|
|
831
|
+
/** GitHub's own check-suite id, as a string (same convention as check_runs.check_run_id). */
|
|
832
|
+
check_suite_id: text("check_suite_id").primaryKey(),
|
|
833
|
+
/** The commit this suite ran against — the join key every CI wait uses. */
|
|
834
|
+
head_sha: text("head_sha"),
|
|
835
|
+
/** The branch GitHub attributes the suite to, when it reports one. */
|
|
836
|
+
head_branch: text("head_branch"),
|
|
837
|
+
/** `queued` | `in_progress` | `completed` — a conclusion is only meaningful once completed. */
|
|
838
|
+
status: text("status"),
|
|
839
|
+
/** GitHub's OWN rollup: success | failure | neutral | cancelled | timed_out | action_required |
|
|
840
|
+
* stale | skipped | startup_failure, or null while the suite is still running. THE contract. */
|
|
841
|
+
conclusion: text("conclusion"),
|
|
842
|
+
/** Which app produced this suite — see the app_slug note in the header. */
|
|
843
|
+
app_slug: text("app_slug"),
|
|
844
|
+
/** GitHub's own count of the runs in the suite; useful for spotting a suite still filling up. */
|
|
845
|
+
latest_check_runs_count: integer("latest_check_runs_count"),
|
|
846
|
+
updated_at: integer("updated_at"),
|
|
847
|
+
},
|
|
848
|
+
(t) => [index("idx_check_suites_sha").on(t.head_sha)],
|
|
849
|
+
);
|
|
850
|
+
|
|
700
851
|
/**
|
|
701
852
|
* CTC-667 — a GitHub DEPLOYMENT, one row per deployment id.
|
|
702
853
|
*
|
|
@@ -805,6 +956,21 @@ export const deployment_statuses = sqliteTable(
|
|
|
805
956
|
* key is a node id, which is why it is said out loud: a reader assuming the numeric convention would
|
|
806
957
|
* write a joiner that never matches.
|
|
807
958
|
*
|
|
959
|
+
* ⛔⛔ WEBHOOK-ONLY, NO BACKFILL — SO THIS TABLE IS NOT YET A MERGE GATE, AND READING IT AS ONE IS A
|
|
960
|
+
* FALSE GREEN. Raised by a reviewer on #758 (Codex P1) and it is right: GitHub emits
|
|
961
|
+
* `pull_request_review_thread` ONLY on `resolved` / `unresolved`. A thread that has been OPENED and
|
|
962
|
+
* never touched again produces no event at all, and nothing else writes here — the
|
|
963
|
+
* `pull_request_review_comment` webhook carries no thread id, and there is no reconcile leg for this
|
|
964
|
+
* table. So a PR whose threads are all still open holds ZERO rows, which is byte-identical to a PR
|
|
965
|
+
* with nothing outstanding.
|
|
966
|
+
*
|
|
967
|
+
* ⚠️ That error points the WORST possible direction for the question this table exists to answer:
|
|
968
|
+
* "does this PR have unresolved threads" would read NO for exactly the PRs that most need a yes. Until
|
|
969
|
+
* a backfill lands (CTC-677 — seed a PR's thread set from GitHub's GraphQL `pullRequest.reviewThreads`
|
|
970
|
+
* on the same reconcile pass that already sweeps its comments), this table answers only the NARROWER
|
|
971
|
+
* question it can actually answer: "of the threads we have observed a resolution event for, what is
|
|
972
|
+
* their current state". Do not wire the AGENTS.md merge gate to it before then.
|
|
973
|
+
*
|
|
808
974
|
* ⚠️ `first_comment_id` is the anchor INTO `pr_review_comments` (the earliest comment the thread's
|
|
809
975
|
* payload carries), because the reverse link does not exist — the `pull_request_review_comment`
|
|
810
976
|
* webhook carries no thread id, so a comment cannot name its thread. Nullable: a thread payload
|