@catalyst-cloud/schema 0.1.48 → 0.1.50
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.
|
|
3
|
+
"version": "0.1.50",
|
|
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/events/registry.ts
CHANGED
|
@@ -791,6 +791,72 @@ export const durableEventTypes = {
|
|
|
791
791
|
wakes: false,
|
|
792
792
|
entity: "ticket",
|
|
793
793
|
},
|
|
794
|
+
// ── CTC-1597 — `intake` is the renamed `triage` phase, ADDITIVE beside it (D4). Same shape as the
|
|
795
|
+
// `phase.triage.*` block below; nothing in this repo emits these yet (the phase is dormant, outside
|
|
796
|
+
// RELAY_PHASES), declared now so CTC-1530 can be re-cut under the right name.
|
|
797
|
+
"phase.intake.abandoned": {
|
|
798
|
+
rationale:
|
|
799
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase's worker abandoned it — a lifecycle terminal fact. CTC-1597 — the renamed `triage` phase.",
|
|
800
|
+
ingestVerb: "coordination.publish",
|
|
801
|
+
wakes: false,
|
|
802
|
+
entity: "ticket",
|
|
803
|
+
},
|
|
804
|
+
"phase.intake.boot-resume": {
|
|
805
|
+
rationale:
|
|
806
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the daemon restarted and resumed this phase — a real coordination fact about continuity across a restart. CTC-1597 — the renamed `triage` phase.",
|
|
807
|
+
ingestVerb: "coordination.publish",
|
|
808
|
+
wakes: false,
|
|
809
|
+
entity: "ticket",
|
|
810
|
+
},
|
|
811
|
+
"phase.intake.complete": {
|
|
812
|
+
rationale:
|
|
813
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase reported success. CTC-1597 — the renamed `triage` phase.",
|
|
814
|
+
ingestVerb: "coordination.publish",
|
|
815
|
+
wakes: false,
|
|
816
|
+
entity: "ticket",
|
|
817
|
+
},
|
|
818
|
+
"phase.intake.escalated": {
|
|
819
|
+
rationale:
|
|
820
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase escalated to a human. CTC-1597 — the renamed `triage` phase.",
|
|
821
|
+
ingestVerb: "coordination.publish",
|
|
822
|
+
wakes: { consumerClass: "host-channel" },
|
|
823
|
+
entity: "ticket",
|
|
824
|
+
},
|
|
825
|
+
"phase.intake.failed": {
|
|
826
|
+
rationale:
|
|
827
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase reported failure. CTC-1597 — the renamed `triage` phase.",
|
|
828
|
+
ingestVerb: "coordination.publish",
|
|
829
|
+
wakes: false,
|
|
830
|
+
entity: "ticket",
|
|
831
|
+
},
|
|
832
|
+
"phase.intake.failure-retracted": {
|
|
833
|
+
rationale:
|
|
834
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): a previously-reported failure was retracted (corrected) — a real correction fact. CTC-1597 — the renamed `triage` phase.",
|
|
835
|
+
ingestVerb: "coordination.publish",
|
|
836
|
+
wakes: false,
|
|
837
|
+
entity: "ticket",
|
|
838
|
+
},
|
|
839
|
+
"phase.intake.linear-transition": {
|
|
840
|
+
rationale:
|
|
841
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase performed a real Linear ticket-state transition as part of its own execution. CTC-1597 — the renamed `triage` phase.",
|
|
842
|
+
ingestVerb: "coordination.publish",
|
|
843
|
+
wakes: false,
|
|
844
|
+
entity: "ticket",
|
|
845
|
+
},
|
|
846
|
+
"phase.intake.orphan-detected": {
|
|
847
|
+
rationale:
|
|
848
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): an orphaned phase-agent worker was detected — a coordination fact (state discovered), not a sample. CTC-1597 — the renamed `triage` phase.",
|
|
849
|
+
ingestVerb: "coordination.publish",
|
|
850
|
+
wakes: false,
|
|
851
|
+
entity: "ticket",
|
|
852
|
+
},
|
|
853
|
+
"phase.intake.revive": {
|
|
854
|
+
rationale:
|
|
855
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): a parked phase was revived. CTC-1597 — the renamed `triage` phase.",
|
|
856
|
+
ingestVerb: "coordination.publish",
|
|
857
|
+
wakes: false,
|
|
858
|
+
entity: "ticket",
|
|
859
|
+
},
|
|
794
860
|
"phase.merge.complete": {
|
|
795
861
|
rationale:
|
|
796
862
|
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase reported success.",
|
|
@@ -1519,65 +1585,70 @@ export const durableEventTypes = {
|
|
|
1519
1585
|
wakes: false,
|
|
1520
1586
|
entity: "ticket",
|
|
1521
1587
|
},
|
|
1588
|
+
// ⛔ CTC-1597 — RETAINED, NOT DELETED (D4). These nine classify a real, committed August-2026 corpus
|
|
1589
|
+
// (docs/event-backbone/observed-types-2026-08.json) produced by the sibling `catalyst` repo's now-
|
|
1590
|
+
// retired daemon. Deleting them would make a legacy re-publish fall through to `unclassified`. The
|
|
1591
|
+
// phase was renamed to `intake` (see the `phase.intake.*` block above) — nothing in this repo emits
|
|
1592
|
+
// `phase.triage.*` going forward; each rationale below says so.
|
|
1522
1593
|
"phase.triage.abandoned": {
|
|
1523
1594
|
rationale:
|
|
1524
|
-
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase's worker abandoned it — a lifecycle terminal fact.",
|
|
1595
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase's worker abandoned it — a lifecycle terminal fact. CTC-1597 renamed this phase to `intake`; this key is RETAINED to classify the historical corpus and any legacy re-publish — nothing emits it going forward.",
|
|
1525
1596
|
ingestVerb: "coordination.publish",
|
|
1526
1597
|
wakes: false,
|
|
1527
1598
|
entity: "ticket",
|
|
1528
1599
|
},
|
|
1529
1600
|
"phase.triage.boot-resume": {
|
|
1530
1601
|
rationale:
|
|
1531
|
-
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the daemon restarted and resumed this phase — a real coordination fact about continuity across a restart.",
|
|
1602
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the daemon restarted and resumed this phase — a real coordination fact about continuity across a restart. CTC-1597 renamed this phase to `intake`; this key is RETAINED to classify the historical corpus and any legacy re-publish — nothing emits it going forward.",
|
|
1532
1603
|
ingestVerb: "coordination.publish",
|
|
1533
1604
|
wakes: false,
|
|
1534
1605
|
entity: "ticket",
|
|
1535
1606
|
},
|
|
1536
1607
|
"phase.triage.complete": {
|
|
1537
1608
|
rationale:
|
|
1538
|
-
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase reported success.",
|
|
1609
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase reported success. CTC-1597 renamed this phase to `intake`; this key is RETAINED to classify the historical corpus and any legacy re-publish — nothing emits it going forward.",
|
|
1539
1610
|
ingestVerb: "coordination.publish",
|
|
1540
1611
|
wakes: false,
|
|
1541
1612
|
entity: "ticket",
|
|
1542
1613
|
},
|
|
1543
1614
|
"phase.triage.escalated": {
|
|
1544
1615
|
rationale:
|
|
1545
|
-
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase escalated to a human.",
|
|
1616
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase escalated to a human. CTC-1597 renamed this phase to `intake`; this key is RETAINED to classify the historical corpus and any legacy re-publish — nothing emits it going forward.",
|
|
1546
1617
|
ingestVerb: "coordination.publish",
|
|
1547
1618
|
wakes: { consumerClass: "host-channel" },
|
|
1548
1619
|
entity: "ticket",
|
|
1549
1620
|
},
|
|
1550
1621
|
"phase.triage.failed": {
|
|
1551
1622
|
rationale:
|
|
1552
|
-
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase reported failure.",
|
|
1623
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase reported failure. CTC-1597 renamed this phase to `intake`; this key is RETAINED to classify the historical corpus and any legacy re-publish — nothing emits it going forward.",
|
|
1553
1624
|
ingestVerb: "coordination.publish",
|
|
1554
1625
|
wakes: false,
|
|
1555
1626
|
entity: "ticket",
|
|
1556
1627
|
},
|
|
1557
1628
|
"phase.triage.failure-retracted": {
|
|
1558
1629
|
rationale:
|
|
1559
|
-
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): a previously-reported failure was retracted (corrected) — a real correction fact.",
|
|
1630
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): a previously-reported failure was retracted (corrected) — a real correction fact. CTC-1597 renamed this phase to `intake`; this key is RETAINED to classify the historical corpus and any legacy re-publish — nothing emits it going forward.",
|
|
1560
1631
|
ingestVerb: "coordination.publish",
|
|
1561
1632
|
wakes: false,
|
|
1562
1633
|
entity: "ticket",
|
|
1563
1634
|
},
|
|
1564
1635
|
"phase.triage.linear-transition": {
|
|
1565
1636
|
rationale:
|
|
1566
|
-
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase performed a real Linear ticket-state transition as part of its own execution.",
|
|
1637
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase performed a real Linear ticket-state transition as part of its own execution. CTC-1597 renamed this phase to `intake`; this key is RETAINED to classify the historical corpus and any legacy re-publish — nothing emits it going forward.",
|
|
1567
1638
|
ingestVerb: "coordination.publish",
|
|
1568
1639
|
wakes: false,
|
|
1569
1640
|
entity: "ticket",
|
|
1570
1641
|
},
|
|
1571
1642
|
"phase.triage.orphan-detected": {
|
|
1572
1643
|
rationale:
|
|
1573
|
-
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): an orphaned phase-agent worker was detected — a coordination fact (state discovered), not a sample.",
|
|
1644
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): an orphaned phase-agent worker was detected — a coordination fact (state discovered), not a sample. CTC-1597 renamed this phase to `intake`; this key is RETAINED to classify the historical corpus and any legacy re-publish — nothing emits it going forward.",
|
|
1574
1645
|
ingestVerb: "coordination.publish",
|
|
1575
1646
|
wakes: false,
|
|
1576
1647
|
entity: "ticket",
|
|
1577
1648
|
},
|
|
1578
1649
|
"phase.triage.revive": {
|
|
1579
1650
|
rationale:
|
|
1580
|
-
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): a parked phase was revived.",
|
|
1651
|
+
"ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): a parked phase was revived. CTC-1597 renamed this phase to `intake`; this key is RETAINED to classify the historical corpus and any legacy re-publish — nothing emits it going forward.",
|
|
1581
1652
|
ingestVerb: "coordination.publish",
|
|
1582
1653
|
wakes: false,
|
|
1583
1654
|
entity: "ticket",
|
|
@@ -270,6 +270,13 @@ export const MIRROR_MIGRATIONS = {
|
|
|
270
270
|
tag: "0036_watery_hercules",
|
|
271
271
|
breakpoints: true,
|
|
272
272
|
},
|
|
273
|
+
{
|
|
274
|
+
idx: 37,
|
|
275
|
+
version: "6",
|
|
276
|
+
when: 1788479421934,
|
|
277
|
+
tag: "0037_ctc1597_workflow_slot_intake",
|
|
278
|
+
breakpoints: true,
|
|
279
|
+
},
|
|
273
280
|
],
|
|
274
281
|
},
|
|
275
282
|
migrations: {
|
|
@@ -344,5 +351,7 @@ export const MIRROR_MIGRATIONS = {
|
|
|
344
351
|
"ALTER TABLE `agent_activities` ADD `action` text;--> statement-breakpoint\nALTER TABLE `agent_activities` ADD `parameter` text;--> statement-breakpoint\nALTER TABLE `agent_activities` ADD `result` text;--> statement-breakpoint\nUPDATE agent_activities\n SET action = json_extract(raw, '$.content.action'),\n parameter = json_extract(raw, '$.content.parameter'),\n result = json_extract(raw, '$.content.result')\n WHERE raw IS NOT NULL\n AND json_valid(raw)\n AND json_extract(raw, '$.content.type') = 'action';\n",
|
|
345
352
|
"0036_watery_hercules":
|
|
346
353
|
"CREATE TABLE `fleet_activity_rate` (\n\t`host_id` text NOT NULL,\n\t`ticket` text NOT NULL,\n\t`bucket_start_ms` integer NOT NULL,\n\t`events` integer NOT NULL,\n\tPRIMARY KEY(`host_id`, `ticket`, `bucket_start_ms`)\n);\n--> statement-breakpoint\nCREATE TABLE `fleet_anomalies` (\n\t`kind` text NOT NULL,\n\t`host_id` text NOT NULL,\n\t`ticket` text NOT NULL,\n\t`detected_at_ms` integer NOT NULL,\n\t`observed` integer NOT NULL,\n\t`baseline` integer NOT NULL,\n\t`detail` text,\n\t`updated_at` integer NOT NULL,\n\tPRIMARY KEY(`kind`, `host_id`, `ticket`)\n);\n--> statement-breakpoint\nCREATE TABLE `fleet_health_sends` (\n\t`host_id` text NOT NULL,\n\t`status` text NOT NULL,\n\t`status_changed_at_ms` integer NOT NULL,\n\t`sent_at_ms` integer NOT NULL,\n\t`delivered` integer NOT NULL,\n\t`failed` integer NOT NULL,\n\tPRIMARY KEY(`host_id`, `status`, `status_changed_at_ms`)\n);\n--> statement-breakpoint\nCREATE TABLE `fleet_host_liveness` (\n\t`host_id` text PRIMARY KEY NOT NULL,\n\t`status` text NOT NULL,\n\t`reason` text,\n\t`last_heartbeat_ms` integer,\n\t`last_activity_ms` integer,\n\t`last_seen_ms` integer,\n\t`period_ms` integer,\n\t`grace_ms` integer,\n\t`agent_version` text,\n\t`status_changed_at_ms` integer,\n\t`updated_at` integer NOT NULL\n);\n",
|
|
354
|
+
"0037_ctc1597_workflow_slot_intake":
|
|
355
|
+
"-- CTC-1597 — rename the persisted workflow slot `triage` -> `intake`.\n--\n-- ⭐ Deliberately a DATA migration in the SHARED bundle (the 0014/0031 pattern): the DO and every\n-- host / OPFS replica apply the same MIRROR_MIGRATIONS through the same runner, so each side renames\n-- its OWN rows locally. That converges every replica WITHOUT emitting a change_log delta per row\n-- (migrations bypass appendChange), so no ring-buffer flood.\n--\n-- ⚠️ THE REGISTRY D1 IS THE AUTHORITY and gets its own migration\n-- (apps/mirror/src/registry/migrations/20260903T1928_workflow_slot_intake.sql); this one exists\n-- because a D1-only rewrite bumps no `workflow_rev` and pushes nothing, so a DO that is never\n-- re-saved would sit on the old value forever. THIS migration is what makes the rename correct on\n-- every DO/replica, including one offline at merge time. There is no separate re-projection script:\n-- an already-connected browser/host that needs to see the rename before its own next open converges\n-- the ordinary way — an operator re-saving that team's stage mapping calls the existing full-replace\n-- projection handler (`handleWorkflowMappingUpsert`, MirrorDO.ts), which DOES emit a change_log\n-- delta. No new production route was added for a one-time rename.\n--\n-- PK-safe: (team_id, slot) — and the PK does NOT consider `removed_at`, so a SOFT-DELETED `intake`\n-- row still occupies the slot. Drop a stale `triage` row for a team that already holds ANY `intake`\n-- row — live or soft-deleted — then rename what is left. Idempotent under execReplaySafe replay — a\n-- row already renamed to `intake` no longer matches `slot = 'triage'` on a re-run.\n--\n-- ⛔ CTC-1597 remediate (C2): an earlier draft of the EXISTS guard filtered `removed_at IS NULL`,\n-- reasoning that a soft-deleted `intake` row should never cause a still-live `triage` row to be\n-- dropped instead of renamed. That reasoning had it backwards — the PK collision this guard exists\n-- to prevent happens precisely BECAUSE `removed_at` is not part of the primary key: a host that\n-- receives a soft-deleted `intake` row via replication (from a DO that already renamed and was then\n-- unmapped) before it has applied this migration locally still has its own live `triage` row: the\n-- DELETE's old filter left the `triage` row in place, and the following blind UPDATE then tried to\n-- rename it to a slot value a row already occupies, violating the PK and aborting the whole bundle.\n-- Matching on ANY `intake` row (regardless of `removed_at`) fixes it: the stale `triage` row is\n-- dropped, not renamed, whenever the slot is already spoken for.\n--\n-- ⛔ CTC-1597 remediate (validate-plan finding 1) — a `triage`-slot row whose OWN mapped state is\n-- ITSELF type='triage' (Linear's reserved Triage inbox, legally name-matched into the OLD\n-- `RECOMMENDED_STAGES` entry before this ticket) must NOT be renamed blind: `SLOT_STATE_TYPES.intake`\n-- no longer accepts that type (Scope B), so renaming it in place would silently flip the team's\n-- `types_compatible` check from pass to a blocking fail with no data fix and no recourse — the write\n-- path's own validation (the same narrowing) would refuse a re-save of the identical row. Soft-delete\n-- it instead, via `removed_at` — this table's own convention for \"this slot is unmapped\" (see the\n-- table's doc comment in packages/schema/src/mirror.ts) — so the team lands in the ordinary,\n-- already-actionable \"needs a stage\" state a brand-new team starts in, which Scope C's per-team\n-- runbook resolves. Runs BEFORE the final rename so no live-or-dead row anywhere keeps the retired\n-- `triage` slot value; guarded by `removed_at IS NULL` so a replay is a no-op.\nUPDATE `team_workflow_mapping`\n SET `removed_at` = CAST(unixepoch() AS INTEGER) * 1000\n WHERE `slot` = 'triage' AND `linear_state_type` = 'triage' AND `removed_at` IS NULL;\n--> statement-breakpoint\nDELETE FROM `team_workflow_mapping`\n WHERE `slot` = 'triage'\n AND EXISTS (SELECT 1 FROM `team_workflow_mapping` m2\n WHERE m2.`team_id` = `team_workflow_mapping`.`team_id` AND m2.`slot` = 'intake');\n--> statement-breakpoint\nUPDATE `team_workflow_mapping` SET `slot` = 'intake' WHERE `slot` = 'triage';\n",
|
|
347
356
|
},
|
|
348
357
|
} as const;
|
package/src/mirror.ts
CHANGED
|
@@ -383,7 +383,11 @@ export const team_workflow_mapping = sqliteTable(
|
|
|
383
383
|
"team_workflow_mapping",
|
|
384
384
|
{
|
|
385
385
|
team_id: text("team_id").notNull(),
|
|
386
|
-
/** One of the eleven Catalyst slots; the five load-bearing ones gate the fleet.
|
|
386
|
+
/** One of the eleven Catalyst slots; the five load-bearing ones gate the fleet. CTC-1597 renamed
|
|
387
|
+
* the `triage` slot to `intake` — migration 0037 rewrites `slot='triage'` rows in place, except a
|
|
388
|
+
* row whose OWN `linear_state_type` is itself `'triage'`, which it soft-deletes rather than
|
|
389
|
+
* renames (that type is no longer valid for `intake` — see `SLOT_STATE_TYPES` in
|
|
390
|
+
* `@catalyst-cloud/types`). */
|
|
387
391
|
slot: text("slot").notNull(),
|
|
388
392
|
/** THE AUTHORITY — joined against workflow_states.id by every consumer. */
|
|
389
393
|
linear_state_id: text("linear_state_id").notNull(),
|
|
@@ -62,6 +62,88 @@ export function teamFromTicket(ticket: string): string | null {
|
|
|
62
62
|
return m === null ? null : m[1]!;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* CTC-1776 — THE ONE DOCUMENTED ORDER of `detail`'s `key=` tokens, and the reason there is one.
|
|
67
|
+
*
|
|
68
|
+
* `detail` is an APPEND-ONLY, fixed-order sequence so an operator's `blob5 LIKE '%status=…%phase=…%'`
|
|
69
|
+
* predicate keeps matching as fields are added (docs/devops.md's own recipe is written against it).
|
|
70
|
+
* A new optional field goes at the END of `buildPhaseLifecyclePoint`'s array AND at the end of this
|
|
71
|
+
* list — never inserted in the middle.
|
|
72
|
+
*
|
|
73
|
+
* `failure_class` and `round` are RESERVED here, ahead of CTC-1771 building them, so that ticket's
|
|
74
|
+
* fields land after `reason=` by construction rather than by anyone remembering to. The subsequence
|
|
75
|
+
* test in this module's spec is what enforces it.
|
|
76
|
+
*
|
|
77
|
+
* ⛔ `reason` is the ONE token whose value may contain spaces and its own `key=value` pairs — a
|
|
78
|
+
* validate row's `reason=ladder:plan-conformance=PASS type-safety=FAIL …` digest (CTC-1541/CTC-1776).
|
|
79
|
+
* A naive space-split therefore sees the digest's `step=verdict` pairs as fields of their own, which
|
|
80
|
+
* is exactly what the round-1 subsequence test missed (CTC-1776 validate round 2, CR-C). Two rules
|
|
81
|
+
* follow. (1) `reason` renders LAST of the free-text-capable tokens, so everything after it up to
|
|
82
|
+
* the next key REGISTERED HERE belongs to its value — a reader (the spec's `parseDetail`, an
|
|
83
|
+
* operator's `LIKE`) can only tell a digest pair from a field by consulting this list. (2) A field
|
|
84
|
+
* appended after `reason` MUST be registered here first: an unregistered one is indistinguishable
|
|
85
|
+
* from a digest token and is swallowed into `reason`'s value, which the spec's round-trip assertion
|
|
86
|
+
* turns into a red test rather than a silent census skew.
|
|
87
|
+
*/
|
|
88
|
+
export const PHASE_LIFECYCLE_DETAIL_ORDER = [
|
|
89
|
+
"status",
|
|
90
|
+
"ticket",
|
|
91
|
+
"phase",
|
|
92
|
+
"nonce",
|
|
93
|
+
"executor",
|
|
94
|
+
"substrate",
|
|
95
|
+
"provider",
|
|
96
|
+
"branch",
|
|
97
|
+
"base_sha",
|
|
98
|
+
"pr_number",
|
|
99
|
+
"artifact_key",
|
|
100
|
+
"reason",
|
|
101
|
+
// Reserved — CTC-1771 Phase 5.
|
|
102
|
+
"failure_class",
|
|
103
|
+
"round",
|
|
104
|
+
] as const;
|
|
105
|
+
|
|
106
|
+
/** CTC-1541/CTC-1776 — the `reason=` value a validate row carries for its per-step ladder digest.
|
|
107
|
+
* Used by BOTH the succeeded-receipt site and the failed-release site in MirrorDO.ts, so "exactly
|
|
108
|
+
* as a succeeded row does" is a shared function rather than two string literals. */
|
|
109
|
+
export const LADDER_REASON_PREFIX = "ladder:";
|
|
110
|
+
export function ladderReason(digest: string): string {
|
|
111
|
+
return `${LADDER_REASON_PREFIX}${digest}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** CTC-1776 — the `reason=` value for a validate row whose ladder block could not be parsed at all.
|
|
115
|
+
* Deliberately NOT a `ladder:` digest: five UNREPORTED verdicts read as a verdict set, and the
|
|
116
|
+
* census must count "I could not look" apart from a real FAIL. Bare, with no cause suffix — the
|
|
117
|
+
* five causes are named in the runner's own log line (loop.ts), keyed by ticket and phase. */
|
|
118
|
+
export const VALIDATION_LADDER_UNPARSED_REASON = "validation-ladder-unparsed";
|
|
119
|
+
|
|
120
|
+
/** CTC-1776 — the one verdict that means "I could not look", mirrored from `LADDER_VERDICTS`
|
|
121
|
+
* (`apps/runner/src/validation-ladder.ts`); this package stays runner-free, same posture
|
|
122
|
+
* `apps/mirror/src/artifacts/relay-receipt.ts` already takes for the ladder's other bounds. */
|
|
123
|
+
export const UNREPORTED_LADDER_VERDICT = "UNREPORTED";
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* CTC-1776 remediate (validate round 1, CR-3) — is every `step=verdict` token in an already-rendered
|
|
127
|
+
* digest `UNREPORTED`?
|
|
128
|
+
*
|
|
129
|
+
* The succeeded-receipt site answers the same question from the parsed `RelayReceiptLadder` it holds;
|
|
130
|
+
* the FAILED-release site only ever holds the rendered digest string the runner wrote, so it needs
|
|
131
|
+
* this form. Both then map to {@link VALIDATION_LADDER_UNPARSED_REASON} rather than publishing five
|
|
132
|
+
* UNREPORTED verdicts as if they were a verdict set — the census has to count "I could not look"
|
|
133
|
+
* apart from a real FAIL on BOTH row statuses, not just on `succeeded`.
|
|
134
|
+
*
|
|
135
|
+
* An empty digest is `false`, never a vacuous `true`: `[].every(…)` is the exact shape AGENTS.md's
|
|
136
|
+
* false-clean-result rule names, and a summary with no tokens at all is not evidence of anything.
|
|
137
|
+
*/
|
|
138
|
+
export function isAllUnreportedLadderDigest(digest: string): boolean {
|
|
139
|
+
const tokens = digest.split(/\s+/).filter((token) => token.length > 0);
|
|
140
|
+
if (tokens.length === 0) return false;
|
|
141
|
+
return tokens.every((token) => {
|
|
142
|
+
const eq = token.lastIndexOf("=");
|
|
143
|
+
return eq !== -1 && token.slice(eq + 1) === UNREPORTED_LADDER_VERDICT;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
65
147
|
/**
|
|
66
148
|
* Build one `phase.lifecycle` TelemetryPoint. Throws (never a falsy sentinel) on an unrecognized
|
|
67
149
|
* `status` — the same posture as `assertEmittable` for the event name.
|