@kontourai/flow-agents 3.6.0 → 3.8.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 +19 -0
- package/build/src/builder-flow-run-adapter.d.ts +22 -2
- package/build/src/builder-flow-run-adapter.js +93 -28
- package/build/src/builder-flow-runtime.d.ts +8 -3
- package/build/src/builder-flow-runtime.js +407 -51
- package/build/src/cli/assignment-provider.d.ts +4 -7
- package/build/src/cli/assignment-provider.js +80 -14
- package/build/src/cli/builder-run.js +14 -18
- package/build/src/cli/workflow-sidecar.d.ts +28 -4
- package/build/src/cli/workflow-sidecar.js +825 -103
- package/build/src/cli/workflow.js +301 -43
- package/build/src/flow-kit/validate.d.ts +17 -0
- package/build/src/flow-kit/validate.js +340 -2
- package/build/src/index.js +5 -5
- package/build/src/lib/observed-command.d.ts +7 -0
- package/build/src/lib/observed-command.js +100 -0
- package/build/src/tools/build-universal-bundles.js +53 -3
- package/build/src/tools/generate-context-map.js +5 -3
- package/context/scripts/hooks/lib/kit-catalog.js +1 -1
- package/context/scripts/hooks/stop-goal-fit.js +78 -9
- package/docs/context-map.md +22 -20
- package/docs/developer-architecture.md +1 -1
- package/docs/getting-started.md +9 -1
- package/docs/public-workflow-cli.md +76 -7
- package/docs/skills-map.md +51 -27
- package/docs/spec/builder-flow-runtime.md +75 -40
- package/docs/workflow-usage-guide.md +109 -42
- package/evals/fixtures/hook-influence/cases.json +2 -2
- package/evals/integration/test_builder_entry_enforcement.sh +52 -41
- package/evals/integration/test_builder_step_producers.sh +330 -6
- package/evals/integration/test_bundle_install.sh +318 -65
- package/evals/integration/test_bundle_lifecycle.sh +4 -6
- package/evals/integration/test_critique_supersession_roundtrip.sh +21 -8
- package/evals/integration/test_current_json_per_actor.sh +12 -0
- package/evals/integration/test_dual_emit_flow_step.sh +62 -43
- package/evals/integration/test_flowdef_session_activation.sh +145 -25
- package/evals/integration/test_flowdef_session_history_preservation.sh +23 -21
- package/evals/integration/test_gate_lockdown.sh +3 -5
- package/evals/integration/test_goal_fit_hook.sh +60 -2
- package/evals/integration/test_install_merge.sh +18 -8
- package/evals/integration/test_liveness_verdict.sh +14 -17
- package/evals/integration/test_phase_map_and_gate_claim.sh +63 -38
- package/evals/integration/test_public_workflow_cli.sh +334 -14
- package/evals/integration/test_pull_work_liveness_preflight.sh +22 -66
- package/evals/integration/test_sidecar_field_preservation.sh +36 -11
- package/evals/integration/test_workflow_sidecar_writer.sh +277 -44
- package/evals/integration/test_workflow_steering_hook.sh +15 -38
- package/evals/run.sh +2 -0
- package/evals/static/test_builder_skill_coherence.sh +247 -0
- package/evals/static/test_library_exports.sh +5 -2
- package/evals/static/test_universal_bundles.sh +44 -1
- package/evals/static/test_workflow_skills.sh +13 -325
- package/kits/builder/flows/build.flow.json +22 -0
- package/kits/builder/flows/shape.flow.json +9 -9
- package/kits/builder/kit.json +70 -16
- package/kits/builder/skills/builder-shape/SKILL.md +75 -75
- package/kits/builder/skills/continue-work/SKILL.md +45 -106
- package/kits/builder/skills/deliver/SKILL.md +96 -442
- package/kits/builder/skills/design-probe/SKILL.md +40 -139
- package/kits/builder/skills/evidence-gate/SKILL.md +59 -201
- package/kits/builder/skills/execute-plan/SKILL.md +54 -125
- package/kits/builder/skills/fix-bug/SKILL.md +42 -132
- package/kits/builder/skills/gate-review/SKILL.md +60 -211
- package/kits/builder/skills/idea-to-backlog/SKILL.md +63 -244
- package/kits/builder/skills/learning-review/SKILL.md +63 -170
- package/kits/builder/skills/pickup-probe/SKILL.md +54 -111
- package/kits/builder/skills/plan-work/SKILL.md +51 -185
- package/kits/builder/skills/pull-work/SKILL.md +136 -485
- package/kits/builder/skills/release-readiness/SKILL.md +66 -107
- package/kits/builder/skills/review-work/SKILL.md +89 -176
- package/kits/builder/skills/tdd-workflow/SKILL.md +53 -147
- package/kits/builder/skills/verify-work/SKILL.md +101 -113
- package/package.json +2 -2
- package/packaging/README.md +1 -1
- package/scripts/README.md +1 -1
- package/scripts/hooks/lib/kit-catalog.js +1 -1
- package/scripts/hooks/stop-goal-fit.js +78 -9
- package/scripts/install-codex-home.sh +63 -23
- package/scripts/install-owned-files.js +18 -2
- package/src/builder-flow-run-adapter.ts +118 -32
- package/src/builder-flow-runtime.ts +426 -64
- package/src/cli/assignment-provider-lock.test.mjs +83 -0
- package/src/cli/assignment-provider.ts +75 -14
- package/src/cli/builder-flow-run-adapter.test.mjs +4 -2
- package/src/cli/builder-flow-runtime.test.mjs +436 -18
- package/src/cli/builder-run.ts +3 -9
- package/src/cli/kit-metadata-security.test.mjs +232 -6
- package/src/cli/public-api.test.mjs +15 -0
- package/src/cli/workflow-sidecar-execution-proof.test.mjs +90 -0
- package/src/cli/workflow-sidecar.ts +746 -103
- package/src/cli/workflow.ts +288 -41
- package/src/flow-kit/validate.ts +320 -2
- package/src/index.ts +6 -5
- package/src/lib/observed-command.ts +96 -0
- package/src/tools/build-universal-bundles.ts +51 -3
- package/src/tools/generate-context-map.ts +5 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { isDeepStrictEqual } from "node:util";
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
normalizeTrustBundle,
|
|
13
13
|
openGates,
|
|
14
14
|
pauseRun,
|
|
15
|
-
readJson,
|
|
16
15
|
resumeRun,
|
|
17
16
|
startRun,
|
|
18
17
|
validateDefinition,
|
|
@@ -26,6 +25,9 @@ import { resolveEffectiveFlowDefinition } from "./lib/flow-resolver.js";
|
|
|
26
25
|
|
|
27
26
|
export const BUILDER_BUILD_FLOW_ID = "builder.build";
|
|
28
27
|
export const BUILDER_BUILD_FLOW_RELATIVE_PATH = "kits/builder/flows/build.flow.json";
|
|
28
|
+
export const BUILDER_SHAPE_FLOW_ID = "builder.shape";
|
|
29
|
+
export const BUILDER_SHAPE_FLOW_RELATIVE_PATH = "kits/builder/flows/shape.flow.json";
|
|
30
|
+
export type BuilderFlowId = typeof BUILDER_BUILD_FLOW_ID | typeof BUILDER_SHAPE_FLOW_ID;
|
|
29
31
|
|
|
30
32
|
export interface BuilderBuildTrustBundleEvidenceInput {
|
|
31
33
|
gate: string;
|
|
@@ -34,6 +36,8 @@ export interface BuilderBuildTrustBundleEvidenceInput {
|
|
|
34
36
|
* Callers must not pass raw user-controlled paths to this local runtime API.
|
|
35
37
|
*/
|
|
36
38
|
file: string;
|
|
39
|
+
/** SHA-256 of the immutable snapshot validated by Flow Agents before Flow attaches it. */
|
|
40
|
+
expectedSha256?: string;
|
|
37
41
|
status?: "passed" | "failed";
|
|
38
42
|
producer?: string;
|
|
39
43
|
authorityTrace?: string;
|
|
@@ -56,6 +60,10 @@ export interface StartBuilderBuildRunInput {
|
|
|
56
60
|
runId?: string;
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
export interface StartBuilderFlowRunInput extends StartBuilderBuildRunInput {
|
|
64
|
+
flowId: BuilderFlowId;
|
|
65
|
+
}
|
|
66
|
+
|
|
59
67
|
export interface EvaluateBuilderBuildRunInput {
|
|
60
68
|
runId: string;
|
|
61
69
|
/**
|
|
@@ -76,8 +84,8 @@ export interface ChangeBuilderBuildRunLifecycleInput extends LoadBuilderBuildRun
|
|
|
76
84
|
at?: string;
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
export interface
|
|
80
|
-
definitionId:
|
|
87
|
+
export interface BuilderFlowRunResult {
|
|
88
|
+
definitionId: BuilderFlowId;
|
|
81
89
|
definitionVersion: string;
|
|
82
90
|
runId: string;
|
|
83
91
|
dir: string;
|
|
@@ -88,6 +96,10 @@ export interface BuilderBuildRunResult {
|
|
|
88
96
|
freshnessTransitions: JsonObject[];
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
export interface BuilderBuildRunResult extends Omit<BuilderFlowRunResult, "definitionId"> {
|
|
100
|
+
definitionId: typeof BUILDER_BUILD_FLOW_ID;
|
|
101
|
+
}
|
|
102
|
+
|
|
91
103
|
export type BuilderBuildRunIdentityMismatch = "definition-id" | "definition-version" | "definition-content";
|
|
92
104
|
|
|
93
105
|
export class BuilderBuildRunInputError extends Error {
|
|
@@ -128,20 +140,29 @@ export class BuilderBuildRunIdentityError extends Error {
|
|
|
128
140
|
}
|
|
129
141
|
|
|
130
142
|
export function resolveBuilderBuildFlowDefinitionPath(startDir = moduleDirectory()): string {
|
|
131
|
-
|
|
132
|
-
return path.join(root, BUILDER_BUILD_FLOW_RELATIVE_PATH);
|
|
143
|
+
return resolveBuilderFlowDefinitionPath(BUILDER_BUILD_FLOW_ID, startDir);
|
|
133
144
|
}
|
|
134
145
|
|
|
135
146
|
export async function startBuilderBuildRun(input: StartBuilderBuildRunInput): Promise<BuilderBuildRunResult> {
|
|
147
|
+
const result = await startBuilderFlowRun({ ...input, flowId: BUILDER_BUILD_FLOW_ID });
|
|
148
|
+
return asBuilderBuildResult(result, input.runId ?? result.runId);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function resolveBuilderFlowDefinitionPath(flowId: BuilderFlowId, startDir = moduleDirectory()): string {
|
|
152
|
+
const root = findPackageRoot(startDir);
|
|
153
|
+
return path.join(root, flowRelativePath(flowId));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function startBuilderFlowRun(input: StartBuilderFlowRunInput): Promise<BuilderFlowRunResult> {
|
|
136
157
|
assertRuntimeInput(input, ["evidence", "now", "gate"]);
|
|
137
158
|
if (!isNonEmptyString(input.subject)) {
|
|
138
159
|
throw new BuilderBuildRunInputError("subject", "must be a non-empty string");
|
|
139
160
|
}
|
|
140
161
|
|
|
141
162
|
const cwd = input.cwd ?? process.cwd();
|
|
142
|
-
const definitionPath =
|
|
143
|
-
const definition = await
|
|
144
|
-
const runtimeDefinitionPath = materializeRuntimeDefinition(cwd, definition);
|
|
163
|
+
const definitionPath = resolveBuilderFlowDefinitionPath(input.flowId);
|
|
164
|
+
const definition = await loadShippedBuilderFlowDefinition(input.flowId, definitionPath);
|
|
165
|
+
const runtimeDefinitionPath = materializeRuntimeDefinition(cwd, input.flowId, definition);
|
|
145
166
|
const started = await startRun(runtimeDefinitionPath, {
|
|
146
167
|
cwd,
|
|
147
168
|
runId: input.runId,
|
|
@@ -150,12 +171,17 @@ export async function startBuilderBuildRun(input: StartBuilderBuildRunInput): Pr
|
|
|
150
171
|
subject: input.subject,
|
|
151
172
|
},
|
|
152
173
|
});
|
|
153
|
-
const run = await
|
|
174
|
+
const run = await loadCanonicalBuilderFlowRun(started.runId, cwd, definition);
|
|
154
175
|
|
|
155
176
|
return resultFromRun(run, started.runId);
|
|
156
177
|
}
|
|
157
178
|
|
|
158
179
|
export async function evaluateBuilderBuildRun(input: EvaluateBuilderBuildRunInput): Promise<BuilderBuildRunResult> {
|
|
180
|
+
const result = await evaluateBuilderFlowRun(input);
|
|
181
|
+
return asBuilderBuildResult(result, input.runId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function evaluateBuilderFlowRun(input: EvaluateBuilderBuildRunInput): Promise<BuilderFlowRunResult> {
|
|
159
185
|
assertRuntimeInput(input, ["now", "gate"]);
|
|
160
186
|
if (Array.isArray(input.evidence)) {
|
|
161
187
|
throw new BuilderBuildRunInputError("evidence", "must be zero or one evidence object, not an array");
|
|
@@ -163,16 +189,26 @@ export async function evaluateBuilderBuildRun(input: EvaluateBuilderBuildRunInpu
|
|
|
163
189
|
|
|
164
190
|
const cwd = input.cwd ?? process.cwd();
|
|
165
191
|
const run = await loadRun(input.runId, cwd);
|
|
166
|
-
const definition = await
|
|
192
|
+
const definition = await loadShippedBuilderFlowDefinitionForRun(input.runId, run.definition);
|
|
167
193
|
assertCanonicalDefinition(input.runId, definition, run.definition);
|
|
168
194
|
|
|
169
195
|
let attachedEvidence: FlowEvidenceEntry[] = [];
|
|
170
196
|
if (input.evidence !== undefined) {
|
|
171
197
|
const evidence = validateEvidenceInput(input.evidence);
|
|
172
198
|
assertCurrentOpenGate(run.definition, run.state, evidence.gate);
|
|
173
|
-
const
|
|
199
|
+
const source = path.resolve(cwd, evidence.file);
|
|
200
|
+
const bytes = readFileSync(source);
|
|
201
|
+
const validatedSha256 = createHash("sha256").update(bytes).digest("hex");
|
|
202
|
+
if (evidence.expectedSha256 && evidence.expectedSha256 !== validatedSha256) {
|
|
203
|
+
throw new BuilderBuildRunInputError("evidence.expectedSha256", "does not match the bytes presented for validation");
|
|
204
|
+
}
|
|
205
|
+
const normalized = normalizeTrustBundle(JSON.parse(bytes.toString("utf8")));
|
|
174
206
|
assertBundleSubjects(normalized.bundle, run.state.subject, openGates(run.definition, run.state)[0]);
|
|
175
|
-
|
|
207
|
+
const attached = await attachEvidence(input.runId, trustBundleAttachOptions(cwd, evidence, validatedSha256));
|
|
208
|
+
if (attached.sha256 !== validatedSha256) {
|
|
209
|
+
throw new BuilderBuildRunInputError("evidence.file", "changed after validation before Flow attachment");
|
|
210
|
+
}
|
|
211
|
+
attachedEvidence = [attached];
|
|
176
212
|
}
|
|
177
213
|
|
|
178
214
|
const evaluated = await evaluateRun(input.runId, { cwd });
|
|
@@ -190,45 +226,65 @@ export async function evaluateBuilderBuildRun(input: EvaluateBuilderBuildRunInpu
|
|
|
190
226
|
}
|
|
191
227
|
|
|
192
228
|
export async function loadBuilderBuildRun(input: LoadBuilderBuildRunInput): Promise<BuilderBuildRunResult> {
|
|
229
|
+
const result = await loadBuilderFlowRun(input);
|
|
230
|
+
return asBuilderBuildResult(result, input.runId);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function loadBuilderFlowRun(input: LoadBuilderBuildRunInput): Promise<BuilderFlowRunResult> {
|
|
193
234
|
assertRuntimeInput(input, ["evidence", "now", "gate"]);
|
|
194
235
|
const cwd = input.cwd ?? process.cwd();
|
|
195
236
|
const run = await loadRun(input.runId, cwd);
|
|
196
|
-
const definition = await
|
|
237
|
+
const definition = await loadShippedBuilderFlowDefinitionForRun(input.runId, run.definition);
|
|
197
238
|
assertCanonicalDefinition(input.runId, definition, run.definition);
|
|
198
239
|
return resultFromRun(run, input.runId);
|
|
199
240
|
}
|
|
200
241
|
|
|
201
242
|
export async function pauseBuilderBuildRun(input: ChangeBuilderBuildRunLifecycleInput): Promise<BuilderBuildRunResult> {
|
|
202
|
-
|
|
243
|
+
const result = await pauseBuilderFlowRun(input);
|
|
244
|
+
return asBuilderBuildResult(result, input.runId);
|
|
203
245
|
}
|
|
204
246
|
|
|
205
247
|
export async function resumeBuilderBuildRun(input: ChangeBuilderBuildRunLifecycleInput): Promise<BuilderBuildRunResult> {
|
|
206
|
-
|
|
248
|
+
const result = await resumeBuilderFlowRun(input);
|
|
249
|
+
return asBuilderBuildResult(result, input.runId);
|
|
207
250
|
}
|
|
208
251
|
|
|
209
252
|
export async function cancelBuilderBuildRun(input: ChangeBuilderBuildRunLifecycleInput): Promise<BuilderBuildRunResult & { idempotent: boolean }> {
|
|
210
|
-
const changed = await
|
|
253
|
+
const changed = await changeBuilderFlowRunLifecycleResult(input, cancelRun);
|
|
254
|
+
return { ...asBuilderBuildResult(resultFromRun(changed, input.runId), input.runId), idempotent: changed.idempotent };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function pauseBuilderFlowRun(input: ChangeBuilderBuildRunLifecycleInput): Promise<BuilderFlowRunResult> {
|
|
258
|
+
return changeBuilderFlowRunLifecycle(input, pauseRun);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export async function resumeBuilderFlowRun(input: ChangeBuilderBuildRunLifecycleInput): Promise<BuilderFlowRunResult> {
|
|
262
|
+
return changeBuilderFlowRunLifecycle(input, resumeRun);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export async function cancelBuilderFlowRun(input: ChangeBuilderBuildRunLifecycleInput): Promise<BuilderFlowRunResult & { idempotent: boolean }> {
|
|
266
|
+
const changed = await changeBuilderFlowRunLifecycleResult(input, cancelRun);
|
|
211
267
|
return { ...resultFromRun(changed, input.runId), idempotent: changed.idempotent };
|
|
212
268
|
}
|
|
213
269
|
|
|
214
|
-
async function
|
|
270
|
+
async function changeBuilderFlowRunLifecycle(
|
|
215
271
|
input: ChangeBuilderBuildRunLifecycleInput,
|
|
216
272
|
operation: typeof pauseRun | typeof resumeRun,
|
|
217
|
-
): Promise<
|
|
218
|
-
const changed = await
|
|
273
|
+
): Promise<BuilderFlowRunResult> {
|
|
274
|
+
const changed = await changeBuilderFlowRunLifecycleResult(input, operation);
|
|
219
275
|
return resultFromRun(changed, input.runId);
|
|
220
276
|
}
|
|
221
277
|
|
|
222
|
-
async function
|
|
278
|
+
async function changeBuilderFlowRunLifecycleResult(
|
|
223
279
|
input: ChangeBuilderBuildRunLifecycleInput,
|
|
224
280
|
operation: typeof pauseRun | typeof resumeRun | typeof cancelRun,
|
|
225
281
|
) {
|
|
226
282
|
assertRuntimeInput(input, []);
|
|
227
283
|
if (!isRecord(input.request)) throw new BuilderBuildRunInputError("request", "must be a lifecycle request object");
|
|
228
284
|
const cwd = input.cwd ?? process.cwd();
|
|
229
|
-
const before = await
|
|
285
|
+
const before = await loadBuilderFlowRun({ runId: input.runId, cwd });
|
|
230
286
|
const changed = await operation(input.runId, { cwd, ...input.request, ...(input.at ? { at: input.at } : {}) });
|
|
231
|
-
const definition = await
|
|
287
|
+
const definition = await loadShippedBuilderFlowDefinitionForRun(input.runId, changed.definition);
|
|
232
288
|
assertCanonicalDefinition(input.runId, definition, changed.definition);
|
|
233
289
|
if (changed.state.subject !== before.state.subject) {
|
|
234
290
|
throw new BuilderBuildRunInputError("flow_run.state.subject", "changed during lifecycle transition");
|
|
@@ -236,7 +292,7 @@ async function changeBuilderBuildRunLifecycleResult(
|
|
|
236
292
|
return changed;
|
|
237
293
|
}
|
|
238
294
|
|
|
239
|
-
function resultFromRun(run: Awaited<ReturnType<typeof loadRun>>, runId: string):
|
|
295
|
+
function resultFromRun(run: Awaited<ReturnType<typeof loadRun>>, runId: string): BuilderFlowRunResult {
|
|
240
296
|
return {
|
|
241
297
|
definitionId: run.definition.id,
|
|
242
298
|
definitionVersion: run.definition.version,
|
|
@@ -250,7 +306,7 @@ function resultFromRun(run: Awaited<ReturnType<typeof loadRun>>, runId: string):
|
|
|
250
306
|
};
|
|
251
307
|
}
|
|
252
308
|
|
|
253
|
-
async function
|
|
309
|
+
async function loadCanonicalBuilderFlowRun(
|
|
254
310
|
runId: string,
|
|
255
311
|
cwd: string,
|
|
256
312
|
definition: { id: string; version: string },
|
|
@@ -260,29 +316,55 @@ async function loadCanonicalBuilderBuildRun(
|
|
|
260
316
|
return run;
|
|
261
317
|
}
|
|
262
318
|
|
|
263
|
-
async function
|
|
319
|
+
async function loadShippedBuilderFlowDefinition(flowId: BuilderFlowId, definitionPath: string): Promise<{ id: string; version: string }> {
|
|
264
320
|
const packageRoot = findPackageRoot(path.dirname(definitionPath));
|
|
265
|
-
const effective = resolveEffectiveFlowDefinition(
|
|
321
|
+
const effective = resolveEffectiveFlowDefinition(flowId, packageRoot, { allowOverride: false });
|
|
266
322
|
if (!effective) {
|
|
267
323
|
throw new BuilderBuildRunInputError("definition", "could not compile the shipped uses_flow composition");
|
|
268
324
|
}
|
|
269
325
|
const definition = validateDefinition(effective);
|
|
270
|
-
if (definition.id !==
|
|
271
|
-
throw new BuilderBuildRunInputError("definition", `expected shipped definition id ${
|
|
326
|
+
if (definition.id !== flowId) {
|
|
327
|
+
throw new BuilderBuildRunInputError("definition", `expected shipped definition id ${flowId}`);
|
|
272
328
|
}
|
|
273
329
|
return definition;
|
|
274
330
|
}
|
|
275
331
|
|
|
276
|
-
function
|
|
332
|
+
async function loadShippedBuilderFlowDefinitionForRun(runId: string, actualDefinition: { id: string; version: string }): Promise<{ id: string; version: string }> {
|
|
333
|
+
const flowId = actualDefinition.id;
|
|
334
|
+
if (!isBuilderFlowId(flowId)) {
|
|
335
|
+
throw new BuilderBuildRunIdentityError(runId, { id: BUILDER_BUILD_FLOW_ID, version: "unknown" }, actualDefinition, "definition-id");
|
|
336
|
+
}
|
|
337
|
+
return loadShippedBuilderFlowDefinition(flowId, resolveBuilderFlowDefinitionPath(flowId));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function materializeRuntimeDefinition(cwd: string, flowId: BuilderFlowId, definition: unknown): string {
|
|
277
341
|
const content = `${JSON.stringify(definition, null, 2)}\n`;
|
|
278
342
|
const digest = createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
279
343
|
const directory = path.join(cwd, ".kontourai", "flow-agents", "runtime-definitions");
|
|
280
344
|
mkdirSync(directory, { recursive: true });
|
|
281
|
-
const file = path.join(directory,
|
|
345
|
+
const file = path.join(directory, `${flowId.replace(".", "-")}-${digest}.flow.json`);
|
|
282
346
|
if (!existsSync(file)) writeFileSync(file, content);
|
|
283
347
|
return file;
|
|
284
348
|
}
|
|
285
349
|
|
|
350
|
+
function flowRelativePath(flowId: BuilderFlowId): string {
|
|
351
|
+
return flowId === BUILDER_BUILD_FLOW_ID ? BUILDER_BUILD_FLOW_RELATIVE_PATH : BUILDER_SHAPE_FLOW_RELATIVE_PATH;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function isBuilderFlowId(value: string): value is BuilderFlowId {
|
|
355
|
+
return value === BUILDER_BUILD_FLOW_ID || value === BUILDER_SHAPE_FLOW_ID;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function assertExpectedFlow(runId: string, actual: BuilderFlowId, expected: BuilderFlowId): void {
|
|
359
|
+
if (actual === expected) return;
|
|
360
|
+
throw new BuilderBuildRunIdentityError(runId, { id: expected, version: "unknown" }, { id: actual, version: "unknown" }, "definition-id");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function asBuilderBuildResult(result: BuilderFlowRunResult, runId: string): BuilderBuildRunResult {
|
|
364
|
+
assertExpectedFlow(runId, result.definitionId, BUILDER_BUILD_FLOW_ID);
|
|
365
|
+
return result as BuilderBuildRunResult;
|
|
366
|
+
}
|
|
367
|
+
|
|
286
368
|
function assertCanonicalDefinition(
|
|
287
369
|
runId: string,
|
|
288
370
|
expectedDefinition: { id: string; version: string },
|
|
@@ -319,6 +401,9 @@ function validateEvidenceInput(evidence: unknown): BuilderBuildTrustBundleEviden
|
|
|
319
401
|
if (!isNonEmptyString(evidence.file)) {
|
|
320
402
|
throw new BuilderBuildRunInputError("evidence.file", "must be a non-empty string");
|
|
321
403
|
}
|
|
404
|
+
if (evidence.expectedSha256 !== undefined && (!isNonEmptyString(evidence.expectedSha256) || !/^[a-f0-9]{64}$/i.test(evidence.expectedSha256))) {
|
|
405
|
+
throw new BuilderBuildRunInputError("evidence.expectedSha256", "must be a SHA-256 hex digest");
|
|
406
|
+
}
|
|
322
407
|
if (evidence.status !== undefined && evidence.status !== "passed" && evidence.status !== "failed") {
|
|
323
408
|
throw new BuilderBuildRunInputError("evidence.status", "must be passed or failed");
|
|
324
409
|
}
|
|
@@ -358,11 +443,12 @@ function assertBundleSubjects(bundle: unknown, subject: string, gate: unknown):
|
|
|
358
443
|
}
|
|
359
444
|
}
|
|
360
445
|
|
|
361
|
-
function trustBundleAttachOptions(cwd: string, evidence: BuilderBuildTrustBundleEvidenceInput): JsonObject {
|
|
446
|
+
function trustBundleAttachOptions(cwd: string, evidence: BuilderBuildTrustBundleEvidenceInput, expectedSha256: string): JsonObject {
|
|
362
447
|
return {
|
|
363
448
|
cwd,
|
|
364
449
|
gate: evidence.gate,
|
|
365
450
|
file: evidence.file,
|
|
451
|
+
expectedSha256,
|
|
366
452
|
kind: "trust.bundle",
|
|
367
453
|
bundle: true,
|
|
368
454
|
...(evidence.status ? { status: evidence.status } : {}),
|