@kontourai/flow-agents 3.11.0 → 3.12.1
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/CHANGELOG.md +14 -0
- package/build/src/builder-flow-run-adapter.d.ts +1 -0
- package/build/src/builder-flow-run-adapter.js +2 -0
- package/build/src/builder-flow-runtime.d.ts +5 -0
- package/build/src/builder-flow-runtime.js +90 -68
- package/build/src/builder-gate-action-envelope.d.ts +152 -0
- package/build/src/builder-gate-action-envelope.js +368 -0
- package/build/src/cli/public-contracts.d.ts +139 -0
- package/build/src/cli/public-contracts.js +37 -0
- package/build/src/cli/workflow-sidecar.js +9 -13
- package/build/src/cli/workflow.d.ts +1 -0
- package/build/src/cli/workflow.js +49 -12
- package/build/src/continuation-driver.d.ts +40 -3
- package/build/src/continuation-driver.js +361 -412
- package/build/src/continuation-persistence.d.ts +4 -0
- package/build/src/continuation-persistence.js +296 -0
- package/build/src/continuation-validation.d.ts +9 -0
- package/build/src/continuation-validation.js +169 -0
- package/build/src/flow-kit/action-metadata.d.ts +28 -0
- package/build/src/flow-kit/action-metadata.js +162 -0
- package/build/src/flow-kit/action-repository-validation.d.ts +16 -0
- package/build/src/flow-kit/action-repository-validation.js +237 -0
- package/build/src/flow-kit/validate.d.ts +2 -14
- package/build/src/flow-kit/validate.js +18 -345
- package/build/src/index.d.ts +4 -2
- package/build/src/index.js +2 -1
- package/dist/base/build/package.json +1 -1
- package/dist/base/build/src/builder-flow-run-adapter.d.ts +1 -0
- package/dist/base/build/src/builder-flow-run-adapter.js +2 -0
- package/dist/base/build/src/builder-flow-runtime.d.ts +5 -0
- package/dist/base/build/src/builder-flow-runtime.js +90 -68
- package/dist/base/build/src/builder-gate-action-envelope.d.ts +152 -0
- package/dist/base/build/src/builder-gate-action-envelope.js +368 -0
- package/dist/base/build/src/cli/public-contracts.d.ts +139 -0
- package/dist/base/build/src/cli/public-contracts.js +37 -0
- package/dist/base/build/src/cli/workflow-sidecar.js +9 -13
- package/dist/base/build/src/cli/workflow.d.ts +1 -0
- package/dist/base/build/src/cli/workflow.js +49 -12
- package/dist/base/build/src/continuation-driver.d.ts +40 -3
- package/dist/base/build/src/continuation-driver.js +361 -412
- package/dist/base/build/src/continuation-persistence.d.ts +4 -0
- package/dist/base/build/src/continuation-persistence.js +296 -0
- package/dist/base/build/src/continuation-validation.d.ts +9 -0
- package/dist/base/build/src/continuation-validation.js +169 -0
- package/dist/base/build/src/flow-kit/action-metadata.d.ts +28 -0
- package/dist/base/build/src/flow-kit/action-metadata.js +162 -0
- package/dist/base/build/src/flow-kit/action-repository-validation.d.ts +16 -0
- package/dist/base/build/src/flow-kit/action-repository-validation.js +237 -0
- package/dist/base/build/src/flow-kit/validate.d.ts +2 -14
- package/dist/base/build/src/flow-kit/validate.js +18 -345
- package/dist/base/build/src/index.d.ts +4 -2
- package/dist/base/build/src/index.js +2 -1
- package/dist/base/docs/migrations.md +5 -0
- package/dist/base/docs/public-workflow-cli.md +41 -5
- package/dist/base/docs/spec/builder-flow-runtime.md +117 -0
- package/dist/base/evals/integration/test_builder_step_producers.sh +60 -21
- package/dist/base/evals/integration/test_bundle_install.sh +15 -15
- package/dist/base/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
- package/dist/base/install.sh +1 -1
- package/dist/base/kits/builder/kit.json +88 -14
- package/dist/claude-code/build/package.json +1 -1
- package/dist/claude-code/build/src/builder-flow-run-adapter.d.ts +1 -0
- package/dist/claude-code/build/src/builder-flow-run-adapter.js +2 -0
- package/dist/claude-code/build/src/builder-flow-runtime.d.ts +5 -0
- package/dist/claude-code/build/src/builder-flow-runtime.js +90 -68
- package/dist/claude-code/build/src/builder-gate-action-envelope.d.ts +152 -0
- package/dist/claude-code/build/src/builder-gate-action-envelope.js +368 -0
- package/dist/claude-code/build/src/cli/public-contracts.d.ts +139 -0
- package/dist/claude-code/build/src/cli/public-contracts.js +37 -0
- package/dist/claude-code/build/src/cli/workflow-sidecar.js +9 -13
- package/dist/claude-code/build/src/cli/workflow.d.ts +1 -0
- package/dist/claude-code/build/src/cli/workflow.js +49 -12
- package/dist/claude-code/build/src/continuation-driver.d.ts +40 -3
- package/dist/claude-code/build/src/continuation-driver.js +361 -412
- package/dist/claude-code/build/src/continuation-persistence.d.ts +4 -0
- package/dist/claude-code/build/src/continuation-persistence.js +296 -0
- package/dist/claude-code/build/src/continuation-validation.d.ts +9 -0
- package/dist/claude-code/build/src/continuation-validation.js +169 -0
- package/dist/claude-code/build/src/flow-kit/action-metadata.d.ts +28 -0
- package/dist/claude-code/build/src/flow-kit/action-metadata.js +162 -0
- package/dist/claude-code/build/src/flow-kit/action-repository-validation.d.ts +16 -0
- package/dist/claude-code/build/src/flow-kit/action-repository-validation.js +237 -0
- package/dist/claude-code/build/src/flow-kit/validate.d.ts +2 -14
- package/dist/claude-code/build/src/flow-kit/validate.js +18 -345
- package/dist/claude-code/build/src/index.d.ts +4 -2
- package/dist/claude-code/build/src/index.js +2 -1
- package/dist/claude-code/docs/migrations.md +5 -0
- package/dist/claude-code/docs/public-workflow-cli.md +41 -5
- package/dist/claude-code/docs/spec/builder-flow-runtime.md +117 -0
- package/dist/claude-code/evals/integration/test_builder_step_producers.sh +60 -21
- package/dist/claude-code/evals/integration/test_bundle_install.sh +15 -15
- package/dist/claude-code/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
- package/dist/claude-code/install.sh +1 -1
- package/dist/claude-code/kits/builder/kit.json +88 -14
- package/dist/codex/build/package.json +1 -1
- package/dist/codex/build/src/builder-flow-run-adapter.d.ts +1 -0
- package/dist/codex/build/src/builder-flow-run-adapter.js +2 -0
- package/dist/codex/build/src/builder-flow-runtime.d.ts +5 -0
- package/dist/codex/build/src/builder-flow-runtime.js +90 -68
- package/dist/codex/build/src/builder-gate-action-envelope.d.ts +152 -0
- package/dist/codex/build/src/builder-gate-action-envelope.js +368 -0
- package/dist/codex/build/src/cli/public-contracts.d.ts +139 -0
- package/dist/codex/build/src/cli/public-contracts.js +37 -0
- package/dist/codex/build/src/cli/workflow-sidecar.js +9 -13
- package/dist/codex/build/src/cli/workflow.d.ts +1 -0
- package/dist/codex/build/src/cli/workflow.js +49 -12
- package/dist/codex/build/src/continuation-driver.d.ts +40 -3
- package/dist/codex/build/src/continuation-driver.js +361 -412
- package/dist/codex/build/src/continuation-persistence.d.ts +4 -0
- package/dist/codex/build/src/continuation-persistence.js +296 -0
- package/dist/codex/build/src/continuation-validation.d.ts +9 -0
- package/dist/codex/build/src/continuation-validation.js +169 -0
- package/dist/codex/build/src/flow-kit/action-metadata.d.ts +28 -0
- package/dist/codex/build/src/flow-kit/action-metadata.js +162 -0
- package/dist/codex/build/src/flow-kit/action-repository-validation.d.ts +16 -0
- package/dist/codex/build/src/flow-kit/action-repository-validation.js +237 -0
- package/dist/codex/build/src/flow-kit/validate.d.ts +2 -14
- package/dist/codex/build/src/flow-kit/validate.js +18 -345
- package/dist/codex/build/src/index.d.ts +4 -2
- package/dist/codex/build/src/index.js +2 -1
- package/dist/codex/docs/migrations.md +5 -0
- package/dist/codex/docs/public-workflow-cli.md +41 -5
- package/dist/codex/docs/spec/builder-flow-runtime.md +117 -0
- package/dist/codex/evals/integration/test_builder_step_producers.sh +60 -21
- package/dist/codex/evals/integration/test_bundle_install.sh +15 -15
- package/dist/codex/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
- package/dist/codex/install.sh +1 -1
- package/dist/codex/kits/builder/kit.json +88 -14
- package/dist/kiro/build/package.json +1 -1
- package/dist/kiro/build/src/builder-flow-run-adapter.d.ts +1 -0
- package/dist/kiro/build/src/builder-flow-run-adapter.js +2 -0
- package/dist/kiro/build/src/builder-flow-runtime.d.ts +5 -0
- package/dist/kiro/build/src/builder-flow-runtime.js +90 -68
- package/dist/kiro/build/src/builder-gate-action-envelope.d.ts +152 -0
- package/dist/kiro/build/src/builder-gate-action-envelope.js +368 -0
- package/dist/kiro/build/src/cli/public-contracts.d.ts +139 -0
- package/dist/kiro/build/src/cli/public-contracts.js +37 -0
- package/dist/kiro/build/src/cli/workflow-sidecar.js +9 -13
- package/dist/kiro/build/src/cli/workflow.d.ts +1 -0
- package/dist/kiro/build/src/cli/workflow.js +49 -12
- package/dist/kiro/build/src/continuation-driver.d.ts +40 -3
- package/dist/kiro/build/src/continuation-driver.js +361 -412
- package/dist/kiro/build/src/continuation-persistence.d.ts +4 -0
- package/dist/kiro/build/src/continuation-persistence.js +296 -0
- package/dist/kiro/build/src/continuation-validation.d.ts +9 -0
- package/dist/kiro/build/src/continuation-validation.js +169 -0
- package/dist/kiro/build/src/flow-kit/action-metadata.d.ts +28 -0
- package/dist/kiro/build/src/flow-kit/action-metadata.js +162 -0
- package/dist/kiro/build/src/flow-kit/action-repository-validation.d.ts +16 -0
- package/dist/kiro/build/src/flow-kit/action-repository-validation.js +237 -0
- package/dist/kiro/build/src/flow-kit/validate.d.ts +2 -14
- package/dist/kiro/build/src/flow-kit/validate.js +18 -345
- package/dist/kiro/build/src/index.d.ts +4 -2
- package/dist/kiro/build/src/index.js +2 -1
- package/dist/kiro/docs/migrations.md +5 -0
- package/dist/kiro/docs/public-workflow-cli.md +41 -5
- package/dist/kiro/docs/spec/builder-flow-runtime.md +117 -0
- package/dist/kiro/evals/integration/test_builder_step_producers.sh +60 -21
- package/dist/kiro/evals/integration/test_bundle_install.sh +15 -15
- package/dist/kiro/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
- package/dist/kiro/install.sh +1 -1
- package/dist/kiro/kits/builder/kit.json +88 -14
- package/dist/opencode/build/package.json +1 -1
- package/dist/opencode/build/src/builder-flow-run-adapter.d.ts +1 -0
- package/dist/opencode/build/src/builder-flow-run-adapter.js +2 -0
- package/dist/opencode/build/src/builder-flow-runtime.d.ts +5 -0
- package/dist/opencode/build/src/builder-flow-runtime.js +90 -68
- package/dist/opencode/build/src/builder-gate-action-envelope.d.ts +152 -0
- package/dist/opencode/build/src/builder-gate-action-envelope.js +368 -0
- package/dist/opencode/build/src/cli/public-contracts.d.ts +139 -0
- package/dist/opencode/build/src/cli/public-contracts.js +37 -0
- package/dist/opencode/build/src/cli/workflow-sidecar.js +9 -13
- package/dist/opencode/build/src/cli/workflow.d.ts +1 -0
- package/dist/opencode/build/src/cli/workflow.js +49 -12
- package/dist/opencode/build/src/continuation-driver.d.ts +40 -3
- package/dist/opencode/build/src/continuation-driver.js +361 -412
- package/dist/opencode/build/src/continuation-persistence.d.ts +4 -0
- package/dist/opencode/build/src/continuation-persistence.js +296 -0
- package/dist/opencode/build/src/continuation-validation.d.ts +9 -0
- package/dist/opencode/build/src/continuation-validation.js +169 -0
- package/dist/opencode/build/src/flow-kit/action-metadata.d.ts +28 -0
- package/dist/opencode/build/src/flow-kit/action-metadata.js +162 -0
- package/dist/opencode/build/src/flow-kit/action-repository-validation.d.ts +16 -0
- package/dist/opencode/build/src/flow-kit/action-repository-validation.js +237 -0
- package/dist/opencode/build/src/flow-kit/validate.d.ts +2 -14
- package/dist/opencode/build/src/flow-kit/validate.js +18 -345
- package/dist/opencode/build/src/index.d.ts +4 -2
- package/dist/opencode/build/src/index.js +2 -1
- package/dist/opencode/docs/migrations.md +5 -0
- package/dist/opencode/docs/public-workflow-cli.md +41 -5
- package/dist/opencode/docs/spec/builder-flow-runtime.md +117 -0
- package/dist/opencode/evals/integration/test_builder_step_producers.sh +60 -21
- package/dist/opencode/evals/integration/test_bundle_install.sh +15 -15
- package/dist/opencode/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
- package/dist/opencode/install.sh +1 -1
- package/dist/opencode/kits/builder/kit.json +88 -14
- package/dist/pi/build/package.json +1 -1
- package/dist/pi/build/src/builder-flow-run-adapter.d.ts +1 -0
- package/dist/pi/build/src/builder-flow-run-adapter.js +2 -0
- package/dist/pi/build/src/builder-flow-runtime.d.ts +5 -0
- package/dist/pi/build/src/builder-flow-runtime.js +90 -68
- package/dist/pi/build/src/builder-gate-action-envelope.d.ts +152 -0
- package/dist/pi/build/src/builder-gate-action-envelope.js +368 -0
- package/dist/pi/build/src/cli/public-contracts.d.ts +139 -0
- package/dist/pi/build/src/cli/public-contracts.js +37 -0
- package/dist/pi/build/src/cli/workflow-sidecar.js +9 -13
- package/dist/pi/build/src/cli/workflow.d.ts +1 -0
- package/dist/pi/build/src/cli/workflow.js +49 -12
- package/dist/pi/build/src/continuation-driver.d.ts +40 -3
- package/dist/pi/build/src/continuation-driver.js +361 -412
- package/dist/pi/build/src/continuation-persistence.d.ts +4 -0
- package/dist/pi/build/src/continuation-persistence.js +296 -0
- package/dist/pi/build/src/continuation-validation.d.ts +9 -0
- package/dist/pi/build/src/continuation-validation.js +169 -0
- package/dist/pi/build/src/flow-kit/action-metadata.d.ts +28 -0
- package/dist/pi/build/src/flow-kit/action-metadata.js +162 -0
- package/dist/pi/build/src/flow-kit/action-repository-validation.d.ts +16 -0
- package/dist/pi/build/src/flow-kit/action-repository-validation.js +237 -0
- package/dist/pi/build/src/flow-kit/validate.d.ts +2 -14
- package/dist/pi/build/src/flow-kit/validate.js +18 -345
- package/dist/pi/build/src/index.d.ts +4 -2
- package/dist/pi/build/src/index.js +2 -1
- package/dist/pi/docs/migrations.md +5 -0
- package/dist/pi/docs/public-workflow-cli.md +41 -5
- package/dist/pi/docs/spec/builder-flow-runtime.md +117 -0
- package/dist/pi/evals/integration/test_builder_step_producers.sh +60 -21
- package/dist/pi/evals/integration/test_bundle_install.sh +15 -15
- package/dist/pi/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
- package/dist/pi/install.sh +1 -1
- package/dist/pi/kits/builder/kit.json +88 -14
- package/docs/migrations.md +5 -0
- package/docs/public-workflow-cli.md +41 -5
- package/docs/spec/builder-flow-runtime.md +117 -0
- package/evals/integration/test_builder_step_producers.sh +60 -21
- package/evals/integration/test_bundle_install.sh +15 -15
- package/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
- package/kits/builder/kit.json +88 -14
- package/package.json +1 -1
- package/src/builder-flow-run-adapter.ts +3 -0
- package/src/builder-flow-runtime.ts +73 -46
- package/src/builder-gate-action-envelope.ts +531 -0
- package/src/cli/builder-flow-runtime.test.mjs +289 -58
- package/src/cli/continuation-driver.test.mjs +542 -24
- package/src/cli/kit-metadata-security.test.mjs +161 -8
- package/src/cli/public-contracts.ts +41 -0
- package/src/cli/workflow-sidecar.ts +9 -13
- package/src/cli/workflow.ts +48 -12
- package/src/continuation-driver.ts +466 -378
- package/src/continuation-persistence.ts +228 -0
- package/src/continuation-validation.ts +137 -0
- package/src/flow-kit/action-metadata.ts +155 -0
- package/src/flow-kit/action-repository-validation.ts +192 -0
- package/src/flow-kit/validate.ts +19 -326
- package/src/index.ts +17 -0
|
@@ -3,12 +3,13 @@ import fs from "node:fs";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
|
-
import { createRequire } from "node:module";
|
|
6
|
+
import { createRequire, syncBuiltinESMExports } from "node:module";
|
|
7
7
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
8
8
|
import { createHash, generateKeyPairSync, randomBytes, sign } from "node:crypto";
|
|
9
9
|
|
|
10
|
-
import { ContinuationAdapterTimeoutError, createFileContinuationStore, runContinuationDriver, withContinuationDriverLock } from "../../build/src/continuation-driver.js";
|
|
10
|
+
import { MAX_CONTINUATION_ADAPTER_EVIDENCE_BYTES, MAX_CONTINUATION_TURN_RESULT_BYTES, ContinuationAdapterTimeoutError, createFileContinuationStore, runContinuationDriver, withContinuationDriverLock } from "../../build/src/continuation-driver.js";
|
|
11
11
|
import { executeContinuationAdapter, executeLoadedContinuationAdapter, loadContinuationAdapterCommand, waitForContinuationBarrier } from "../../build/src/cli/continuation-adapter.js";
|
|
12
|
+
import { validateSnapshot } from "../../build/src/continuation-validation.js";
|
|
12
13
|
|
|
13
14
|
const require = createRequire(import.meta.url);
|
|
14
15
|
const activeTurnAuthority = require("../../scripts/hooks/lib/continuation-turn-authority.js");
|
|
@@ -35,15 +36,63 @@ function snapshot(step, status = "active") {
|
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
function envelopeSnapshot(step, { evidence = [], artifacts = [], implementationAllowed = false, status = "active" } = {}) {
|
|
40
|
+
const value = snapshot(step, status);
|
|
41
|
+
value.gate_action_envelope = {
|
|
42
|
+
schema_version: "2.0",
|
|
43
|
+
flow: { current_step: step, status },
|
|
44
|
+
action: { implementation_allowed: implementationAllowed },
|
|
45
|
+
stop_condition: { kind: "one_turn", adapter_evidence_is_gate_evidence: false },
|
|
46
|
+
progress: { canonical_evidence: evidence, observed_artifacts: artifacts },
|
|
47
|
+
};
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test("gate-action envelope rejects partial accepted-exception bindings", () => {
|
|
52
|
+
const partial = envelopeSnapshot("execute");
|
|
53
|
+
partial.gate_action_envelope.flow.gate_ids = ["execute-gate"];
|
|
54
|
+
partial.gate_action_envelope.gate = {
|
|
55
|
+
requirements: [
|
|
56
|
+
{ id: "implemented", gate_id: "execute-gate", required: true, status: "accepted_exception" },
|
|
57
|
+
{ id: "verified", gate_id: "execute-gate", required: true, status: "unresolved" },
|
|
58
|
+
],
|
|
59
|
+
unresolved_requirement_ids: ["verified"],
|
|
60
|
+
accepted_exceptions: [{ gate_id: "execute-gate", exception_id: "exception-1" }],
|
|
61
|
+
};
|
|
62
|
+
assert.throws(() => validateSnapshot(partial), /inconsistent exception bindings/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
function terminalProgressSnapshot(status, { step = "learn", evidence = [], artifacts = [] } = {}) {
|
|
66
|
+
const value = snapshot(step, status);
|
|
67
|
+
value.progress_snapshot = {
|
|
68
|
+
current_step: step,
|
|
69
|
+
canonical_status: status,
|
|
70
|
+
canonical_evidence: evidence,
|
|
71
|
+
observed_artifacts: artifacts,
|
|
72
|
+
};
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
|
|
38
76
|
function memoryStore(initial = null) {
|
|
39
77
|
let value = initial;
|
|
40
78
|
const events = [];
|
|
79
|
+
const accepted = [];
|
|
41
80
|
return {
|
|
42
81
|
load: () => value,
|
|
43
82
|
save: (next) => { value = structuredClone(next); },
|
|
44
|
-
append: (event) => {
|
|
83
|
+
append: (event) => {
|
|
84
|
+
if (event.turn_id && events.some((entry) => entry.type === event.type && entry.turn_id === event.turn_id)) return;
|
|
85
|
+
events.push(structuredClone(event));
|
|
86
|
+
},
|
|
87
|
+
captureAcceptedTurn: (turn) => {
|
|
88
|
+
const prior = accepted.find((entry) => entry.turn_id === turn.turn_id);
|
|
89
|
+
if (prior) assert.deepEqual(prior, turn);
|
|
90
|
+
else accepted.push(structuredClone(turn));
|
|
91
|
+
},
|
|
92
|
+
acceptedTurns: () => structuredClone(accepted),
|
|
45
93
|
value: () => value,
|
|
46
94
|
events,
|
|
95
|
+
accepted,
|
|
47
96
|
};
|
|
48
97
|
}
|
|
49
98
|
|
|
@@ -106,6 +155,327 @@ test("driver advances multiple canonical Flow steps without a human continuation
|
|
|
106
155
|
assert.equal(store.value().status, "done");
|
|
107
156
|
});
|
|
108
157
|
|
|
158
|
+
test("weak structured consumers cannot turn adapter prose or adapter evidence into gate evidence", async () => {
|
|
159
|
+
const store = memoryStore();
|
|
160
|
+
const requests = [];
|
|
161
|
+
const result = await runContinuationDriver({
|
|
162
|
+
maxTurns: 1,
|
|
163
|
+
store,
|
|
164
|
+
runtime: {
|
|
165
|
+
inspect: async () => envelopeSnapshot("design-probe"),
|
|
166
|
+
synchronize: async () => envelopeSnapshot("design-probe"),
|
|
167
|
+
execute: async (request) => {
|
|
168
|
+
requests.push(request);
|
|
169
|
+
return { status: "completed", summary: "implemented directly", evidence: { forged: "gate evidence" } };
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
assert.equal(result.outcome, "budget_exhausted");
|
|
175
|
+
assert.equal(requests[0].gate_action_envelope.action.implementation_allowed, false);
|
|
176
|
+
assert.equal(requests[0].gate_action_envelope.stop_condition.adapter_evidence_is_gate_evidence, false);
|
|
177
|
+
assert.equal(store.events.some((event) => event.type === "gate_not_advanced"), true);
|
|
178
|
+
const completed = store.events.find((event) => event.type === "turn_completed");
|
|
179
|
+
assert.equal(completed.progress.no_progress, true);
|
|
180
|
+
assert.equal(completed.progress.evidence_added.length, 0);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("conforming canonical advancement and same-step evidence/artifact progress are distinct", async () => {
|
|
184
|
+
const advancingStore = memoryStore();
|
|
185
|
+
let advanced = false;
|
|
186
|
+
await runContinuationDriver({
|
|
187
|
+
maxTurns: 1,
|
|
188
|
+
store: advancingStore,
|
|
189
|
+
runtime: {
|
|
190
|
+
inspect: async () => envelopeSnapshot("plan"),
|
|
191
|
+
synchronize: async () => advanced
|
|
192
|
+
? envelopeSnapshot("execute", { evidence: ["plan-gate:advancing-evidence"], artifacts: ["plan.md:new-hash"], implementationAllowed: true })
|
|
193
|
+
: envelopeSnapshot("plan", { artifacts: ["plan.md:absent"] }),
|
|
194
|
+
execute: async () => { advanced = true; return { status: "completed" }; },
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
const advancedProgress = advancingStore.events.find((event) => event.type === "turn_completed").progress;
|
|
198
|
+
assert.equal(advancedProgress.step_advanced, true);
|
|
199
|
+
assert.deepEqual(advancedProgress.evidence_added, ["plan-gate:advancing-evidence"]);
|
|
200
|
+
assert.deepEqual(advancedProgress.artifact_changes, ["plan.md:new-hash"]);
|
|
201
|
+
assert.equal(advancedProgress.no_progress, false);
|
|
202
|
+
|
|
203
|
+
const sameStepStore = memoryStore();
|
|
204
|
+
let evidenceAdded = false;
|
|
205
|
+
await runContinuationDriver({
|
|
206
|
+
maxTurns: 1,
|
|
207
|
+
store: sameStepStore,
|
|
208
|
+
runtime: {
|
|
209
|
+
inspect: async () => envelopeSnapshot("verify"),
|
|
210
|
+
synchronize: async () => evidenceAdded
|
|
211
|
+
? envelopeSnapshot("verify", { evidence: ["verify-gate:canonical-attachment"], artifacts: ["report.md:abc"] })
|
|
212
|
+
: envelopeSnapshot("verify"),
|
|
213
|
+
execute: async () => { evidenceAdded = true; return { status: "completed" }; },
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
const sameStepProgress = sameStepStore.events.find((event) => event.type === "turn_completed").progress;
|
|
217
|
+
assert.equal(sameStepProgress.step_advanced, false);
|
|
218
|
+
assert.deepEqual(sameStepProgress.evidence_added, ["verify-gate:canonical-attachment"]);
|
|
219
|
+
assert.deepEqual(sameStepProgress.artifact_changes, ["report.md:abc"]);
|
|
220
|
+
assert.equal(sameStepProgress.no_progress, false);
|
|
221
|
+
assert.equal(sameStepStore.events.some((event) => event.type === "gate_not_advanced"), true, "legacy same-step event remains compatible");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("accepted turns retain final progress when canonical Flow becomes done or failed without a terminal action envelope", async (t) => {
|
|
225
|
+
for (const [status, outcome] of [["completed", "done"], ["failed", "failed"]]) await t.test(outcome, async () => {
|
|
226
|
+
const store = memoryStore();
|
|
227
|
+
let terminal = false;
|
|
228
|
+
const result = await runContinuationDriver({
|
|
229
|
+
maxTurns: 1,
|
|
230
|
+
store,
|
|
231
|
+
runtime: {
|
|
232
|
+
inspect: async () => envelopeSnapshot("learn", { evidence: ["learn:before"], artifacts: ["learning.json:before"] }),
|
|
233
|
+
synchronize: async () => terminal
|
|
234
|
+
? terminalProgressSnapshot(status, { evidence: ["learn:before"], artifacts: ["learning.json:before"] })
|
|
235
|
+
: envelopeSnapshot("learn", { evidence: ["learn:before"], artifacts: ["learning.json:before"] }),
|
|
236
|
+
execute: async () => { terminal = true; return { status: "completed" }; },
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
assert.equal(result.outcome, outcome);
|
|
240
|
+
assert.equal(Object.hasOwn(result.snapshot, "gate_action_envelope"), false);
|
|
241
|
+
assert.equal(result.snapshot.current_step, "learn");
|
|
242
|
+
assert.equal(result.snapshot.progress_snapshot.current_step, "learn");
|
|
243
|
+
assert.equal(result.snapshot.progress_snapshot.canonical_status, status);
|
|
244
|
+
const completed = store.events.find((event) => event.type === "turn_completed");
|
|
245
|
+
assert.equal(completed.progress.step_advanced, true);
|
|
246
|
+
assert.deepEqual(completed.progress.evidence_added, []);
|
|
247
|
+
assert.deepEqual(completed.progress.artifact_changes, []);
|
|
248
|
+
assert.equal(completed.progress.no_progress, false);
|
|
249
|
+
assert.equal(store.value().prior_progress.stagnation, "none");
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("progress snapshot canonical status is additive and validated", async () => {
|
|
254
|
+
const legacy = snapshot("plan");
|
|
255
|
+
legacy.progress_snapshot = { current_step: "plan", canonical_evidence: [], observed_artifacts: [] };
|
|
256
|
+
const legacyResult = await runContinuationDriver({
|
|
257
|
+
maxTurns: 1,
|
|
258
|
+
store: memoryStore(),
|
|
259
|
+
runtime: {
|
|
260
|
+
inspect: async () => legacy,
|
|
261
|
+
synchronize: async () => legacy,
|
|
262
|
+
execute: async () => ({ status: "completed" }),
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
assert.equal(legacyResult.outcome, "budget_exhausted");
|
|
266
|
+
|
|
267
|
+
const malformed = structuredClone(legacy);
|
|
268
|
+
malformed.progress_snapshot.canonical_status = 42;
|
|
269
|
+
await assert.rejects(
|
|
270
|
+
runContinuationDriver({
|
|
271
|
+
maxTurns: 1,
|
|
272
|
+
store: memoryStore(),
|
|
273
|
+
runtime: {
|
|
274
|
+
inspect: async () => malformed,
|
|
275
|
+
synchronize: async () => malformed,
|
|
276
|
+
execute: async () => ({ status: "completed" }),
|
|
277
|
+
},
|
|
278
|
+
}),
|
|
279
|
+
/progress snapshot is malformed/,
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("failed adapter turns record canonical artifact progress and repeated no-progress becomes stagnant", async () => {
|
|
284
|
+
const failedStore = memoryStore();
|
|
285
|
+
let artifactAdded = false;
|
|
286
|
+
await runContinuationDriver({
|
|
287
|
+
maxTurns: 1,
|
|
288
|
+
store: failedStore,
|
|
289
|
+
runtime: {
|
|
290
|
+
inspect: async () => envelopeSnapshot("plan"),
|
|
291
|
+
synchronize: async () => artifactAdded ? envelopeSnapshot("plan", { artifacts: ["plan.md:canonical"] }) : envelopeSnapshot("plan"),
|
|
292
|
+
execute: async () => { artifactAdded = true; throw new Error("adapter failed after writing artifact"); },
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
const failedProgress = failedStore.events.find((event) => event.type === "turn_failed").progress;
|
|
296
|
+
assert.deepEqual(failedProgress.artifact_changes, ["plan.md:canonical"]);
|
|
297
|
+
assert.equal(failedProgress.no_progress, false);
|
|
298
|
+
|
|
299
|
+
const stagnantStore = memoryStore();
|
|
300
|
+
const requests = [];
|
|
301
|
+
await runContinuationDriver({
|
|
302
|
+
maxTurns: 2,
|
|
303
|
+
store: stagnantStore,
|
|
304
|
+
runtime: {
|
|
305
|
+
inspect: async () => envelopeSnapshot("design-probe"),
|
|
306
|
+
synchronize: async () => envelopeSnapshot("design-probe"),
|
|
307
|
+
execute: async (request) => { requests.push(request); return { status: "completed" }; },
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
assert.equal(requests[1].gate_action_envelope.progress.prior_turn.stagnation, "possible");
|
|
311
|
+
const turns = stagnantStore.events.filter((event) => event.type === "turn_completed");
|
|
312
|
+
assert.equal(turns[1].progress.stagnation, "stagnant");
|
|
313
|
+
assert.equal(turns[1].progress.consecutive_no_progress, 2);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("reinvoked missions compare synchronized progress with the durable baseline", async () => {
|
|
317
|
+
const barrier = { kind: "deadline", at: "2026-07-13T12:00:00.000Z" };
|
|
318
|
+
const store = memoryStore();
|
|
319
|
+
let canonicalEvidence = [];
|
|
320
|
+
|
|
321
|
+
const first = await runContinuationDriver({
|
|
322
|
+
maxTurns: 2,
|
|
323
|
+
store,
|
|
324
|
+
waitForBarrier: async () => "pending",
|
|
325
|
+
runtime: {
|
|
326
|
+
inspect: async () => envelopeSnapshot("verify", { evidence: canonicalEvidence }),
|
|
327
|
+
synchronize: async () => envelopeSnapshot("verify", { evidence: canonicalEvidence }),
|
|
328
|
+
execute: async () => ({ status: "wait", barrier }),
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
assert.equal(first.outcome, "waiting");
|
|
332
|
+
|
|
333
|
+
canonicalEvidence = ["verify-gate:interrupted-turn-evidence"];
|
|
334
|
+
let resumedRequest;
|
|
335
|
+
await runContinuationDriver({
|
|
336
|
+
maxTurns: 2,
|
|
337
|
+
store,
|
|
338
|
+
waitForBarrier: async () => "ready",
|
|
339
|
+
runtime: {
|
|
340
|
+
inspect: async () => envelopeSnapshot("verify", { evidence: canonicalEvidence }),
|
|
341
|
+
synchronize: async () => envelopeSnapshot("verify", { evidence: canonicalEvidence }),
|
|
342
|
+
execute: async (request) => {
|
|
343
|
+
resumedRequest = request;
|
|
344
|
+
return { status: "completed" };
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
assert.deepEqual(resumedRequest.gate_action_envelope.progress.prior_turn.evidence_added, canonicalEvidence);
|
|
350
|
+
assert.equal(resumedRequest.gate_action_envelope.progress.prior_turn.no_progress, false);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("reinvocation counts one interrupted unchanged turn as no progress exactly once", async () => {
|
|
354
|
+
const baseline = { current_step: "execute", canonical_evidence: [], observed_artifacts: [] };
|
|
355
|
+
const store = memoryStore({
|
|
356
|
+
schema_version: "1.0", run_id: "run-251", definition_id: "builder.build", max_turns: 2,
|
|
357
|
+
adapter_command_identity: null, status: "active", turns_started: 1, active_turn_step: "execute",
|
|
358
|
+
active_turn_public_key_digest: null, active_turn_phase: "started", active_turn_progress: baseline,
|
|
359
|
+
last_progress: baseline,
|
|
360
|
+
prior_progress: { step_advanced: false, evidence_added: [], artifact_changes: [], no_progress: true, consecutive_no_progress: 1, stagnation: "possible" },
|
|
361
|
+
pending_barrier: null, updated_at: "2026-07-13T12:00:00.000Z",
|
|
362
|
+
});
|
|
363
|
+
const barrier = { kind: "deadline", at: "2026-07-14T12:00:00.000Z" };
|
|
364
|
+
let request;
|
|
365
|
+
const runtime = {
|
|
366
|
+
inspect: async () => envelopeSnapshot("execute"),
|
|
367
|
+
synchronize: async () => envelopeSnapshot("execute"),
|
|
368
|
+
execute: async (value) => { request = value; return { status: "wait", barrier }; },
|
|
369
|
+
};
|
|
370
|
+
await runContinuationDriver({ maxTurns: 2, store, runtime, waitForBarrier: async () => "pending" });
|
|
371
|
+
assert.equal(request.gate_action_envelope.progress.prior_turn.stagnation, "stagnant");
|
|
372
|
+
assert.equal(request.gate_action_envelope.progress.prior_turn.consecutive_no_progress, 2);
|
|
373
|
+
assert.equal(store.events.filter((event) => event.type === "turn_recovered").length, 1);
|
|
374
|
+
await runContinuationDriver({ maxTurns: 2, store, runtime, waitForBarrier: async () => "pending" });
|
|
375
|
+
assert.equal(store.value().prior_progress.consecutive_no_progress, 3, "the accepted wait turn is also measured once");
|
|
376
|
+
assert.equal(store.events.filter((event) => event.type === "turn_recovered").length, 1);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("interrupted turns are reconciled before waiting and terminal disposition branches", async (t) => {
|
|
380
|
+
for (const [name, canonical, expectedOutcome] of [
|
|
381
|
+
["waiting", envelopeSnapshot("verify", { status: "paused" }), "waiting"],
|
|
382
|
+
["terminal", terminalProgressSnapshot("completed", { step: "verify" }), "done"],
|
|
383
|
+
]) await t.test(name, async () => {
|
|
384
|
+
const baseline = { current_step: "verify", canonical_status: "active", canonical_evidence: [], observed_artifacts: [] };
|
|
385
|
+
const store = memoryStore({
|
|
386
|
+
schema_version: "1.0", run_id: "run-251", definition_id: "builder.build", max_turns: 2,
|
|
387
|
+
adapter_command_identity: null, status: "active", turns_started: 1, active_turn_step: "verify",
|
|
388
|
+
active_turn_public_key_digest: null, active_turn_phase: "started", active_turn_progress: baseline,
|
|
389
|
+
last_progress: baseline, prior_progress: null, pending_barrier: null, updated_at: "2026-07-13T12:00:00.000Z",
|
|
390
|
+
});
|
|
391
|
+
const result = await runContinuationDriver({
|
|
392
|
+
maxTurns: 2, store,
|
|
393
|
+
runtime: { inspect: async () => canonical, synchronize: async () => canonical, execute: async () => assert.fail("must not execute") },
|
|
394
|
+
});
|
|
395
|
+
assert.equal(result.outcome, expectedOutcome);
|
|
396
|
+
const recovered = store.events.find((event) => event.type === "turn_recovered");
|
|
397
|
+
assert.ok(recovered);
|
|
398
|
+
assert.equal(recovered.progress.no_progress, name === "waiting");
|
|
399
|
+
assert.equal(recovered.progress.step_advanced, name === "terminal");
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("interruption recovery retains final terminal progress snapshots", async (t) => {
|
|
404
|
+
for (const [status, outcome] of [["completed", "done"], ["failed", "failed"]]) await t.test(outcome, async () => {
|
|
405
|
+
const baseline = { current_step: "learn", canonical_status: "active", canonical_evidence: ["learn:before"], observed_artifacts: ["learning.json:before"] };
|
|
406
|
+
const store = memoryStore({
|
|
407
|
+
schema_version: "1.0", run_id: "run-251", definition_id: "builder.build", max_turns: 2,
|
|
408
|
+
adapter_command_identity: null, status: "active", turns_started: 1, active_turn_step: "learn",
|
|
409
|
+
active_turn_public_key_digest: null, active_turn_phase: "started", active_turn_progress: baseline,
|
|
410
|
+
last_progress: baseline, prior_progress: null, pending_barrier: null, updated_at: "2026-07-13T12:00:00.000Z",
|
|
411
|
+
});
|
|
412
|
+
const result = await runContinuationDriver({
|
|
413
|
+
maxTurns: 2,
|
|
414
|
+
store,
|
|
415
|
+
runtime: {
|
|
416
|
+
inspect: async () => terminalProgressSnapshot(status, { evidence: ["learn:before"], artifacts: ["learning.json:before"] }),
|
|
417
|
+
synchronize: async () => terminalProgressSnapshot(status, { evidence: ["learn:before"], artifacts: ["learning.json:before"] }),
|
|
418
|
+
execute: async () => assert.fail("terminal recovery must not execute"),
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
assert.equal(result.outcome, outcome);
|
|
422
|
+
assert.equal(result.snapshot.current_step, "learn");
|
|
423
|
+
assert.equal(result.snapshot.progress_snapshot.canonical_status, status);
|
|
424
|
+
const recovered = store.events.find((event) => event.type === "turn_recovered");
|
|
425
|
+
assert.equal(recovered.progress.step_advanced, true);
|
|
426
|
+
assert.deepEqual(recovered.progress.evidence_added, []);
|
|
427
|
+
assert.deepEqual(recovered.progress.artifact_changes, []);
|
|
428
|
+
assert.equal(recovered.progress.no_progress, false);
|
|
429
|
+
assert.deepEqual(store.value().prior_progress, recovered.progress);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("a crash after accepted-turn measurement is journaled and completed exactly once on restart", async () => {
|
|
434
|
+
const durable = memoryStore();
|
|
435
|
+
let injected = false;
|
|
436
|
+
const crashing = {
|
|
437
|
+
...durable,
|
|
438
|
+
load: () => {
|
|
439
|
+
if (injected) throw new Error("injected process death");
|
|
440
|
+
return durable.load();
|
|
441
|
+
},
|
|
442
|
+
save: (state) => {
|
|
443
|
+
durable.save(state);
|
|
444
|
+
if (!injected && state.active_turn_phase === "measured") {
|
|
445
|
+
injected = true;
|
|
446
|
+
throw new Error("injected process death");
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
await assert.rejects(runContinuationDriver({
|
|
451
|
+
maxTurns: 2,
|
|
452
|
+
store: crashing,
|
|
453
|
+
runtime: {
|
|
454
|
+
inspect: async () => envelopeSnapshot("plan"),
|
|
455
|
+
synchronize: async () => envelopeSnapshot("plan"),
|
|
456
|
+
execute: async () => ({ status: "completed", summary: "accepted before crash" }),
|
|
457
|
+
},
|
|
458
|
+
}), /injected process death/);
|
|
459
|
+
assert.equal(durable.value().active_turn_phase, "measured");
|
|
460
|
+
assert.equal(durable.accepted.length, 0, "capture append had not occurred before the injected crash");
|
|
461
|
+
|
|
462
|
+
const resumed = await runContinuationDriver({
|
|
463
|
+
maxTurns: 2,
|
|
464
|
+
store: durable,
|
|
465
|
+
runtime: {
|
|
466
|
+
inspect: async () => snapshot("done", "completed"),
|
|
467
|
+
synchronize: async () => snapshot("done", "completed"),
|
|
468
|
+
execute: async () => assert.fail("restart must not execute another turn"),
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
assert.equal(resumed.outcome, "done");
|
|
472
|
+
assert.equal(durable.accepted.length, 1);
|
|
473
|
+
assert.equal(durable.accepted[0].request.current_step, "plan");
|
|
474
|
+
assert.equal(durable.events.filter((event) => event.type === "turn_completed").length, 1);
|
|
475
|
+
assert.equal(durable.events.filter((event) => event.type === "turn_recovered").length, 1);
|
|
476
|
+
assert.equal(durable.value().active_turn_phase, null);
|
|
477
|
+
});
|
|
478
|
+
|
|
109
479
|
test("driver parks on a wait barrier without burning another turn and resumes durably", async () => {
|
|
110
480
|
const barrier = { kind: "deadline", at: "2026-07-12T12:00:00.000Z" };
|
|
111
481
|
const store = memoryStore();
|
|
@@ -228,13 +598,14 @@ test("canonical completion clears a stale barrier without waiting", async () =>
|
|
|
228
598
|
updated_at: "2026-07-12T12:00:00.000Z",
|
|
229
599
|
});
|
|
230
600
|
let waitCalls = 0;
|
|
601
|
+
let synchronizeCalls = 0;
|
|
231
602
|
|
|
232
603
|
const result = await runContinuationDriver({
|
|
233
604
|
maxTurns: 3,
|
|
234
605
|
store,
|
|
235
606
|
runtime: {
|
|
236
607
|
inspect: async () => snapshot("done", "completed"),
|
|
237
|
-
synchronize: async () =>
|
|
608
|
+
synchronize: async () => { synchronizeCalls += 1; return snapshot("done", "completed"); },
|
|
238
609
|
execute: async () => assert.fail("terminal inspection must not execute"),
|
|
239
610
|
},
|
|
240
611
|
waitForBarrier: async () => {
|
|
@@ -244,6 +615,7 @@ test("canonical completion clears a stale barrier without waiting", async () =>
|
|
|
244
615
|
});
|
|
245
616
|
|
|
246
617
|
assert.equal(result.outcome, "done");
|
|
618
|
+
assert.equal(synchronizeCalls, 1, "terminal disposition is confirmed after canonical synchronization");
|
|
247
619
|
assert.equal(waitCalls, 0);
|
|
248
620
|
assert.equal(store.value().pending_barrier, null);
|
|
249
621
|
});
|
|
@@ -281,25 +653,73 @@ test("adapter evidence is bounded before the driver accepts the turn", async ()
|
|
|
281
653
|
assert.equal(acceptedTurns, 0);
|
|
282
654
|
});
|
|
283
655
|
|
|
284
|
-
test("
|
|
656
|
+
test("complete turn-result bytes are bounded with maximal evidence and a multibyte summary", async () => {
|
|
657
|
+
const evidenceOverhead = Buffer.byteLength(JSON.stringify({ value: "" }), "utf8");
|
|
658
|
+
const evidence = { value: "x".repeat(MAX_CONTINUATION_ADAPTER_EVIDENCE_BYTES - evidenceOverhead) };
|
|
659
|
+
assert.equal(Buffer.byteLength(JSON.stringify(evidence), "utf8"), MAX_CONTINUATION_ADAPTER_EVIDENCE_BYTES);
|
|
660
|
+
const boundary = { status: "completed", summary: "😀".repeat(2_000), evidence };
|
|
661
|
+
assert.ok(Buffer.byteLength(JSON.stringify(boundary), "utf8") <= MAX_CONTINUATION_TURN_RESULT_BYTES);
|
|
662
|
+
const accepted = await runContinuationDriver({
|
|
663
|
+
maxTurns: 1,
|
|
664
|
+
store: memoryStore(),
|
|
665
|
+
runtime: { inspect: async () => snapshot("plan"), synchronize: async () => snapshot("plan"), execute: async () => boundary },
|
|
666
|
+
});
|
|
667
|
+
assert.equal(accepted.outcome, "budget_exhausted");
|
|
668
|
+
|
|
669
|
+
const oversized = { status: "completed", summary: "ok", evidence: {}, padding: "x".repeat(MAX_CONTINUATION_TURN_RESULT_BYTES) };
|
|
285
670
|
const store = memoryStore();
|
|
286
|
-
await
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
671
|
+
await runContinuationDriver({
|
|
672
|
+
maxTurns: 1,
|
|
673
|
+
store,
|
|
674
|
+
runtime: { inspect: async () => snapshot("plan"), synchronize: async () => snapshot("plan"), execute: async () => oversized },
|
|
675
|
+
});
|
|
676
|
+
assert.match(store.events.find((event) => event.type === "turn_failed")?.summary, /result must not exceed 74000 bytes/);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
test("accepted-turn capture failures measure canonical mutation and no-op before terminating", async (t) => {
|
|
680
|
+
for (const mutated of [false, true]) await t.test(mutated ? "canonical mutation" : "no-op", async () => {
|
|
681
|
+
const store = memoryStore();
|
|
682
|
+
let adapterFinished = false;
|
|
683
|
+
await assert.rejects(
|
|
684
|
+
runContinuationDriver({
|
|
685
|
+
maxTurns: 2,
|
|
686
|
+
store,
|
|
687
|
+
onTurnAccepted: async () => { throw new Error("attestation aggregate exceeded"); },
|
|
688
|
+
runtime: {
|
|
689
|
+
inspect: async () => envelopeSnapshot("plan"),
|
|
690
|
+
synchronize: async () => adapterFinished && mutated
|
|
691
|
+
? envelopeSnapshot("execute", { evidence: ["plan-gate:canonical"] })
|
|
692
|
+
: envelopeSnapshot("plan"),
|
|
693
|
+
execute: async () => { adapterFinished = true; return { status: "completed", evidence: { value: "accepted" } }; },
|
|
694
|
+
},
|
|
695
|
+
}),
|
|
696
|
+
/attestation aggregate exceeded/,
|
|
697
|
+
);
|
|
698
|
+
const failed = store.events.find((event) => event.type === "turn_failed");
|
|
699
|
+
assert.equal(failed.progress.no_progress, !mutated);
|
|
700
|
+
assert.equal(failed.progress.step_advanced, mutated);
|
|
701
|
+
assert.equal(store.events.some((event) => event.type === "turn_completed"), false);
|
|
702
|
+
assert.equal(store.value().turns_started, 1);
|
|
703
|
+
assert.equal(store.value().active_turn_step, null);
|
|
704
|
+
assert.equal(store.value().active_turn_phase, null);
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
test("turn preflight failure occurs before adapter execution or durable turn start", async () => {
|
|
709
|
+
const store = memoryStore();
|
|
710
|
+
let executions = 0;
|
|
711
|
+
await assert.rejects(runContinuationDriver({
|
|
712
|
+
maxTurns: 1,
|
|
713
|
+
store,
|
|
714
|
+
preflightTurn: async () => { throw new Error("aggregate capacity exhausted"); },
|
|
715
|
+
runtime: {
|
|
716
|
+
inspect: async () => snapshot("plan"), synchronize: async () => snapshot("plan"),
|
|
717
|
+
execute: async () => { executions += 1; return { status: "completed" }; },
|
|
718
|
+
},
|
|
719
|
+
}), /aggregate capacity exhausted/);
|
|
720
|
+
assert.equal(executions, 0);
|
|
721
|
+
assert.equal(store.value().turns_started, 0);
|
|
722
|
+
assert.equal(store.events.some((event) => event.type === "turn_started"), false);
|
|
303
723
|
});
|
|
304
724
|
|
|
305
725
|
test("unsigned adapters retain extension-field compatibility", async () => {
|
|
@@ -310,7 +730,10 @@ test("unsigned adapters retain extension-field compatibility", async () => {
|
|
|
310
730
|
runtime: {
|
|
311
731
|
inspect: async () => snapshot("plan"),
|
|
312
732
|
synchronize: async () => snapshot("plan"),
|
|
313
|
-
execute: async () =>
|
|
733
|
+
execute: async (request) => {
|
|
734
|
+
assert.equal(Object.hasOwn(request, "gate_action_envelope"), false, "generic external adapters keep their schema-1.0 request shape");
|
|
735
|
+
return { status: "completed", metadata: { extension: true } };
|
|
736
|
+
},
|
|
314
737
|
},
|
|
315
738
|
});
|
|
316
739
|
assert.equal(result.outcome, "budget_exhausted");
|
|
@@ -318,6 +741,28 @@ test("unsigned adapters retain extension-field compatibility", async () => {
|
|
|
318
741
|
assert.equal(store.events.some((event) => event.type === "turn_failed"), false);
|
|
319
742
|
});
|
|
320
743
|
|
|
744
|
+
test("legacy continuation stores remain compatible without accepted-turn journaling", async () => {
|
|
745
|
+
let value = null;
|
|
746
|
+
const events = [];
|
|
747
|
+
const store = {
|
|
748
|
+
load: () => value,
|
|
749
|
+
save: (next) => { value = structuredClone(next); },
|
|
750
|
+
append: (event) => { events.push(structuredClone(event)); },
|
|
751
|
+
};
|
|
752
|
+
const result = await runContinuationDriver({
|
|
753
|
+
maxTurns: 1,
|
|
754
|
+
store,
|
|
755
|
+
runtime: {
|
|
756
|
+
inspect: async () => snapshot("plan"),
|
|
757
|
+
synchronize: async () => snapshot("plan"),
|
|
758
|
+
execute: async () => ({ status: "completed", summary: "legacy store turn" }),
|
|
759
|
+
},
|
|
760
|
+
});
|
|
761
|
+
assert.equal(result.outcome, "budget_exhausted");
|
|
762
|
+
assert.equal(value.active_turn_phase, null);
|
|
763
|
+
assert.equal(events.some((event) => event.type === "turn_completed"), true);
|
|
764
|
+
});
|
|
765
|
+
|
|
321
766
|
test("adapter errors fail open to another bounded turn and remain auditable", async () => {
|
|
322
767
|
const store = memoryStore();
|
|
323
768
|
let executeCalls = 0;
|
|
@@ -1269,7 +1714,80 @@ test("file continuation store refuses a symlinked state target", (t) => {
|
|
|
1269
1714
|
const outside = path.join(root, "outside.json");
|
|
1270
1715
|
fs.writeFileSync(outside, "{}\n");
|
|
1271
1716
|
fs.symlinkSync(outside, path.join(root, "continuation-driver", "state.json"));
|
|
1272
|
-
assert.throws(() => store.load(), /must be a regular file/);
|
|
1717
|
+
assert.throws(() => store.load(), /ELOOP|must be a regular file/);
|
|
1718
|
+
});
|
|
1719
|
+
|
|
1720
|
+
test("file continuation store bounds and identity-checks state, event, and accepted-turn reads", (t) => {
|
|
1721
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "continuation-store-hardening-"));
|
|
1722
|
+
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
1723
|
+
const store = createFileContinuationStore(root);
|
|
1724
|
+
const driverDir = path.join(root, "continuation-driver");
|
|
1725
|
+
const stateFile = path.join(driverDir, "state.json");
|
|
1726
|
+
const eventsFile = path.join(driverDir, "events.jsonl");
|
|
1727
|
+
const acceptedFile = path.join(driverDir, "accepted-turns.jsonl");
|
|
1728
|
+
|
|
1729
|
+
fs.writeFileSync(stateFile, Buffer.alloc(1_048_577));
|
|
1730
|
+
assert.throws(() => store.load(), /state must be a regular file no larger than 1048576 bytes/);
|
|
1731
|
+
fs.rmSync(stateFile);
|
|
1732
|
+
fs.writeFileSync(eventsFile, `${"x".repeat(16_385)}\n`);
|
|
1733
|
+
assert.throws(() => store.load(), /event log line exceeds 16384 bytes/);
|
|
1734
|
+
fs.rmSync(eventsFile);
|
|
1735
|
+
fs.writeFileSync(eventsFile, `${Array.from({ length: 617 }, () => "{}").join("\n")}\n`);
|
|
1736
|
+
assert.throws(() => store.load(), /event log exceeds 616 lines/);
|
|
1737
|
+
fs.rmSync(eventsFile);
|
|
1738
|
+
fs.writeFileSync(acceptedFile, `${"x".repeat(196_609)}\n`);
|
|
1739
|
+
assert.throws(() => store.acceptedTurns(), /accepted-turn log line exceeds 196608 bytes/);
|
|
1740
|
+
fs.rmSync(acceptedFile);
|
|
1741
|
+
fs.writeFileSync(path.join(root, "outside-events"), "{}\n");
|
|
1742
|
+
fs.symlinkSync(path.join(root, "outside-events"), eventsFile);
|
|
1743
|
+
assert.throws(() => store.load(), /ELOOP|changed identity/);
|
|
1744
|
+
});
|
|
1745
|
+
|
|
1746
|
+
test("file continuation store rejects a state path replaced during descriptor open", (t) => {
|
|
1747
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "continuation-store-replaced-"));
|
|
1748
|
+
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
1749
|
+
const store = createFileContinuationStore(root);
|
|
1750
|
+
const state = {
|
|
1751
|
+
schema_version: "1.0", run_id: "run-251", definition_id: "builder.build", max_turns: 1,
|
|
1752
|
+
adapter_command_identity: null, status: "active", turns_started: 0, pending_barrier: null,
|
|
1753
|
+
updated_at: "2026-07-12T12:00:00.000Z",
|
|
1754
|
+
};
|
|
1755
|
+
store.save(state);
|
|
1756
|
+
const stateFile = path.join(root, "continuation-driver", "state.json");
|
|
1757
|
+
const replacement = path.join(root, "replacement.json");
|
|
1758
|
+
fs.writeFileSync(replacement, JSON.stringify(state));
|
|
1759
|
+
const originalOpen = fs.openSync;
|
|
1760
|
+
let swapped = false;
|
|
1761
|
+
fs.openSync = (file, flags, mode) => {
|
|
1762
|
+
const fd = originalOpen(file, flags, mode);
|
|
1763
|
+
if (!swapped && path.resolve(String(file)) === path.resolve(stateFile)) {
|
|
1764
|
+
fs.renameSync(replacement, stateFile);
|
|
1765
|
+
swapped = true;
|
|
1766
|
+
}
|
|
1767
|
+
return fd;
|
|
1768
|
+
};
|
|
1769
|
+
syncBuiltinESMExports();
|
|
1770
|
+
try {
|
|
1771
|
+
assert.throws(() => store.load(), /changed identity while reading/);
|
|
1772
|
+
assert.equal(swapped, true);
|
|
1773
|
+
} finally {
|
|
1774
|
+
fs.openSync = originalOpen;
|
|
1775
|
+
syncBuiltinESMExports();
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
|
|
1779
|
+
test("attestation turns fail closed when accepted events lack durable request/result coverage", (t) => {
|
|
1780
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "continuation-store-coverage-"));
|
|
1781
|
+
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
1782
|
+
const store = createFileContinuationStore(root);
|
|
1783
|
+
store.save({
|
|
1784
|
+
schema_version: "1.0", run_id: "run-251", definition_id: "builder.build", max_turns: 1,
|
|
1785
|
+
adapter_command_identity: null, status: "active", turns_started: 1, pending_barrier: null,
|
|
1786
|
+
updated_at: "2026-07-12T12:00:00.000Z",
|
|
1787
|
+
});
|
|
1788
|
+
store.append({ schema_version: "1.0", type: "turn_started", run_id: "run-251", definition_id: "builder.build", current_step: "plan", turns_started: 1, at: "2026-07-12T12:00:00.000Z" });
|
|
1789
|
+
store.append({ schema_version: "1.0", type: "turn_completed", run_id: "run-251", definition_id: "builder.build", current_step: "plan", turns_started: 1, at: "2026-07-12T12:00:01.000Z" });
|
|
1790
|
+
assert.throws(() => store.acceptedTurns(), /attestation coverage is incomplete/);
|
|
1273
1791
|
});
|
|
1274
1792
|
|
|
1275
1793
|
test("file continuation store detects deleted and rolled-back mission state", (t) => {
|