@cotal-ai/core 0.15.0 → 0.17.0

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.
Files changed (146) hide show
  1. package/dist/acls.d.ts +21 -0
  2. package/dist/acls.d.ts.map +1 -1
  3. package/dist/acls.js +75 -12
  4. package/dist/acls.js.map +1 -1
  5. package/dist/artifact.d.ts +45 -0
  6. package/dist/artifact.d.ts.map +1 -0
  7. package/dist/artifact.js +82 -0
  8. package/dist/artifact.js.map +1 -0
  9. package/dist/backup-config.d.ts +9 -2
  10. package/dist/backup-config.d.ts.map +1 -1
  11. package/dist/backup-config.js +6 -2
  12. package/dist/backup-config.js.map +1 -1
  13. package/dist/broker-tls.d.ts +70 -0
  14. package/dist/broker-tls.d.ts.map +1 -0
  15. package/dist/broker-tls.js +195 -0
  16. package/dist/broker-tls.js.map +1 -0
  17. package/dist/canonical.d.ts +14 -1
  18. package/dist/canonical.d.ts.map +1 -1
  19. package/dist/canonical.js +21 -1
  20. package/dist/canonical.js.map +1 -1
  21. package/dist/channels.d.ts.map +1 -1
  22. package/dist/channels.js +18 -9
  23. package/dist/channels.js.map +1 -1
  24. package/dist/endpoint-action.d.ts +108 -3
  25. package/dist/endpoint-action.d.ts.map +1 -1
  26. package/dist/endpoint-action.js +209 -20
  27. package/dist/endpoint-action.js.map +1 -1
  28. package/dist/endpoint-binding.d.ts +138 -8
  29. package/dist/endpoint-binding.d.ts.map +1 -1
  30. package/dist/endpoint-binding.js +233 -26
  31. package/dist/endpoint-binding.js.map +1 -1
  32. package/dist/endpoint-contract-store.d.ts +24 -1
  33. package/dist/endpoint-contract-store.d.ts.map +1 -1
  34. package/dist/endpoint-contract-store.js +79 -27
  35. package/dist/endpoint-contract-store.js.map +1 -1
  36. package/dist/endpoint-envelope.d.ts +15 -37
  37. package/dist/endpoint-envelope.d.ts.map +1 -1
  38. package/dist/endpoint-envelope.js +93 -44
  39. package/dist/endpoint-envelope.js.map +1 -1
  40. package/dist/endpoint-error.d.ts +33 -0
  41. package/dist/endpoint-error.d.ts.map +1 -0
  42. package/dist/endpoint-error.js +34 -0
  43. package/dist/endpoint-error.js.map +1 -0
  44. package/dist/endpoint-grants.d.ts +48 -7
  45. package/dist/endpoint-grants.d.ts.map +1 -1
  46. package/dist/endpoint-grants.js +97 -9
  47. package/dist/endpoint-grants.js.map +1 -1
  48. package/dist/endpoint-invoke.d.ts +138 -0
  49. package/dist/endpoint-invoke.d.ts.map +1 -0
  50. package/dist/endpoint-invoke.js +340 -0
  51. package/dist/endpoint-invoke.js.map +1 -0
  52. package/dist/endpoint-records.d.ts.map +1 -1
  53. package/dist/endpoint-records.js +17 -0
  54. package/dist/endpoint-records.js.map +1 -1
  55. package/dist/endpoint-serve-kv.d.ts +97 -0
  56. package/dist/endpoint-serve-kv.d.ts.map +1 -0
  57. package/dist/endpoint-serve-kv.js +336 -0
  58. package/dist/endpoint-serve-kv.js.map +1 -0
  59. package/dist/endpoint-serve.d.ts.map +1 -1
  60. package/dist/endpoint-serve.js +21 -1
  61. package/dist/endpoint-serve.js.map +1 -1
  62. package/dist/endpoint-service.d.ts +1 -1
  63. package/dist/endpoint-service.d.ts.map +1 -1
  64. package/dist/endpoint-service.js +34 -10
  65. package/dist/endpoint-service.js.map +1 -1
  66. package/dist/endpoint-session-rail.d.ts +127 -0
  67. package/dist/endpoint-session-rail.d.ts.map +1 -0
  68. package/dist/endpoint-session-rail.js +325 -0
  69. package/dist/endpoint-session-rail.js.map +1 -0
  70. package/dist/endpoint-session.d.ts +1 -115
  71. package/dist/endpoint-session.d.ts.map +1 -1
  72. package/dist/endpoint-session.js +4 -316
  73. package/dist/endpoint-session.js.map +1 -1
  74. package/dist/endpoint-subjects.d.ts +10 -1
  75. package/dist/endpoint-subjects.d.ts.map +1 -1
  76. package/dist/endpoint-subjects.js +10 -1
  77. package/dist/endpoint-subjects.js.map +1 -1
  78. package/dist/endpoint-verbs.d.ts +1 -2
  79. package/dist/endpoint-verbs.d.ts.map +1 -1
  80. package/dist/endpoint-verbs.js +6 -6
  81. package/dist/endpoint-verbs.js.map +1 -1
  82. package/dist/endpoint.d.ts +169 -15
  83. package/dist/endpoint.d.ts.map +1 -1
  84. package/dist/endpoint.js +491 -43
  85. package/dist/endpoint.js.map +1 -1
  86. package/dist/identity.d.ts +19 -0
  87. package/dist/identity.d.ts.map +1 -1
  88. package/dist/identity.js +12 -0
  89. package/dist/identity.js.map +1 -1
  90. package/dist/index.d.ts +9 -0
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +9 -0
  93. package/dist/index.js.map +1 -1
  94. package/dist/kv-scan.d.ts +105 -0
  95. package/dist/kv-scan.d.ts.map +1 -0
  96. package/dist/kv-scan.js +192 -0
  97. package/dist/kv-scan.js.map +1 -0
  98. package/dist/lease.d.ts +12 -5
  99. package/dist/lease.d.ts.map +1 -1
  100. package/dist/lease.js.map +1 -1
  101. package/dist/lifecycle-saga.d.ts +174 -0
  102. package/dist/lifecycle-saga.d.ts.map +1 -0
  103. package/dist/lifecycle-saga.js +418 -0
  104. package/dist/lifecycle-saga.js.map +1 -0
  105. package/dist/lifecycle-state.d.ts +202 -0
  106. package/dist/lifecycle-state.d.ts.map +1 -0
  107. package/dist/lifecycle-state.js +347 -0
  108. package/dist/lifecycle-state.js.map +1 -0
  109. package/dist/members.d.ts +8 -4
  110. package/dist/members.d.ts.map +1 -1
  111. package/dist/members.js +19 -9
  112. package/dist/members.js.map +1 -1
  113. package/dist/parts.d.ts +13 -0
  114. package/dist/parts.d.ts.map +1 -0
  115. package/dist/parts.js +17 -0
  116. package/dist/parts.js.map +1 -0
  117. package/dist/provision.d.ts +106 -9
  118. package/dist/provision.d.ts.map +1 -1
  119. package/dist/provision.js +506 -110
  120. package/dist/provision.js.map +1 -1
  121. package/dist/schema-profile.d.ts +3 -2
  122. package/dist/schema-profile.d.ts.map +1 -1
  123. package/dist/schema-profile.js +242 -18
  124. package/dist/schema-profile.js.map +1 -1
  125. package/dist/session-browser.d.ts +15 -0
  126. package/dist/session-browser.d.ts.map +1 -0
  127. package/dist/session-browser.js +14 -0
  128. package/dist/session-browser.js.map +1 -0
  129. package/dist/session-terminal-frames.d.ts +36 -0
  130. package/dist/session-terminal-frames.d.ts.map +1 -0
  131. package/dist/session-terminal-frames.js +149 -0
  132. package/dist/session-terminal-frames.js.map +1 -0
  133. package/dist/spaces.d.ts.map +1 -1
  134. package/dist/spaces.js +6 -1
  135. package/dist/spaces.js.map +1 -1
  136. package/dist/streams.d.ts +53 -1
  137. package/dist/streams.d.ts.map +1 -1
  138. package/dist/streams.js +135 -8
  139. package/dist/streams.js.map +1 -1
  140. package/dist/subjects.d.ts +32 -26
  141. package/dist/subjects.d.ts.map +1 -1
  142. package/dist/subjects.js +38 -25
  143. package/dist/subjects.js.map +1 -1
  144. package/dist/types.d.ts +27 -1
  145. package/dist/types.d.ts.map +1 -1
  146. package/package.json +6 -1
