@catalyst-cloud/schema 0.1.9 → 0.1.11
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 +2 -2
- package/src/index.ts +4 -0
- package/src/migrations.generated.ts +18 -0
- package/src/mirror.ts +53 -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.11",
|
|
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",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"drizzle-orm": "0.44.7"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@catalyst-cloud/typescript-config": "
|
|
33
|
+
"@catalyst-cloud/typescript-config": "workspace:*",
|
|
34
34
|
"@types/node": "^25.9.3",
|
|
35
35
|
"drizzle-kit": "^0.31.0",
|
|
36
36
|
"prettier": "^3.8.4",
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
agent_activities,
|
|
35
35
|
fleet_activity_pruned,
|
|
36
36
|
pull_sweeps,
|
|
37
|
+
pr_review_backfill,
|
|
37
38
|
} from "./mirror.js";
|
|
38
39
|
|
|
39
40
|
export * from "./mirror.js";
|
|
@@ -86,6 +87,9 @@ export const mirrorSchema = {
|
|
|
86
87
|
// migration bundle must produce) but DELIBERATELY ABSENT from `mirrorEntityTables` below — see the
|
|
87
88
|
// no-feed arithmetic on the table definition in ./mirror.ts.
|
|
88
89
|
pull_sweeps,
|
|
90
|
+
// CTC-575: the closed/merged-PR review-body backfill marker. Same posture as pull_sweeps — a real
|
|
91
|
+
// table, deliberately absent from mirrorEntityTables (see the doc on the table itself).
|
|
92
|
+
pr_review_backfill,
|
|
89
93
|
// CTC-393: the fleet_activity anti-resurrection tombstone. Same posture as pull_sweeps — a real
|
|
90
94
|
// table the DO creates, but pure internal bookkeeping that must NEVER ride the change-feed (a
|
|
91
95
|
// consumer has no use for "a row was once terminal here"; see the doc on the table itself).
|
|
@@ -144,6 +144,20 @@ export const MIRROR_MIGRATIONS = {
|
|
|
144
144
|
tag: "0018_brainy_clint_barton",
|
|
145
145
|
breakpoints: true,
|
|
146
146
|
},
|
|
147
|
+
{
|
|
148
|
+
idx: 19,
|
|
149
|
+
version: "6",
|
|
150
|
+
when: 1786809590855,
|
|
151
|
+
tag: "0019_mushy_sinister_six",
|
|
152
|
+
breakpoints: true,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
idx: 20,
|
|
156
|
+
version: "6",
|
|
157
|
+
when: 1786856395245,
|
|
158
|
+
tag: "0020_wealthy_snowbird",
|
|
159
|
+
breakpoints: true,
|
|
160
|
+
},
|
|
147
161
|
],
|
|
148
162
|
},
|
|
149
163
|
migrations: {
|
|
@@ -183,5 +197,9 @@ export const MIRROR_MIGRATIONS = {
|
|
|
183
197
|
"0017_clammy_doctor_octopus": "ALTER TABLE `users` ADD `email` text;",
|
|
184
198
|
"0018_brainy_clint_barton":
|
|
185
199
|
"CREATE TABLE `agent_activities` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`session_id` text NOT NULL,\n\t`type` text,\n\t`body` text,\n\t`signal` text,\n\t`created_at` integer,\n\t`updated_at` integer,\n\t`raw` text\n);\n--> statement-breakpoint\nCREATE INDEX `idx_agent_activities_session_created` ON `agent_activities` (`session_id`,`created_at`);--> statement-breakpoint\nCREATE TABLE `agent_sessions` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`issue_id` text,\n\t`status` text,\n\t`app_user_id` text,\n\t`creator_id` text,\n\t`created_at` integer,\n\t`updated_at` integer,\n\t`removed_at` integer,\n\t`raw` text\n);\n--> statement-breakpoint\nCREATE INDEX `idx_agent_sessions_issue` ON `agent_sessions` (`issue_id`,`updated_at`);",
|
|
200
|
+
"0019_mushy_sinister_six":
|
|
201
|
+
"ALTER TABLE `agent_sessions` ADD `archived_at` integer;--> statement-breakpoint\nALTER TABLE `agent_sessions` ADD `dismissed_at` integer;--> statement-breakpoint\nALTER TABLE `agent_sessions` ADD `dismissed_by` text;",
|
|
202
|
+
"0020_wealthy_snowbird":
|
|
203
|
+
"CREATE TABLE `pr_review_backfill` (\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`backfilled_at` integer NOT NULL,\n\tPRIMARY KEY(`repo_id`, `pr_number`)\n);\n--> statement-breakpoint\nALTER TABLE `reviews` ADD `body` text;",
|
|
186
204
|
},
|
|
187
205
|
} as const;
|
package/src/mirror.ts
CHANGED
|
@@ -230,6 +230,15 @@ export const agent_sessions = sqliteTable(
|
|
|
230
230
|
created_at: integer("created_at"),
|
|
231
231
|
updated_at: integer("updated_at"), // drives the last-write-wins upsert guard
|
|
232
232
|
removed_at: integer("removed_at"),
|
|
233
|
+
// CTC-529 — a session can END in ways that are NOT the same as being abandoned, and a reader has
|
|
234
|
+
// to tell them apart. `archived_at`/`dismissed_at` are Linear's own terminal markers; a dismissed
|
|
235
|
+
// session was closed BY SOMEONE (`dismissed_by`), which is a different story from one that simply
|
|
236
|
+
// stopped emitting. Promoted out of `raw` to real columns because CTC-503's render branches on
|
|
237
|
+
// them — a render that has to json_extract its own state machine is one bad path from silently
|
|
238
|
+
// reading a dismissed session as still-running.
|
|
239
|
+
archived_at: integer("archived_at"),
|
|
240
|
+
dismissed_at: integer("dismissed_at"),
|
|
241
|
+
dismissed_by: text("dismissed_by"), // FK → users.id; null when Linear reported no actor
|
|
233
242
|
raw: text("raw"), // full fidelity / forward-compat, like issues.raw
|
|
234
243
|
},
|
|
235
244
|
(t) => [index("idx_agent_sessions_issue").on(t.issue_id, t.updated_at)],
|
|
@@ -429,6 +438,12 @@ export const reviews = sqliteTable("reviews", {
|
|
|
429
438
|
user_id: text("user_id"),
|
|
430
439
|
state: text("state"),
|
|
431
440
|
submitted_at: integer("submitted_at"),
|
|
441
|
+
// CTC-575: the review's text. GitHub always sends it; it was destructured away at the type boundary
|
|
442
|
+
// in three places (github/queries.ts, normalize/github.ts ×2) before this. ⛔ `reviews` has NO
|
|
443
|
+
// updated_at, so upsertRow's last-write-wins guard is inert here — the reconcile's hand-rolled
|
|
444
|
+
// read-before-emit (github-reconcile.ts) is the ONLY thing that decides whether an edited body
|
|
445
|
+
// re-emits a change_log delta, and it must compare `body` too or an edit never propagates.
|
|
446
|
+
body: text("body"),
|
|
432
447
|
});
|
|
433
448
|
|
|
434
449
|
// ── Mirror infra (not domain entities — never ride the change-feed) ─────────────────────────────────
|
|
@@ -612,3 +627,41 @@ export const pull_sweeps = sqliteTable(
|
|
|
612
627
|
},
|
|
613
628
|
(t) => [primaryKey({ columns: [t.repo_id, t.pr_number] })],
|
|
614
629
|
);
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* CTC-575 — a marker that a CLOSED/MERGED PR's reviews have been swept AT LEAST ONCE for `body`.
|
|
633
|
+
*
|
|
634
|
+
* ⛔ WHY THIS EXISTS. `reconcileGithub`'s open-PR walk sweeps reviews only for PRs whose `state` is
|
|
635
|
+
* still `open` — a closed/merged PR's reviews are frozen at whatever the last webhook or open-PR sweep
|
|
636
|
+
* wrote, which for every review that predates this ticket is `body: null` (the column did not exist).
|
|
637
|
+
* Nothing else ever revisits a closed PR, so without a dedicated backfill leg every historical review
|
|
638
|
+
* body on every already-closed PR would stay null forever.
|
|
639
|
+
*
|
|
640
|
+
* ⛔ WHY A MARKER PER PR, NOT A SINGLE RESUME CURSOR. PR numbers are monotonic by CREATION order, not
|
|
641
|
+
* by CLOSE order — PR #5 can close well after PR #50 already did. A single "highest number backfilled"
|
|
642
|
+
* watermark (the `resumeEntity`/`mergeableEntity` pattern above) would permanently skip a low-numbered
|
|
643
|
+
* PR that closes late, once the watermark has already passed its number. A per-(repo_id, pr_number)
|
|
644
|
+
* marker is checked independently, so a PR is swept exactly once, whenever it happens to close.
|
|
645
|
+
*
|
|
646
|
+
* ⛔ DELIBERATELY NOT `pull_sweeps`. That table's three-state decode (no row / SHA-mismatch / count-or-
|
|
647
|
+
* error) is a documented invariant for the OPEN-PR CI-enrichment receipt (check_runs/commit_statuses),
|
|
648
|
+
* and this backfill sweeps reviews ONLY for a closed PR — writing a `pull_sweeps` row with
|
|
649
|
+
* check_run_count/commit_status_count left null and no *_error set would forge a fourth, undocumented
|
|
650
|
+
* state indistinguishable from "the checks fetch failed". A dedicated table keeps the two receipts'
|
|
651
|
+
* invariants independently true.
|
|
652
|
+
*
|
|
653
|
+
* ⛔ HUB-READ-ONLY, same posture as `pull_sweeps` / `fleet_activity_pruned`: never rides the change
|
|
654
|
+
* feed (absent from mirrorEntityTables / SNAPSHOT_TABLES / GITHUB_ENTITIES, no appendChange) — a
|
|
655
|
+
* consumer has no use for "we once swept this PR's reviews", and self-limiting at |closed PRs in the
|
|
656
|
+
* repo| (one row per PR, written once, never revisited).
|
|
657
|
+
*/
|
|
658
|
+
export const pr_review_backfill = sqliteTable(
|
|
659
|
+
"pr_review_backfill",
|
|
660
|
+
{
|
|
661
|
+
repo_id: text("repo_id").notNull(),
|
|
662
|
+
pr_number: integer("pr_number").notNull(),
|
|
663
|
+
/** When the backfill swept this PR's reviews (ms). */
|
|
664
|
+
backfilled_at: integer("backfilled_at").notNull(),
|
|
665
|
+
},
|
|
666
|
+
(t) => [primaryKey({ columns: [t.repo_id, t.pr_number] })],
|
|
667
|
+
);
|