@catalyst-cloud/schema 0.1.49 → 0.1.51

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.49",
3
+ "version": "0.1.51",
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",
@@ -791,68 +791,86 @@ 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.
794
+ // CTC-1530 — `intake` joins `RELAY_PHASES` at its HEAD, gated dark behind `CATALYST_INTAKE_PHASE`.
795
+ // This block mirrors `phase.remediate.*`'s own set exactly (no git/rebase-specific suffixes —
796
+ // intake never touches a branch), so the generic phase-lifecycle/park machinery every relay phase
797
+ // already shares works for it with no special-casing. Supersedes CTC-1597's placeholder
798
+ // declaration of the same family (a smaller, `phase.triage.*`-shaped set, since at that point
799
+ // `intake` was still dormant outside `RELAY_PHASES`) now that this ticket makes it a real,
800
+ // lease-backed, preemptable phase.
797
801
  "phase.intake.abandoned": {
798
802
  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.",
803
+ "ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase's worker abandoned it — a lifecycle terminal fact.",
800
804
  ingestVerb: "coordination.publish",
801
805
  wakes: false,
802
806
  entity: "ticket",
803
807
  },
804
808
  "phase.intake.boot-resume": {
805
809
  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.",
810
+ "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.",
807
811
  ingestVerb: "coordination.publish",
808
812
  wakes: false,
809
813
  entity: "ticket",
810
814
  },
811
- "phase.intake.complete": {
815
+ "phase.intake.boot-resume-gated": {
812
816
  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.",
817
+ "ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the daemon restarted but gated (did not immediately resume) this phase.",
814
818
  ingestVerb: "coordination.publish",
815
819
  wakes: false,
816
820
  entity: "ticket",
817
821
  },
818
- "phase.intake.escalated": {
822
+ "phase.intake.complete": {
819
823
  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.",
824
+ "ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase reported success.",
821
825
  ingestVerb: "coordination.publish",
822
- wakes: { consumerClass: "host-channel" },
826
+ wakes: false,
823
827
  entity: "ticket",
824
828
  },
825
829
  "phase.intake.failed": {
826
830
  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.",
831
+ "ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase reported failure.",
828
832
  ingestVerb: "coordination.publish",
829
833
  wakes: false,
830
834
  entity: "ticket",
831
835
  },
832
- "phase.intake.failure-retracted": {
836
+ "phase.intake.orphan-detected": {
833
837
  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.",
838
+ "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.",
835
839
  ingestVerb: "coordination.publish",
836
840
  wakes: false,
837
841
  entity: "ticket",
838
842
  },
839
- "phase.intake.linear-transition": {
843
+ "phase.intake.park": {
840
844
  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.",
845
+ "ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the intake phase was PARKED after N consecutive failures (CTC-1270) — a terminal-until-released outcome, distinct from `phase.intake.failed`, which a reader must be able to tell apart. Paired with `phase.intake.revive`.",
842
846
  ingestVerb: "coordination.publish",
843
847
  wakes: false,
844
848
  entity: "ticket",
845
849
  },
846
- "phase.intake.orphan-detected": {
850
+ "phase.intake.preempted": {
847
851
  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.",
852
+ "ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): a higher-priority worker preempted this phase.",
853
+ ingestVerb: "coordination.publish",
854
+ wakes: false,
855
+ entity: "ticket",
856
+ },
857
+ "phase.intake.reclaim": {
858
+ rationale:
859
+ "ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): ownership of orphaned work was reclaimed by a new worker — an ownership transition akin to a lease reassignment.",
860
+ ingestVerb: "coordination.publish",
861
+ wakes: false,
862
+ entity: "ticket",
863
+ },
864
+ "phase.intake.resumed-after-preemption": {
865
+ rationale:
866
+ "ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): the phase resumed after being preempted earlier — the paired fact to *.preempted.",
849
867
  ingestVerb: "coordination.publish",
850
868
  wakes: false,
851
869
  entity: "ticket",
852
870
  },
853
871
  "phase.intake.revive": {
854
872
  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.",
873
+ "ADR-0039 durable-fact example ('a phase started, completed, failed, or was preempted'): a repeated-failure park of the intake phase was revived (CTC-1270) — the paired fact to `phase.intake.park`.",
856
874
  ingestVerb: "coordination.publish",
857
875
  wakes: false,
858
876
  entity: "ticket",
@@ -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.