@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
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { evaluateGate, expectationsForGate, openGates, type FlowExpectation, type FlowGate, type FlowRunState } from "@kontourai/flow";
|
|
6
|
+
import { parseKitFlowStepActions, type KitFlowStepActionEntry, type KitFlowStepExpectationBinding } from "./flow-kit/validate.js";
|
|
7
|
+
import { PUBLIC_OPERATION_CONTRACTS, WORKFLOW_CRITIQUE_STATUSES } from "./cli/public-contracts.js";
|
|
8
|
+
import { flowAgentsPackageRoot } from "./lib/package-version.js";
|
|
9
|
+
import { pinnedFlowAgentsCommand } from "./lib/pinned-cli-command.js";
|
|
10
|
+
|
|
11
|
+
const MAX_METADATA_BYTES = 1_048_576;
|
|
12
|
+
const MAX_ENVELOPE_BYTES = 65_536;
|
|
13
|
+
const MAX_SKILLS = 16;
|
|
14
|
+
const MAX_REQUIREMENTS = 32;
|
|
15
|
+
const MAX_ARTIFACTS = 32;
|
|
16
|
+
const MAX_CANONICAL_EVIDENCE = 256;
|
|
17
|
+
const MAX_OBSERVED_ARTIFACTS = 128;
|
|
18
|
+
const MAX_OBSERVED_ARTIFACT_BYTES = 8 * 1_048_576;
|
|
19
|
+
const CONTROL_ARTIFACTS = new Set(["state.json"]);
|
|
20
|
+
|
|
21
|
+
type AnyRecord = Record<string, unknown>;
|
|
22
|
+
|
|
23
|
+
export type GateActionInterfaceParameter = {
|
|
24
|
+
name: string;
|
|
25
|
+
flag: string;
|
|
26
|
+
required: boolean;
|
|
27
|
+
allowed_values?: string[];
|
|
28
|
+
repeatable?: boolean;
|
|
29
|
+
required_when?: { parameter: string; equals: string };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type GateActionWorkflowMutation = {
|
|
33
|
+
expectation_id: string;
|
|
34
|
+
interface: "workflow.evidence" | "workflow.critique";
|
|
35
|
+
package: { name: "@kontourai/flow-agents"; version: string };
|
|
36
|
+
command: "flow-agents";
|
|
37
|
+
argv: string[];
|
|
38
|
+
parameters: GateActionInterfaceParameter[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type GateActionPublicMutation =
|
|
42
|
+
| GateActionWorkflowMutation
|
|
43
|
+
| {
|
|
44
|
+
expectation_id: string;
|
|
45
|
+
interface: "operation";
|
|
46
|
+
operation: string;
|
|
47
|
+
protocol: (typeof PUBLIC_OPERATION_CONTRACTS)[keyof typeof PUBLIC_OPERATION_CONTRACTS];
|
|
48
|
+
completion: {
|
|
49
|
+
status: "external_verification_required";
|
|
50
|
+
executable_by_flow_agents: false;
|
|
51
|
+
gate_evidence_interface: null;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type GateActionEnvelope = {
|
|
56
|
+
schema_version: "1.0";
|
|
57
|
+
flow: {
|
|
58
|
+
run_id: string;
|
|
59
|
+
definition_id: string;
|
|
60
|
+
definition_version: string;
|
|
61
|
+
status: string;
|
|
62
|
+
current_step: string;
|
|
63
|
+
gate_ids: string[];
|
|
64
|
+
};
|
|
65
|
+
gate: {
|
|
66
|
+
requirements: Array<{
|
|
67
|
+
id: string;
|
|
68
|
+
required: boolean;
|
|
69
|
+
description: string;
|
|
70
|
+
claim_type: string;
|
|
71
|
+
subject_type: string | null;
|
|
72
|
+
status: "satisfied" | "accepted_exception" | "unresolved";
|
|
73
|
+
}>;
|
|
74
|
+
unresolved_requirement_ids: string[];
|
|
75
|
+
accepted_exceptions: Array<{ gate_id: string; exception_id: string }>;
|
|
76
|
+
};
|
|
77
|
+
action: {
|
|
78
|
+
skills: Array<{
|
|
79
|
+
id: string;
|
|
80
|
+
package: { name: "@kontourai/flow-agents"; version: string };
|
|
81
|
+
/** Stable package-relative source path, bound to package version and SHA-256. */
|
|
82
|
+
path: string;
|
|
83
|
+
sha256: string;
|
|
84
|
+
}>;
|
|
85
|
+
operations: string[];
|
|
86
|
+
declared_artifacts: string[];
|
|
87
|
+
declared_evidence: string[];
|
|
88
|
+
implementation_allowed: boolean;
|
|
89
|
+
};
|
|
90
|
+
public_interfaces: {
|
|
91
|
+
status: { command: string };
|
|
92
|
+
mutations: GateActionPublicMutation[];
|
|
93
|
+
};
|
|
94
|
+
stop_condition: {
|
|
95
|
+
kind: "one_turn";
|
|
96
|
+
scope: {
|
|
97
|
+
run_id: string;
|
|
98
|
+
current_step: string;
|
|
99
|
+
gate_ids: string[];
|
|
100
|
+
current_gate_only: true;
|
|
101
|
+
};
|
|
102
|
+
required: {
|
|
103
|
+
skill_ids: string[];
|
|
104
|
+
artifact_refs: string[];
|
|
105
|
+
unresolved_evidence_ids: string[];
|
|
106
|
+
};
|
|
107
|
+
sequence: ["activate_required_skills", "produce_declared_artifacts", "record_bound_evidence", "synchronize_canonical_flow", "return_adapter_result"];
|
|
108
|
+
after: "return_adapter_result";
|
|
109
|
+
synchronize_canonical_flow: true;
|
|
110
|
+
adapter_evidence_is_gate_evidence: false;
|
|
111
|
+
external_capability?: {
|
|
112
|
+
status: "waiting";
|
|
113
|
+
operation: string;
|
|
114
|
+
capability: string;
|
|
115
|
+
completion: "external_verification_required";
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
progress: {
|
|
119
|
+
canonical_evidence: string[];
|
|
120
|
+
observed_artifacts: string[];
|
|
121
|
+
prior_turn?: GateActionPriorProgress;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export type GateActionProgressSnapshot = Pick<GateActionEnvelope["progress"], "canonical_evidence" | "observed_artifacts"> & {
|
|
126
|
+
current_step: string;
|
|
127
|
+
/** Optional for compatibility with snapshots persisted before terminal status capture. */
|
|
128
|
+
canonical_status?: string;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export type GateActionPriorProgress = {
|
|
132
|
+
step_advanced: boolean;
|
|
133
|
+
evidence_added: string[];
|
|
134
|
+
artifact_changes: string[];
|
|
135
|
+
no_progress: boolean;
|
|
136
|
+
consecutive_no_progress: number;
|
|
137
|
+
stagnation: "none" | "possible" | "stagnant";
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type BuilderGateActionEnvelopeInput = {
|
|
141
|
+
sessionDir: string;
|
|
142
|
+
projectRoot: string;
|
|
143
|
+
run: {
|
|
144
|
+
runId: string;
|
|
145
|
+
definitionId: string;
|
|
146
|
+
definitionVersion: string;
|
|
147
|
+
state: FlowRunState;
|
|
148
|
+
manifest: AnyRecord;
|
|
149
|
+
config: AnyRecord;
|
|
150
|
+
};
|
|
151
|
+
definition: AnyRecord;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
type LoadedGateAction = {
|
|
155
|
+
packageRoot: string;
|
|
156
|
+
packageVersion: string;
|
|
157
|
+
kit: AnyRecord;
|
|
158
|
+
action: KitFlowStepActionEntry;
|
|
159
|
+
actions: KitFlowStepActionEntry[];
|
|
160
|
+
actionableArtifacts: string[];
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
type DerivedGateRequirements = {
|
|
164
|
+
gates: Array<FlowGate & { id: string }>;
|
|
165
|
+
requirements: GateActionEnvelope["gate"]["requirements"];
|
|
166
|
+
unresolved: string[];
|
|
167
|
+
unresolvedRequired: string[];
|
|
168
|
+
acceptedExceptions: GateActionEnvelope["gate"]["accepted_exceptions"];
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/** Derive bounded adapter context without trusting adapter telemetry. */
|
|
172
|
+
export function deriveBuilderGateActionEnvelope(input: BuilderGateActionEnvelopeInput): GateActionEnvelope {
|
|
173
|
+
const loaded = loadGateAction(input);
|
|
174
|
+
const derived = deriveFlowRequirements(input, loaded.action);
|
|
175
|
+
const envelope = assembleGateActionEnvelope(input, loaded, derived);
|
|
176
|
+
assertBoundedEnvelope(envelope);
|
|
177
|
+
return envelope;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Canonical progress is useful after a run becomes terminal, when no adapter
|
|
182
|
+
* action envelope may be emitted. Keep this deliberately separate from the
|
|
183
|
+
* request-only envelope so terminal snapshots cannot be mistaken for work.
|
|
184
|
+
*/
|
|
185
|
+
export function deriveBuilderGateActionProgressSnapshot(input: BuilderGateActionEnvelopeInput): GateActionProgressSnapshot {
|
|
186
|
+
const { actions } = loadGateAction(input);
|
|
187
|
+
return {
|
|
188
|
+
current_step: input.run.state.current_step,
|
|
189
|
+
canonical_status: input.run.state.status,
|
|
190
|
+
canonical_evidence: canonicalEvidence(input.run.manifest),
|
|
191
|
+
observed_artifacts: observeBuilderArtifactsForProgress(input.sessionDir, actions.flatMap((entry) => entry.artifacts)),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function loadGateAction(input: BuilderGateActionEnvelopeInput): LoadedGateAction {
|
|
196
|
+
const packageRoot = flowAgentsPackageRoot();
|
|
197
|
+
const packageMetadata = readBoundedJson(packageRoot, "package.json", "flow-agents package metadata");
|
|
198
|
+
if (typeof packageMetadata.version !== "string" || !packageMetadata.version) throw new Error("flow-agents package metadata has no version");
|
|
199
|
+
const packageVersion = packageMetadata.version;
|
|
200
|
+
const kit = readBoundedJson(packageRoot, path.join("kits", "builder", "kit.json"), "Builder kit metadata");
|
|
201
|
+
const parsed = parseKitFlowStepActions(kit, "kits/builder/kit.json");
|
|
202
|
+
if (parsed.errors.length) throw new Error(`Builder gate-action metadata is invalid: ${parsed.errors.join("; ")}`);
|
|
203
|
+
const actions = parsed.entries.filter((entry) => entry.flow_id === input.run.definitionId);
|
|
204
|
+
const selected = actions.filter((entry) => entry.step_id === input.run.state.current_step);
|
|
205
|
+
if (selected.length !== 1) throw new Error(`Builder gate-action metadata must declare exactly one action for ${input.run.definitionId}/${input.run.state.current_step}`);
|
|
206
|
+
const action = selected[0]!;
|
|
207
|
+
const actionableArtifacts = action.artifacts.filter((artifact) => !isControlArtifact(artifact));
|
|
208
|
+
if (action.skills.length > MAX_SKILLS || action.artifacts.length > MAX_ARTIFACTS) {
|
|
209
|
+
throw new Error("Builder gate-action metadata exceeds the supported envelope bound");
|
|
210
|
+
}
|
|
211
|
+
return { packageRoot, packageVersion, kit, action, actions, actionableArtifacts };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function deriveFlowRequirements(input: BuilderGateActionEnvelopeInput, action: KitFlowStepActionEntry): DerivedGateRequirements {
|
|
215
|
+
const gates = openGates(input.definition, input.run.state) as Array<FlowGate & { id: string }>;
|
|
216
|
+
const acceptedExceptions: GateActionEnvelope["gate"]["accepted_exceptions"] = [];
|
|
217
|
+
const requirements = gates.flatMap((gate) => {
|
|
218
|
+
const outcome = evaluateGate(input.definition, input.run.state, input.run.manifest, gate.id, input.run.config);
|
|
219
|
+
if (typeof outcome.accepted_exception_id === "string") {
|
|
220
|
+
acceptedExceptions.push({ gate_id: gate.id, exception_id: outcome.accepted_exception_id });
|
|
221
|
+
}
|
|
222
|
+
const matched = new Set((Array.isArray(outcome.matched_expectations) ? outcome.matched_expectations : [])
|
|
223
|
+
.flatMap((entry) => isRecord(entry) && typeof entry.expectation_id === "string" ? [entry.expectation_id] : []));
|
|
224
|
+
return (expectationsForGate(gate, input.run.config) as FlowExpectation[])
|
|
225
|
+
.map((expectation) => requirementFromExpectation(expectation, matched, typeof outcome.accepted_exception_id === "string"));
|
|
226
|
+
});
|
|
227
|
+
if (requirements.length > MAX_REQUIREMENTS) throw new Error("Builder gate-action envelope requirements exceed the supported bound");
|
|
228
|
+
if (!sameSet(action.expectation_ids, requirements.map((requirement) => requirement.id))) {
|
|
229
|
+
throw new Error(`Builder gate-action metadata evidence does not exactly match canonical Flow requirements for ${input.run.definitionId}/${input.run.state.current_step}`);
|
|
230
|
+
}
|
|
231
|
+
const unresolved = requirements.filter((requirement) => requirement.status === "unresolved").map((requirement) => requirement.id);
|
|
232
|
+
const unresolvedRequired = requirements.filter((requirement) => requirement.required && requirement.status === "unresolved").map((requirement) => requirement.id);
|
|
233
|
+
return { gates, requirements, unresolved, unresolvedRequired, acceptedExceptions };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function assembleGateActionEnvelope(input: BuilderGateActionEnvelopeInput, loaded: LoadedGateAction, derived: DerivedGateRequirements): GateActionEnvelope {
|
|
237
|
+
const { packageRoot, packageVersion, kit, action, actions, actionableArtifacts } = loaded;
|
|
238
|
+
const { gates, requirements, unresolved, unresolvedRequired, acceptedExceptions } = derived;
|
|
239
|
+
const sessionDir = path.resolve(input.sessionDir);
|
|
240
|
+
const sessionArgument = sessionPathForPublicCommand(input.projectRoot, sessionDir);
|
|
241
|
+
const statusCommand = pinnedFlowAgentsCommand(packageVersion, ["workflow", "status", "--session-dir", sessionArgument, "--json"]);
|
|
242
|
+
const requiredArtifacts = action.artifact_bindings
|
|
243
|
+
.filter((binding) => !isControlArtifact(binding.artifact) && binding.expectation_ids.some((id) => unresolvedRequired.includes(id)))
|
|
244
|
+
.map((binding) => binding.artifact);
|
|
245
|
+
const envelope: GateActionEnvelope = {
|
|
246
|
+
schema_version: "1.0",
|
|
247
|
+
flow: {
|
|
248
|
+
run_id: input.run.runId,
|
|
249
|
+
definition_id: input.run.definitionId,
|
|
250
|
+
definition_version: input.run.definitionVersion,
|
|
251
|
+
status: input.run.state.status,
|
|
252
|
+
current_step: input.run.state.current_step,
|
|
253
|
+
gate_ids: gates.map((gate) => gate.id),
|
|
254
|
+
},
|
|
255
|
+
gate: { requirements, unresolved_requirement_ids: unresolved, accepted_exceptions: acceptedExceptions },
|
|
256
|
+
action: envelopeAction(action, actionableArtifacts, kit, packageRoot, packageVersion),
|
|
257
|
+
public_interfaces: {
|
|
258
|
+
status: { command: statusCommand },
|
|
259
|
+
mutations: action.expectation_bindings.map((binding) => publicMutation(binding, sessionArgument, packageVersion)),
|
|
260
|
+
},
|
|
261
|
+
stop_condition: envelopeStopCondition(input, gates, action, requiredArtifacts, unresolvedRequired),
|
|
262
|
+
progress: {
|
|
263
|
+
canonical_evidence: canonicalEvidence(input.run.manifest),
|
|
264
|
+
observed_artifacts: observeBuilderArtifactsForProgress(sessionDir, actions.flatMap((entry) => entry.artifacts)),
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
return envelope;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function envelopeAction(action: KitFlowStepActionEntry, artifacts: string[], kit: AnyRecord, packageRoot: string, packageVersion: string): GateActionEnvelope["action"] {
|
|
271
|
+
return {
|
|
272
|
+
skills: action.skills.map((skill) => skillIdentity(kit, packageRoot, packageVersion, skill)),
|
|
273
|
+
operations: [...action.operations],
|
|
274
|
+
declared_artifacts: [...artifacts],
|
|
275
|
+
declared_evidence: [...action.expectation_ids],
|
|
276
|
+
implementation_allowed: action.implementation_allowed,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function envelopeStopCondition(
|
|
281
|
+
input: BuilderGateActionEnvelopeInput,
|
|
282
|
+
gates: Array<FlowGate & { id: string }>,
|
|
283
|
+
action: KitFlowStepActionEntry,
|
|
284
|
+
artifactRefs: string[],
|
|
285
|
+
unresolvedEvidenceIds: string[],
|
|
286
|
+
): GateActionEnvelope["stop_condition"] {
|
|
287
|
+
const operation = action.operations[0];
|
|
288
|
+
const protocol = operation ? PUBLIC_OPERATION_CONTRACTS[operation as keyof typeof PUBLIC_OPERATION_CONTRACTS] : undefined;
|
|
289
|
+
return {
|
|
290
|
+
kind: "one_turn",
|
|
291
|
+
scope: { run_id: input.run.runId, current_step: input.run.state.current_step, gate_ids: gates.map((gate) => gate.id), current_gate_only: true },
|
|
292
|
+
required: { skill_ids: unresolvedEvidenceIds.length > 0 ? action.skills : [], artifact_refs: artifactRefs, unresolved_evidence_ids: unresolvedEvidenceIds },
|
|
293
|
+
sequence: ["activate_required_skills", "produce_declared_artifacts", "record_bound_evidence", "synchronize_canonical_flow", "return_adapter_result"],
|
|
294
|
+
after: "return_adapter_result",
|
|
295
|
+
synchronize_canonical_flow: true,
|
|
296
|
+
adapter_evidence_is_gate_evidence: false,
|
|
297
|
+
...(protocol?.availability.status === "external_capability_required" ? {
|
|
298
|
+
external_capability: {
|
|
299
|
+
status: "waiting",
|
|
300
|
+
operation: protocol.operation,
|
|
301
|
+
capability: protocol.capability,
|
|
302
|
+
completion: "external_verification_required",
|
|
303
|
+
},
|
|
304
|
+
} : {}),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function gateActionProgressSnapshot(envelope: GateActionEnvelope): GateActionProgressSnapshot {
|
|
309
|
+
return {
|
|
310
|
+
current_step: envelope.flow.current_step,
|
|
311
|
+
canonical_status: envelope.flow.status,
|
|
312
|
+
canonical_evidence: [...envelope.progress.canonical_evidence],
|
|
313
|
+
observed_artifacts: [...envelope.progress.observed_artifacts],
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function withGateActionPriorProgress(envelope: GateActionEnvelope, priorTurn: GateActionPriorProgress): GateActionEnvelope {
|
|
318
|
+
const copy = structuredClone(envelope);
|
|
319
|
+
copy.progress.prior_turn = structuredClone(priorTurn);
|
|
320
|
+
assertBoundedEnvelope(copy);
|
|
321
|
+
return copy;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function publicMutation(binding: KitFlowStepExpectationBinding, sessionArgument: string, packageVersion: string): GateActionPublicMutation {
|
|
325
|
+
if (binding.interface === "operation") {
|
|
326
|
+
const operation = binding.operation!;
|
|
327
|
+
const protocol = PUBLIC_OPERATION_CONTRACTS[operation as keyof typeof PUBLIC_OPERATION_CONTRACTS];
|
|
328
|
+
if (!protocol) throw new Error(`Builder gate-action operation '${operation}' has no canonical public protocol`);
|
|
329
|
+
return {
|
|
330
|
+
expectation_id: binding.expectation_id,
|
|
331
|
+
interface: "operation",
|
|
332
|
+
operation,
|
|
333
|
+
protocol: structuredClone(protocol),
|
|
334
|
+
completion: {
|
|
335
|
+
status: "external_verification_required",
|
|
336
|
+
executable_by_flow_agents: false,
|
|
337
|
+
gate_evidence_interface: null,
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
if (binding.interface === "workflow.critique") {
|
|
342
|
+
return {
|
|
343
|
+
expectation_id: binding.expectation_id,
|
|
344
|
+
interface: binding.interface,
|
|
345
|
+
package: { name: "@kontourai/flow-agents", version: packageVersion },
|
|
346
|
+
command: "flow-agents",
|
|
347
|
+
argv: ["workflow", "critique", "--session-dir", sessionArgument, "--json"],
|
|
348
|
+
parameters: [
|
|
349
|
+
{ name: "id", flag: "--id", required: false },
|
|
350
|
+
{ name: "verdict", flag: "--verdict", required: true, allowed_values: [...WORKFLOW_CRITIQUE_STATUSES] },
|
|
351
|
+
{ name: "summary", flag: "--summary", required: true },
|
|
352
|
+
{ name: "lane_json", flag: "--lane-json", required: true, repeatable: true },
|
|
353
|
+
{ name: "artifact_ref", flag: "--artifact-ref", required: false, repeatable: true, required_when: { parameter: "verdict", equals: "pass" } },
|
|
354
|
+
{ name: "finding_json", flag: "--finding-json", required: false, repeatable: true },
|
|
355
|
+
{ name: "timestamp", flag: "--timestamp", required: false },
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return workflowEvidenceMutation(binding.expectation_id, sessionArgument, packageVersion);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function workflowEvidenceMutation(expectationId: string, sessionArgument: string, packageVersion: string): GateActionWorkflowMutation {
|
|
363
|
+
return {
|
|
364
|
+
expectation_id: expectationId,
|
|
365
|
+
interface: "workflow.evidence",
|
|
366
|
+
package: { name: "@kontourai/flow-agents", version: packageVersion },
|
|
367
|
+
command: "flow-agents",
|
|
368
|
+
argv: ["workflow", "evidence", "--session-dir", sessionArgument, "--expectation", expectationId, "--json"],
|
|
369
|
+
parameters: [
|
|
370
|
+
{ name: "status", flag: "--status", required: true, allowed_values: ["pass", "fail", "not_verified"] },
|
|
371
|
+
{ name: "summary", flag: "--summary", required: true },
|
|
372
|
+
{ name: "evidence_ref_json", flag: "--evidence-ref-json", required: true, repeatable: true },
|
|
373
|
+
{ name: "route_reason", flag: "--route-reason", required: false },
|
|
374
|
+
{ name: "criterion_json", flag: "--criterion-json", required: false, repeatable: true },
|
|
375
|
+
{ name: "accepted_gap_reason", flag: "--accepted-gap-reason", required: false },
|
|
376
|
+
{ name: "waived_by", flag: "--waived-by", required: false },
|
|
377
|
+
{ name: "command", flag: "--command", required: false, repeatable: true },
|
|
378
|
+
],
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function requirementFromExpectation(expectation: FlowExpectation, satisfied: Set<string>, acceptedException: boolean): GateActionEnvelope["gate"]["requirements"][number] {
|
|
383
|
+
const record = expectation as unknown as AnyRecord;
|
|
384
|
+
const claim = isRecord(record.bundle_claim) ? record.bundle_claim : null;
|
|
385
|
+
if (typeof record.id !== "string" || typeof record.description !== "string" || !claim || typeof claim.claimType !== "string") {
|
|
386
|
+
throw new Error("canonical Flow gate contains malformed expectation metadata");
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
id: record.id,
|
|
390
|
+
required: record.required === true,
|
|
391
|
+
description: record.description,
|
|
392
|
+
claim_type: claim.claimType,
|
|
393
|
+
subject_type: typeof claim.subjectType === "string" ? claim.subjectType : null,
|
|
394
|
+
status: satisfied.has(record.id) ? "satisfied" : acceptedException ? "accepted_exception" : "unresolved",
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function skillIdentity(kit: AnyRecord, packageRoot: string, version: string, skill: string): GateActionEnvelope["action"]["skills"][number] {
|
|
399
|
+
const skills = Array.isArray(kit.skills) ? kit.skills : [];
|
|
400
|
+
const entry = skills.find((candidate): candidate is AnyRecord => isRecord(candidate) && candidate.id === `builder.${skill}`);
|
|
401
|
+
if (!entry || typeof entry.path !== "string") throw new Error(`Builder gate-action skill '${skill}' has no installed skill source`);
|
|
402
|
+
const kitRoot = path.join(packageRoot, "kits", "builder");
|
|
403
|
+
const source = readStableRegularFile(kitRoot, entry.path, `Builder skill '${skill}'`, MAX_METADATA_BYTES);
|
|
404
|
+
if (!source) throw new Error(`Builder skill '${skill}' has no installed skill source`);
|
|
405
|
+
return {
|
|
406
|
+
id: skill,
|
|
407
|
+
package: { name: "@kontourai/flow-agents", version },
|
|
408
|
+
path: path.relative(fs.realpathSync(packageRoot), source.path).split(path.sep).join("/"),
|
|
409
|
+
sha256: createHash("sha256").update(source.bytes).digest("hex"),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function canonicalEvidence(manifest: AnyRecord): string[] {
|
|
414
|
+
const ids = (Array.isArray(manifest.evidence) ? manifest.evidence : []).flatMap((entry) => {
|
|
415
|
+
if (!isRecord(entry) || entry.superseded_by || typeof entry.gate_id !== "string") return [];
|
|
416
|
+
const identity = typeof entry.sha256 === "string" ? entry.sha256 : typeof entry.id === "string" ? entry.id : null;
|
|
417
|
+
return identity ? [`${entry.gate_id}:${identity}`] : [];
|
|
418
|
+
});
|
|
419
|
+
const unique = [...new Set(ids)].sort();
|
|
420
|
+
if (unique.length > MAX_CANONICAL_EVIDENCE) throw new Error("canonical Flow evidence exceeds the supported envelope bound");
|
|
421
|
+
return unique;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function observeBuilderArtifactsForProgress(sessionDir: string, artifacts: string[]): string[] {
|
|
425
|
+
const observed: string[] = [];
|
|
426
|
+
const candidates = [...new Set(artifacts)].filter((artifact) => !artifact.includes("#") && !isControlArtifact(artifact));
|
|
427
|
+
if (candidates.length > MAX_OBSERVED_ARTIFACTS) throw new Error(`Builder artifact progress exceeds ${MAX_OBSERVED_ARTIFACTS} unique files`);
|
|
428
|
+
const aggregateBudget = { remaining: MAX_OBSERVED_ARTIFACT_BYTES };
|
|
429
|
+
for (const artifact of candidates) {
|
|
430
|
+
const relative = artifact.replaceAll("<slug>", path.basename(sessionDir));
|
|
431
|
+
const file = readStableRegularFile(sessionDir, relative, `Builder artifact '${artifact}'`, MAX_METADATA_BYTES, false, aggregateBudget);
|
|
432
|
+
observed.push(file
|
|
433
|
+
? `${artifact}:${createHash("sha256").update(file.bytes).digest("hex")}`
|
|
434
|
+
: `${artifact}:absent`);
|
|
435
|
+
}
|
|
436
|
+
return observed.sort();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function isControlArtifact(artifact: string): boolean {
|
|
440
|
+
return CONTROL_ARTIFACTS.has(path.posix.normalize(artifact.replaceAll("\\", "/")));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function readBoundedJson(root: string, candidate: string, label: string): AnyRecord {
|
|
444
|
+
const file = readStableRegularFile(root, candidate, label, MAX_METADATA_BYTES);
|
|
445
|
+
if (!file) throw new Error(`${label} is missing`);
|
|
446
|
+
const value = JSON.parse(file.bytes.toString("utf8")) as unknown;
|
|
447
|
+
if (!isRecord(value)) throw new Error(`${label} must be a JSON object`);
|
|
448
|
+
return value;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function readStableRegularFile(
|
|
452
|
+
root: string,
|
|
453
|
+
candidate: string,
|
|
454
|
+
label: string,
|
|
455
|
+
maxBytes: number,
|
|
456
|
+
required = true,
|
|
457
|
+
aggregateBudget?: { remaining: number },
|
|
458
|
+
): { path: string; bytes: Buffer } | null {
|
|
459
|
+
if (path.isAbsolute(candidate) || candidate.includes("\0")) throw new Error(`${label} must be a relative path`);
|
|
460
|
+
const canonicalRoot = fs.realpathSync(root);
|
|
461
|
+
const resolved = path.resolve(canonicalRoot, candidate);
|
|
462
|
+
const relative = path.relative(canonicalRoot, resolved);
|
|
463
|
+
if (relative === "" || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
464
|
+
throw new Error(`${label} must remain within its package or session`);
|
|
465
|
+
}
|
|
466
|
+
let fd: number | undefined;
|
|
467
|
+
try {
|
|
468
|
+
const canonicalParentBefore = fs.realpathSync(path.dirname(resolved));
|
|
469
|
+
assertWithinRoot(canonicalRoot, canonicalParentBefore, label);
|
|
470
|
+
const parentBefore = fs.statSync(canonicalParentBefore);
|
|
471
|
+
if (!parentBefore.isDirectory()) throw new Error(`${label} parent must be a directory`);
|
|
472
|
+
if (typeof fsConstants.O_NOFOLLOW !== "number") throw new Error(`${label} cannot be read safely because O_NOFOLLOW is unavailable`);
|
|
473
|
+
fd = fs.openSync(resolved, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
474
|
+
const before = fs.fstatSync(fd);
|
|
475
|
+
if (!before.isFile() || before.size > maxBytes) throw new Error(`${label} must be a bounded regular file`);
|
|
476
|
+
if (aggregateBudget && before.size > aggregateBudget.remaining) throw new Error(`Builder artifact progress exceeds ${MAX_OBSERVED_ARTIFACT_BYTES} aggregate bytes`);
|
|
477
|
+
if (aggregateBudget) aggregateBudget.remaining -= before.size;
|
|
478
|
+
const pathBefore = fs.lstatSync(resolved);
|
|
479
|
+
if (pathBefore.isSymbolicLink() || !sameIdentity(before, pathBefore)) throw new Error(`${label} changed identity while opening`);
|
|
480
|
+
const bytes = fs.readFileSync(fd);
|
|
481
|
+
const after = fs.fstatSync(fd);
|
|
482
|
+
if (!sameIdentity(before, after) || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs || bytes.length !== after.size) {
|
|
483
|
+
throw new Error(`${label} changed while reading`);
|
|
484
|
+
}
|
|
485
|
+
const pathAfter = fs.lstatSync(resolved);
|
|
486
|
+
if (pathAfter.isSymbolicLink() || !sameIdentity(after, pathAfter)) throw new Error(`${label} changed identity while reading`);
|
|
487
|
+
const canonicalParentAfter = fs.realpathSync(path.dirname(resolved));
|
|
488
|
+
const parentAfter = fs.statSync(canonicalParentAfter);
|
|
489
|
+
if (canonicalParentAfter !== canonicalParentBefore || !sameIdentity(parentBefore, parentAfter)) throw new Error(`${label} parent changed identity while reading`);
|
|
490
|
+
assertWithinRoot(canonicalRoot, canonicalParentAfter, label);
|
|
491
|
+
return { path: resolved, bytes };
|
|
492
|
+
} catch (error) {
|
|
493
|
+
if (!required && (error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
494
|
+
throw error;
|
|
495
|
+
} finally {
|
|
496
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function assertWithinRoot(root: string, candidate: string, label: string): void {
|
|
501
|
+
const relative = path.relative(root, candidate);
|
|
502
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
503
|
+
throw new Error(`${label} must remain within its package or session`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function sameIdentity(left: fs.Stats, right: fs.Stats): boolean {
|
|
508
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function sessionPathForPublicCommand(projectRoot: string, sessionDir: string): string {
|
|
512
|
+
const relative = path.relative(path.resolve(projectRoot), sessionDir);
|
|
513
|
+
if (relative === "" || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
514
|
+
throw new Error("Builder session must remain within the project root for public commands");
|
|
515
|
+
}
|
|
516
|
+
return relative;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function assertBoundedEnvelope(envelope: GateActionEnvelope): void {
|
|
520
|
+
if (Buffer.byteLength(JSON.stringify(envelope), "utf8") > MAX_ENVELOPE_BYTES) throw new Error("Builder gate-action envelope exceeds 65536 bytes");
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function sameSet(left: string[], right: string[]): boolean {
|
|
524
|
+
return left.length === right.length && new Set(left).size === left.length && left.every((entry) => right.includes(entry));
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function isRecord(value: unknown): value is AnyRecord {
|
|
528
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
529
|
+
}
|