@catalyst-cloud/schema 0.1.13 → 0.1.15
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 +13 -0
- package/src/migrations.generated.ts +18 -0
- package/src/mirror.ts +67 -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.15",
|
|
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
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
projects,
|
|
20
20
|
cycles,
|
|
21
21
|
workflow_states,
|
|
22
|
+
// CTC-624: the tenant's per-team workflow DECISION, projected from the D1 registry (ADR-0033).
|
|
23
|
+
team_workflow_mapping,
|
|
22
24
|
initiatives,
|
|
23
25
|
project_initiatives,
|
|
24
26
|
comments,
|
|
@@ -41,6 +43,7 @@ import {
|
|
|
41
43
|
pr_commits,
|
|
42
44
|
pr_events,
|
|
43
45
|
pr_ancillary_backfill,
|
|
46
|
+
push_subscriptions,
|
|
44
47
|
} from "./mirror.js";
|
|
45
48
|
|
|
46
49
|
export * from "./mirror.js";
|
|
@@ -78,6 +81,8 @@ export const mirrorSchema = {
|
|
|
78
81
|
projects,
|
|
79
82
|
cycles,
|
|
80
83
|
workflow_states,
|
|
84
|
+
// CTC-624: projected from the D1 registry, the one mirror table whose authority is not a provider.
|
|
85
|
+
team_workflow_mapping,
|
|
81
86
|
initiatives,
|
|
82
87
|
project_initiatives,
|
|
83
88
|
comments,
|
|
@@ -112,6 +117,9 @@ export const mirrorSchema = {
|
|
|
112
117
|
pr_commits,
|
|
113
118
|
pr_events,
|
|
114
119
|
pr_ancillary_backfill,
|
|
120
|
+
// CTC-619: a member's Web Push subscription. Rides the change feed like any other entity — a
|
|
121
|
+
// member-facing settings UI can reflect subscribe/unsubscribe reactively.
|
|
122
|
+
push_subscriptions,
|
|
115
123
|
} as const;
|
|
116
124
|
|
|
117
125
|
/**
|
|
@@ -139,6 +147,9 @@ export const mirrorEntityTables = {
|
|
|
139
147
|
projects,
|
|
140
148
|
cycles,
|
|
141
149
|
workflow_states,
|
|
150
|
+
// CTC-624: the tenant's per-team workflow decision. On this plane deliberately — it is how
|
|
151
|
+
// anything reaches a host at all, and the browser board is a legitimate consumer (ADR-0033).
|
|
152
|
+
team_workflow_mapping,
|
|
142
153
|
initiatives,
|
|
143
154
|
project_initiatives,
|
|
144
155
|
comments,
|
|
@@ -157,6 +168,8 @@ export const mirrorEntityTables = {
|
|
|
157
168
|
pr_conversation_comments,
|
|
158
169
|
pr_commits,
|
|
159
170
|
pr_events,
|
|
171
|
+
// CTC-619: a member's Web Push subscription.
|
|
172
|
+
push_subscriptions,
|
|
160
173
|
} as const;
|
|
161
174
|
|
|
162
175
|
/** The wire `entity` name of a change-feed-carried table (keys of mirrorEntityTables). */
|
|
@@ -172,6 +172,20 @@ export const MIRROR_MIGRATIONS = {
|
|
|
172
172
|
tag: "0022_chilly_gorgon",
|
|
173
173
|
breakpoints: true,
|
|
174
174
|
},
|
|
175
|
+
{
|
|
176
|
+
idx: 23,
|
|
177
|
+
version: "6",
|
|
178
|
+
when: 1786935258669,
|
|
179
|
+
tag: "0023_ambiguous_micromax",
|
|
180
|
+
breakpoints: true,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
idx: 24,
|
|
184
|
+
version: "6",
|
|
185
|
+
when: 1786984427741,
|
|
186
|
+
tag: "0024_long_spencer_smythe",
|
|
187
|
+
breakpoints: true,
|
|
188
|
+
},
|
|
175
189
|
],
|
|
176
190
|
},
|
|
177
191
|
migrations: {
|
|
@@ -219,5 +233,9 @@ export const MIRROR_MIGRATIONS = {
|
|
|
219
233
|
"CREATE TABLE `pr_ancillary_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\nCREATE TABLE `pr_commits` (\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`sha` text NOT NULL,\n\t`message` text,\n\t`author_login` text,\n\t`author_name` text,\n\t`author_email` text,\n\t`authored_at` integer,\n\t`committed_at` integer,\n\tPRIMARY KEY(`repo_id`, `pr_number`, `sha`)\n);\n--> statement-breakpoint\nCREATE TABLE `pr_conversation_comments` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`author_id` text,\n\t`body` text,\n\t`created_at` integer,\n\t`updated_at` integer,\n\t`removed_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_pr_conversation_comments_pr` ON `pr_conversation_comments` (`repo_id`,`pr_number`);--> statement-breakpoint\nCREATE TABLE `pr_events` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`action` text,\n\t`actor_id` text,\n\t`created_at` integer,\n\t`updated_at` integer,\n\t`raw` text\n);\n--> statement-breakpoint\nCREATE INDEX `idx_pr_events_pr` ON `pr_events` (`repo_id`,`pr_number`,`created_at`);--> statement-breakpoint\nCREATE TABLE `pr_review_comments` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`review_id` text,\n\t`commit_id` text,\n\t`path` text,\n\t`line` integer,\n\t`diff_hunk` text,\n\t`in_reply_to_id` text,\n\t`author_id` text,\n\t`body` text,\n\t`created_at` integer,\n\t`updated_at` integer,\n\t`removed_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_pr_review_comments_pr` ON `pr_review_comments` (`repo_id`,`pr_number`);",
|
|
220
234
|
"0022_chilly_gorgon":
|
|
221
235
|
"CREATE TABLE `workflow_states` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`team_id` text,\n\t`name` text,\n\t`type` text,\n\t`position` real,\n\t`color` text,\n\t`archived_at` integer,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_workflow_states_team` ON `workflow_states` (`team_id`);--> statement-breakpoint\nALTER TABLE `issues` ADD `state_type` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `state_position` real;",
|
|
236
|
+
"0023_ambiguous_micromax":
|
|
237
|
+
"CREATE TABLE `push_subscriptions` (\n\t`endpoint` text PRIMARY KEY NOT NULL,\n\t`member_id` text NOT NULL,\n\t`p256dh` text NOT NULL,\n\t`auth` text NOT NULL,\n\t`created_at` integer,\n\t`last_used_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_push_subscriptions_member` ON `push_subscriptions` (`member_id`);",
|
|
238
|
+
"0024_long_spencer_smythe":
|
|
239
|
+
"CREATE TABLE `team_workflow_mapping` (\n\t`team_id` text NOT NULL,\n\t`slot` text NOT NULL,\n\t`linear_state_id` text NOT NULL,\n\t`linear_state_name` text,\n\t`linear_state_type` text,\n\t`workflow_rev` integer,\n\t`updated_at` integer,\n\t`removed_at` integer,\n\tPRIMARY KEY(`team_id`, `slot`)\n);\n",
|
|
222
240
|
},
|
|
223
241
|
} as const;
|
package/src/mirror.ts
CHANGED
|
@@ -318,6 +318,51 @@ export const cycles = sqliteTable("cycles", {
|
|
|
318
318
|
// (the prerequisite CTC-277's kanban board blocks on). Small, workspace-wide set — full sweep each
|
|
319
319
|
// pass, same discipline as `labels`/`cycles`. No `removed_at`: Linear ARCHIVES a workflow state rather
|
|
320
320
|
// than deleting it (`archived_at`), same convention `issues.archived_at` already uses.
|
|
321
|
+
/**
|
|
322
|
+
* CTC-624 — the tenant's per-team workflow DECISION, replicated to hosts and the browser.
|
|
323
|
+
*
|
|
324
|
+
* ⭐ THIS IS THE ONE MIRROR TABLE WHOSE AUTHORITY IS NOT A PROVIDER. Every other table here is a
|
|
325
|
+
* projection of something Linear or GitHub owns; this is a projection of the **D1 registry**
|
|
326
|
+
* (`team_workflow_mapping`), which is where the tenant's choice actually lives. It rides this plane
|
|
327
|
+
* because the plane is how anything reaches a host at all, and ADR-0033 records why that does not
|
|
328
|
+
* re-open ADR-0028: the states themselves stay a mirrored Linear fact (`workflow_states`, below),
|
|
329
|
+
* while "which state means dispatchable" is a decision Linear has no field for.
|
|
330
|
+
*
|
|
331
|
+
* ⛔ `linear_state_name` / `linear_state_type` ARE DISPLAY SNAPSHOTS AND MUST NEVER BE JOINED ON.
|
|
332
|
+
* CTC-403: a Linear-to-Linear import preserved every human identifier and re-minted ~248 state UUIDs,
|
|
333
|
+
* so every cache still MATCHED by name while pointing at nothing. `linear_state_id` is the authority;
|
|
334
|
+
* a consumer resolving a slot joins THAT against `workflow_states` and finds the row absent when the
|
|
335
|
+
* workspace has been re-minted. That absence is the alarm, and it only works because the join key is
|
|
336
|
+
* the id.
|
|
337
|
+
*
|
|
338
|
+
* ⭐ `workflow_rev` RIDES EVERY ROW, and that is what makes the receipt possible. A host reads the max
|
|
339
|
+
* `workflow_rev` present in its own replica and echoes it on `/connect` (CTC-628) — read at RUNTIME
|
|
340
|
+
* FROM THE REPLICA, never from a config file, the same LOADED-not-LOCKED discipline
|
|
341
|
+
* `loadedSchemaIdentity` enforces for the migration bundle. Without the column on the row there is
|
|
342
|
+
* nothing for the host to read, and "we pushed it" would again be unfalsifiable.
|
|
343
|
+
*
|
|
344
|
+
* PK is `(team_id, slot)` — no `account_id`, because a Mirror DO **is** one account (same convention
|
|
345
|
+
* as every table here). Soft-deleted via `removed_at` so clearing a slot propagates as a change rather
|
|
346
|
+
* than as a row that silently stops being mentioned.
|
|
347
|
+
*/
|
|
348
|
+
export const team_workflow_mapping = sqliteTable(
|
|
349
|
+
"team_workflow_mapping",
|
|
350
|
+
{
|
|
351
|
+
team_id: text("team_id").notNull(),
|
|
352
|
+
/** One of the eleven Catalyst slots; the five load-bearing ones gate the fleet. */
|
|
353
|
+
slot: text("slot").notNull(),
|
|
354
|
+
/** THE AUTHORITY — joined against workflow_states.id by every consumer. */
|
|
355
|
+
linear_state_id: text("linear_state_id").notNull(),
|
|
356
|
+
linear_state_name: text("linear_state_name"),
|
|
357
|
+
linear_state_type: text("linear_state_type"),
|
|
358
|
+
/** The ACCOUNT-wide revision this row was written at. The scalar a host echoes back. */
|
|
359
|
+
workflow_rev: integer("workflow_rev"),
|
|
360
|
+
updated_at: integer("updated_at"),
|
|
361
|
+
removed_at: integer("removed_at"),
|
|
362
|
+
},
|
|
363
|
+
(t) => [primaryKey({ columns: [t.team_id, t.slot] })],
|
|
364
|
+
);
|
|
365
|
+
|
|
321
366
|
export const workflow_states = sqliteTable(
|
|
322
367
|
"workflow_states",
|
|
323
368
|
{
|
|
@@ -851,3 +896,25 @@ export const pr_ancillary_backfill = sqliteTable(
|
|
|
851
896
|
},
|
|
852
897
|
(t) => [primaryKey({ columns: [t.repo_id, t.pr_number] })],
|
|
853
898
|
);
|
|
899
|
+
|
|
900
|
+
// CTC-619 — a member's Web Push subscription (one browser/device). PK is the subscription `endpoint`
|
|
901
|
+
// itself — a push endpoint URL is unique per browser install by construction, so keying on it is what
|
|
902
|
+
// makes "a duplicate subscribe for the same endpoint does not create a second row" (the ticket's own
|
|
903
|
+
// AC) a plain upsert, no synthetic id needed. `member_id` is the Linear user id (FK-shaped to
|
|
904
|
+
// users.id, same as issues.assignee_id) — the ingest trigger's own correlation key. HARD delete on
|
|
905
|
+
// unsubscribe (no removed_at): a stale/revoked push subscription has no historical value, matching
|
|
906
|
+
// `users`' own "HARD-UPSERT, no soft delete" precedent for similarly overwrite-in-place data.
|
|
907
|
+
export const push_subscriptions = sqliteTable(
|
|
908
|
+
"push_subscriptions",
|
|
909
|
+
{
|
|
910
|
+
endpoint: text("endpoint").primaryKey(),
|
|
911
|
+
member_id: text("member_id").notNull(),
|
|
912
|
+
p256dh: text("p256dh").notNull(),
|
|
913
|
+
auth: text("auth").notNull(),
|
|
914
|
+
created_at: integer("created_at"),
|
|
915
|
+
last_used_at: integer("last_used_at"),
|
|
916
|
+
},
|
|
917
|
+
// The ingest trigger's own read pattern: "every subscription for this member" (member_id, NOT the
|
|
918
|
+
// PK), keyed off issues.assignee_id — the same shape idx_workflow_states_team indexes on team_id.
|
|
919
|
+
(t) => [index("idx_push_subscriptions_member").on(t.member_id)],
|
|
920
|
+
);
|