@hol-org/cigar 0.9.4
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/LICENSE +201 -0
- package/NOTICE +5 -0
- package/README.md +68 -0
- package/dist/client.d.ts +16 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +839 -0
- package/dist/client.js.map +1 -0
- package/dist/digest.d.ts +21 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +406 -0
- package/dist/digest.js.map +1 -0
- package/dist/errors.d.ts +35 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +54 -0
- package/dist/errors.js.map +1 -0
- package/dist/examples/quickstart.d.ts +2 -0
- package/dist/examples/quickstart.d.ts.map +1 -0
- package/dist/examples/quickstart.js +32 -0
- package/dist/examples/quickstart.js.map +1 -0
- package/dist/examples/two-agent-observer.d.ts +9 -0
- package/dist/examples/two-agent-observer.d.ts.map +1 -0
- package/dist/examples/two-agent-observer.js +59 -0
- package/dist/examples/two-agent-observer.js.map +1 -0
- package/dist/examples/verify-shared-bundle.d.ts +2 -0
- package/dist/examples/verify-shared-bundle.d.ts.map +1 -0
- package/dist/examples/verify-shared-bundle.js +13 -0
- package/dist/examples/verify-shared-bundle.js.map +1 -0
- package/dist/generated/cigar_service_pb.d.ts +642 -0
- package/dist/generated/cigar_service_pb.d.ts.map +1 -0
- package/dist/generated/cigar_service_pb.js +55 -0
- package/dist/generated/cigar_service_pb.js.map +1 -0
- package/dist/generated/context_abi_pb.d.ts +3429 -0
- package/dist/generated/context_abi_pb.d.ts.map +1 -0
- package/dist/generated/context_abi_pb.js +1386 -0
- package/dist/generated/context_abi_pb.js.map +1 -0
- package/dist/generated/errors.d.ts +242 -0
- package/dist/generated/errors.d.ts.map +1 -0
- package/dist/generated/errors.js +38 -0
- package/dist/generated/errors.js.map +1 -0
- package/dist/generated/generated/error_codes_pb.d.ts +188 -0
- package/dist/generated/generated/error_codes_pb.d.ts.map +1 -0
- package/dist/generated/generated/error_codes_pb.js +191 -0
- package/dist/generated/generated/error_codes_pb.js.map +1 -0
- package/dist/generated/models.d.ts +472 -0
- package/dist/generated/models.d.ts.map +1 -0
- package/dist/generated/models.js +314 -0
- package/dist/generated/models.js.map +1 -0
- package/dist/generated/operations.d.ts +912 -0
- package/dist/generated/operations.d.ts.map +1 -0
- package/dist/generated/operations.js +50 -0
- package/dist/generated/operations.js.map +1 -0
- package/dist/idempotency.d.ts +3 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +16 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/release.json +6 -0
- package/dist/types.d.ts +115 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/verify-replay.d.ts +3 -0
- package/dist/verify-replay.d.ts.map +1 -0
- package/dist/verify-replay.js +391 -0
- package/dist/verify-replay.js.map +1 -0
- package/dist/verify-vectors.d.ts +3 -0
- package/dist/verify-vectors.d.ts.map +1 -0
- package/dist/verify-vectors.js +383 -0
- package/dist/verify-vectors.js.map +1 -0
- package/dist/workflow-session.d.ts +93 -0
- package/dist/workflow-session.d.ts.map +1 -0
- package/dist/workflow-session.js +550 -0
- package/dist/workflow-session.js.map +1 -0
- package/fixtures/semantic-bundle-v1.json +13 -0
- package/package.json +60 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
/** Identity-only state tracking for deterministic workflow context cycles. */
|
|
2
|
+
import { CigarError } from "./errors.js";
|
|
3
|
+
export const MAX_WORKFLOW_DELTA_CHAIN_LENGTH = 8;
|
|
4
|
+
export const MAX_WORKFLOW_REPLAY_CYCLES = 64;
|
|
5
|
+
export const WORKFLOW_CONTEXT_PHASES = [
|
|
6
|
+
"new", "plan_created", "target_bundle_loaded", "delta_compiled", "bundle_ready", "materialized",
|
|
7
|
+
"model_invocation_pending", "model_result_recorded", "effect_prepared", "observation_recorded",
|
|
8
|
+
"effect_authorization_revalidated", "effect_authorized", "effect_revalidated", "effect_dispatching",
|
|
9
|
+
"effect_ambiguous", "effect_settled", "checkpointed", "finished", "replay_verified", "quarantined",
|
|
10
|
+
];
|
|
11
|
+
export const WORKFLOW_RESUME_ACTIONS = [
|
|
12
|
+
"create_context_plan", "compile_context_bundle", "compile_context_delta", "apply_context_delta",
|
|
13
|
+
"materialize_context_bundle", "begin_model_invocation", "resume_model_invocation",
|
|
14
|
+
"prepare_effect_or_ingest_observation", "ingest_observation", "authorize_effect_or_checkpoint",
|
|
15
|
+
"revalidate_context_bundle", "dispatch_effect", "observe_effect", "reconcile_effect", "checkpoint",
|
|
16
|
+
"materialize_or_finish", "replay", "complete",
|
|
17
|
+
];
|
|
18
|
+
export const WORKFLOW_SESSION_ERROR_CODES = [
|
|
19
|
+
"invalid_transition", "invalid_event", "identity_mismatch", "invalidated", "limit_exceeded",
|
|
20
|
+
];
|
|
21
|
+
export const WORKFLOW_SESSION_EVENT_NAMES = [
|
|
22
|
+
"plan_created", "bundle_compiled", "delta_compiled", "delta_applied", "materialized",
|
|
23
|
+
"model_invocation_started", "model_result_recorded", "effect_prepared", "observation_recorded",
|
|
24
|
+
"effect_authorized", "effect_revalidated", "effect_dispatched", "effect_observed", "cycle_checkpointed",
|
|
25
|
+
"finished", "replay_verified", "context_quarantined",
|
|
26
|
+
];
|
|
27
|
+
export const WORKFLOW_QUARANTINE_REASONS = ["cancelled", "revoked", "invalidated"];
|
|
28
|
+
const ACTION_OPERATIONS = {
|
|
29
|
+
create_context_plan: "createContextPlan",
|
|
30
|
+
compile_context_bundle: "compileContextBundle",
|
|
31
|
+
compile_context_delta: "compileContextDelta",
|
|
32
|
+
apply_context_delta: null,
|
|
33
|
+
materialize_context_bundle: "materializeContextBundle",
|
|
34
|
+
begin_model_invocation: null,
|
|
35
|
+
resume_model_invocation: null,
|
|
36
|
+
prepare_effect_or_ingest_observation: null,
|
|
37
|
+
ingest_observation: "ingestCatalog",
|
|
38
|
+
authorize_effect_or_checkpoint: "authorizeEffect",
|
|
39
|
+
revalidate_context_bundle: "revalidateContextBundle",
|
|
40
|
+
dispatch_effect: "dispatchEffect",
|
|
41
|
+
observe_effect: "getEffectStatus",
|
|
42
|
+
reconcile_effect: "reconcileEffect",
|
|
43
|
+
checkpoint: null,
|
|
44
|
+
materialize_or_finish: null,
|
|
45
|
+
replay: "createReplay",
|
|
46
|
+
complete: null,
|
|
47
|
+
};
|
|
48
|
+
export function workflowOperationId(action) {
|
|
49
|
+
return ACTION_OPERATIONS[action];
|
|
50
|
+
}
|
|
51
|
+
export class WorkflowSessionError extends CigarError {
|
|
52
|
+
code;
|
|
53
|
+
constructor(code) {
|
|
54
|
+
super(`workflow context transition failed: ${code}`);
|
|
55
|
+
this.code = code;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const DIGEST = /^1220[0-9a-f]{64}$/;
|
|
59
|
+
const UUID_V7 = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
60
|
+
const TERMINAL_EFFECT_STATES = new Set([
|
|
61
|
+
"succeeded", "failed", "manual_resolution", "rejected", "expired", "cancelled", "compensated",
|
|
62
|
+
"compensation_failed",
|
|
63
|
+
]);
|
|
64
|
+
/** Mutable, content-safe helper for one context lifecycle. Successful boundaries advance it atomically. */
|
|
65
|
+
export class WorkflowContextSession {
|
|
66
|
+
#phase = "new";
|
|
67
|
+
#completedTurns = 0;
|
|
68
|
+
#deltaChainLength = 0;
|
|
69
|
+
#activeContext;
|
|
70
|
+
#pendingContext;
|
|
71
|
+
#pendingDelta;
|
|
72
|
+
#selectedDelta;
|
|
73
|
+
#materialization;
|
|
74
|
+
#invocation;
|
|
75
|
+
#modelResultDigest;
|
|
76
|
+
#observationDigest;
|
|
77
|
+
#observationRevision;
|
|
78
|
+
#effect;
|
|
79
|
+
#completedCycles = [];
|
|
80
|
+
#replayVerified = false;
|
|
81
|
+
#quarantineReason;
|
|
82
|
+
get phase() { return this.#phase; }
|
|
83
|
+
get completedTurns() { return this.#completedTurns; }
|
|
84
|
+
get deltaChainLength() { return this.#deltaChainLength; }
|
|
85
|
+
get activeBundleId() { return this.#activeContext?.bundleId; }
|
|
86
|
+
replayIdentity() {
|
|
87
|
+
this.#requirePhase("finished", "replay_verified");
|
|
88
|
+
if (this.#completedCycles.length === 0)
|
|
89
|
+
fail("invalid_transition");
|
|
90
|
+
return cloneReplayIdentity({ cycles: this.#completedCycles });
|
|
91
|
+
}
|
|
92
|
+
compareReplay(candidate) {
|
|
93
|
+
const baseline = this.replayIdentity();
|
|
94
|
+
validateReplayIdentity(candidate);
|
|
95
|
+
return compareWorkflowReplay(baseline, candidate);
|
|
96
|
+
}
|
|
97
|
+
get resumeAction() {
|
|
98
|
+
switch (this.#phase) {
|
|
99
|
+
case "new":
|
|
100
|
+
case "observation_recorded": return "create_context_plan";
|
|
101
|
+
case "plan_created": return "compile_context_bundle";
|
|
102
|
+
case "target_bundle_loaded": return "compile_context_delta";
|
|
103
|
+
case "delta_compiled": return "apply_context_delta";
|
|
104
|
+
case "bundle_ready":
|
|
105
|
+
if (this.#modelResultDigest === undefined)
|
|
106
|
+
return "materialize_context_bundle";
|
|
107
|
+
return this.#effect === undefined ? "checkpoint" : "revalidate_context_bundle";
|
|
108
|
+
case "materialized": return "begin_model_invocation";
|
|
109
|
+
case "model_invocation_pending": return "resume_model_invocation";
|
|
110
|
+
case "model_result_recorded": return "prepare_effect_or_ingest_observation";
|
|
111
|
+
case "effect_prepared": return "ingest_observation";
|
|
112
|
+
case "effect_authorization_revalidated": return "authorize_effect_or_checkpoint";
|
|
113
|
+
case "effect_authorized": return "revalidate_context_bundle";
|
|
114
|
+
case "effect_revalidated": return "dispatch_effect";
|
|
115
|
+
case "effect_dispatching": return "observe_effect";
|
|
116
|
+
case "effect_ambiguous": return "reconcile_effect";
|
|
117
|
+
case "effect_settled": return "checkpoint";
|
|
118
|
+
case "checkpointed": return "materialize_or_finish";
|
|
119
|
+
case "finished": return "replay";
|
|
120
|
+
case "replay_verified":
|
|
121
|
+
case "quarantined": return "complete";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
toString() {
|
|
125
|
+
return `WorkflowContextSession(phase=${this.#phase}, completedTurns=${this.#completedTurns}, `
|
|
126
|
+
+ `deltaChainLength=${this.#deltaChainLength}, hasActiveContext=${this.#activeContext !== undefined}, `
|
|
127
|
+
+ `hasPendingContext=${this.#pendingContext !== undefined}, hasPendingDelta=${this.#pendingDelta !== undefined}, `
|
|
128
|
+
+ `hasSelectedDelta=${this.#selectedDelta !== undefined}, hasInvocation=${this.#invocation !== undefined}, `
|
|
129
|
+
+ `hasProviderIdempotencyKey=${this.#invocation !== undefined}, `
|
|
130
|
+
+ `hasModelResult=${this.#modelResultDigest !== undefined}, `
|
|
131
|
+
+ `hasObservation=${this.#observationDigest !== undefined}, effectState=${this.#effect?.state ?? "none"}, `
|
|
132
|
+
+ `completedCycleCount=${this.#completedCycles.length}, `
|
|
133
|
+
+ `quarantineReason=${this.#quarantineReason ?? "none"})`;
|
|
134
|
+
}
|
|
135
|
+
recordPlanCreated(planId, bundleId, contractDigest) {
|
|
136
|
+
this.#requirePhase("new", "observation_recorded");
|
|
137
|
+
record(planId);
|
|
138
|
+
digest(bundleId);
|
|
139
|
+
digest(contractDigest);
|
|
140
|
+
this.#pendingContext = { planId, bundleId, contractDigest };
|
|
141
|
+
this.#pendingDelta = undefined;
|
|
142
|
+
this.#phase = "plan_created";
|
|
143
|
+
}
|
|
144
|
+
recordBundleCompiled(bundleId, contractDigest) {
|
|
145
|
+
this.#requirePhase("plan_created");
|
|
146
|
+
digest(bundleId);
|
|
147
|
+
digest(contractDigest);
|
|
148
|
+
const pending = this.#pendingContext;
|
|
149
|
+
if (pending === undefined)
|
|
150
|
+
fail("invalid_transition");
|
|
151
|
+
if (pending.bundleId !== bundleId || pending.contractDigest !== contractDigest)
|
|
152
|
+
fail("identity_mismatch");
|
|
153
|
+
if (this.#activeContext === undefined || this.#activeContext.bundleId === bundleId) {
|
|
154
|
+
this.#activeContext = pending;
|
|
155
|
+
this.#pendingContext = undefined;
|
|
156
|
+
this.#selectedDelta = undefined;
|
|
157
|
+
if (this.#deltaChainLength >= MAX_WORKFLOW_DELTA_CHAIN_LENGTH)
|
|
158
|
+
this.#deltaChainLength = 0;
|
|
159
|
+
this.#phase = "bundle_ready";
|
|
160
|
+
}
|
|
161
|
+
else if (this.#deltaChainLength >= MAX_WORKFLOW_DELTA_CHAIN_LENGTH) {
|
|
162
|
+
this.#activeContext = pending;
|
|
163
|
+
this.#pendingContext = undefined;
|
|
164
|
+
this.#selectedDelta = undefined;
|
|
165
|
+
this.#deltaChainLength = 0;
|
|
166
|
+
this.#phase = "bundle_ready";
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.#phase = "target_bundle_loaded";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
recordDeltaCompiled(baseBundleId, targetBundleId, deltaDigest) {
|
|
173
|
+
this.#requirePhase("target_bundle_loaded");
|
|
174
|
+
if (this.#deltaChainLength >= MAX_WORKFLOW_DELTA_CHAIN_LENGTH)
|
|
175
|
+
fail("limit_exceeded");
|
|
176
|
+
digest(baseBundleId);
|
|
177
|
+
digest(targetBundleId);
|
|
178
|
+
digest(deltaDigest);
|
|
179
|
+
if (this.#activeContext === undefined || this.#pendingContext === undefined)
|
|
180
|
+
fail("invalid_transition");
|
|
181
|
+
if (this.#activeContext.bundleId !== baseBundleId || this.#pendingContext.bundleId !== targetBundleId) {
|
|
182
|
+
fail("identity_mismatch");
|
|
183
|
+
}
|
|
184
|
+
this.#pendingDelta = { baseBundleId, targetBundleId, deltaDigest };
|
|
185
|
+
this.#phase = "delta_compiled";
|
|
186
|
+
}
|
|
187
|
+
recordDeltaApplied(baseBundleId, targetBundleId, deltaDigest) {
|
|
188
|
+
this.#requirePhase("delta_compiled");
|
|
189
|
+
digest(baseBundleId);
|
|
190
|
+
digest(targetBundleId);
|
|
191
|
+
digest(deltaDigest);
|
|
192
|
+
const pending = this.#pendingDelta;
|
|
193
|
+
if (pending === undefined)
|
|
194
|
+
fail("invalid_transition");
|
|
195
|
+
if (pending.baseBundleId !== baseBundleId || pending.targetBundleId !== targetBundleId
|
|
196
|
+
|| pending.deltaDigest !== deltaDigest)
|
|
197
|
+
fail("identity_mismatch");
|
|
198
|
+
if (this.#deltaChainLength >= MAX_WORKFLOW_DELTA_CHAIN_LENGTH)
|
|
199
|
+
fail("limit_exceeded");
|
|
200
|
+
this.#deltaChainLength += 1;
|
|
201
|
+
this.#selectedDelta = pending;
|
|
202
|
+
this.#activeContext = this.#pendingContext;
|
|
203
|
+
this.#pendingContext = undefined;
|
|
204
|
+
this.#pendingDelta = undefined;
|
|
205
|
+
this.#phase = "bundle_ready";
|
|
206
|
+
}
|
|
207
|
+
recordMaterialized(bundleId, tokenizerFingerprint, materializerFingerprint, physicalInputTokens) {
|
|
208
|
+
this.#requirePhase("bundle_ready", "checkpointed");
|
|
209
|
+
digest(bundleId);
|
|
210
|
+
digest(tokenizerFingerprint);
|
|
211
|
+
digest(materializerFingerprint);
|
|
212
|
+
if (this.#modelResultDigest !== undefined || !positiveInteger(physicalInputTokens, 0xffff_ffff)) {
|
|
213
|
+
fail("invalid_event");
|
|
214
|
+
}
|
|
215
|
+
if (this.activeBundleId !== bundleId)
|
|
216
|
+
fail("identity_mismatch");
|
|
217
|
+
this.#materialization = {
|
|
218
|
+
bundleId, tokenizerFingerprint, materializerFingerprint, physicalInputTokens,
|
|
219
|
+
};
|
|
220
|
+
this.#phase = "materialized";
|
|
221
|
+
}
|
|
222
|
+
beginModelInvocation(invocationId, requestDigest, idempotencyKeyDigest) {
|
|
223
|
+
this.#requirePhase("materialized");
|
|
224
|
+
record(invocationId);
|
|
225
|
+
digest(requestDigest);
|
|
226
|
+
digest(idempotencyKeyDigest);
|
|
227
|
+
this.#invocation = { invocationId, requestDigest, idempotencyKeyDigest };
|
|
228
|
+
this.#phase = "model_invocation_pending";
|
|
229
|
+
}
|
|
230
|
+
recordModelResult(invocationId, resultDigest) {
|
|
231
|
+
this.#requirePhase("model_invocation_pending");
|
|
232
|
+
record(invocationId);
|
|
233
|
+
digest(resultDigest);
|
|
234
|
+
if (this.#invocation?.invocationId !== invocationId)
|
|
235
|
+
fail("identity_mismatch");
|
|
236
|
+
this.#modelResultDigest = resultDigest;
|
|
237
|
+
this.#phase = "model_result_recorded";
|
|
238
|
+
}
|
|
239
|
+
recordEffectPrepared(effectId, intentDigest, effectVersion, state = "prepared", attemptCount = 0, reconciliationCount = 0) {
|
|
240
|
+
this.#requirePhase("model_result_recorded");
|
|
241
|
+
record(effectId);
|
|
242
|
+
digest(intentDigest);
|
|
243
|
+
positiveBigint(effectVersion);
|
|
244
|
+
if (this.#effect !== undefined || state !== "prepared" || !count(attemptCount)
|
|
245
|
+
|| !count(reconciliationCount) || attemptCount !== 0 || reconciliationCount !== 0)
|
|
246
|
+
fail("invalid_event");
|
|
247
|
+
this.#effect = { effectId, intentDigest, effectVersion, state, attemptCount, reconciliationCount };
|
|
248
|
+
this.#phase = "effect_prepared";
|
|
249
|
+
}
|
|
250
|
+
recordObservation(publicationDigest, revision) {
|
|
251
|
+
this.#requirePhase("model_result_recorded", "effect_prepared");
|
|
252
|
+
digest(publicationDigest);
|
|
253
|
+
positiveBigint(revision);
|
|
254
|
+
this.#observationDigest = publicationDigest;
|
|
255
|
+
this.#observationRevision = revision;
|
|
256
|
+
this.#phase = "observation_recorded";
|
|
257
|
+
}
|
|
258
|
+
recordEffectAuthorized(effectId, intentDigest, effectVersion, state = "authorized", attemptCount = 0, reconciliationCount = 0) {
|
|
259
|
+
this.#requirePhase("effect_authorization_revalidated");
|
|
260
|
+
if (this.#modelResultDigest === undefined || state !== "authorized")
|
|
261
|
+
fail("invalid_event");
|
|
262
|
+
if (this.#effect === undefined || attemptCount !== this.#effect.attemptCount
|
|
263
|
+
|| reconciliationCount !== this.#effect.reconciliationCount)
|
|
264
|
+
fail("invalid_event");
|
|
265
|
+
this.#updateEffect(effectId, intentDigest, effectVersion, state, attemptCount, reconciliationCount, true);
|
|
266
|
+
this.#phase = "effect_authorized";
|
|
267
|
+
}
|
|
268
|
+
recordEffectRevalidated(bundleId, valid) {
|
|
269
|
+
const beforeAuthorization = this.#phase === "bundle_ready"
|
|
270
|
+
&& this.#modelResultDigest !== undefined && this.#effect?.state === "prepared";
|
|
271
|
+
if (!beforeAuthorization && this.#phase !== "effect_authorized")
|
|
272
|
+
fail("invalid_transition");
|
|
273
|
+
digest(bundleId);
|
|
274
|
+
if (typeof valid !== "boolean")
|
|
275
|
+
fail("invalid_event");
|
|
276
|
+
if (this.activeBundleId !== bundleId)
|
|
277
|
+
fail("identity_mismatch");
|
|
278
|
+
if (!valid) {
|
|
279
|
+
this.#enterQuarantine("invalidated");
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
this.#phase = beforeAuthorization ? "effect_authorization_revalidated" : "effect_revalidated";
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
quarantineContext(bundleId, reason) {
|
|
286
|
+
digest(bundleId);
|
|
287
|
+
if (!WORKFLOW_QUARANTINE_REASONS.includes(reason))
|
|
288
|
+
fail("invalid_event");
|
|
289
|
+
if (["new", "finished", "replay_verified", "quarantined"].includes(this.#phase)) {
|
|
290
|
+
fail("invalid_transition");
|
|
291
|
+
}
|
|
292
|
+
if (this.activeBundleId !== bundleId)
|
|
293
|
+
fail("identity_mismatch");
|
|
294
|
+
this.#enterQuarantine(reason);
|
|
295
|
+
}
|
|
296
|
+
recordEffectDispatched(effectId, intentDigest, effectVersion, state, attemptCount, reconciliationCount) {
|
|
297
|
+
this.#requirePhase("effect_revalidated");
|
|
298
|
+
if (state !== "dispatching" && state !== "unknown" && !TERMINAL_EFFECT_STATES.has(state)) {
|
|
299
|
+
fail("invalid_event");
|
|
300
|
+
}
|
|
301
|
+
if (this.#effect === undefined || attemptCount !== this.#effect.attemptCount + 1
|
|
302
|
+
|| reconciliationCount !== this.#effect.reconciliationCount)
|
|
303
|
+
fail("invalid_event");
|
|
304
|
+
this.#updateEffect(effectId, intentDigest, effectVersion, state, attemptCount, reconciliationCount, true);
|
|
305
|
+
this.#phase = effectPhase(state);
|
|
306
|
+
}
|
|
307
|
+
recordEffectObserved(effectId, intentDigest, effectVersion, state, attemptCount, reconciliationCount) {
|
|
308
|
+
const allowed = this.#phase === "effect_dispatching"
|
|
309
|
+
? state === "dispatching" || state === "unknown" || TERMINAL_EFFECT_STATES.has(state)
|
|
310
|
+
: this.#phase === "effect_ambiguous"
|
|
311
|
+
&& (state === "unknown" || state === "authorized_for_retry" || TERMINAL_EFFECT_STATES.has(state));
|
|
312
|
+
if (!allowed)
|
|
313
|
+
fail("invalid_transition");
|
|
314
|
+
if (this.#effect === undefined || attemptCount < this.#effect.attemptCount
|
|
315
|
+
|| reconciliationCount < this.#effect.reconciliationCount
|
|
316
|
+
|| state === "authorized_for_retry" && (attemptCount !== this.#effect.attemptCount
|
|
317
|
+
|| reconciliationCount <= this.#effect.reconciliationCount))
|
|
318
|
+
fail("invalid_event");
|
|
319
|
+
this.#updateEffect(effectId, intentDigest, effectVersion, state, attemptCount, reconciliationCount, false);
|
|
320
|
+
this.#phase = effectPhase(state);
|
|
321
|
+
}
|
|
322
|
+
checkpointCycle() {
|
|
323
|
+
const effectComplete = this.#effect === undefined
|
|
324
|
+
? this.#phase === "bundle_ready"
|
|
325
|
+
: this.#phase === "effect_settled" && TERMINAL_EFFECT_STATES.has(this.#effect.state);
|
|
326
|
+
if (!effectComplete || this.#modelResultDigest === undefined || this.#observationDigest === undefined) {
|
|
327
|
+
fail("invalid_transition");
|
|
328
|
+
}
|
|
329
|
+
if (this.#completedCycles.length >= MAX_WORKFLOW_REPLAY_CYCLES || this.#completedTurns >= 0xffff_ffff) {
|
|
330
|
+
fail("limit_exceeded");
|
|
331
|
+
}
|
|
332
|
+
const selected = this.#activeContext;
|
|
333
|
+
const materialization = this.#materialization;
|
|
334
|
+
const invocation = this.#invocation;
|
|
335
|
+
const outcomeRevision = this.#observationRevision;
|
|
336
|
+
if (selected === undefined || materialization === undefined || invocation === undefined
|
|
337
|
+
|| outcomeRevision === undefined)
|
|
338
|
+
fail("invalid_transition");
|
|
339
|
+
this.#completedCycles.push({
|
|
340
|
+
planId: selected.planId,
|
|
341
|
+
bundleId: selected.bundleId,
|
|
342
|
+
contractDigest: selected.contractDigest,
|
|
343
|
+
...(this.#selectedDelta === undefined ? {} : { selectedDelta: { ...this.#selectedDelta } }),
|
|
344
|
+
materializedBundleId: materialization.bundleId,
|
|
345
|
+
tokenizerFingerprint: materialization.tokenizerFingerprint,
|
|
346
|
+
materializerFingerprint: materialization.materializerFingerprint,
|
|
347
|
+
physicalInputTokens: materialization.physicalInputTokens,
|
|
348
|
+
invocationId: invocation.invocationId,
|
|
349
|
+
requestDigest: invocation.requestDigest,
|
|
350
|
+
idempotencyKeyDigest: invocation.idempotencyKeyDigest,
|
|
351
|
+
modelResultDigest: this.#modelResultDigest,
|
|
352
|
+
...(this.#effect === undefined ? {} : { effect: { ...this.#effect } }),
|
|
353
|
+
outcomeDigest: this.#observationDigest,
|
|
354
|
+
outcomeRevision,
|
|
355
|
+
});
|
|
356
|
+
this.#completedTurns += 1;
|
|
357
|
+
this.#pendingContext = undefined;
|
|
358
|
+
this.#pendingDelta = undefined;
|
|
359
|
+
this.#selectedDelta = undefined;
|
|
360
|
+
this.#materialization = undefined;
|
|
361
|
+
this.#invocation = undefined;
|
|
362
|
+
this.#modelResultDigest = undefined;
|
|
363
|
+
this.#observationDigest = undefined;
|
|
364
|
+
this.#observationRevision = undefined;
|
|
365
|
+
this.#effect = undefined;
|
|
366
|
+
this.#phase = "checkpointed";
|
|
367
|
+
}
|
|
368
|
+
finish() {
|
|
369
|
+
this.#requirePhase("checkpointed");
|
|
370
|
+
if (this.#completedTurns === 0)
|
|
371
|
+
fail("invalid_transition");
|
|
372
|
+
this.#phase = "finished";
|
|
373
|
+
}
|
|
374
|
+
recordReplayVerified(decisionId, executionId, candidate) {
|
|
375
|
+
this.#requirePhase("finished");
|
|
376
|
+
digest(decisionId);
|
|
377
|
+
record(executionId);
|
|
378
|
+
const comparison = this.compareReplay(candidate);
|
|
379
|
+
if (!comparison.exactMatch)
|
|
380
|
+
fail("identity_mismatch");
|
|
381
|
+
this.#replayVerified = true;
|
|
382
|
+
this.#phase = "replay_verified";
|
|
383
|
+
return comparison;
|
|
384
|
+
}
|
|
385
|
+
#requirePhase(...allowed) {
|
|
386
|
+
if (!allowed.includes(this.#phase))
|
|
387
|
+
fail("invalid_transition");
|
|
388
|
+
}
|
|
389
|
+
#updateEffect(effectId, intentDigest, effectVersion, state, attemptCount, reconciliationCount, requireNewVersion) {
|
|
390
|
+
record(effectId);
|
|
391
|
+
digest(intentDigest);
|
|
392
|
+
positiveBigint(effectVersion);
|
|
393
|
+
if (!count(attemptCount) || !count(reconciliationCount)
|
|
394
|
+
|| !effectCountsValid(state, attemptCount, reconciliationCount))
|
|
395
|
+
fail("invalid_event");
|
|
396
|
+
const current = this.#effect;
|
|
397
|
+
if (current === undefined)
|
|
398
|
+
fail("invalid_transition");
|
|
399
|
+
const versionValid = effectVersion > current.effectVersion
|
|
400
|
+
|| (!requireNewVersion && effectVersion === current.effectVersion && state === current.state);
|
|
401
|
+
if (effectId !== current.effectId || intentDigest !== current.intentDigest || !versionValid) {
|
|
402
|
+
fail("identity_mismatch");
|
|
403
|
+
}
|
|
404
|
+
this.#effect = { effectId, intentDigest, effectVersion, state, attemptCount, reconciliationCount };
|
|
405
|
+
}
|
|
406
|
+
#enterQuarantine(reason) {
|
|
407
|
+
this.#pendingContext = undefined;
|
|
408
|
+
this.#pendingDelta = undefined;
|
|
409
|
+
this.#selectedDelta = undefined;
|
|
410
|
+
this.#materialization = undefined;
|
|
411
|
+
this.#invocation = undefined;
|
|
412
|
+
this.#modelResultDigest = undefined;
|
|
413
|
+
this.#observationDigest = undefined;
|
|
414
|
+
this.#observationRevision = undefined;
|
|
415
|
+
this.#effect = undefined;
|
|
416
|
+
this.#replayVerified = false;
|
|
417
|
+
this.#quarantineReason = reason;
|
|
418
|
+
this.#phase = "quarantined";
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function cloneReplayIdentity(identity) {
|
|
422
|
+
return {
|
|
423
|
+
cycles: identity.cycles.map((cycle) => ({
|
|
424
|
+
...cycle,
|
|
425
|
+
...(cycle.selectedDelta === undefined ? {} : { selectedDelta: { ...cycle.selectedDelta } }),
|
|
426
|
+
...(cycle.effect === undefined ? {} : { effect: { ...cycle.effect } }),
|
|
427
|
+
})),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function validateReplayIdentity(identity) {
|
|
431
|
+
if (typeof identity !== "object" || identity === null || !Array.isArray(identity.cycles)
|
|
432
|
+
|| identity.cycles.length === 0 || identity.cycles.length > MAX_WORKFLOW_REPLAY_CYCLES) {
|
|
433
|
+
fail("invalid_event");
|
|
434
|
+
}
|
|
435
|
+
for (const cycle of identity.cycles) {
|
|
436
|
+
record(cycle.planId);
|
|
437
|
+
digest(cycle.bundleId);
|
|
438
|
+
digest(cycle.contractDigest);
|
|
439
|
+
digest(cycle.materializedBundleId);
|
|
440
|
+
digest(cycle.tokenizerFingerprint);
|
|
441
|
+
digest(cycle.materializerFingerprint);
|
|
442
|
+
record(cycle.invocationId);
|
|
443
|
+
digest(cycle.requestDigest);
|
|
444
|
+
digest(cycle.idempotencyKeyDigest);
|
|
445
|
+
digest(cycle.modelResultDigest);
|
|
446
|
+
digest(cycle.outcomeDigest);
|
|
447
|
+
if (!positiveInteger(cycle.physicalInputTokens, 0xffff_ffff))
|
|
448
|
+
fail("invalid_event");
|
|
449
|
+
positiveBigint(cycle.outcomeRevision);
|
|
450
|
+
if (cycle.selectedDelta !== undefined) {
|
|
451
|
+
digest(cycle.selectedDelta.baseBundleId);
|
|
452
|
+
digest(cycle.selectedDelta.targetBundleId);
|
|
453
|
+
digest(cycle.selectedDelta.deltaDigest);
|
|
454
|
+
if (cycle.selectedDelta.targetBundleId !== cycle.bundleId
|
|
455
|
+
|| cycle.selectedDelta.baseBundleId !== cycle.materializedBundleId
|
|
456
|
+
|| cycle.selectedDelta.baseBundleId === cycle.selectedDelta.targetBundleId) {
|
|
457
|
+
fail("identity_mismatch");
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (cycle.effect !== undefined) {
|
|
461
|
+
record(cycle.effect.effectId);
|
|
462
|
+
digest(cycle.effect.intentDigest);
|
|
463
|
+
positiveBigint(cycle.effect.effectVersion);
|
|
464
|
+
if (!TERMINAL_EFFECT_STATES.has(cycle.effect.state) || !count(cycle.effect.attemptCount)
|
|
465
|
+
|| !count(cycle.effect.reconciliationCount)
|
|
466
|
+
|| !effectCountsValid(cycle.effect.state, cycle.effect.attemptCount, cycle.effect.reconciliationCount))
|
|
467
|
+
fail("invalid_event");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function compareWorkflowReplay(baseline, candidate) {
|
|
472
|
+
const sameLength = baseline.cycles.length === candidate.cycles.length;
|
|
473
|
+
const pairs = baseline.cycles.map((cycle, index) => [cycle, candidate.cycles[index]]);
|
|
474
|
+
const bundleDeltaSelection = comparisonStatus(sameLength && pairs.every(([left, right]) => left.planId === right.planId && left.bundleId === right.bundleId
|
|
475
|
+
&& left.contractDigest === right.contractDigest && equalDelta(left.selectedDelta, right.selectedDelta)));
|
|
476
|
+
const materialization = comparisonStatus(sameLength && pairs.every(([left, right]) => left.materializedBundleId === right.materializedBundleId
|
|
477
|
+
&& left.tokenizerFingerprint === right.tokenizerFingerprint
|
|
478
|
+
&& left.materializerFingerprint === right.materializerFingerprint
|
|
479
|
+
&& left.physicalInputTokens === right.physicalInputTokens));
|
|
480
|
+
const modelResultIdentity = comparisonStatus(sameLength && pairs.every(([left, right]) => left.invocationId === right.invocationId && left.requestDigest === right.requestDigest
|
|
481
|
+
&& left.idempotencyKeyDigest === right.idempotencyKeyDigest
|
|
482
|
+
&& left.modelResultDigest === right.modelResultDigest));
|
|
483
|
+
const toolEffectDecisions = comparisonStatus(sameLength && pairs.every(([left, right]) => equalEffect(left.effect, right.effect)));
|
|
484
|
+
const outcome = comparisonStatus(sameLength && pairs.every(([left, right]) => left.outcomeDigest === right.outcomeDigest && left.outcomeRevision === right.outcomeRevision));
|
|
485
|
+
return {
|
|
486
|
+
bundleDeltaSelection,
|
|
487
|
+
materialization,
|
|
488
|
+
modelResultIdentity,
|
|
489
|
+
toolEffectDecisions,
|
|
490
|
+
outcome,
|
|
491
|
+
exactMatch: [bundleDeltaSelection, materialization, modelResultIdentity, toolEffectDecisions, outcome]
|
|
492
|
+
.every((status) => status === "equal"),
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
function comparisonStatus(equal) {
|
|
496
|
+
return equal ? "equal" : "different";
|
|
497
|
+
}
|
|
498
|
+
function equalDelta(left, right) {
|
|
499
|
+
return left === undefined || right === undefined
|
|
500
|
+
? left === right
|
|
501
|
+
: left.baseBundleId === right.baseBundleId && left.targetBundleId === right.targetBundleId
|
|
502
|
+
&& left.deltaDigest === right.deltaDigest;
|
|
503
|
+
}
|
|
504
|
+
function equalEffect(left, right) {
|
|
505
|
+
return left === undefined || right === undefined
|
|
506
|
+
? left === right
|
|
507
|
+
: left.effectId === right.effectId && left.intentDigest === right.intentDigest
|
|
508
|
+
&& left.effectVersion === right.effectVersion && left.state === right.state
|
|
509
|
+
&& left.attemptCount === right.attemptCount && left.reconciliationCount === right.reconciliationCount;
|
|
510
|
+
}
|
|
511
|
+
function fail(code) { throw new WorkflowSessionError(code); }
|
|
512
|
+
function digest(value) { if (!DIGEST.test(value))
|
|
513
|
+
fail("invalid_event"); }
|
|
514
|
+
function record(value) { if (!UUID_V7.test(value))
|
|
515
|
+
fail("invalid_event"); }
|
|
516
|
+
function positiveBigint(value) {
|
|
517
|
+
if (typeof value !== "bigint" || value <= 0n || value > 0xffffffffffffffffn)
|
|
518
|
+
fail("invalid_event");
|
|
519
|
+
}
|
|
520
|
+
function positiveInteger(value, maximum) {
|
|
521
|
+
return Number.isSafeInteger(value) && value > 0 && value <= maximum;
|
|
522
|
+
}
|
|
523
|
+
function count(value) { return Number.isInteger(value) && value >= 0 && value <= 0xffff_ffff; }
|
|
524
|
+
function effectCountsValid(state, attempts, reconciliations) {
|
|
525
|
+
if (reconciliations !== 0 && attempts === 0)
|
|
526
|
+
return false;
|
|
527
|
+
if (["prepared", "authorized", "rejected"].includes(state)) {
|
|
528
|
+
return attempts === 0 && reconciliations === 0;
|
|
529
|
+
}
|
|
530
|
+
if ([
|
|
531
|
+
"dispatching", "succeeded", "failed", "unknown", "compensated", "compensation_failed",
|
|
532
|
+
].includes(state))
|
|
533
|
+
return attempts !== 0;
|
|
534
|
+
if (state === "authorized_for_retry" || state === "manual_resolution") {
|
|
535
|
+
return attempts !== 0 && reconciliations !== 0;
|
|
536
|
+
}
|
|
537
|
+
return state === "expired" || state === "cancelled";
|
|
538
|
+
}
|
|
539
|
+
function effectPhase(state) {
|
|
540
|
+
if (state === "dispatching")
|
|
541
|
+
return "effect_dispatching";
|
|
542
|
+
if (state === "unknown")
|
|
543
|
+
return "effect_ambiguous";
|
|
544
|
+
if (state === "authorized_for_retry")
|
|
545
|
+
return "effect_authorized";
|
|
546
|
+
if (TERMINAL_EFFECT_STATES.has(state))
|
|
547
|
+
return "effect_settled";
|
|
548
|
+
return fail("invalid_event");
|
|
549
|
+
}
|
|
550
|
+
//# sourceMappingURL=workflow-session.js.map
|