@catalyst-cloud/schema 0.1.14 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catalyst-cloud/schema",
3
- "version": "0.1.14",
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,
@@ -79,6 +81,8 @@ export const mirrorSchema = {
79
81
  projects,
80
82
  cycles,
81
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,
82
86
  initiatives,
83
87
  project_initiatives,
84
88
  comments,
@@ -143,6 +147,9 @@ export const mirrorEntityTables = {
143
147
  projects,
144
148
  cycles,
145
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,
146
153
  initiatives,
147
154
  project_initiatives,
148
155
  comments,
@@ -179,6 +179,13 @@ export const MIRROR_MIGRATIONS = {
179
179
  tag: "0023_ambiguous_micromax",
180
180
  breakpoints: true,
181
181
  },
182
+ {
183
+ idx: 24,
184
+ version: "6",
185
+ when: 1786984427741,
186
+ tag: "0024_long_spencer_smythe",
187
+ breakpoints: true,
188
+ },
182
189
  ],
183
190
  },
184
191
  migrations: {
@@ -228,5 +235,7 @@ export const MIRROR_MIGRATIONS = {
228
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;",
229
236
  "0023_ambiguous_micromax":
230
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",
231
240
  },
232
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
  {