@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
|
@@ -8,6 +8,7 @@ import { createRequire, syncBuiltinESMExports } from "node:module";
|
|
|
8
8
|
import { readKitInventory } from "../../build/src/runtime-adapters.js";
|
|
9
9
|
import { main as validateHookInfluence } from "../../build/src/cli/validate-hook-influence.js";
|
|
10
10
|
import { parseKitFlowStepActions, parseKitSkillRoles, validateKitRepository } from "../../build/src/flow-kit/validate.js";
|
|
11
|
+
import { observeBuilderArtifactsForProgress } from "../../build/src/builder-gate-action-envelope.js";
|
|
11
12
|
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
13
14
|
const { workflowTriggersFor } = require("../../scripts/hooks/lib/kit-catalog.js");
|
|
@@ -70,35 +71,75 @@ test("Builder flow step actions are structured, complete, and operation-aware",
|
|
|
70
71
|
assert.deepEqual(result.errors, []);
|
|
71
72
|
assert.equal(result.entries.length, 14);
|
|
72
73
|
assert.deepEqual(result.entries.find((entry) => entry.step_id === "plan")?.skills, ["plan-work"]);
|
|
74
|
+
assert.deepEqual(result.entries.filter((entry) => entry.implementation_allowed).map((entry) => `${entry.flow_id}/${entry.step_id}`), ["builder.build/execute"]);
|
|
75
|
+
assert.equal(result.entries.find((entry) => entry.step_id === "verify")?.expectation_bindings.find((binding) => binding.expectation_id === "clean-critique")?.interface, "workflow.critique");
|
|
73
76
|
assert.deepEqual(result.entries.find((entry) => entry.step_id === "pr-open"), {
|
|
74
77
|
flow_id: "builder.build",
|
|
75
78
|
step_id: "pr-open",
|
|
76
79
|
skills: [],
|
|
77
80
|
operations: ["publish-change"],
|
|
78
|
-
artifacts: ["
|
|
81
|
+
artifacts: ["publish-change.result.json"],
|
|
79
82
|
expectation_ids: ["pull-request-opened"],
|
|
83
|
+
implementation_allowed: false,
|
|
84
|
+
expectation_bindings: [{ expectation_id: "pull-request-opened", interface: "operation", operation: "publish-change" }],
|
|
85
|
+
artifact_bindings: [{ artifact: "publish-change.result.json", expectation_ids: ["pull-request-opened"] }],
|
|
80
86
|
});
|
|
81
87
|
});
|
|
82
88
|
|
|
83
89
|
test("flow step action metadata rejects malformed and duplicate entries", () => {
|
|
84
90
|
const result = parseKitFlowStepActions({
|
|
85
91
|
flow_step_actions: [
|
|
86
|
-
{ flow_id: "builder.build", step_id: "plan", skills: ["plan-work"] },
|
|
87
|
-
{ flow_id: "builder.build", step_id: "plan", skills: [] },
|
|
88
|
-
{ flow_id: "builder.build", step_id: "verify", skills: "verify-work" },
|
|
89
|
-
{ flow_id: "builder.build", step_id: "publish", skills: [], operations: ["publish-change"], expectation_ids: ["pull-request-opened", "pull-request-opened"] },
|
|
92
|
+
{ flow_id: "builder.build", step_id: "plan", skills: ["plan-work"], implementation_allowed: false, artifacts: [], expectation_ids: [], expectation_bindings: [] },
|
|
93
|
+
{ flow_id: "builder.build", step_id: "plan", skills: [], implementation_allowed: false, artifacts: [], expectation_ids: [], expectation_bindings: [] },
|
|
94
|
+
{ flow_id: "builder.build", step_id: "verify", skills: "verify-work", implementation_allowed: false, artifacts: [], expectation_ids: [], expectation_bindings: [] },
|
|
95
|
+
{ flow_id: "builder.build", step_id: "publish", skills: [], operations: ["publish-change"], implementation_allowed: false, artifacts: [], expectation_ids: ["pull-request-opened", "pull-request-opened"], expectation_bindings: [] },
|
|
96
|
+
{ flow_id: "builder.build", step_id: "execute", skills: ["execute-plan"], implementation_allowed: true, artifacts: ["x".repeat(1025)], expectation_ids: [], expectation_bindings: [], unexpected: "ignored before #577" },
|
|
90
97
|
],
|
|
91
98
|
}, "fixture/kit.json");
|
|
92
99
|
|
|
93
100
|
assert.match(result.errors.join("\n"), /duplicates 'builder\.build\/plan'/);
|
|
94
101
|
assert.match(result.errors.join("\n"), /skills must be an identifier list/);
|
|
95
|
-
assert.match(result.errors.join("\n"), /expectation_ids must be a unique identifier list
|
|
102
|
+
assert.match(result.errors.join("\n"), /expectation_ids must be a unique identifier list/);
|
|
103
|
+
assert.match(result.errors.join("\n"), /unsupported field\(s\): unexpected/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("flow step action metadata requires progress and policy fields", () => {
|
|
107
|
+
const base = {
|
|
108
|
+
flow_id: "builder.build",
|
|
109
|
+
step_id: "plan",
|
|
110
|
+
skills: ["plan-work"],
|
|
111
|
+
implementation_allowed: false,
|
|
112
|
+
artifacts: [],
|
|
113
|
+
expectation_ids: [],
|
|
114
|
+
expectation_bindings: [],
|
|
115
|
+
};
|
|
116
|
+
for (const field of ["expectation_ids", "artifacts", "implementation_allowed", "expectation_bindings"]) {
|
|
117
|
+
const action = { ...base };
|
|
118
|
+
delete action[field];
|
|
119
|
+
const result = parseKitFlowStepActions({ flow_step_actions: [action] }, "fixture/kit.json");
|
|
120
|
+
assert.match(result.errors.join("\n"), new RegExp(`flow_step_actions\\[0\\]\\.${field} must be explicitly declared`), field);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("flow step action metadata rejects oversized action lists", () => {
|
|
125
|
+
const result = parseKitFlowStepActions({
|
|
126
|
+
flow_step_actions: [{
|
|
127
|
+
flow_id: "builder.build",
|
|
128
|
+
step_id: "plan",
|
|
129
|
+
skills: Array.from({ length: 33 }, (_, index) => `skill-${index}`),
|
|
130
|
+
implementation_allowed: false,
|
|
131
|
+
artifacts: [],
|
|
132
|
+
expectation_ids: [],
|
|
133
|
+
expectation_bindings: [],
|
|
134
|
+
}],
|
|
135
|
+
}, "fixture/kit.json");
|
|
136
|
+
assert.match(result.errors.join("\n"), /list exceeds 32 entries/);
|
|
96
137
|
});
|
|
97
138
|
|
|
98
139
|
test("flow step action metadata preserves explicit operation expectation ownership", () => {
|
|
99
140
|
const result = parseKitFlowStepActions({
|
|
100
141
|
flow_step_actions: [
|
|
101
|
-
{ flow_id: "builder.build", step_id: "pr-open", skills: [], operations: ["publish-change"], expectation_ids: ["pull-request-opened"] },
|
|
142
|
+
{ flow_id: "builder.build", step_id: "pr-open", skills: [], operations: ["publish-change"], implementation_allowed: false, artifacts: [], expectation_ids: ["pull-request-opened"], expectation_bindings: [{ expectation_id: "pull-request-opened", interface: "operation", operation: "publish-change" }], artifact_bindings: [] },
|
|
102
143
|
],
|
|
103
144
|
}, "fixture/kit.json");
|
|
104
145
|
|
|
@@ -107,6 +148,94 @@ test("flow step action metadata preserves explicit operation expectation ownersh
|
|
|
107
148
|
assert.deepEqual(result.entries[0]?.artifacts, []);
|
|
108
149
|
});
|
|
109
150
|
|
|
151
|
+
test("Builder action validation rejects expectation omissions, unsafe artifacts, and unknown operations", async () => {
|
|
152
|
+
async function errorsFor(name, mutate) {
|
|
153
|
+
const root = tempRoot(`flow-agents-action-contract-${name}-`);
|
|
154
|
+
const kit = path.join(root, "builder");
|
|
155
|
+
fs.cpSync("kits/builder", kit, { recursive: true });
|
|
156
|
+
const manifestFile = path.join(kit, "kit.json");
|
|
157
|
+
const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
158
|
+
mutate(manifest);
|
|
159
|
+
writeJson(manifestFile, manifest);
|
|
160
|
+
return (await validateKitRepository(kit)).join("\n");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const omitted = await errorsFor("omitted-expectation", (manifest) => {
|
|
164
|
+
const action = manifest.flow_step_actions.find((entry) => entry.step_id === "verify");
|
|
165
|
+
action.expectation_ids = action.expectation_ids.filter((id) => id !== "policy-compliance");
|
|
166
|
+
action.expectation_bindings = action.expectation_bindings.filter((binding) => binding.expectation_id !== "policy-compliance");
|
|
167
|
+
action.artifacts = action.artifacts.filter((artifact) => artifact !== "trust.bundle#policy-compliance");
|
|
168
|
+
action.artifact_bindings = action.artifact_bindings.filter((binding) => binding.artifact !== "trust.bundle#policy-compliance");
|
|
169
|
+
});
|
|
170
|
+
assert.match(omitted, /expectation_ids must exactly equal its resolved Flow expectation set/);
|
|
171
|
+
|
|
172
|
+
const traversal = await errorsFor("artifact-traversal", (manifest) => {
|
|
173
|
+
const action = manifest.flow_step_actions.find((entry) => entry.step_id === "plan");
|
|
174
|
+
action.artifacts.push("../outside.md");
|
|
175
|
+
action.artifact_bindings.push({ artifact: "../outside.md", expectation_ids: ["implementation-plan"] });
|
|
176
|
+
});
|
|
177
|
+
assert.match(traversal, /safe session-relative paths/);
|
|
178
|
+
|
|
179
|
+
for (const [name, artifact] of [["absolute", "/tmp/outside.md"], ["fragment", "release.json#arbitrary"]]) {
|
|
180
|
+
const unsafe = await errorsFor(name, (manifest) => {
|
|
181
|
+
const action = manifest.flow_step_actions.find((entry) => entry.step_id === "pr-open");
|
|
182
|
+
action.artifacts = [artifact];
|
|
183
|
+
action.artifact_bindings = [{ artifact, expectation_ids: ["pull-request-opened"] }];
|
|
184
|
+
});
|
|
185
|
+
assert.match(unsafe, /safe session-relative paths/, name);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const typo = await errorsFor("operation-typo", (manifest) => {
|
|
189
|
+
const action = manifest.flow_step_actions.find((entry) => entry.step_id === "pr-open");
|
|
190
|
+
action.operations = ["publish-chagne"];
|
|
191
|
+
action.expectation_bindings[0].operation = "publish-chagne";
|
|
192
|
+
});
|
|
193
|
+
assert.match(typo, /canonical public operation catalog/);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("artifact progress enforces deduplicated count and aggregate read budgets", (t) => {
|
|
197
|
+
const root = tempRoot("flow-agents-artifact-budget-");
|
|
198
|
+
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
199
|
+
assert.throws(
|
|
200
|
+
() => observeBuilderArtifactsForProgress(root, Array.from({ length: 129 }, (_, index) => `artifact-${index}.json`)),
|
|
201
|
+
/exceeds 128 unique files/,
|
|
202
|
+
);
|
|
203
|
+
const refs = [];
|
|
204
|
+
for (let index = 0; index < 9; index += 1) {
|
|
205
|
+
const ref = `artifact-${index}.bin`;
|
|
206
|
+
fs.writeFileSync(path.join(root, ref), Buffer.alloc(1024 * 1024));
|
|
207
|
+
refs.push(ref);
|
|
208
|
+
}
|
|
209
|
+
assert.throws(() => observeBuilderArtifactsForProgress(root, refs), /exceeds 8388608 aggregate bytes/);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("metadata validation matches runtime skill and run-wide observable artifact bounds", () => {
|
|
213
|
+
const tooManySkills = parseKitFlowStepActions({
|
|
214
|
+
flow_step_actions: [{
|
|
215
|
+
flow_id: "builder.build", step_id: "execute", skills: Array.from({ length: 17 }, (_, index) => `skill${index}`),
|
|
216
|
+
operations: [], implementation_allowed: true, artifacts: [], expectation_ids: [], expectation_bindings: [], artifact_bindings: [],
|
|
217
|
+
}],
|
|
218
|
+
}, "fixture/kit.json");
|
|
219
|
+
assert.match(tooManySkills.errors.join("\n"), /skills exceeds 16 entries/);
|
|
220
|
+
|
|
221
|
+
const artifacts = Array.from({ length: 129 }, (_, index) => `artifact-${index}.json`);
|
|
222
|
+
const actions = Array.from({ length: 5 }, (_, actionIndex) => {
|
|
223
|
+
const slice = artifacts.slice(actionIndex * 32, (actionIndex + 1) * 32);
|
|
224
|
+
return {
|
|
225
|
+
flow_id: "builder.build", step_id: `step${actionIndex}`, skills: [], operations: [], implementation_allowed: false,
|
|
226
|
+
artifacts: slice, expectation_ids: [], expectation_bindings: [],
|
|
227
|
+
artifact_bindings: slice.map((artifact) => ({ artifact, expectation_ids: [] })),
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
const tooManyArtifacts = parseKitFlowStepActions({ flow_step_actions: actions }, "fixture/kit.json");
|
|
231
|
+
assert.match(tooManyArtifacts.errors.join("\n"), /exceed 128 distinct observable file artifacts/);
|
|
232
|
+
|
|
233
|
+
actions[4].artifacts = ["state.json", "trust.bundle#virtual"];
|
|
234
|
+
actions[4].artifact_bindings = actions[4].artifacts.map((artifact) => ({ artifact, expectation_ids: [] }));
|
|
235
|
+
const excluded = parseKitFlowStepActions({ flow_step_actions: actions }, "fixture/kit.json");
|
|
236
|
+
assert.doesNotMatch(excluded.errors.join("\n"), /distinct observable file artifacts/);
|
|
237
|
+
});
|
|
238
|
+
|
|
110
239
|
test("Builder skill roles form one complete role and producer matrix", async () => {
|
|
111
240
|
const manifest = JSON.parse(fs.readFileSync("kits/builder/kit.json", "utf8"));
|
|
112
241
|
const result = parseKitSkillRoles(manifest, "kits/builder/kit.json");
|
|
@@ -225,7 +354,7 @@ test("operation-only composed actions must explicitly and exclusively own expect
|
|
|
225
354
|
action.operations = ["publish-change"];
|
|
226
355
|
delete action.expectation_ids;
|
|
227
356
|
});
|
|
228
|
-
assert.match(missing, /
|
|
357
|
+
assert.match(missing, /expectation_ids must be explicitly declared/);
|
|
229
358
|
assert.match(missing, /flow expectation 'builder\.publish-learn\/pr-open\/pull-request-opened' must have exactly one producer owner; found 0/);
|
|
230
359
|
|
|
231
360
|
const duplicate = await errorsFor("duplicate", (manifest) => {
|
|
@@ -240,6 +369,30 @@ test("operation-only composed actions must explicitly and exclusively own expect
|
|
|
240
369
|
assert.match(duplicate, /flow expectation 'builder\.publish-learn\/pr-open\/pull-request-opened' must have exactly one producer owner; found 2/);
|
|
241
370
|
});
|
|
242
371
|
|
|
372
|
+
test("mixed skill and operation ownership follows each expectation binding interface", async () => {
|
|
373
|
+
const root = tempRoot("flow-agents-mixed-operation-owner-");
|
|
374
|
+
const kit = path.join(root, "builder");
|
|
375
|
+
fs.cpSync("kits/builder", kit, { recursive: true });
|
|
376
|
+
const manifestFile = path.join(kit, "kit.json");
|
|
377
|
+
const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
378
|
+
const action = manifest.flow_step_actions.find((entry) => entry.flow_id === "builder.build" && entry.step_id === "verify");
|
|
379
|
+
action.operations = ["publish-change"];
|
|
380
|
+
const policyBinding = action.expectation_bindings.find((entry) => entry.expectation_id === "policy-compliance");
|
|
381
|
+
policyBinding.interface = "operation";
|
|
382
|
+
policyBinding.operation = "publish-change";
|
|
383
|
+
const verifier = manifest.skill_roles.find((entry) => entry.skill_id === "builder.verify-work");
|
|
384
|
+
verifier.expectation_ids = verifier.expectation_ids.filter((id) => id !== "policy-compliance");
|
|
385
|
+
writeJson(manifestFile, manifest);
|
|
386
|
+
const valid = (await validateKitRepository(kit)).join("\n");
|
|
387
|
+
assert.doesNotMatch(valid, /verify\/policy-compliance.*producer owner/);
|
|
388
|
+
|
|
389
|
+
policyBinding.interface = "workflow.evidence";
|
|
390
|
+
delete policyBinding.operation;
|
|
391
|
+
writeJson(manifestFile, manifest);
|
|
392
|
+
const missing = (await validateKitRepository(kit)).join("\n");
|
|
393
|
+
assert.match(missing, /flow expectation 'builder\.build\/verify\/policy-compliance' must have exactly one producer owner; found 0/);
|
|
394
|
+
});
|
|
395
|
+
|
|
243
396
|
test("skill role cross-reference never reads Flow definitions through traversal, symlinks, or oversized files", async () => {
|
|
244
397
|
async function errorsFor(name, mutate) {
|
|
245
398
|
const root = tempRoot(`flow-agents-skill-role-safe-flow-${name}-`);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const WORKFLOW_CRITIQUE_STATUSES = ["pass", "fail", "not_verified"] as const;
|
|
2
|
+
|
|
3
|
+
export const PUBLISH_CHANGE_OPERATION = "publish-change" as const;
|
|
4
|
+
|
|
5
|
+
export const PUBLISH_CHANGE_OPERATION_PROTOCOL = {
|
|
6
|
+
schema_version: "1.0",
|
|
7
|
+
operation: PUBLISH_CHANGE_OPERATION,
|
|
8
|
+
kind: "provider_capability",
|
|
9
|
+
capability: "pull_request.create",
|
|
10
|
+
parameters: [
|
|
11
|
+
{ name: "title", required: true, type: "string", max_length: 512 },
|
|
12
|
+
{ name: "body", required: true, type: "string", max_length: 65_536 },
|
|
13
|
+
{ name: "head_ref", required: true, type: "string", max_length: 255 },
|
|
14
|
+
{ name: "base_ref", required: true, type: "string", max_length: 255 },
|
|
15
|
+
{ name: "draft", required: false, type: "boolean" },
|
|
16
|
+
],
|
|
17
|
+
result: {
|
|
18
|
+
required: ["provider", "repository", "number", "url", "head_ref", "base_ref"],
|
|
19
|
+
properties: {
|
|
20
|
+
provider: { type: "string", max_length: 128 },
|
|
21
|
+
repository: { type: "string", max_length: 1_024 },
|
|
22
|
+
number: { type: "integer", minimum: 1 },
|
|
23
|
+
url: { type: "string", max_length: 8_192 },
|
|
24
|
+
head_ref: { type: "string", max_length: 255 },
|
|
25
|
+
base_ref: { type: "string", max_length: 255 },
|
|
26
|
+
},
|
|
27
|
+
url_protocols: ["https:"],
|
|
28
|
+
persist_as: "publish-change.result.json",
|
|
29
|
+
},
|
|
30
|
+
availability: {
|
|
31
|
+
status: "external_capability_required",
|
|
32
|
+
executable_by_flow_agents: false,
|
|
33
|
+
completion_verification: "authenticated_change_provider_required",
|
|
34
|
+
},
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
export const PUBLIC_OPERATION_CONTRACTS = {
|
|
38
|
+
[PUBLISH_CHANGE_OPERATION]: PUBLISH_CHANGE_OPERATION_PROTOCOL,
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
export const PUBLIC_OPERATION_IDS = new Set<string>(Object.keys(PUBLIC_OPERATION_CONTRACTS));
|
|
@@ -14,6 +14,7 @@ import { flowAgentsPackageRoot, flowAgentsPackageVersion } from "../lib/package-
|
|
|
14
14
|
import { pinnedFlowAgentsCommand } from "../lib/pinned-cli-command.js";
|
|
15
15
|
import { runObservedCommand } from "../lib/observed-command.js";
|
|
16
16
|
import { captureReviewWorkspaceSnapshot, startBuilderFlowSession, syncBuilderFlowSession } from "../builder-flow-runtime.js";
|
|
17
|
+
import { WORKFLOW_CRITIQUE_STATUSES } from "./public-contracts.js";
|
|
17
18
|
// #291 Wave 1 Task 1.1 exports: ensure-session's ownership guard reuses the EXACT same
|
|
18
19
|
// assignment ⋈ liveness join / claim / supersede logic #290 already ships for the
|
|
19
20
|
// `assignment-provider` CLI, rather than reimplementing a second, parallel join (static ESM
|
|
@@ -2504,24 +2505,19 @@ function expectedGateProducer(flowId: string, stepId: string, expectationId: str
|
|
|
2504
2505
|
const actions = Array.isArray(manifest.flow_step_actions) ? manifest.flow_step_actions as AnyObj[] : [];
|
|
2505
2506
|
const action = actions.find((candidate) => candidate.flow_id === flowId && candidate.step_id === stepId);
|
|
2506
2507
|
if (!action) die(`record-gate-claim cannot derive a producer for unknown Flow step ${flowId}/${stepId}`);
|
|
2508
|
+
const binding = Array.isArray(action.expectation_bindings)
|
|
2509
|
+
? action.expectation_bindings.find((candidate): candidate is AnyObj => candidate && typeof candidate === "object" && !Array.isArray(candidate) && candidate.expectation_id === expectationId)
|
|
2510
|
+
: undefined;
|
|
2511
|
+
if (binding?.interface === "operation") {
|
|
2512
|
+
const operation = typeof binding.operation === "string" ? binding.operation : "the declared external operation";
|
|
2513
|
+
die(`record-gate-claim cannot satisfy operation-bound expectation ${expectationId}; ${operation} requires authenticated external ChangeProvider completion`);
|
|
2514
|
+
}
|
|
2507
2515
|
const skills = Array.isArray(action.skills) ? action.skills.filter((value: unknown): value is string => typeof value === "string") : [];
|
|
2508
2516
|
const roles = Array.isArray(manifest.skill_roles) ? manifest.skill_roles as AnyObj[] : [];
|
|
2509
2517
|
const owners = roles.filter((role) => typeof role.skill_id === "string"
|
|
2510
2518
|
&& Array.isArray(role.step_ids) && role.step_ids.includes(stepId)
|
|
2511
2519
|
&& Array.isArray(role.expectation_ids) && role.expectation_ids.includes(expectationId)
|
|
2512
2520
|
&& skills.includes(role.skill_id.replace(/^builder\./, "")));
|
|
2513
|
-
if (owners.length === 0) {
|
|
2514
|
-
const operations = Array.isArray(action.operations) ? action.operations.filter((value: unknown): value is string => typeof value === "string") : [];
|
|
2515
|
-
const operationExpectations = Array.isArray(action.expectation_ids) ? action.expectation_ids : [];
|
|
2516
|
-
if (operations.length === 1 && operationExpectations.includes(expectationId)) {
|
|
2517
|
-
const artifacts = Array.isArray(action.artifacts) ? action.artifacts.filter((value: unknown): value is string => typeof value === "string") : [];
|
|
2518
|
-
return {
|
|
2519
|
-
id: `operation:${operations[0]}`,
|
|
2520
|
-
artifactPatterns: artifacts.filter((value) => !value.includes("#")),
|
|
2521
|
-
selfProducedTrustSlices: artifacts.filter((value) => value.startsWith("trust.bundle#")).map((value) => value.slice("trust.bundle#".length)),
|
|
2522
|
-
};
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
2521
|
if (owners.length !== 1) die(`record-gate-claim cannot derive exactly one producer for ${flowId}/${stepId}/${expectationId}`);
|
|
2526
2522
|
const owner = owners[0]!;
|
|
2527
2523
|
const artifacts = (Array.isArray(owner.artifacts) ? owner.artifacts : []).filter((value): value is string => typeof value === "string" && value !== "ephemeral decision record");
|
|
@@ -2778,7 +2774,7 @@ function completePassingCriteria(existing: AnyObj[], raw: string[], observedComm
|
|
|
2778
2774
|
});
|
|
2779
2775
|
}
|
|
2780
2776
|
|
|
2781
|
-
const critiqueStatuses = new Set(
|
|
2777
|
+
const critiqueStatuses = new Set<string>(WORKFLOW_CRITIQUE_STATUSES);
|
|
2782
2778
|
const safeCritiqueId = /^[a-z][a-z0-9_-]*$/;
|
|
2783
2779
|
|
|
2784
2780
|
function normalizeCritiqueLanes(raw: string[]): AnyObj[] {
|
package/src/cli/workflow.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { validateDefinition } from "@kontourai/flow";
|
|
8
8
|
import { loadBuilderFlowRun } from "../builder-flow-run-adapter.js";
|
|
9
|
-
import {
|
|
9
|
+
import { parseKitFlowStepActions } from "../flow-kit/validate.js";
|
|
10
|
+
import { MAX_CONTINUATION_TURN_RESULT_BYTES, createFileContinuationStore, driveBuilderFlowSession, withContinuationDriverLock } from "../continuation-driver.js";
|
|
10
11
|
import { inspectBuilderFlowSession, recoverBuilderFlowSession, syncBuilderFlowSession } from "../builder-flow-runtime.js";
|
|
11
12
|
import { flowAgentsPackageRoot, flowAgentsPackageVersion } from "../lib/package-version.js";
|
|
12
13
|
import { pinnedFlowAgentsCommand } from "../lib/pinned-cli-command.js";
|
|
@@ -86,12 +87,14 @@ async function drive(sessionDir: string, argv: string[], json: boolean): Promise
|
|
|
86
87
|
assertOrdinaryMatchingAssignmentActor(sessionDir, slug);
|
|
87
88
|
const adapterCommand = loadContinuationAdapterCommand(adapterCommandFile);
|
|
88
89
|
let evidenceSigner: EvidenceSigner | null = null;
|
|
89
|
-
|
|
90
|
+
let observedAdapterTurns: Array<Record<string, unknown>> = [];
|
|
90
91
|
const result = await withContinuationDriverLock(sessionDir, async (lock) => {
|
|
91
92
|
assertOrdinaryMatchingAssignmentActor(sessionDir, slug);
|
|
92
93
|
evidenceSigner = evidenceSigningKeyFile ? consumeEvidenceSigningKey(evidenceSigningKeyFile) : null;
|
|
93
|
-
|
|
94
|
+
const continuationStore = createFileContinuationStore(sessionDir);
|
|
95
|
+
const outcome = await driveBuilderFlowSession({
|
|
94
96
|
sessionDir,
|
|
97
|
+
store: continuationStore,
|
|
95
98
|
maxTurns,
|
|
96
99
|
adapterCommandIdentity: adapterCommand.identity,
|
|
97
100
|
authorizeTurn: async () => { assertOrdinaryMatchingAssignmentActor(sessionDir, slug); },
|
|
@@ -119,18 +122,13 @@ async function drive(sessionDir: string, argv: string[], json: boolean): Promise
|
|
|
119
122
|
continuationTurnSecret: context?.continuationTurnSecret,
|
|
120
123
|
continuationRunId: context?.continuationRunId,
|
|
121
124
|
}),
|
|
122
|
-
...(evidenceSigningKeyFile ? {
|
|
123
|
-
|
|
124
|
-
request: structuredClone(request),
|
|
125
|
-
result: structuredClone(adapterResult),
|
|
126
|
-
}];
|
|
127
|
-
if (Buffer.byteLength(JSON.stringify(next), "utf8") > 1_048_576) {
|
|
128
|
-
throw new Error("continuation accepted-turn evidence must not exceed 1048576 aggregate bytes");
|
|
129
|
-
}
|
|
130
|
-
observedAdapterTurns.push(next.at(-1)!);
|
|
125
|
+
...(evidenceSigningKeyFile ? { preflightTurn: async (request) => {
|
|
126
|
+
assertAcceptedTurnEvidenceCapacity(attestationTurns(continuationStore.acceptedTurns()), request);
|
|
131
127
|
} } : {}),
|
|
132
128
|
waitForBarrier: async (barrier) => waitForContinuationBarrier(barrier, { maxWaitMs: barrierWaitMs, pollMs: barrierPollMs }),
|
|
133
129
|
});
|
|
130
|
+
if (evidenceSigningKeyFile) observedAdapterTurns = attestationTurns(continuationStore.acceptedTurns());
|
|
131
|
+
return outcome;
|
|
134
132
|
});
|
|
135
133
|
const output = evidenceSigner
|
|
136
134
|
? { ...result, evidence_attestation: signDriveEvidence(evidenceSigner, adapterCommand.identity, maxTurns, result, observedAdapterTurns) }
|
|
@@ -140,6 +138,23 @@ async function drive(sessionDir: string, argv: string[], json: boolean): Promise
|
|
|
140
138
|
return 0;
|
|
141
139
|
}
|
|
142
140
|
|
|
141
|
+
function attestationTurns(turns: Array<{ request: Record<string, unknown>; result: Record<string, unknown> }>): Array<Record<string, unknown>> {
|
|
142
|
+
const covered = turns.map((turn) => ({ request: structuredClone(turn.request), result: structuredClone(turn.result) }));
|
|
143
|
+
if (Buffer.byteLength(JSON.stringify(covered), "utf8") > 1_048_576) {
|
|
144
|
+
throw new Error("continuation accepted-turn evidence must not exceed 1048576 aggregate bytes");
|
|
145
|
+
}
|
|
146
|
+
return covered;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function assertAcceptedTurnEvidenceCapacity(observedTurns: Array<Record<string, unknown>>, request: Record<string, unknown>): void {
|
|
150
|
+
const base = [...observedTurns, { request: structuredClone(request), result: null }];
|
|
151
|
+
const bytesWithPlaceholder = Buffer.byteLength(JSON.stringify(base), "utf8");
|
|
152
|
+
const exactReservedBytes = bytesWithPlaceholder - Buffer.byteLength("null", "utf8") + MAX_CONTINUATION_TURN_RESULT_BYTES;
|
|
153
|
+
if (exactReservedBytes > 1_048_576) {
|
|
154
|
+
throw new Error("continuation accepted-turn evidence lacks capacity for another bounded result");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
143
158
|
type EvidenceSigner = { privateKey: KeyObject; publicKeySpkiB64: string };
|
|
144
159
|
|
|
145
160
|
function consumeEvidenceSigningKey(fileInput: string): EvidenceSigner {
|
|
@@ -328,6 +343,15 @@ async function evidence(sessionDir: string, argv: string[], json: boolean): Prom
|
|
|
328
343
|
if (Object.hasOwn(parsed.flags, "command") && commands.length === 0) {
|
|
329
344
|
throw new Error("workflow evidence --command requires a shell command value");
|
|
330
345
|
}
|
|
346
|
+
const expectation = flagString(parsed.flags, "expectation")!;
|
|
347
|
+
// Operation-bound expectations deliberately have no generic evidence writer.
|
|
348
|
+
// Check before recovery, locking, or actor resolution so a locally authored
|
|
349
|
+
// operation result cannot cause any canonical or projection mutation.
|
|
350
|
+
const inspected = await inspectBuilderFlowSession({ sessionDir });
|
|
351
|
+
const operation = builderOperationForExpectation(inspected.run.definitionId, expectation);
|
|
352
|
+
if (operation) {
|
|
353
|
+
throw new Error(`workflow evidence cannot satisfy operation-bound expectation ${expectation}; ${operation} requires authenticated external ChangeProvider completion`);
|
|
354
|
+
}
|
|
331
355
|
const requiresTestEvidence = flagString(parsed.flags, "expectation") === "tests-evidence" && flagString(parsed.flags, "status") === "pass";
|
|
332
356
|
// Argument and command-shape rejection must be read-only. Recovery below may
|
|
333
357
|
// repair stale projections, so it runs only after every command is accepted.
|
|
@@ -377,6 +401,18 @@ async function evidence(sessionDir: string, argv: string[], json: boolean): Prom
|
|
|
377
401
|
return 0;
|
|
378
402
|
}
|
|
379
403
|
|
|
404
|
+
function builderOperationForExpectation(flowId: string, expectationId: string): string | null {
|
|
405
|
+
const kit = readJsonFile(path.join(PACKAGE_ROOT, "kits", "builder", "kit.json"), "Builder kit metadata");
|
|
406
|
+
const parsed = parseKitFlowStepActions(kit, "kits/builder/kit.json");
|
|
407
|
+
if (parsed.errors.length) throw new Error(`Builder kit metadata is invalid: ${parsed.errors.join("; ")}`);
|
|
408
|
+
for (const action of parsed.entries) {
|
|
409
|
+
if (action.flow_id !== flowId) continue;
|
|
410
|
+
const binding = action.expectation_bindings.find((candidate) => candidate.expectation_id === expectationId);
|
|
411
|
+
if (binding?.interface === "operation") return binding.operation ?? "the declared external operation";
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
|
|
380
416
|
async function critique(sessionDir: string, argv: string[], json: boolean): Promise<number> {
|
|
381
417
|
const parsed = parseArgs(argv);
|
|
382
418
|
assertOnlyFlags(parsed.flags, new Set(["artifact-root", "session-dir", "json", "id", "verdict", "summary", "artifact-ref", "finding-json", "lane-json", "timestamp"]), "workflow critique");
|