@catalyst-cloud/schema 0.1.26 → 0.1.28

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catalyst-cloud/schema",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
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",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "devDependencies": {
33
33
  "@catalyst-cloud/typescript-config": "workspace:*",
34
- "@types/node": "^25.9.3",
34
+ "@types/node": "^26.3.0",
35
35
  "drizzle-kit": "^0.31.0",
36
36
  "prettier": "^3.9.6",
37
37
  "typescript": "^7.0.2",
@@ -221,6 +221,27 @@ export const MIRROR_MIGRATIONS = {
221
221
  tag: "0029_square_silhouette",
222
222
  breakpoints: true,
223
223
  },
224
+ {
225
+ idx: 30,
226
+ version: "6",
227
+ when: 1787972489820,
228
+ tag: "0030_wandering_bastion",
229
+ breakpoints: true,
230
+ },
231
+ {
232
+ idx: 31,
233
+ version: "6",
234
+ when: 1787972497785,
235
+ tag: "0031_ctc851_comment_parent_backfill",
236
+ breakpoints: true,
237
+ },
238
+ {
239
+ idx: 32,
240
+ version: "6",
241
+ when: 1788057508383,
242
+ tag: "0032_elite_donald_blake",
243
+ breakpoints: true,
244
+ },
224
245
  ],
225
246
  },
226
247
  migrations: {
@@ -281,5 +302,11 @@ export const MIRROR_MIGRATIONS = {
281
302
  "0028_burly_nemesis": "ALTER TABLE `check_suites` ADD `pull_request_numbers` text;",
282
303
  "0029_square_silhouette":
283
304
  "CREATE INDEX `idx_relations_type_related` ON `relations` (`type`,`related_identifier`);",
305
+ "0030_wandering_bastion":
306
+ "ALTER TABLE `comments` ADD `parent_type` text;--> statement-breakpoint\nALTER TABLE `comments` ADD `parent_entity_id` text;--> statement-breakpoint\nCREATE INDEX `idx_comments_parent` ON `comments` (`parent_type`,`parent_entity_id`,`removed_at`);",
307
+ "0031_ctc851_comment_parent_backfill":
308
+ "-- CTC-851 — backfill comment parents + quarantine the pre-fix empty-issue_id orphans.\n--\n-- 0030 adds the parent_type/parent_entity_id columns and fixes both WRITE legs (webhook normalizer +\n-- reconcile) so new/re-swept comments carry a real parent, but that alone converges only rows the\n-- reconcile re-sweeps — and it walks OPEN issues, so a non-issue comment (or a comment on a closed\n-- issue) keeps its corrupt shape forever. This backfill is what makes the CORPUS canonical rather\n-- than just the new writes.\n--\n-- ⭐ Deliberately a DATA migration in the SHARED bundle (the 0014 pattern): the DO and every host /\n-- OPFS replica apply the same MIRROR_MIGRATIONS through the same runner, so each side repairs its OWN\n-- rows locally. That converges every replica WITHOUT emitting a change_log delta per row (migrations\n-- bypass appendChange), so no ring-buffer flood (the CTC-279 concern).\n--\n-- (a) Healthy rows (a real issue_id) → parent_type='issue', parent_entity_id=issue_id.\n-- (b) Orphans (issue_id empty-string — the measured shape — or NULL, defensively) cannot be repaired\n-- from row data: the replica comments table has no `raw` column and no parent signal beyond the\n-- comment-reply parent_id. A pure in-bundle DML migration cannot call Linear, so it QUARANTINES\n-- them: parent_type='unresolved', issue_id nulled — MARKED with a stated reason, never deleted\n-- (the AC explicitly permits quarantine-with-reason). CTC-851 measured 54 such rows live.\n--\n-- Idempotent by the `parent_type IS NULL` guard: once a row is stamped it is skipped on replay, so a\n-- crash between a statement succeeding and its tag being recorded (execReplaySafe) is safe.\nUPDATE `comments`\n SET `parent_type` = 'issue', `parent_entity_id` = `issue_id`\n WHERE `parent_type` IS NULL AND `issue_id` IS NOT NULL AND `issue_id` <> '';\n--> statement-breakpoint\nUPDATE `comments`\n SET `parent_type` = 'unresolved', `issue_id` = NULL\n WHERE `parent_type` IS NULL AND (`issue_id` = '' OR `issue_id` IS NULL);\n",
309
+ "0032_elite_donald_blake":
310
+ "ALTER TABLE `issues` ADD `creator_id` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `creator_name` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `creator_avatar_url` text;",
284
311
  },
285
312
  } as const;
package/src/mirror.ts CHANGED
@@ -62,6 +62,10 @@ export const issues = sqliteTable(
62
62
  bot_actor_name: text("bot_actor_name"),
63
63
  bot_actor_type: text("bot_actor_type"),
64
64
  bot_actor_sub_type: text("bot_actor_sub_type"),
65
+ // CTC-867: the raiser — the Linear issue creator, inline triplet copying comments.author_*.
66
+ creator_id: text("creator_id"),
67
+ creator_name: text("creator_name"),
68
+ creator_avatar_url: text("creator_avatar_url"),
65
69
  parent_id: text("parent_id"),
66
70
  parent_identifier: text("parent_identifier"),
67
71
  // Ticket field enrichment (CTC-TICKET-FIELDS): lifecycle timestamps + due date + snooze + url +
@@ -422,6 +426,13 @@ export const comments = sqliteTable(
422
426
  {
423
427
  id: text("id").primaryKey(),
424
428
  issue_id: text("issue_id"),
429
+ // CTC-851: the real parent of a non-issue comment (project/initiative update, document, …).
430
+ // `parent_type` ∈ 'issue'|'projectUpdate'|'initiativeUpdate'|'documentContent'|'project'|
431
+ // 'initiative'|'unresolved'; `parent_entity_id` is the id of that parent. For an issue comment
432
+ // both `issue_id` and `parent_entity_id` hold the issue id (parent_type 'issue'), so every
433
+ // existing `WHERE issue_id = ?` read is untouched. Nullable: a quarantined orphan has neither.
434
+ parent_type: text("parent_type"),
435
+ parent_entity_id: text("parent_entity_id"),
425
436
  body: text("body"),
426
437
  author_id: text("author_id"),
427
438
  author_name: text("author_name"),
@@ -435,7 +446,12 @@ export const comments = sqliteTable(
435
446
  },
436
447
  // CTC-59: the detail comments fetch — `WHERE issue_id = ? AND removed_at IS NULL ORDER BY updated_at`.
437
448
  // Composite covers the filter AND the sort, so SQLite seeks instead of building a temp b-tree.
438
- (t) => [index("idx_comments_issue").on(t.issue_id, t.removed_at, t.updated_at)],
449
+ (t) => [
450
+ index("idx_comments_issue").on(t.issue_id, t.removed_at, t.updated_at),
451
+ // CTC-851: retrievability by parent — `WHERE parent_type = ? AND parent_entity_id = ? AND
452
+ // removed_at IS NULL`. Makes the non-issue-comment corpus queryable without a table scan.
453
+ index("idx_comments_parent").on(t.parent_type, t.parent_entity_id, t.removed_at),
454
+ ],
439
455
  );
440
456
 
441
457
  // ── GitHub ───────────────────────────────────────────────────────────────────────────────────────