@@ -0,0 +1,174 @@
1
+ import { type LifecycleMapping, type EpGateRow } from "./lifecycle-state.js";
2
+ /** One raw store entry as the sequencing consumes it (a KV entry stripped to what a CAS needs). */
3
+ export interface LifecycleKvEntry {
4
+ value: Uint8Array;
5
+ revision: number;
6
+ operation: string;
7
+ }
8
+ /**
9
+ * The injected write transport (§13.1 three-way split): the ONLY store access this sequencing
10
+ * uses. `getRecord`/`createRecord`/`updateRecord` bind the space's RECORDS store (head + uid
11
+ * keys; create/update map a CAS loss to an `EpEnvelopeError` `conflict`, the contract of core
12
+ * `createRecordEntry`/`updateRecordEntry`); `getAuth`/`putAuth` bind the AUTH store (gate keys;
13
+ * `putAuth` throws the broker's RAW error on a CAS loss — this module classifies it). A
14
+ * transport implementation carries no sequencing decisions of its own.
15
+ */
16
+ export interface LifecycleStateTransport {
17
+ getRecord(key: string): Promise<LifecycleKvEntry | undefined>;
18
+ createRecord(key: string, value: unknown): Promise<number>;
19
+ updateRecord(key: string, value: unknown, expectedRevision: number): Promise<number>;
20
+ getAuth(key: string): Promise<LifecycleKvEntry | undefined>;
21
+ putAuth(key: string, payload: Uint8Array, expectedRevision: number): Promise<number>;
22
+ }
23
+ /** Candidate read for a CAS-fenced head mutation (raw get; the auth decision is the CAS itself,
24
+ * §13.1: a read is never a fence). A DEL/PURGE marker is CORRUPTION, never absence. */
25
+ export declare function headCandidate(t: LifecycleStateTransport, owner: string, actor: string): Promise<{
26
+ mapping: LifecycleMapping;
27
+ revision: number;
28
+ } | undefined>;
29
+ /** Observe the gate (the candidate read feeding a revision-pinned CAS). A DEL/PURGE marker
30
+ * refuses loudly. */
31
+ export declare function gateObserve(t: LifecycleStateTransport, lifecycleUid: string): Promise<{
32
+ row: EpGateRow;
33
+ revision: number;
34
+ } | undefined>;
35
+ /** Try to reserve ONE explicit candidate UID. Create-only: `"won"` reserves it forever;
36
+ * `"burned"` means the candidate already exists OR carries a deletion marker — either way it
37
+ * is unusable, per the never-reuse rule. */
38
+ export declare function uidTryReserve(t: LifecycleStateTransport, lifecycleUid: string, audit: {
39
+ owner: string;
40
+ actor: string;
41
+ mintedBy: string;
42
+ }): Promise<"won" | "burned">;
43
+ /** Reserve a fresh lifecycle UID space-globally (§13.1): mint a CSPRNG candidate, win its
44
+ * create-only reservation, and on a collision burn the candidate and draw another. */
45
+ export declare function uidReserveFresh(t: LifecycleStateTransport, audit: {
46
+ owner: string;
47
+ actor: string;
48
+ mintedBy: string;
49
+ }): Promise<string>;
50
+ /** Read a UID reservation's audit `{ owner, actor }` (recorded at {@link uidTryReserve}). A
51
+ * DEL/PURGE marker refuses loudly. */
52
+ export declare function uidReadReservation(t: LifecycleStateTransport, lifecycleUid: string): Promise<{
53
+ owner: string;
54
+ actor: string;
55
+ } | undefined>;
56
+ /** Create the gate FROZEN under its operation's durable intent (create-only). A gate is BORN
57
+ * only under an ACTIVATION intent, and only for a UID whose space-global reservation was
58
+ * already WON. Born unmintable at generation 0. */
59
+ export declare function gateCreateFrozen(t: LifecycleStateTransport, args: {
60
+ lifecycleUid: string;
61
+ op: {
62
+ opId: string;
63
+ kind: "activation";
64
+ };
65
+ }): Promise<{
66
+ row: EpGateRow;
67
+ revision: number;
68
+ }>;
69
+ /** CAS the gate `open → frozen` carrying the freezing operation's durable intent, at the
70
+ * observed revision. The bar of every barrier. */
71
+ export declare function gateFreeze(t: LifecycleStateTransport, args: {
72
+ lifecycleUid: string;
73
+ revision: number;
74
+ op: {
75
+ opId: string;
76
+ kind: "takeover" | "registration" | "retirement";
77
+ successor?: string;
78
+ };
79
+ }): Promise<{
80
+ row: EpGateRow;
81
+ revision: number;
82
+ }>;
83
+ /** CAS the gate `frozen → open` at the NEXT generation — op-pinned: only the freeze's own
84
+ * operation reopens, as its barrier's final step. NEVER retirement (a retirement freeze never
85
+ * reopens; its only exit is the terminal). */
86
+ export declare function gateReopen(t: LifecycleStateTransport, args: {
87
+ lifecycleUid: string;
88
+ revision: number;
89
+ opId: string;
90
+ }): Promise<{
91
+ row: EpGateRow;
92
+ revision: number;
93
+ }>;
94
+ /** CAS the gate `frozen → retired` (terminal; never reopened) — op-pinned like the reopen. Only
95
+ * an ACTIVATION orphan or a RETIREMENT terminalizes. Idempotence at `retired` is SAME-OP
96
+ * idempotence. */
97
+ export declare function gateRetire(t: LifecycleStateTransport, args: {
98
+ lifecycleUid: string;
99
+ revision: number;
100
+ opId: string;
101
+ }): Promise<{
102
+ row: EpGateRow;
103
+ revision: number;
104
+ }>;
105
+ /** The takeover barrier's epoch-advance head CAS. Advances the epoch by exactly one,
106
+ * revision-pinned, only while the head is ACTIVE at the SAME uid; clears the revoked root
107
+ * stamp in the SAME CAS; idempotent only for the barrier's OWN completed advance. */
108
+ export declare function headAdvanceEpochWithinTakeover(t: LifecycleStateTransport, args: {
109
+ owner: string;
110
+ actor: string;
111
+ lifecycleUid: string;
112
+ fromEpoch: number;
113
+ opId: string;
114
+ }): Promise<"advanced" | "already-advanced">;
115
+ /** The issuance path's head CAS stamping the incarnation's ROOT credential — the mint protocol's
116
+ * RELEASE-LAST final step. ABSENT → value ONLY (idempotent for the SAME value): root ROTATION
117
+ * is exclusively a barrier's job, never this seam's. */
118
+ export declare function headSetCurrentRootCredential(t: LifecycleStateTransport, args: {
119
+ owner: string;
120
+ actor: string;
121
+ lifecycleUid: string;
122
+ credentialId: string;
123
+ }): Promise<void>;
124
+ /** The retirement's head CONTAINMENT CAS (`active → retiring`, bound to the retirement
125
+ * operation's durable intent). Idempotent for the operation's crash-resume; a stranger never
126
+ * advances it. */
127
+ export declare function headBeginRetirement(t: LifecycleStateTransport, args: {
128
+ owner: string;
129
+ actor: string;
130
+ lifecycleUid: string;
131
+ opId: string;
132
+ }): Promise<"retiring" | "already-retiring">;
133
+ /** The retirement's TERMINAL head CAS (`retiring → retired`, op-pinned) — the LAST step:
134
+ * `retired` ASSERTS completed cleanup, which is what makes the alias replaceable. The op
135
+ * intent is dropped (it belongs to `retiring` only). */
136
+ export declare function headCompleteRetirement(t: LifecycleStateTransport, args: {
137
+ owner: string;
138
+ actor: string;
139
+ lifecycleUid: string;
140
+ opId: string;
141
+ }): Promise<"retired" | "already-retired">;
142
+ /** The full §13.1 initial-activation saga for a FRESH uid. See the executor's public doc
143
+ * (implementations/auth `activateLifecycle`) for the refusal matrix; the head-CAS loser
144
+ * terminalizes its own orphan gate (uid stays burned) and rethrows the `conflict`. */
145
+ export declare function runActivationSaga(t: LifecycleStateTransport, args: {
146
+ owner: string;
147
+ actor: string;
148
+ managerInstance: string;
149
+ }): Promise<{
150
+ mapping: LifecycleMapping;
151
+ revision: number;
152
+ opId: string;
153
+ }>;
154
+ /** The production ISSUANCE activation AT THE CALLER'S uid: same saga order with
155
+ * ADOPT-instead-of-burn resume semantics (a reservation or frozen ACTIVATION gate already
156
+ * carried by OUR alias at this uid is prior durable progress; a CAS loss to a SIBLING at the
157
+ * same coordinates converges on the winner's state). See the executor's public doc
158
+ * (implementations/auth `activateLifecycleAtUid`) for the refusal matrix. */
159
+ export declare function runActivationSagaAtUid(t: LifecycleStateTransport, args: {
160
+ owner: string;
161
+ actor: string;
162
+ lifecycleUid: string;
163
+ managerInstance: string;
164
+ }): Promise<void>;
165
+ /** Resume a crashed activation saga from its durable coordinates. Reads the durable state and
166
+ * finishes the SAME operation deterministically; idempotent; never advances another
167
+ * operation's freeze. */
168
+ export declare function resumeActivationSaga(t: LifecycleStateTransport, args: {
169
+ owner: string;
170
+ actor: string;
171
+ lifecycleUid: string;
172
+ opId: string;
173
+ }): Promise<"completed" | "terminalized" | "already-settled">;
174
+ //# sourceMappingURL=lifecycle-saga.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle-saga.d.ts","sourceRoot":"","sources":["../src/lifecycle-saga.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,418 @@
1
+ /**
2
+ * The §13.1 lifecycle SAGA SEQUENCING: the one shared CAS state machine over the lifecycle
3
+ * state grammar (`lifecycle-state.ts`) — uid reservation, issuance-gate transitions, alias-head
4
+ * transitions, the initial-activation saga (reserve → gate frozen → head CAS → reopen LAST) with
5
+ * its crash-resume, and the barrier-internal head steps (epoch advance, root stamp, retirement
6
+ * begin/complete).
7
+ *
8
+ * Three-way split (the Unit B design note, panel-locked): the key/value GRAMMAR lives in
9
+ * `lifecycle-state.ts`; THIS module is the SEQUENCING, parameterized by an injected
10
+ * {@link LifecycleStateTransport} so every executor (the user-mesh auth service over its sealed
11
+ * registry, the manager's static adapter over its direct KV binding) drives the SAME saga —
12
+ * the adapters are TRANSPORT, never a second copy of this state machine. The barrier
13
+ * ORCHESTRATION (credential revoke, cluster-verified eviction, ledger enumeration) is
14
+ * deliberately NOT here: it stays with the user-mode executor in implementations/auth.
15
+ *
16
+ * Authority note: this module grants nothing. A transport is constructed only by an executor
17
+ * that already holds its stores' authenticated bindings; every fence below is the store's own
18
+ * revision-pinned CAS, and an `opId` is an identifier, never a bearer capability (§13.1).
19
+ */
20
+ import { EpEnvelopeError } from "./endpoint-envelope.js";
21
+ import { isCasLoss as isRawCasLoss } from "./endpoint-records.js";
22
+ import { mintLifecycleUid, assertLifecycleToken } from "./subjects.js";
23
+ import { lifecycleHeadKey as headKey, uidReservationKey as uidKey, issuanceGateKey as gateKey, parseLifecycleHead as parseMapping, parseIssuanceGate as parseGate, } from "./lifecycle-state.js";
24
+ const enc = new TextEncoder();
25
+ const dec = new TextDecoder();
26
+ const isRec = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
27
+ const isCasLoss = (e) => e instanceof EpEnvelopeError && e.code === "conflict";
28
+ // ---- candidate reads ------------------------------------------------------------------------
29
+ /** Candidate read for a CAS-fenced head mutation (raw get; the auth decision is the CAS itself,
30
+ * §13.1: a read is never a fence). A DEL/PURGE marker is CORRUPTION, never absence. */
31
+ export async function headCandidate(t, owner, actor) {
32
+ const key = headKey(owner, actor);
33
+ const entry = await t.getRecord(key);
34
+ if (!entry)
35
+ return undefined;
36
+ if (entry.operation !== "PUT")
37
+ throw new EpEnvelopeError("failed-precondition", `the lifecycle head ${key} carries a ${entry.operation} marker; an authority head is never deleted (a deletion is corruption, not absence, SPEC 13.12)`);
38
+ return { mapping: parseMapping(entry.value, key, owner, actor), revision: entry.revision };
39
+ }
40
+ /** Observe the gate (the candidate read feeding a revision-pinned CAS). A DEL/PURGE marker
41
+ * refuses loudly. */
42
+ export async function gateObserve(t, lifecycleUid) {
43
+ const key = gateKey(lifecycleUid);
44
+ const entry = await t.getAuth(key);
45
+ if (!entry)
46
+ return undefined;
47
+ if (entry.operation !== "PUT")
48
+ throw new EpEnvelopeError("failed-precondition", `the issuance gate ${key} carries a ${entry.operation} marker; a gate is never deleted (a deletion is corruption, not absence, SPEC 13.12)`);
49
+ return { row: parseGate(entry.value, key, lifecycleUid), revision: entry.revision };
50
+ }
51
+ async function putGate(t, lifecycleUid, row, expectedRevision) {
52
+ try {
53
+ return await t.putAuth(gateKey(lifecycleUid), enc.encode(JSON.stringify(row)), expectedRevision);
54
+ }
55
+ catch (e) {
56
+ if (isRawCasLoss(e))
57
+ throw new EpEnvelopeError("conflict", `the issuance gate CAS for ${gateKey(lifecycleUid)} lost (expected revision ${expectedRevision}); re-read and re-decide (SPEC 13.8)`);
58
+ throw e;
59
+ }
60
+ }
61
+ // ---- the space-global UID reservation --------------------------------------------------------
62
+ /** Try to reserve ONE explicit candidate UID. Create-only: `"won"` reserves it forever;
63
+ * `"burned"` means the candidate already exists OR carries a deletion marker — either way it
64
+ * is unusable, per the never-reuse rule. */
65
+ export async function uidTryReserve(t, lifecycleUid, audit) {
66
+ assertLifecycleToken(lifecycleUid);
67
+ try {
68
+ await t.createRecord(uidKey(lifecycleUid), { owner: audit.owner, actor: audit.actor, mintedBy: audit.mintedBy });
69
+ return "won";
70
+ }
71
+ catch (e) {
72
+ if (isCasLoss(e))
73
+ return "burned";
74
+ throw e;
75
+ }
76
+ }
77
+ /** Reserve a fresh lifecycle UID space-globally (§13.1): mint a CSPRNG candidate, win its
78
+ * create-only reservation, and on a collision burn the candidate and draw another. */
79
+ export async function uidReserveFresh(t, audit) {
80
+ for (let i = 0; i < 4; i++) {
81
+ const candidate = mintLifecycleUid();
82
+ if ((await uidTryReserve(t, candidate, audit)) === "won")
83
+ return candidate;
84
+ }
85
+ throw new EpEnvelopeError("internal", "four fresh 128-bit UID candidates collided with existing reservations; that is not chance; inspect the uid.> family (SPEC 13.1)");
86
+ }
87
+ /** Read a UID reservation's audit `{ owner, actor }` (recorded at {@link uidTryReserve}). A
88
+ * DEL/PURGE marker refuses loudly. */
89
+ export async function uidReadReservation(t, lifecycleUid) {
90
+ const entry = await t.getRecord(uidKey(assertLifecycleToken(lifecycleUid)));
91
+ if (!entry)
92
+ return undefined;
93
+ if (entry.operation !== "PUT")
94
+ throw new EpEnvelopeError("failed-precondition", `the uid reservation for ${lifecycleUid} carries a ${entry.operation} marker; a reservation is never deleted (corruption, SPEC 13.12)`);
95
+ let o;
96
+ try {
97
+ o = JSON.parse(dec.decode(entry.value));
98
+ }
99
+ catch {
100
+ throw new EpEnvelopeError("internal", `the uid reservation for ${lifecycleUid} is not JSON (SPEC 13.1)`);
101
+ }
102
+ if (!isRec(o) || typeof o.owner !== "string" || typeof o.actor !== "string" || o.owner.length === 0 || o.actor.length === 0)
103
+ throw new EpEnvelopeError("internal", `the uid reservation for ${lifecycleUid} does not carry a valid owner/actor audit (SPEC 13.1)`);
104
+ return { owner: o.owner, actor: o.actor };
105
+ }
106
+ // ---- the issuance-gate CAS transitions -------------------------------------------------------
107
+ /** Create the gate FROZEN under its operation's durable intent (create-only). A gate is BORN
108
+ * only under an ACTIVATION intent, and only for a UID whose space-global reservation was
109
+ * already WON. Born unmintable at generation 0. */
110
+ export async function gateCreateFrozen(t, args) {
111
+ if (args.op.kind !== "activation")
112
+ throw new EpEnvelopeError("failed-precondition", `an issuance gate is born only under an activation intent, not "${String(args.op.kind)}" (SPEC 13.1: other operations freeze an existing open gate)`);
113
+ const reservation = await t.getRecord(uidKey(assertLifecycleToken(args.lifecycleUid)));
114
+ if (!reservation)
115
+ throw new EpEnvelopeError("failed-precondition", `no uid reservation exists for ${args.lifecycleUid}; the reservation is won BEFORE any gate or head write (SPEC 13.1)`);
116
+ if (reservation.operation !== "PUT")
117
+ throw new EpEnvelopeError("failed-precondition", `the uid reservation for ${args.lifecycleUid} carries a ${reservation.operation} marker; a reservation is never deleted (corruption, not absence, SPEC 13.12)`);
118
+ const row = { lifecycleUid: args.lifecycleUid, state: "frozen", generation: 0, op: { opId: assertLifecycleToken(args.op.opId), kind: "activation" } };
119
+ const revision = await putGate(t, args.lifecycleUid, row, 0);
120
+ return { row, revision };
121
+ }
122
+ /** CAS the gate `open → frozen` carrying the freezing operation's durable intent, at the
123
+ * observed revision. The bar of every barrier. */
124
+ export async function gateFreeze(t, args) {
125
+ if (args.op.successor !== undefined && args.op.kind === "retirement")
126
+ throw new EpEnvelopeError("failed-precondition", "a retirement freeze carries no successor (SPEC 13.1: a retirement has none)");
127
+ if (args.op.successor !== undefined && args.op.successor.length === 0)
128
+ throw new EpEnvelopeError("failed-precondition", "the freeze carries an empty successor token; a summary token is a non-empty stage.<opId> reference or absent (SPEC 13.1); validate before the CAS, never persist corruption");
129
+ const current = await gateObserve(t, args.lifecycleUid);
130
+ if (current === undefined)
131
+ throw new EpEnvelopeError("not-found", `the issuance gate for ${args.lifecycleUid} does not exist (SPEC 13.1)`);
132
+ if (current.row.state !== "open")
133
+ throw new EpEnvelopeError("failed-precondition", `the issuance gate for ${args.lifecycleUid} is "${current.row.state}", not open; only an open gate freezes (a frozen/retired gate belongs to its own operation, SPEC 13.1)`);
134
+ const op = { opId: assertLifecycleToken(args.op.opId), kind: args.op.kind };
135
+ if (args.op.successor !== undefined)
136
+ op.successor = args.op.successor;
137
+ const row = { lifecycleUid: current.row.lifecycleUid, state: "frozen", generation: current.row.generation, op };
138
+ const revision = await putGate(t, args.lifecycleUid, row, args.revision);
139
+ return { row, revision };
140
+ }
141
+ /** CAS the gate `frozen → open` at the NEXT generation — op-pinned: only the freeze's own
142
+ * operation reopens, as its barrier's final step. NEVER retirement (a retirement freeze never
143
+ * reopens; its only exit is the terminal). */
144
+ export async function gateReopen(t, args) {
145
+ const current = await gateObserve(t, args.lifecycleUid);
146
+ if (current === undefined)
147
+ throw new EpEnvelopeError("not-found", `the issuance gate for ${args.lifecycleUid} does not exist (SPEC 13.1)`);
148
+ if (current.row.state !== "frozen")
149
+ throw new EpEnvelopeError("failed-precondition", `the issuance gate for ${args.lifecycleUid} is "${current.row.state}", not frozen; there is no freeze to reopen (SPEC 13.1)`);
150
+ if (current.row.op?.opId !== args.opId)
151
+ throw new EpEnvelopeError("permission-denied", `the issuance gate for ${args.lifecycleUid} is frozen by operation ${current.row.op?.opId ?? "<none>"}, not ${args.opId}; only the completing operation reopens its own freeze (SPEC 13.1)`);
152
+ if (current.row.op.kind === "retirement")
153
+ throw new EpEnvelopeError("failed-precondition", `the issuance gate for ${args.lifecycleUid} is frozen by a RETIREMENT; a retirement freeze never reopens (SPEC 13.1: its only exit is the terminal)`);
154
+ const row = { lifecycleUid: current.row.lifecycleUid, state: "open", generation: current.row.generation + 1 };
155
+ const revision = await putGate(t, args.lifecycleUid, row, args.revision);
156
+ return { row, revision };
157
+ }
158
+ /** CAS the gate `frozen → retired` (terminal; never reopened) — op-pinned like the reopen. Only
159
+ * an ACTIVATION orphan or a RETIREMENT terminalizes. Idempotence at `retired` is SAME-OP
160
+ * idempotence. */
161
+ export async function gateRetire(t, args) {
162
+ const current = await gateObserve(t, args.lifecycleUid);
163
+ if (current === undefined)
164
+ throw new EpEnvelopeError("not-found", `the issuance gate for ${args.lifecycleUid} does not exist (SPEC 13.1)`);
165
+ if (current.row.state === "retired") {
166
+ if (current.row.op?.opId !== args.opId)
167
+ throw new EpEnvelopeError("permission-denied", `the issuance gate for ${args.lifecycleUid} was terminalized by operation ${current.row.op?.opId ?? "<none>"}, not ${args.opId}; terminal idempotence is same-op idempotence (SPEC 13.1)`);
168
+ return current; // idempotent terminal, same op
169
+ }
170
+ if (current.row.state !== "frozen")
171
+ throw new EpEnvelopeError("failed-precondition", `the issuance gate for ${args.lifecycleUid} is "${current.row.state}"; only a frozen gate terminalizes (freeze first; the bar precedes the terminal, SPEC 13.1)`);
172
+ if (current.row.op?.opId !== args.opId)
173
+ throw new EpEnvelopeError("permission-denied", `the issuance gate for ${args.lifecycleUid} is frozen by operation ${current.row.op?.opId ?? "<none>"}, not ${args.opId}; only the owning operation terminalizes its freeze (SPEC 13.1)`);
174
+ if (current.row.op.kind !== "activation" && current.row.op.kind !== "retirement")
175
+ throw new EpEnvelopeError("failed-precondition", `the issuance gate for ${args.lifecycleUid} is frozen by a ${current.row.op.kind}; only an activation orphan or a retirement terminalizes (a ${current.row.op.kind} aborts by reopening, SPEC 13.1)`);
176
+ const row = { lifecycleUid: current.row.lifecycleUid, state: "retired", generation: current.row.generation, op: current.row.op };
177
+ const revision = await putGate(t, args.lifecycleUid, row, args.revision);
178
+ return { row, revision };
179
+ }
180
+ // ---- the barrier-internal head transitions ---------------------------------------------------
181
+ /** The takeover barrier's epoch-advance head CAS. Advances the epoch by exactly one,
182
+ * revision-pinned, only while the head is ACTIVE at the SAME uid; clears the revoked root
183
+ * stamp in the SAME CAS; idempotent only for the barrier's OWN completed advance. */
184
+ export async function headAdvanceEpochWithinTakeover(t, args) {
185
+ const cur = await headCandidate(t, args.owner, args.actor);
186
+ if (cur === undefined || cur.mapping.state !== "active" || cur.mapping.lifecycleUid !== args.lifecycleUid)
187
+ throw new EpEnvelopeError("failed-precondition", `the takeover epoch advance for "${args.owner}/${args.actor}" requires an ACTIVE head at uid ${args.lifecycleUid}; found ${cur === undefined ? "no head" : `${cur.mapping.state} at ${cur.mapping.lifecycleUid}`} (SPEC 13.1)`);
188
+ if (cur.mapping.processEpoch === args.fromEpoch + 1) {
189
+ if (cur.mapping.lastTakeoverOpId !== args.opId)
190
+ throw new EpEnvelopeError("conflict", `the head for "${args.owner}/${args.actor}" is at epoch ${args.fromEpoch + 1} advanced by operation ${cur.mapping.lastTakeoverOpId ?? "<none>"}, not ${args.opId}; a concurrent takeover won and this operation lost (SPEC 13.1)`);
191
+ if (cur.mapping.currentCredentialId !== undefined)
192
+ throw new EpEnvelopeError("failed-precondition", `the head for "${args.owner}/${args.actor}" advanced under takeover ${args.opId} but still names root credential ${cur.mapping.currentCredentialId}; the epoch CAS clears it atomically, so a residual stamp is impossible persisted state (SPEC 13.1)`);
193
+ return "already-advanced";
194
+ }
195
+ if (cur.mapping.processEpoch !== args.fromEpoch)
196
+ throw new EpEnvelopeError("failed-precondition", `the head for "${args.owner}/${args.actor}" is at epoch ${cur.mapping.processEpoch}, not the takeover's captured epoch ${args.fromEpoch} (or its +1); a foreign operation moved it (SPEC 13.1)`);
197
+ const { currentCredentialId: _revoked, ...rest } = cur.mapping;
198
+ void _revoked;
199
+ await t.updateRecord(headKey(args.owner, args.actor), { ...rest, processEpoch: args.fromEpoch + 1, lastTakeoverOpId: assertLifecycleToken(args.opId) }, cur.revision);
200
+ return "advanced";
201
+ }
202
+ /** The issuance path's head CAS stamping the incarnation's ROOT credential — the mint protocol's
203
+ * RELEASE-LAST final step. ABSENT → value ONLY (idempotent for the SAME value): root ROTATION
204
+ * is exclusively a barrier's job, never this seam's. */
205
+ export async function headSetCurrentRootCredential(t, args) {
206
+ if (typeof args.credentialId !== "string" || args.credentialId.length === 0)
207
+ throw new EpEnvelopeError("failed-precondition", "setCurrentRootCredential requires a credentialId");
208
+ const cur = await headCandidate(t, args.owner, args.actor);
209
+ if (cur === undefined || cur.mapping.state !== "active" || cur.mapping.lifecycleUid !== args.lifecycleUid)
210
+ throw new EpEnvelopeError("failed-precondition", `stamping the root credential for "${args.owner}/${args.actor}" requires an ACTIVE head at uid ${args.lifecycleUid}; found ${cur === undefined ? "no head" : `${cur.mapping.state} at ${cur.mapping.lifecycleUid}`} (SPEC 13.1)`);
211
+ if (cur.mapping.currentCredentialId === args.credentialId)
212
+ return; // our own completed stamp
213
+ if (cur.mapping.currentCredentialId !== undefined)
214
+ throw new EpEnvelopeError("permission-denied", `the head for "${args.owner}/${args.actor}" already names root credential ${cur.mapping.currentCredentialId}; rotating it takes the full family-revoke barrier, never a bare head flip (the old root's descendants would stay connectable under the leaf check, SPEC 13.1)`);
215
+ await t.updateRecord(headKey(args.owner, args.actor), { ...cur.mapping, currentCredentialId: args.credentialId }, cur.revision);
216
+ }
217
+ /** The retirement's head CONTAINMENT CAS (`active → retiring`, bound to the retirement
218
+ * operation's durable intent). Idempotent for the operation's crash-resume; a stranger never
219
+ * advances it. */
220
+ export async function headBeginRetirement(t, args) {
221
+ const cur = await headCandidate(t, args.owner, args.actor);
222
+ if (cur === undefined || cur.mapping.lifecycleUid !== args.lifecycleUid)
223
+ throw new EpEnvelopeError("failed-precondition", `the retirement of uid ${args.lifecycleUid} requires the head for "${args.owner}/${args.actor}" to name it; found ${cur === undefined ? "no head" : `uid ${cur.mapping.lifecycleUid}`} (SPEC 13.1)`);
224
+ if (cur.mapping.state === "retiring") {
225
+ if (cur.mapping.op?.opId !== args.opId)
226
+ throw new EpEnvelopeError("permission-denied", `the head for "${args.owner}/${args.actor}" is retiring under operation ${cur.mapping.op?.opId ?? "<none>"}, not ${args.opId}; one retirement at a time, and a stranger never advances it (SPEC 13.1)`);
227
+ return "already-retiring";
228
+ }
229
+ if (cur.mapping.state !== "active")
230
+ throw new EpEnvelopeError("failed-precondition", `the head for "${args.owner}/${args.actor}" is "${cur.mapping.state}", not active; only an active head enters retirement containment (a completed terminal is decided at the gate, never re-entered here, SPEC 13.1)`);
231
+ await t.updateRecord(headKey(args.owner, args.actor), { ...cur.mapping, state: "retiring", op: { opId: assertLifecycleToken(args.opId), kind: "retirement" } }, cur.revision);
232
+ return "retiring";
233
+ }
234
+ /** The retirement's TERMINAL head CAS (`retiring → retired`, op-pinned) — the LAST step:
235
+ * `retired` ASSERTS completed cleanup, which is what makes the alias replaceable. The op
236
+ * intent is dropped (it belongs to `retiring` only). */
237
+ export async function headCompleteRetirement(t, args) {
238
+ const cur = await headCandidate(t, args.owner, args.actor);
239
+ if (cur === undefined || cur.mapping.lifecycleUid !== args.lifecycleUid)
240
+ throw new EpEnvelopeError("failed-precondition", `the retirement terminal for uid ${args.lifecycleUid} requires the head for "${args.owner}/${args.actor}" to name it; found ${cur === undefined ? "no head" : `uid ${cur.mapping.lifecycleUid}`}; a replaced head is settled at the gate, never here (SPEC 13.1)`);
241
+ if (cur.mapping.state === "retired")
242
+ return "already-retired";
243
+ if (cur.mapping.state !== "retiring" || cur.mapping.op?.opId !== args.opId)
244
+ throw new EpEnvelopeError("permission-denied", `the head for "${args.owner}/${args.actor}" is ${cur.mapping.state === "retiring" ? `retiring under operation ${cur.mapping.op?.opId ?? "<none>"}` : `"${cur.mapping.state}"`}, not retiring under ${args.opId}; only the containing operation terminalizes its own retirement (SPEC 13.1)`);
245
+ const { op: _op, ...rest } = cur.mapping;
246
+ void _op;
247
+ await t.updateRecord(headKey(args.owner, args.actor), { ...rest, state: "retired" }, cur.revision);
248
+ return "retired";
249
+ }
250
+ // ---- the activation saga (§13.1: reserve → gate frozen → head CAS → reopen LAST) -------------
251
+ /** The full §13.1 initial-activation saga for a FRESH uid. See the executor's public doc
252
+ * (implementations/auth `activateLifecycle`) for the refusal matrix; the head-CAS loser
253
+ * terminalizes its own orphan gate (uid stays burned) and rethrows the `conflict`. */
254
+ export async function runActivationSaga(t, args) {
255
+ const { owner, actor } = args;
256
+ if (typeof args.managerInstance !== "string" || args.managerInstance.length === 0)
257
+ throw new EpEnvelopeError("failed-precondition", "activateLifecycle requires a managerInstance (the minting authority)");
258
+ const current = await headCandidate(t, owner, actor);
259
+ if (current !== undefined && current.mapping.state === "active")
260
+ throw new EpEnvelopeError("already-exists", `lifecycle "${owner}/${actor}" is already active (uid ${current.mapping.lifecycleUid}); a takeover advances the epoch through its barrier, it does not re-activate (SPEC 13.1)`);
261
+ if (current !== undefined && current.mapping.state === "retiring")
262
+ throw new EpEnvelopeError("failed-precondition", `lifecycle "${owner}/${actor}" is retiring (op ${current.mapping.op?.opId}); a retiring alias is not replaceable until its barrier completes (SPEC 13.1)`);
263
+ const opId = mintLifecycleUid();
264
+ // 1. Win the space-global UID reservation.
265
+ const lifecycleUid = await uidReserveFresh(t, { owner, actor, mintedBy: args.managerInstance });
266
+ // 2. Create the gate FROZEN under this activation's durable intent (unmintable from birth).
267
+ const gate = await gateCreateFrozen(t, { lifecycleUid, op: { opId, kind: "activation" } });
268
+ // 3. CAS the alias head (create-only for virgin; revision-pinned over the retired predecessor).
269
+ const mapping = { owner, actor, lifecycleUid, managerInstance: args.managerInstance, processEpoch: 1, state: "active" };
270
+ let revision;
271
+ try {
272
+ revision = current === undefined
273
+ ? await t.createRecord(headKey(owner, actor), mapping)
274
+ : await t.updateRecord(headKey(owner, actor), mapping, current.revision);
275
+ }
276
+ catch (e) {
277
+ if (isCasLoss(e)) {
278
+ // The loser terminalizes ITS OWN orphan gate; its UID stays burned (never deleted, never
279
+ // reused). A cleanup failure is NEVER swallowed as success: the caller gets the durable
280
+ // coordinates and resumes the SAME op (resumeActivationSaga) to finish the terminalization.
281
+ try {
282
+ await gateRetire(t, { lifecycleUid, revision: gate.revision, opId });
283
+ }
284
+ catch (cleanup) {
285
+ throw new EpEnvelopeError("unavailable", `lifecycle activation for "${owner}/${actor}" lost the head CAS AND terminalizing its orphan gate failed; the uid ${lifecycleUid} is burned but its gate is still frozen by op ${opId}; resume the same op with resumeActivation: ${cleanup?.message ?? String(cleanup)}`, [{ kind: "resume-activation", owner, actor, lifecycleUid, opId }]);
286
+ }
287
+ throw new EpEnvelopeError("conflict", `lifecycle activation for "${owner}/${actor}" lost the head CAS (a concurrent activation won); this saga's uid ${lifecycleUid} is burned and its gate terminalized (SPEC 13.1)`);
288
+ }
289
+ throw e;
290
+ }
291
+ // 4. Reopen the gate at its first mintable generation — the saga's LAST step.
292
+ await gateReopen(t, { lifecycleUid, revision: gate.revision, opId });
293
+ return { mapping, revision, opId };
294
+ }
295
+ /** The production ISSUANCE activation AT THE CALLER'S uid: same saga order with
296
+ * ADOPT-instead-of-burn resume semantics (a reservation or frozen ACTIVATION gate already
297
+ * carried by OUR alias at this uid is prior durable progress; a CAS loss to a SIBLING at the
298
+ * same coordinates converges on the winner's state). See the executor's public doc
299
+ * (implementations/auth `activateLifecycleAtUid`) for the refusal matrix. */
300
+ export async function runActivationSagaAtUid(t, args) {
301
+ const { owner, actor, lifecycleUid } = args;
302
+ assertLifecycleToken(lifecycleUid);
303
+ if (typeof args.managerInstance !== "string" || args.managerInstance.length === 0)
304
+ throw new EpEnvelopeError("failed-precondition", "activateLifecycleAtUid requires a managerInstance (the minting authority)");
305
+ const current = await headCandidate(t, owner, actor);
306
+ if (current !== undefined && current.mapping.state === "active" && current.mapping.lifecycleUid !== lifecycleUid)
307
+ throw new EpEnvelopeError("already-exists", `lifecycle "${owner}/${actor}" is active at uid ${current.mapping.lifecycleUid}, not this grant's ${lifecycleUid}; retiring a live predecessor is the takeover barrier's job and production issuance does not run it (R1) - despawn/retire the predecessor first, or grant a fresh actor name (SPEC 13.1)`);
308
+ if (current !== undefined && current.mapping.state === "retiring")
309
+ throw new EpEnvelopeError("failed-precondition", `lifecycle "${owner}/${actor}" is retiring (op ${current.mapping.op?.opId}); a retiring alias is not replaceable until its barrier completes (SPEC 13.1)`);
310
+ const headIsOurs = current !== undefined && current.mapping.state === "active"; // same uid, by the guard above
311
+ // 1. The uid reservation: win it, or adopt a prior attempt's — SAME alias only.
312
+ if (!headIsOurs && (await uidTryReserve(t, lifecycleUid, { owner, actor, mintedBy: args.managerInstance })) === "burned") {
313
+ const res = await uidReadReservation(t, lifecycleUid);
314
+ if (res === undefined || res.owner !== owner || res.actor !== actor)
315
+ throw new EpEnvelopeError("permission-denied", `uid ${lifecycleUid} is reserved by ${res ? `"${res.owner}/${res.actor}"` : "an unreadable reservation"}, not "${owner}/${actor}"; a grant's uid is never adopted across aliases (SPEC 13.1)`);
316
+ }
317
+ // 2. The activation gate: create frozen, or adopt OUR prior attempt's frozen activation gate
318
+ // (the reservation above already binds this uid to this alias, so any activation freeze on
319
+ // it is this alias's own activation). A retry loop absorbs the sibling-race CAS losses.
320
+ for (let attempt = 0;; attempt++) {
321
+ if (attempt > 4)
322
+ throw new EpEnvelopeError("unavailable", `activation for "${owner}/${actor}" at uid ${lifecycleUid} keeps losing its gate/head CASes to concurrent movement; re-read and re-decide (SPEC 13.1)`);
323
+ let gate = await gateObserve(t, lifecycleUid);
324
+ let opId;
325
+ if (gate === undefined) {
326
+ try {
327
+ gate = await gateCreateFrozen(t, { lifecycleUid, op: { opId: mintLifecycleUid(), kind: "activation" } });
328
+ }
329
+ catch (e) {
330
+ if (isCasLoss(e))
331
+ continue; // a sibling created it; re-observe and adopt
332
+ throw e;
333
+ }
334
+ opId = gate.row.op.opId;
335
+ }
336
+ else if (gate.row.state === "frozen") {
337
+ if (gate.row.op?.kind !== "activation")
338
+ throw new EpEnvelopeError("failed-precondition", `the issuance gate for ${lifecycleUid} is frozen by a ${gate.row.op?.kind ?? "<unknown>"} (op ${gate.row.op?.opId ?? "<none>"}); a barrier is in flight - issuance activation neither adopts nor overrides it (SPEC 13.1)`);
339
+ opId = gate.row.op.opId;
340
+ }
341
+ else if (gate.row.state === "retired") {
342
+ throw new EpEnvelopeError("permission-denied", `uid ${lifecycleUid} has a terminally retired issuance gate; a burned uid never re-activates - re-grant the actor for a fresh incarnation (SPEC 13.1)`);
343
+ }
344
+ else {
345
+ // Open gate: the saga writes the head BEFORE its reopen, so an open gate with the head
346
+ // active at our uid is a COMPLETED activation; anything else is foreign movement.
347
+ const head = await headCandidate(t, owner, actor);
348
+ if (head !== undefined && head.mapping.state === "active" && head.mapping.lifecycleUid === lifecycleUid)
349
+ return;
350
+ throw new EpEnvelopeError("failed-precondition", `the issuance gate for ${lifecycleUid} is open but the head for "${owner}/${actor}" is ${head === undefined ? "absent" : `${head.mapping.state} at ${head.mapping.lifecycleUid}`}; an activation reopens only AFTER its head CAS - this is foreign movement or corruption, refuse (SPEC 13.1/13.12)`);
351
+ }
352
+ // 3. The head CAS (create-only for virgin; revision-pinned over a retired predecessor). A
353
+ // loss converges if the sibling won for the SAME uid, refuses on a foreign winner.
354
+ if (!headIsOurs) {
355
+ const mapping = { owner, actor, lifecycleUid, managerInstance: args.managerInstance, processEpoch: 1, state: "active" };
356
+ try {
357
+ if (current === undefined)
358
+ await t.createRecord(headKey(owner, actor), mapping);
359
+ else
360
+ await t.updateRecord(headKey(owner, actor), mapping, current.revision);
361
+ }
362
+ catch (e) {
363
+ if (!isCasLoss(e))
364
+ throw e;
365
+ const head = await headCandidate(t, owner, actor);
366
+ if (!(head !== undefined && head.mapping.state === "active" && head.mapping.lifecycleUid === lifecycleUid))
367
+ throw new EpEnvelopeError("conflict", `activation for "${owner}/${actor}" at uid ${lifecycleUid} lost the head CAS to a foreign movement (now ${head === undefined ? "absent" : `${head.mapping.state} at ${head.mapping.lifecycleUid}`}); re-grant raced this exchange - re-exchange (SPEC 13.1)`);
368
+ }
369
+ }
370
+ // 4. Reopen the gate — the saga's LAST step. A loss to the sibling's reopen is convergence.
371
+ try {
372
+ await gateReopen(t, { lifecycleUid, revision: gate.revision, opId });
373
+ return;
374
+ }
375
+ catch (e) {
376
+ const g = await gateObserve(t, lifecycleUid);
377
+ if (g !== undefined && g.row.state === "open")
378
+ return; // the sibling finished it
379
+ if (e instanceof EpEnvelopeError && e.code === "conflict")
380
+ continue; // revision moved; re-observe
381
+ throw e;
382
+ }
383
+ }
384
+ }
385
+ /** Resume a crashed activation saga from its durable coordinates. Reads the durable state and
386
+ * finishes the SAME operation deterministically; idempotent; never advances another
387
+ * operation's freeze. */
388
+ export async function resumeActivationSaga(t, args) {
389
+ const head = await headCandidate(t, args.owner, args.actor);
390
+ const gate = await gateObserve(t, args.lifecycleUid);
391
+ const won = head !== undefined && head.mapping.state === "active" && head.mapping.lifecycleUid === args.lifecycleUid;
392
+ if (gate === undefined) {
393
+ // Crash before step 2: nothing durable beyond the reservation; the uid stays burned.
394
+ if (won)
395
+ throw new EpEnvelopeError("internal", `the head names uid ${args.lifecycleUid} but its gate does not exist; an active head without a gate is corruption (SPEC 13.1)`);
396
+ return "already-settled";
397
+ }
398
+ if (gate.row.state === "retired") {
399
+ // Terminal idempotence is SAME-OP idempotence: a stranger cannot claim another
400
+ // operation's terminal as its own settlement.
401
+ if (gate.row.op?.opId !== args.opId)
402
+ throw new EpEnvelopeError("permission-denied", `the gate for uid ${args.lifecycleUid} was terminalized by operation ${gate.row.op?.opId ?? "<none>"}, not ${args.opId} (SPEC 13.1)`);
403
+ return "already-settled";
404
+ }
405
+ if (gate.row.state === "open") {
406
+ if (!won)
407
+ throw new EpEnvelopeError("internal", `the gate for uid ${args.lifecycleUid} is open but the head does not name it; an open gate without its active head is corruption (SPEC 13.1)`);
408
+ return "already-settled";
409
+ }
410
+ // frozen: only OUR op may advance it (reopen/retire are op-pinned and will refuse a stranger).
411
+ if (won) {
412
+ await gateReopen(t, { lifecycleUid: args.lifecycleUid, revision: gate.revision, opId: args.opId });
413
+ return "completed";
414
+ }
415
+ await gateRetire(t, { lifecycleUid: args.lifecycleUid, revision: gate.revision, opId: args.opId });
416
+ return "terminalized";
417
+ }
418
+ //# sourceMappingURL=lifecycle-saga.js.map