@kontourai/flow-agents 3.4.3 → 3.6.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/.github/workflows/ci.yml +4 -0
- package/CHANGELOG.md +15 -0
- package/build/src/builder-flow-run-adapter.d.ts +10 -1
- package/build/src/builder-flow-run-adapter.js +29 -1
- package/build/src/builder-flow-runtime.d.ts +18 -0
- package/build/src/builder-flow-runtime.js +205 -13
- package/build/src/builder-lifecycle-authority.d.ts +35 -0
- package/build/src/builder-lifecycle-authority.js +219 -0
- package/build/src/cli/assignment-provider.d.ts +13 -0
- package/build/src/cli/assignment-provider.js +120 -62
- package/build/src/cli/builder-run.js +46 -5
- package/build/src/cli/workflow-artifact-cleanup-audit.js +3 -0
- package/build/src/cli/workflow-sidecar.d.ts +3 -0
- package/build/src/cli/workflow-sidecar.js +140 -30
- package/build/src/cli/workflow.d.ts +2 -0
- package/build/src/cli/workflow.js +521 -0
- package/build/src/cli.js +2 -0
- package/build/src/index.d.ts +4 -0
- package/build/src/index.js +2 -0
- package/build/src/lib/flow-resolver.js +7 -2
- package/build/src/lib/package-version.d.ts +2 -0
- package/build/src/lib/package-version.js +13 -0
- package/build/src/lib/pinned-cli-command.d.ts +6 -0
- package/build/src/lib/pinned-cli-command.js +21 -0
- package/context/contracts/artifact-contract.md +1 -1
- package/context/contracts/assignment-provider-contract.md +5 -2
- package/context/scripts/hooks/config-protection.js +8 -1
- package/context/scripts/hooks/lib/config-protection-remedies.js +3 -0
- package/context/scripts/hooks/stop-goal-fit.js +1 -1
- package/docs/context-map.md +2 -0
- package/docs/public-workflow-cli.md +63 -0
- package/docs/spec/builder-flow-runtime.md +49 -5
- package/docs/workflow-usage-guide.md +5 -0
- package/evals/ci/run-baseline.sh +2 -0
- package/evals/integration/test_assignment_provider_local_file.sh +54 -0
- package/evals/integration/test_builder_entry_enforcement.sh +241 -24
- package/evals/integration/test_bundle_install.sh +97 -0
- package/evals/integration/test_current_json_per_actor.sh +1 -0
- package/evals/integration/test_dual_emit_flow_step.sh +2 -0
- package/evals/integration/test_flowdef_session_activation.sh +6 -3
- package/evals/integration/test_goal_fit_escape_hatch.sh +3 -3
- package/evals/integration/test_phase_map_and_gate_claim.sh +4 -0
- package/evals/integration/test_public_workflow_cli.sh +259 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +1 -0
- package/package.json +2 -2
- package/schemas/builder-lifecycle-authorization.schema.json +57 -0
- package/schemas/lifecycle-authority-keys.schema.json +25 -0
- package/schemas/workflow-state.schema.json +1 -1
- package/scripts/hooks/config-protection.js +8 -1
- package/scripts/hooks/lib/config-protection-remedies.js +3 -0
- package/scripts/hooks/stop-goal-fit.js +1 -1
- package/src/builder-flow-run-adapter.ts +47 -0
- package/src/builder-flow-runtime.ts +216 -4
- package/src/builder-lifecycle-authority.ts +218 -0
- package/src/cli/assignment-provider.ts +84 -20
- package/src/cli/builder-flow-runtime.test.mjs +404 -1
- package/src/cli/builder-run.ts +56 -5
- package/src/cli/workflow-artifact-cleanup-audit.ts +3 -0
- package/src/cli/workflow-sidecar.ts +138 -31
- package/src/cli/workflow.ts +471 -0
- package/src/cli.ts +2 -0
- package/src/index.ts +14 -0
- package/src/lib/flow-resolver.ts +6 -2
- package/src/lib/package-version.ts +15 -0
- package/src/lib/pinned-cli-command.ts +23 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
5
|
+
TMP="$(mktemp -d)"
|
|
6
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
7
|
+
|
|
8
|
+
fail() { printf 'FAIL %s\n' "$*" >&2; exit 1; }
|
|
9
|
+
pass() { printf 'PASS %s\n' "$*"; }
|
|
10
|
+
|
|
11
|
+
cd "$ROOT_DIR"
|
|
12
|
+
npm run build --silent
|
|
13
|
+
npm run build:bundles --silent
|
|
14
|
+
npm pack --silent --pack-destination "$TMP" >/dev/null
|
|
15
|
+
TARBALL="$(find "$TMP" -maxdepth 1 -name 'kontourai-flow-agents-*.tgz' -print -quit)"
|
|
16
|
+
[[ -n "$TARBALL" ]] || fail "npm pack did not produce a tarball"
|
|
17
|
+
VERSION="$(node -p "require('./package.json').version")"
|
|
18
|
+
CONSUMER="$TMP/consumer"
|
|
19
|
+
ARTIFACT_ROOT="$CONSUMER/.kontourai/flow-agents"
|
|
20
|
+
mkdir -p "$CONSUMER"
|
|
21
|
+
|
|
22
|
+
run_candidate() {
|
|
23
|
+
(cd "$CONSUMER" && CODEX_SESSION_ID=public-workflow-eval npx --yes --package="file:$TARBALL" flow-agents workflow "$@")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
PRIMARY_HELP="$(cd "$CONSUMER" && npx --yes --package="file:$TARBALL" flow-agents --help)"
|
|
27
|
+
WORKFLOW_HELP="$(run_candidate --help)"
|
|
28
|
+
[[ "$PRIMARY_HELP" == *"workflow"* && "$WORKFLOW_HELP" != *"workflow-sidecar"* && "$WORKFLOW_HELP" != *"npm run workflow:sidecar"* ]] || fail "public help exposes internal writer terminology or omits workflow"
|
|
29
|
+
pass "primary help exposes the public workflow command without internal writer terminology"
|
|
30
|
+
|
|
31
|
+
run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#101 --summary "Release fixture" >/dev/null
|
|
32
|
+
RELEASE_SESSION="$ARTIFACT_ROOT/acme-widgets-101"
|
|
33
|
+
[[ -f "$RELEASE_SESSION/state.json" ]] || fail "packed start did not create a session"
|
|
34
|
+
[[ ! -e "$CONSUMER/package.json" ]] || fail "consumer unexpectedly gained package.json"
|
|
35
|
+
pass "packed start works in a non-Node consumer"
|
|
36
|
+
set +e
|
|
37
|
+
UNSAFE_START="$(run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#103 --skip-ownership-guard 2>&1)"
|
|
38
|
+
UNSAFE_START_RC=$?
|
|
39
|
+
set -e
|
|
40
|
+
[[ "$UNSAFE_START_RC" -ne 0 && "$UNSAFE_START" == *"does not support --skip-ownership-guard"* && ! -e "$ARTIFACT_ROOT/acme-widgets-103" ]] || fail "public start accepted an internal ownership bypass"
|
|
41
|
+
pass "public start rejects internal authority flags before mutation"
|
|
42
|
+
|
|
43
|
+
LOCAL_RETRY_PROJECT="$TMP/local-retry-project"
|
|
44
|
+
LOCAL_RETRY_ROOT="$LOCAL_RETRY_PROJECT/.kontourai/flow-agents"
|
|
45
|
+
mkdir -p "$LOCAL_RETRY_PROJECT/.kontourai"
|
|
46
|
+
printf 'not a run-store directory\n' >"$LOCAL_RETRY_PROJECT/.kontourai/flow"
|
|
47
|
+
set +e
|
|
48
|
+
(cd "$LOCAL_RETRY_PROJECT" && CODEX_SESSION_ID=public-workflow-eval npx --yes --package="file:$TARBALL" flow-agents-workflow-sidecar ensure-session \
|
|
49
|
+
--artifact-root "$LOCAL_RETRY_ROOT" --task-slug local-retry \
|
|
50
|
+
--title "Local retry" --summary "Resume the bound local workflow." --flow-id builder.build >/dev/null 2>&1)
|
|
51
|
+
LOCAL_SEED_RC=$?
|
|
52
|
+
set -e
|
|
53
|
+
[[ "$LOCAL_SEED_RC" -ne 0 ]] || fail "local retry fixture unexpectedly started against an invalid Flow store"
|
|
54
|
+
LOCAL_RETRY_COMMAND="$(node -p "JSON.parse(require('fs').readFileSync('$LOCAL_RETRY_ROOT/local-retry/state.json')).next_action.command")"
|
|
55
|
+
[[ "$LOCAL_RETRY_COMMAND" == *"'--work-item' 'local:local-retry'"* && "$LOCAL_RETRY_COMMAND" == *"'--task-slug' 'local-retry'"* && "$LOCAL_RETRY_COMMAND" == *"'--artifact-root' '$LOCAL_RETRY_ROOT'"* ]] || fail "emitted local retry did not bind its Work Item, slug, and originating artifact root"
|
|
56
|
+
rm -f "$LOCAL_RETRY_PROJECT/.kontourai/flow"
|
|
57
|
+
FOREIGN_RETRY_CWD="$TMP/foreign-retry-cwd"
|
|
58
|
+
mkdir -p "$FOREIGN_RETRY_CWD"
|
|
59
|
+
EXECUTABLE_RETRY="$(node -e 'process.stdout.write(process.argv[1].replace(process.argv[2], process.argv[3]))' "$LOCAL_RETRY_COMMAND" "'@kontourai/flow-agents@$VERSION'" "'file:$TARBALL'")"
|
|
60
|
+
(cd "$FOREIGN_RETRY_CWD" && CODEX_SESSION_ID=public-workflow-eval eval "$EXECUTABLE_RETRY" >/dev/null)
|
|
61
|
+
[[ -f "$LOCAL_RETRY_PROJECT/.kontourai/flow/runs/local-retry/state.json" && ! -e "$FOREIGN_RETRY_CWD/.kontourai" ]] || fail "emitted local retry mutated the caller cwd instead of the originating store"
|
|
62
|
+
pass "emitted local retry executes from a foreign cwd against its exact originating store"
|
|
63
|
+
|
|
64
|
+
snapshot_tree() {
|
|
65
|
+
local root="$1"
|
|
66
|
+
find "$root" -type f -print0 | sort -z | xargs -0 shasum -a 256
|
|
67
|
+
}
|
|
68
|
+
BEFORE_STATUS="$(snapshot_tree "$CONSUMER/.kontourai")"
|
|
69
|
+
STATUS_JSON="$(run_candidate status --artifact-root "$ARTIFACT_ROOT" --json)"
|
|
70
|
+
AFTER_STATUS="$(snapshot_tree "$CONSUMER/.kontourai")"
|
|
71
|
+
[[ "$BEFORE_STATUS" == "$AFTER_STATUS" ]] || fail "workflow status mutated durable artifacts"
|
|
72
|
+
node -e 'const r=JSON.parse(process.argv[1]); if(r.definition_id!=="builder.build"||r.current_step!=="design-probe")process.exit(1)' "$STATUS_JSON" || fail "status did not report canonical run"
|
|
73
|
+
pass "status is canonical and byte-read-only"
|
|
74
|
+
|
|
75
|
+
FLOW_MANIFEST="$CONSUMER/.kontourai/flow/runs/acme-widgets-101/evidence/manifest.json"
|
|
76
|
+
BEFORE_EVIDENCE="$(node -p "JSON.parse(require('fs').readFileSync('$FLOW_MANIFEST')).evidence.length")"
|
|
77
|
+
run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation pickup-probe-readiness --status not_verified --summary "Consumer fixture intentionally leaves this claim unverified." --json >/dev/null
|
|
78
|
+
AFTER_EVIDENCE="$(node -p "JSON.parse(require('fs').readFileSync('$FLOW_MANIFEST')).evidence.length")"
|
|
79
|
+
[[ "$AFTER_EVIDENCE" -eq $((BEFORE_EVIDENCE + 1)) ]] || fail "evidence invocation did not attach exactly once"
|
|
80
|
+
pass "evidence records and synchronizes exactly once"
|
|
81
|
+
|
|
82
|
+
OUTSIDE="$TMP/outside-session"
|
|
83
|
+
mkdir -p "$OUTSIDE"
|
|
84
|
+
printf '{"schema_version":"1.0","task_slug":"outside"}\n' >"$OUTSIDE/state.json"
|
|
85
|
+
ln -s "$OUTSIDE" "$ARTIFACT_ROOT/symlink-session"
|
|
86
|
+
OUTSIDE_BEFORE="$(snapshot_tree "$OUTSIDE")"
|
|
87
|
+
set +e
|
|
88
|
+
SYMLINK_EVIDENCE="$(run_candidate evidence --session-dir "$ARTIFACT_ROOT/symlink-session" --expectation pickup-probe-readiness --status not_verified --summary rejected 2>&1)"
|
|
89
|
+
SYMLINK_RC=$?
|
|
90
|
+
set -e
|
|
91
|
+
[[ "$SYMLINK_RC" -ne 0 && "$SYMLINK_EVIDENCE" == *"session directory must be a non-symlink directory"* && "$(snapshot_tree "$OUTSIDE")" == "$OUTSIDE_BEFORE" ]] || fail "evidence followed a symlinked session"
|
|
92
|
+
pass "evidence rejects symlinked session paths before mutation"
|
|
93
|
+
|
|
94
|
+
run_candidate pause --session-dir "$RELEASE_SESSION" --reason "consumer pause" >/dev/null
|
|
95
|
+
run_candidate resume --session-dir "$RELEASE_SESSION" --reason "consumer resume" >/dev/null
|
|
96
|
+
run_candidate release --session-dir "$RELEASE_SESSION" --reason "consumer release" >/dev/null
|
|
97
|
+
node -e 'const fs=require("fs");const r=JSON.parse(fs.readFileSync(process.argv[1]));if(r.status!=="released")process.exit(1)' "$ARTIFACT_ROOT/assignment/acme-widgets-101.json" || fail "release did not release assignment"
|
|
98
|
+
pass "pause, resume, and release use the public command"
|
|
99
|
+
|
|
100
|
+
run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#102 --summary "Cancel fixture" >/dev/null
|
|
101
|
+
CANCEL_SESSION="$ARTIFACT_ROOT/acme-widgets-102"
|
|
102
|
+
node --input-type=module - "$CONSUMER" "$CANCEL_SESSION" <<'NODE'
|
|
103
|
+
import fs from 'node:fs';
|
|
104
|
+
import path from 'node:path';
|
|
105
|
+
import { generateKeyPairSync, sign } from 'node:crypto';
|
|
106
|
+
const [project, session] = process.argv.slice(2);
|
|
107
|
+
const slug = path.basename(session);
|
|
108
|
+
const assignment = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow-agents', 'assignment', `${slug}.json`), 'utf8'));
|
|
109
|
+
const state = JSON.parse(fs.readFileSync(path.join(session, 'state.json'), 'utf8'));
|
|
110
|
+
const keys = generateKeyPairSync('ed25519');
|
|
111
|
+
fs.mkdirSync(path.join(project, '.flow-agents'), { recursive: true });
|
|
112
|
+
fs.writeFileSync(path.join(project, '.flow-agents', 'lifecycle-authority-keys.json'), JSON.stringify({ schema_version: '1.0', keys: [{ id: 'consumer', algorithm: 'ed25519', public_key_pem: keys.publicKey.export({ type: 'spki', format: 'pem' }) }] }, null, 2));
|
|
113
|
+
for (const operation of ['cancel', 'archive']) {
|
|
114
|
+
const requestedAt = new Date();
|
|
115
|
+
const unsigned = {
|
|
116
|
+
schema_version: '1.0', operation, run_id: slug, subject: state.work_item_refs[0],
|
|
117
|
+
assignment_actor_key: assignment.actor_key,
|
|
118
|
+
assignment_actor: { ...assignment.actor, human: assignment.actor.human ?? null },
|
|
119
|
+
nonce: `consumer-${operation}`,
|
|
120
|
+
expires_at: new Date(requestedAt.getTime() + 3600000).toISOString(),
|
|
121
|
+
request: { reason: `consumer ${operation}`, authority: { kind: 'user_request', actor: 'consumer-user', request_ref: `fixture://consumer/${operation}`, requested_at: requestedAt.toISOString() } },
|
|
122
|
+
};
|
|
123
|
+
const authorization = { ...unsigned, signature: { algorithm: 'ed25519', key_id: 'consumer', value: sign(null, Buffer.from(JSON.stringify(unsigned)), keys.privateKey).toString('base64') } };
|
|
124
|
+
fs.writeFileSync(path.join(project, `${operation}.authorization.json`), JSON.stringify(authorization, null, 2));
|
|
125
|
+
}
|
|
126
|
+
NODE
|
|
127
|
+
run_candidate cancel --session-dir "$CANCEL_SESSION" --authorization-file "$CONSUMER/cancel.authorization.json" >/dev/null
|
|
128
|
+
run_candidate archive --session-dir "$CANCEL_SESSION" --authorization-file "$CONSUMER/archive.authorization.json" >/dev/null
|
|
129
|
+
[[ -f "$ARTIFACT_ROOT/archive/acme-widgets-102/state.json" && ! -e "$CANCEL_SESSION" ]] || fail "cancel/archive did not retain archived session"
|
|
130
|
+
pass "signed cancel and archive execute through the public command"
|
|
131
|
+
|
|
132
|
+
DOCTOR="$TMP/doctor-consumer"
|
|
133
|
+
mkdir -p "$DOCTOR/node_modules/@kontourai/flow-agents" "$DOCTOR/node_modules/.bin" "$DOCTOR/.flow-agents" "$DOCTOR/kits/builder/flows" "$DOCTOR/.kontourai/flow-agents/doctor-session"
|
|
134
|
+
cat >"$DOCTOR/node_modules/@kontourai/flow-agents/package.json" <<'JSON'
|
|
135
|
+
{"name":"@kontourai/flow-agents","version":"3.4.3","bin":{"flow-agents":"bin.js"}}
|
|
136
|
+
JSON
|
|
137
|
+
cat >"$DOCTOR/node_modules/.bin/flow-agents" <<'SH'
|
|
138
|
+
#!/usr/bin/env bash
|
|
139
|
+
echo STALE_LOCAL_BINARY
|
|
140
|
+
SH
|
|
141
|
+
chmod +x "$DOCTOR/node_modules/.bin/flow-agents"
|
|
142
|
+
cat >"$DOCTOR/.flow-agents/install.json" <<'JSON'
|
|
143
|
+
{"version":"3.4.3","runtime":"codex","active_kit_ids":["builder"]}
|
|
144
|
+
JSON
|
|
145
|
+
cat >"$DOCTOR/kits/builder/kit.json" <<'JSON'
|
|
146
|
+
{"schema_version":"0.9","id":"builder"}
|
|
147
|
+
JSON
|
|
148
|
+
cat >"$DOCTOR/kits/builder/flows/build.flow.json" <<'JSON'
|
|
149
|
+
{"id":"builder.build","version":"0.9"}
|
|
150
|
+
JSON
|
|
151
|
+
cat >"$DOCTOR/.kontourai/flow-agents/current.json" <<'JSON'
|
|
152
|
+
{"active_slug":"doctor-session","artifact_dir":"doctor-session"}
|
|
153
|
+
JSON
|
|
154
|
+
cat >"$DOCTOR/.kontourai/flow-agents/doctor-session/state.json" <<'JSON'
|
|
155
|
+
{"schema_version":"0.9","task_slug":"doctor-session","flow_run":{"definition_id":"builder.build","definition_version":"0.9"}}
|
|
156
|
+
JSON
|
|
157
|
+
cat >"$DOCTOR/.kontourai/flow-agents/doctor-session/trust.bundle" <<'JSON'
|
|
158
|
+
{"schema_version":"0.9"}
|
|
159
|
+
JSON
|
|
160
|
+
set +e
|
|
161
|
+
DOCTOR_JSON="$(cd "$DOCTOR" && npx --yes --package="file:$TARBALL" flow-agents workflow doctor --project-root "$DOCTOR" --artifact-root "$DOCTOR/.kontourai/flow-agents" --json 2>/dev/null)"
|
|
162
|
+
DOCTOR_RC=$?
|
|
163
|
+
set -e
|
|
164
|
+
[[ "$DOCTOR_RC" -eq 2 ]] || fail "doctor should return 2 for incompatible consumer fixtures"
|
|
165
|
+
node - "$DOCTOR_JSON" "$VERSION" "$DOCTOR" <<'NODE'
|
|
166
|
+
const [reportText, version, root] = process.argv.slice(2);
|
|
167
|
+
const report = JSON.parse(reportText);
|
|
168
|
+
if (report.cli.version !== version || report.cli.workflow_contract_version !== '1.0') process.exit(1);
|
|
169
|
+
if (report.local_dependency.version !== '3.4.3' || report.local_dependency.selected !== false) process.exit(2);
|
|
170
|
+
if (!report.warnings.some((w) => w.includes('hook/writer version 3.4.3'))) process.exit(3);
|
|
171
|
+
if (!report.warnings.some((w) => w.includes('Builder Kit'))) process.exit(4);
|
|
172
|
+
if (!report.warnings.some((w) => w.includes('builder.build version 0.9'))) process.exit(5);
|
|
173
|
+
if (!report.warnings.some((w) => w.includes('Artifact schema 0.9'))) process.exit(6);
|
|
174
|
+
if (!report.warnings.some((w) => w.includes('Trust bundle schema 0.9'))) process.exit(7);
|
|
175
|
+
if (!report.warnings.some((w) => w.includes('hook/writer assets failed integrity'))) process.exit(10);
|
|
176
|
+
if (!report.remediation.startsWith('sh -c ') || !report.remediation.includes(`'@kontourai/flow-agents@${version}'`) || !report.remediation.includes("'--runtime' 'codex'") || !report.remediation.includes("'--activate-kit' 'builder'")) process.exit(8);
|
|
177
|
+
if (report.cli.package_root.startsWith(root)) process.exit(9);
|
|
178
|
+
NODE
|
|
179
|
+
pass "doctor detects same-major hook/writer, Kit, Flow, and schema skew with exact remediation"
|
|
180
|
+
[[ "$DOCTOR_JSON" != *"STALE_LOCAL_BINARY"* ]] || fail "explicit package invocation selected stale local binary"
|
|
181
|
+
pass "explicit packed package wins over an old local dependency"
|
|
182
|
+
|
|
183
|
+
node - "$DOCTOR/node_modules/@kontourai/flow-agents/package.json" "$VERSION" <<'NODE'
|
|
184
|
+
const fs = require('node:fs');
|
|
185
|
+
const [file, version] = process.argv.slice(2);
|
|
186
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
187
|
+
value.version = version;
|
|
188
|
+
fs.writeFileSync(file, JSON.stringify(value));
|
|
189
|
+
NODE
|
|
190
|
+
SAME_VERSION_HELP="$(cd "$DOCTOR" && npx --yes --package="file:$TARBALL" flow-agents --help)"
|
|
191
|
+
[[ "$SAME_VERSION_HELP" == *"workflow"* && "$SAME_VERSION_HELP" != *"STALE_LOCAL_BINARY"* ]] || fail "explicit tarball selected a hostile same-version local binary"
|
|
192
|
+
pass "explicit tarball wins over a hostile same-version local binary"
|
|
193
|
+
|
|
194
|
+
PINNED_COMMAND_MODULE="pinned-cli-command.js"
|
|
195
|
+
ISOLATED_COMMAND="$(node --input-type=module - "$ROOT_DIR/build/src/lib/$PINNED_COMMAND_MODULE" "file:$TARBALL" <<'NODE'
|
|
196
|
+
import { pathToFileURL } from 'node:url';
|
|
197
|
+
const [modulePath, packageSpec] = process.argv.slice(2);
|
|
198
|
+
const { isolatedPackageCommand } = await import(pathToFileURL(modulePath));
|
|
199
|
+
console.log(isolatedPackageCommand(packageSpec, 'flow-agents', ['--help']));
|
|
200
|
+
NODE
|
|
201
|
+
)"
|
|
202
|
+
ISOLATED_HELP="$(cd "$DOCTOR" && eval "$ISOLATED_COMMAND")"
|
|
203
|
+
[[ "$ISOLATED_HELP" == *"workflow"* && "$ISOLATED_HELP" != *"STALE_LOCAL_BINARY"* ]] || fail "isolated package command selected a hostile same-version local binary"
|
|
204
|
+
pass "generated isolated package command defeats a hostile same-version local binary"
|
|
205
|
+
|
|
206
|
+
rm -f "$DOCTOR/kits/builder/kit.json" "$DOCTOR/kits/builder/flows/build.flow.json"
|
|
207
|
+
printf '{"version":"%s","runtime":"codex","active_kit_ids":["builder"]}\n' "$VERSION" >"$DOCTOR/.flow-agents/install.json"
|
|
208
|
+
set +e
|
|
209
|
+
MISSING_JSON="$(cd "$DOCTOR" && npx --yes --package="file:$TARBALL" flow-agents workflow doctor --project-root "$DOCTOR" --artifact-root "$DOCTOR/.kontourai/flow-agents" --json 2>/dev/null)"
|
|
210
|
+
MISSING_RC=$?
|
|
211
|
+
set -e
|
|
212
|
+
[[ "$MISSING_RC" -eq 2 ]] || fail "doctor should fail when an activated Kit is missing"
|
|
213
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(!r.warnings.some(w=>w.includes("Activated Builder Kit is missing"))||!r.warnings.some(w=>w.includes("Activated builder.build definition is missing")))process.exit(1)' "$MISSING_JSON" || fail "doctor did not report missing activated Kit components"
|
|
214
|
+
pass "doctor fails closed for missing activated Kit components"
|
|
215
|
+
|
|
216
|
+
HEALTHY="$TMP/healthy-install"
|
|
217
|
+
mkdir -p "$HEALTHY"
|
|
218
|
+
(cd "$HEALTHY" && npx --yes --package="file:$TARBALL" flow-agents init --runtime codex --dest "$HEALTHY" --activate-kit builder --yes >/dev/null)
|
|
219
|
+
HEALTHY_JSON="$(cd "$HEALTHY" && npx --yes --package="file:$TARBALL" flow-agents workflow doctor --project-root "$HEALTHY" --artifact-root "$HEALTHY/.kontourai/flow-agents" --json)"
|
|
220
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(!r.ok||r.warnings.length||!r.hook.integrity.ok||r.installed.active_kit_ids[0]!=="builder")process.exit(1)' "$HEALTHY_JSON" || fail "doctor did not pass immediately after its own remediation install"
|
|
221
|
+
pass "real init converges to doctor PASS"
|
|
222
|
+
|
|
223
|
+
cp "$HEALTHY/build/src/cli/workflow.js" "$TMP/workflow.js.clean"
|
|
224
|
+
printf '\n// WORKFLOW_CONTRACT_VERSION = "1.0"\n' >>"$HEALTHY/build/src/cli/workflow.js"
|
|
225
|
+
set +e
|
|
226
|
+
TAMPERED_CLI_JSON="$(cd "$HEALTHY" && npx --yes --package="file:$TARBALL" flow-agents workflow doctor --project-root "$HEALTHY" --artifact-root "$HEALTHY/.kontourai/flow-agents" --json 2>/dev/null)"
|
|
227
|
+
TAMPERED_CLI_RC=$?
|
|
228
|
+
set -e
|
|
229
|
+
[[ "$TAMPERED_CLI_RC" -eq 2 && "$TAMPERED_CLI_JSON" == *"asset mismatch: build/src/cli/workflow.js"* ]] || fail "doctor accepted marker-preserving CLI tampering"
|
|
230
|
+
cp "$TMP/workflow.js.clean" "$HEALTHY/build/src/cli/workflow.js"
|
|
231
|
+
pass "doctor rejects marker-preserving CLI tampering"
|
|
232
|
+
|
|
233
|
+
cp "$HEALTHY/.codex/hooks.json" "$TMP/hooks.json.clean"
|
|
234
|
+
node - "$HEALTHY/.codex/hooks.json" <<'NODE'
|
|
235
|
+
const fs = require('node:fs');
|
|
236
|
+
const file = process.argv[2];
|
|
237
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
238
|
+
const group = value.hooks.SessionStart.find((entry) => JSON.stringify(entry).includes('workflow-steering'));
|
|
239
|
+
group.hooks[0].command += '; true';
|
|
240
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
241
|
+
NODE
|
|
242
|
+
set +e
|
|
243
|
+
TAMPERED_HOOK_JSON="$(cd "$HEALTHY" && npx --yes --package="file:$TARBALL" flow-agents workflow doctor --project-root "$HEALTHY" --artifact-root "$HEALTHY/.kontourai/flow-agents" --json 2>/dev/null)"
|
|
244
|
+
TAMPERED_HOOK_RC=$?
|
|
245
|
+
set -e
|
|
246
|
+
[[ "$TAMPERED_HOOK_RC" -eq 2 && "$TAMPERED_HOOK_JSON" == *"does not contain the packaged managed hooks"* ]] || fail "doctor accepted name-preserving hook tampering"
|
|
247
|
+
cp "$TMP/hooks.json.clean" "$HEALTHY/.codex/hooks.json"
|
|
248
|
+
pass "doctor rejects name-preserving hook configuration tampering"
|
|
249
|
+
|
|
250
|
+
for RUNTIME in base claude-code opencode pi kiro; do
|
|
251
|
+
RUNTIME_ROOT="$TMP/runtime-$RUNTIME"
|
|
252
|
+
mkdir -p "$RUNTIME_ROOT"
|
|
253
|
+
(cd "$RUNTIME_ROOT" && npx --yes --package="file:$TARBALL" flow-agents init --runtime "$RUNTIME" --dest "$RUNTIME_ROOT" --activate-kit builder --yes >/dev/null)
|
|
254
|
+
RUNTIME_JSON="$(cd "$RUNTIME_ROOT" && npx --yes --package="file:$TARBALL" flow-agents workflow doctor --project-root "$RUNTIME_ROOT" --artifact-root "$RUNTIME_ROOT/.kontourai/flow-agents" --json)"
|
|
255
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(!r.ok||r.warnings.length||!r.hook.integrity.ok)process.exit(1)' "$RUNTIME_JSON" || fail "doctor did not validate $RUNTIME runtime wiring"
|
|
256
|
+
pass "doctor validates $RUNTIME runtime wiring"
|
|
257
|
+
done
|
|
258
|
+
|
|
259
|
+
printf 'public workflow CLI integration passed\n'
|
|
@@ -4228,6 +4228,7 @@ flow_agents_node "$WRITER" ensure-session \
|
|
|
4228
4228
|
--task-slug "$UNRESOLVABLE_SLUG" \
|
|
4229
4229
|
--actor unresolvable-actor \
|
|
4230
4230
|
--flow-id builder.build \
|
|
4231
|
+
--skip-ownership-guard \
|
|
4231
4232
|
--title "Unresolvable FlowDefinition regression" \
|
|
4232
4233
|
--source-request "Regression: an unresolvable FlowDefinition must die with the dedicated cannot-be-loaded message." \
|
|
4233
4234
|
--summary "Seed session with a REAL flow so a real stamped gate claim can be recorded." \
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontourai/flow-agents",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Flow Agents — a Kontour product that applies Flow and Veritas discipline as a portable process layer inside the agent tools you already use: Claude Code, Codex, Kiro, opencode, pi, and GitHub Actions — with framework adapters (AWS Strands preview) on the same policy-engine contract.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
"typescript": "^6.0.3"
|
|
150
150
|
},
|
|
151
151
|
"dependencies": {
|
|
152
|
-
"@kontourai/flow": "^3.
|
|
152
|
+
"@kontourai/flow": "^3.1.0"
|
|
153
153
|
},
|
|
154
154
|
"optionalDependencies": {
|
|
155
155
|
"@kontourai/surface": "^2.0.0",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://kontourai.dev/schemas/builder-lifecycle-authorization.schema.json",
|
|
4
|
+
"title": "Builder Lifecycle Authorization",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "operation", "run_id", "subject", "assignment_actor_key", "assignment_actor", "nonce", "expires_at", "request", "signature"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": "1.0" },
|
|
10
|
+
"operation": { "enum": ["cancel", "archive"] },
|
|
11
|
+
"run_id": { "type": "string", "minLength": 1 },
|
|
12
|
+
"subject": { "type": "string", "minLength": 1 },
|
|
13
|
+
"assignment_actor_key": { "type": "string", "minLength": 1 },
|
|
14
|
+
"assignment_actor": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"required": ["runtime", "session_id", "host", "human"],
|
|
18
|
+
"properties": {
|
|
19
|
+
"runtime": { "type": "string", "minLength": 1 },
|
|
20
|
+
"session_id": { "type": "string", "minLength": 1 },
|
|
21
|
+
"host": { "type": "string", "minLength": 1 },
|
|
22
|
+
"human": { "type": ["string", "null"] }
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"nonce": { "type": "string", "minLength": 1, "maxLength": 256 },
|
|
26
|
+
"expires_at": { "type": "string", "format": "date-time" },
|
|
27
|
+
"request": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"additionalProperties": false,
|
|
30
|
+
"required": ["reason", "authority"],
|
|
31
|
+
"properties": {
|
|
32
|
+
"reason": { "type": "string", "minLength": 1 },
|
|
33
|
+
"authority": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"additionalProperties": false,
|
|
36
|
+
"required": ["kind", "actor", "request_ref", "requested_at"],
|
|
37
|
+
"properties": {
|
|
38
|
+
"kind": { "enum": ["user_request", "operator_request"] },
|
|
39
|
+
"actor": { "type": "string", "minLength": 1 },
|
|
40
|
+
"request_ref": { "type": "string", "minLength": 1 },
|
|
41
|
+
"requested_at": { "type": "string", "format": "date-time" }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"signature": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"additionalProperties": false,
|
|
49
|
+
"required": ["algorithm", "key_id", "value"],
|
|
50
|
+
"properties": {
|
|
51
|
+
"algorithm": { "const": "ed25519" },
|
|
52
|
+
"key_id": { "type": "string", "minLength": 1 },
|
|
53
|
+
"value": { "type": "string", "minLength": 1 }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://kontourai.dev/schemas/lifecycle-authority-keys.schema.json",
|
|
4
|
+
"title": "Lifecycle Authority Key Registry",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "keys"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": "1.0" },
|
|
10
|
+
"keys": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"minItems": 1,
|
|
13
|
+
"items": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"additionalProperties": false,
|
|
16
|
+
"required": ["id", "algorithm", "public_key_pem"],
|
|
17
|
+
"properties": {
|
|
18
|
+
"id": { "type": "string", "minLength": 1 },
|
|
19
|
+
"algorithm": { "const": "ed25519" },
|
|
20
|
+
"public_key_pem": { "type": "string", "minLength": 1 }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"status": {
|
|
29
29
|
"type": "string",
|
|
30
|
-
"enum": ["new", "planning", "planned", "in_progress", "blocked", "verifying", "verified", "needs_decision", "not_verified", "failed", "delivered", "accepted", "archived"]
|
|
30
|
+
"enum": ["new", "planning", "planned", "in_progress", "blocked", "verifying", "verified", "needs_decision", "not_verified", "failed", "delivered", "canceled", "accepted", "archived"]
|
|
31
31
|
},
|
|
32
32
|
"phase": {
|
|
33
33
|
"type": "string",
|
|
@@ -120,6 +120,13 @@ function checkProtectedPathPattern(filePath) {
|
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
if (/(?:^|\/)\.flow-agents\/lifecycle-authority-keys\.json$/.test(norm)) {
|
|
124
|
+
return {
|
|
125
|
+
name: '.flow-agents/lifecycle-authority-keys.json',
|
|
126
|
+
reason: 'an agent could replace the pinned lifecycle authority key and forge cancellation authority',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
123
130
|
// .kontourai/flow-agents/current.json — an agent could forge active_flow_id / active_step_id
|
|
124
131
|
// to route the gate to a permissive or empty-expects FlowDefinition.
|
|
125
132
|
// SAFE: the workflow CLI writes current.json via fs (writeJson → fs.writeFileSync),
|
|
@@ -426,7 +433,7 @@ function checkCommandForBypass(command) {
|
|
|
426
433
|
*/
|
|
427
434
|
// #379: the delivery/ arms carry an optional (?:[^/]+\/)? segment so redirects/tee to the
|
|
428
435
|
// per-session path delivery/<slug>/trust.bundle (+ checkpoint) are caught, not just the flat path.
|
|
429
|
-
const REDIRECT_PROTECTED_RE = /(?:^|\/|~\/)(\.bash_profile|\.bashrc|\.profile|\.zprofile|\.zshrc)$|(?:^|\/)\.claude\/settings(?:\.local)?\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\/[^/]+\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/\.goal-fit-block-streak\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/state\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/trust\.bundle$|(?:^|\/)delivery\/(?:[^/]+\/)?trust\.bundle$|(?:^|\/)delivery\/(?:[^/]+\/)?trust\.checkpoint\.json$/;
|
|
436
|
+
const REDIRECT_PROTECTED_RE = /(?:^|\/|~\/)(\.bash_profile|\.bashrc|\.profile|\.zprofile|\.zshrc)$|(?:^|\/)\.claude\/settings(?:\.local)?\.json$|(?:^|\/)\.flow-agents\/lifecycle-authority-keys\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\/[^/]+\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/\.goal-fit-block-streak\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/state\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/trust\.bundle$|(?:^|\/)delivery\/(?:[^/]+\/)?trust\.bundle$|(?:^|\/)delivery\/(?:[^/]+\/)?trust\.checkpoint\.json$/;
|
|
430
437
|
|
|
431
438
|
/**
|
|
432
439
|
* Return true when a token (an unquoted redirect target or tee argument) matches
|
|
@@ -20,6 +20,8 @@ const SANCTIONED_REMEDIES = {
|
|
|
20
20
|
'There is no sanctioned automated writer for this file. Ask a human maintainer to edit it directly. Never disable this hook to make the write.',
|
|
21
21
|
'.claude/settings.local.json':
|
|
22
22
|
'There is no sanctioned automated writer for this file. Ask a human maintainer to edit it directly. Never disable this hook to make the write.',
|
|
23
|
+
'.flow-agents/lifecycle-authority-keys.json':
|
|
24
|
+
'Only the trusted harness installer or a human maintainer may rotate lifecycle authority keys. Never let an agent replace this trust root.',
|
|
23
25
|
'.kontourai/flow-agents/current.json':
|
|
24
26
|
'Use `npm run workflow:sidecar -- ensure-session` (or `advance-state`), which writes this file for you. Never disable this hook to make the write.',
|
|
25
27
|
'.kontourai/flow-agents/current/<actor>.json':
|
|
@@ -58,6 +60,7 @@ function remedyFor(name) {
|
|
|
58
60
|
* basename 'trust.bundle') -- first match wins, deterministically.
|
|
59
61
|
*/
|
|
60
62
|
const REMEDY_COMMAND_CANDIDATES = [
|
|
63
|
+
{ name: '.flow-agents/lifecycle-authority-keys.json', needles: ['lifecycle-authority-keys.json'] },
|
|
61
64
|
{ name: 'delivery/trust.checkpoint.json', needles: ['delivery/trust.checkpoint.json', 'trust.checkpoint.json'] },
|
|
62
65
|
{ name: 'delivery/trust.bundle', needles: ['delivery/trust.bundle'] },
|
|
63
66
|
{ name: '.kontourai/flow-agents/<slug>/trust.bundle', needles: ['trust.bundle'] },
|
|
@@ -77,7 +77,7 @@ const PRE_EXECUTION_PHASES = new Set(['idea', 'backlog', 'pickup', 'planning']);
|
|
|
77
77
|
// Terminal tasks are complete — they must never gate a stop or count as "active".
|
|
78
78
|
// A stale current.json pointing at one, or a graveyard of finished states, must
|
|
79
79
|
// not block an unrelated session.
|
|
80
|
-
const TERMINAL_STATUSES = new Set(['done', 'delivered', 'accepted', 'archived', 'complete', 'completed']);
|
|
80
|
+
const TERMINAL_STATUSES = new Set(['done', 'delivered', 'canceled', 'accepted', 'archived', 'complete', 'completed']);
|
|
81
81
|
|
|
82
82
|
function isTerminalDeliveredState(state) {
|
|
83
83
|
if (!state || typeof state !== 'object') return false;
|
|
@@ -5,15 +5,19 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { isDeepStrictEqual } from "node:util";
|
|
6
6
|
import {
|
|
7
7
|
attachEvidence,
|
|
8
|
+
cancelRun,
|
|
8
9
|
evaluateRun,
|
|
9
10
|
expectationsForGate,
|
|
10
11
|
loadRun,
|
|
11
12
|
normalizeTrustBundle,
|
|
12
13
|
openGates,
|
|
14
|
+
pauseRun,
|
|
13
15
|
readJson,
|
|
16
|
+
resumeRun,
|
|
14
17
|
startRun,
|
|
15
18
|
validateDefinition,
|
|
16
19
|
type FlowEvidenceEntry,
|
|
20
|
+
type FlowLifecycleRequest,
|
|
17
21
|
type FlowRunState,
|
|
18
22
|
type GateOutcome,
|
|
19
23
|
type JsonObject,
|
|
@@ -67,6 +71,11 @@ export interface LoadBuilderBuildRunInput {
|
|
|
67
71
|
cwd?: string;
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
export interface ChangeBuilderBuildRunLifecycleInput extends LoadBuilderBuildRunInput {
|
|
75
|
+
request: FlowLifecycleRequest;
|
|
76
|
+
at?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
70
79
|
export interface BuilderBuildRunResult {
|
|
71
80
|
definitionId: typeof BUILDER_BUILD_FLOW_ID;
|
|
72
81
|
definitionVersion: string;
|
|
@@ -189,6 +198,44 @@ export async function loadBuilderBuildRun(input: LoadBuilderBuildRunInput): Prom
|
|
|
189
198
|
return resultFromRun(run, input.runId);
|
|
190
199
|
}
|
|
191
200
|
|
|
201
|
+
export async function pauseBuilderBuildRun(input: ChangeBuilderBuildRunLifecycleInput): Promise<BuilderBuildRunResult> {
|
|
202
|
+
return changeBuilderBuildRunLifecycle(input, pauseRun);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function resumeBuilderBuildRun(input: ChangeBuilderBuildRunLifecycleInput): Promise<BuilderBuildRunResult> {
|
|
206
|
+
return changeBuilderBuildRunLifecycle(input, resumeRun);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function cancelBuilderBuildRun(input: ChangeBuilderBuildRunLifecycleInput): Promise<BuilderBuildRunResult & { idempotent: boolean }> {
|
|
210
|
+
const changed = await changeBuilderBuildRunLifecycleResult(input, cancelRun);
|
|
211
|
+
return { ...resultFromRun(changed, input.runId), idempotent: changed.idempotent };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function changeBuilderBuildRunLifecycle(
|
|
215
|
+
input: ChangeBuilderBuildRunLifecycleInput,
|
|
216
|
+
operation: typeof pauseRun | typeof resumeRun,
|
|
217
|
+
): Promise<BuilderBuildRunResult> {
|
|
218
|
+
const changed = await changeBuilderBuildRunLifecycleResult(input, operation);
|
|
219
|
+
return resultFromRun(changed, input.runId);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function changeBuilderBuildRunLifecycleResult(
|
|
223
|
+
input: ChangeBuilderBuildRunLifecycleInput,
|
|
224
|
+
operation: typeof pauseRun | typeof resumeRun | typeof cancelRun,
|
|
225
|
+
) {
|
|
226
|
+
assertRuntimeInput(input, []);
|
|
227
|
+
if (!isRecord(input.request)) throw new BuilderBuildRunInputError("request", "must be a lifecycle request object");
|
|
228
|
+
const cwd = input.cwd ?? process.cwd();
|
|
229
|
+
const before = await loadBuilderBuildRun({ runId: input.runId, cwd });
|
|
230
|
+
const changed = await operation(input.runId, { cwd, ...input.request, ...(input.at ? { at: input.at } : {}) });
|
|
231
|
+
const definition = await loadShippedBuilderBuildDefinition(resolveBuilderBuildFlowDefinitionPath());
|
|
232
|
+
assertCanonicalDefinition(input.runId, definition, changed.definition);
|
|
233
|
+
if (changed.state.subject !== before.state.subject) {
|
|
234
|
+
throw new BuilderBuildRunInputError("flow_run.state.subject", "changed during lifecycle transition");
|
|
235
|
+
}
|
|
236
|
+
return changed;
|
|
237
|
+
}
|
|
238
|
+
|
|
192
239
|
function resultFromRun(run: Awaited<ReturnType<typeof loadRun>>, runId: string): BuilderBuildRunResult {
|
|
193
240
|
return {
|
|
194
241
|
definitionId: run.definition.id,
|