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