@kontourai/flow-agents 3.5.0 → 3.7.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 +32 -3
- package/build/src/builder-flow-run-adapter.js +113 -20
- package/build/src/builder-flow-runtime.d.ts +26 -3
- package/build/src/builder-flow-runtime.js +502 -49
- 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 +14 -7
- package/build/src/cli/assignment-provider.js +128 -65
- package/build/src/cli/builder-run.js +46 -9
- package/build/src/cli/workflow-artifact-cleanup-audit.js +3 -0
- package/build/src/cli/workflow-sidecar.d.ts +30 -3
- package/build/src/cli/workflow-sidecar.js +825 -99
- package/build/src/cli/workflow.d.ts +2 -0
- package/build/src/cli/workflow.js +769 -0
- package/build/src/cli.js +2 -0
- package/build/src/flow-kit/validate.d.ts +17 -0
- package/build/src/flow-kit/validate.js +340 -2
- package/build/src/index.d.ts +4 -0
- package/build/src/index.js +7 -5
- package/build/src/lib/observed-command.d.ts +7 -0
- package/build/src/lib/observed-command.js +100 -0
- 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/build/src/tools/generate-context-map.js +5 -3
- package/context/contracts/artifact-contract.md +1 -1
- package/context/scripts/hooks/config-protection.js +8 -1
- package/context/scripts/hooks/lib/config-protection-remedies.js +3 -0
- package/context/scripts/hooks/lib/kit-catalog.js +1 -1
- package/context/scripts/hooks/stop-goal-fit.js +79 -10
- package/docs/context-map.md +24 -20
- package/docs/developer-architecture.md +1 -1
- package/docs/public-workflow-cli.md +132 -0
- package/docs/skills-map.md +51 -27
- package/docs/spec/builder-flow-runtime.md +78 -40
- package/docs/workflow-usage-guide.md +110 -38
- package/evals/ci/run-baseline.sh +2 -0
- package/evals/fixtures/hook-influence/cases.json +2 -2
- package/evals/integration/test_builder_entry_enforcement.sh +57 -45
- package/evals/integration/test_builder_step_producers.sh +297 -6
- package/evals/integration/test_bundle_install.sh +258 -55
- 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_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 +573 -0
- 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_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/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/lib/kit-catalog.js +1 -1
- package/scripts/hooks/stop-goal-fit.js +79 -10
- package/src/builder-flow-run-adapter.ts +156 -23
- package/src/builder-flow-runtime.ts +535 -53
- package/src/builder-lifecycle-authority.ts +218 -0
- package/src/cli/assignment-provider-lock.test.mjs +83 -0
- package/src/cli/assignment-provider.ts +91 -22
- package/src/cli/builder-flow-run-adapter.test.mjs +4 -2
- package/src/cli/builder-flow-runtime.test.mjs +597 -8
- package/src/cli/builder-run.ts +54 -9
- package/src/cli/kit-metadata-security.test.mjs +232 -6
- package/src/cli/public-api.test.mjs +15 -0
- package/src/cli/workflow-artifact-cleanup-audit.ts +3 -0
- package/src/cli/workflow-sidecar-execution-proof.test.mjs +90 -0
- package/src/cli/workflow-sidecar.ts +748 -99
- package/src/cli/workflow.ts +708 -0
- package/src/cli.ts +2 -0
- package/src/flow-kit/validate.ts +320 -2
- package/src/index.ts +20 -5
- package/src/lib/observed-command.ts +96 -0
- package/src/lib/package-version.ts +15 -0
- package/src/lib/pinned-cli-command.ts +23 -0
- package/src/tools/generate-context-map.ts +5 -3
|
@@ -0,0 +1,573 @@
|
|
|
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
|
+
mkdir -p "$CONSUMER/checks"
|
|
22
|
+
printf '#!/usr/bin/env bash\nset -eu\ntest -f "$1"\nprintf "1..1\\nok 1 - session exists\\n"\n' > "$CONSUMER/checks/check-public-workflow.sh"
|
|
23
|
+
chmod +x "$CONSUMER/checks/check-public-workflow.sh"
|
|
24
|
+
printf '#!/usr/bin/env bash\nset -eu\ntouch "$1"\nsleep 1\n' > "$CONSUMER/checks/check-command-lock.sh"
|
|
25
|
+
chmod +x "$CONSUMER/checks/check-command-lock.sh"
|
|
26
|
+
printf '#!/usr/bin/env bash\nset -eu\ntest -f "$1"\nprintf "run\\n" >> "$2"\nprintf "1..1\\nok 1 - session exists\\n"\n' > "$CONSUMER/checks/check-multi-command.sh"
|
|
27
|
+
chmod +x "$CONSUMER/checks/check-multi-command.sh"
|
|
28
|
+
printf '#!/usr/bin/env bash\nset -eu\ntrap "" TERM\n( trap "" TERM; sleep 5; touch "$2" ) &\nchild=$!\nprintf "%s\\n" "$child" > "$1"\nwait "$child"\ntouch "$2"\n' > "$CONSUMER/checks/check-command-timeout.sh"
|
|
29
|
+
chmod +x "$CONSUMER/checks/check-command-timeout.sh"
|
|
30
|
+
printf '#!/usr/bin/env bash\nset -eu\n( trap "" TERM; while ! sleep 5; do :; done; touch "$2" ) &\nprintf "%s\\n" "$!" > "$1"\n' > "$CONSUMER/checks/check-success-background.sh"
|
|
31
|
+
chmod +x "$CONSUMER/checks/check-success-background.sh"
|
|
32
|
+
|
|
33
|
+
run_candidate() {
|
|
34
|
+
(cd "$CONSUMER" && CODEX_SESSION_ID=public-workflow-eval npx --yes --package="file:$TARBALL" flow-agents workflow "$@")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
run_candidate_as() {
|
|
38
|
+
local actor="$1"
|
|
39
|
+
shift
|
|
40
|
+
(cd "$CONSUMER" && CODEX_SESSION_ID="$actor" npx --yes --package="file:$TARBALL" flow-agents workflow "$@")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
snapshot_tree() {
|
|
44
|
+
local root="$1"
|
|
45
|
+
find "$root" -type f -print0 | sort -z | xargs -0 shasum -a 256
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
PRIMARY_HELP="$(cd "$CONSUMER" && npx --yes --package="file:$TARBALL" flow-agents --help)"
|
|
49
|
+
WORKFLOW_HELP="$(run_candidate --help)"
|
|
50
|
+
[[ "$PRIMARY_HELP" == *"workflow"* && "$WORKFLOW_HELP" != *"workflow-sidecar"* && "$WORKFLOW_HELP" != *"npm run workflow:sidecar"* ]] || fail "public help exposes internal writer terminology or omits workflow"
|
|
51
|
+
pass "primary help exposes the public workflow command without internal writer terminology"
|
|
52
|
+
|
|
53
|
+
seed_pull_work() {
|
|
54
|
+
local work_item="$1"
|
|
55
|
+
local slug
|
|
56
|
+
slug="$(printf '%s' "$work_item" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')"
|
|
57
|
+
mkdir -p "$ARTIFACT_ROOT/$slug"
|
|
58
|
+
printf 'Selected Work Item: %s\n' "$work_item" >"$ARTIFACT_ROOT/$slug/$slug--pull-work.md"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
seed_pull_work acme/widgets#101
|
|
62
|
+
run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#101 --assignment-provider local-file --summary "Release fixture" >/dev/null
|
|
63
|
+
RELEASE_SESSION="$ARTIFACT_ROOT/acme-widgets-101"
|
|
64
|
+
[[ -f "$RELEASE_SESSION/state.json" ]] || fail "packed start did not create a session"
|
|
65
|
+
[[ ! -e "$CONSUMER/package.json" ]] || fail "consumer unexpectedly gained package.json"
|
|
66
|
+
pass "packed start works in a non-Node consumer"
|
|
67
|
+
seed_pull_work provider:work-item-123
|
|
68
|
+
run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item provider:work-item-123 --assignment-provider local-file --summary "Provider-neutral fixture" >/dev/null
|
|
69
|
+
PROVIDER_STATUS="$(run_candidate status --session-dir "$ARTIFACT_ROOT/provider-work-item-123" --json)"
|
|
70
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.definition_id!=="builder.build"||r.current_step!=="design-probe")process.exit(1)' "$PROVIDER_STATUS" || fail "provider-neutral Work Item did not start canonically"
|
|
71
|
+
pass "documented provider-neutral Work Item refs start without GitHub identity inference"
|
|
72
|
+
seed_pull_work provider:externally-owned-456
|
|
73
|
+
PROVIDER_STATE="$CONSUMER/provider-assignment-state.json"
|
|
74
|
+
node - "$PROVIDER_STATE" "$ARTIFACT_ROOT/assignment/acme-widgets-101.json" <<'NODE'
|
|
75
|
+
const fs = require('node:fs');
|
|
76
|
+
const local = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
|
|
77
|
+
const subject = 'provider-externally-owned-456';
|
|
78
|
+
const record = { ...local, subject_id: subject, work_item_ref: 'provider:externally-owned-456' };
|
|
79
|
+
fs.writeFileSync(process.argv[2], `${JSON.stringify({ role: 'AssignmentStatus', provider: 'example-provider', assignment: { subject_id: subject, provider: 'example-provider', assignee: local.actor_key, record }, effective: { effective_state: 'held', reason: 'self_is_holder', holder: { actor: local.actor_key } } }, null, 2)}\n`);
|
|
80
|
+
NODE
|
|
81
|
+
run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item provider:externally-owned-456 --assignment-provider example-provider --effective-state-json "$PROVIDER_STATE" --summary "Externally assigned fixture" >/dev/null
|
|
82
|
+
EXTERNAL_SESSION="$ARTIFACT_ROOT/provider-externally-owned-456"
|
|
83
|
+
node - "$ARTIFACT_ROOT/assignment/provider-externally-owned-456.json" "$EXTERNAL_SESSION/assignment-provider-state.json" "$EXTERNAL_SESSION/trust.bundle" <<'NODE'
|
|
84
|
+
const fs = require('node:fs');
|
|
85
|
+
const assignment = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
86
|
+
const provider = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
|
|
87
|
+
const bundle = JSON.parse(fs.readFileSync(process.argv[4], 'utf8'));
|
|
88
|
+
const selected = bundle.claims.find((claim) => claim.metadata?.gate_claim?.expectation_id === 'selected-work');
|
|
89
|
+
if (assignment.work_item_ref !== 'provider:externally-owned-456' || provider.effective?.reason !== 'self_is_holder' || !selected?.metadata?.artifact_refs?.some((ref) => ref.file?.endsWith('assignment-provider-state.json'))) process.exit(1);
|
|
90
|
+
NODE
|
|
91
|
+
pass "provider-confirmed ownership is retained as evidence and mirrored into a local runtime lease"
|
|
92
|
+
seed_pull_work provider:missing-provider
|
|
93
|
+
set +e
|
|
94
|
+
MISSING_PROVIDER="$(run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item provider:missing-provider --summary "Missing provider fixture" 2>&1)"
|
|
95
|
+
MISSING_PROVIDER_RC=$?
|
|
96
|
+
set -e
|
|
97
|
+
[[ "$MISSING_PROVIDER_RC" -ne 0 && "$MISSING_PROVIDER" == *"requires --assignment-provider"* && ! -e "$ARTIFACT_ROOT/provider-missing-provider/state.json" ]] || fail "public start inferred an assignment provider or mutated before rejecting it"
|
|
98
|
+
pass "public start requires explicit assignment-provider resolution before mutation"
|
|
99
|
+
set +e
|
|
100
|
+
MISSING_PULL_WORK="$(run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#105 --assignment-provider local-file --summary "Missing selection evidence" 2>&1)"
|
|
101
|
+
MISSING_PULL_WORK_RC=$?
|
|
102
|
+
set -e
|
|
103
|
+
[[ "$MISSING_PULL_WORK_RC" -ne 0 && "$MISSING_PULL_WORK" == *"requires concrete pull-work selection evidence"* && ! -e "$ARTIFACT_ROOT/acme-widgets-105/state.json" ]] || fail "public start produced selected-work without a concrete pull-work report"
|
|
104
|
+
pass "public start requires session-local pull-work selection evidence before auto-producing selected-work"
|
|
105
|
+
run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.shape --task-slug shape-fixture --summary "Shape fixture" >/dev/null
|
|
106
|
+
SHAPE_SESSION="$ARTIFACT_ROOT/shape-fixture"
|
|
107
|
+
SHAPE_STATUS="$(run_candidate status --session-dir "$SHAPE_SESSION" --json)"
|
|
108
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.definition_id!=="builder.shape"||r.current_step!=="shape")process.exit(1)' "$SHAPE_STATUS" || fail "shape status did not resolve canonical Flow run"
|
|
109
|
+
node - "$SHAPE_SESSION/work-item.json" <<'NODE'
|
|
110
|
+
const fs = require('node:fs');
|
|
111
|
+
const record = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
112
|
+
if (record.id !== 'shape-fixture' || record.source_provider?.kind !== 'local') process.exit(1);
|
|
113
|
+
NODE
|
|
114
|
+
run_candidate evidence --session-dir "$SHAPE_SESSION" --expectation shaped-problem --status not_verified --summary "Shape fixture remains intentionally unverified." --json >/dev/null
|
|
115
|
+
pass "shape starts as an explicit local Work Item and records public evidence through Flow"
|
|
116
|
+
SHAPE_RETRY_BEFORE="$(snapshot_tree "$ARTIFACT_ROOT/shape-fixture")"
|
|
117
|
+
set +e
|
|
118
|
+
SHAPE_TO_BUILD="$(run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item local:shape-fixture --task-slug shape-fixture --assignment-provider local-file --summary "Invalid shape retry" 2>&1)"
|
|
119
|
+
SHAPE_TO_BUILD_RC=$?
|
|
120
|
+
set -e
|
|
121
|
+
SHAPE_RETRY_AFTER="$(snapshot_tree "$ARTIFACT_ROOT/shape-fixture")"
|
|
122
|
+
[[ "$SHAPE_TO_BUILD_RC" -ne 0 && "$SHAPE_TO_BUILD" == *"local shape sessions are not build retries"* && "$SHAPE_RETRY_BEFORE" == "$SHAPE_RETRY_AFTER" ]] || fail "public local shape-to-build retry did not fail clearly before mutation"
|
|
123
|
+
pass "public start rejects local shape-to-build retries; provider Work Items are the build handoff"
|
|
124
|
+
set +e
|
|
125
|
+
UNSAFE_START="$(run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#103 --assignment-provider local-file --skip-ownership-guard 2>&1)"
|
|
126
|
+
UNSAFE_START_RC=$?
|
|
127
|
+
set -e
|
|
128
|
+
[[ "$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"
|
|
129
|
+
pass "public start rejects internal authority flags before mutation"
|
|
130
|
+
|
|
131
|
+
LOCAL_RETRY_PROJECT="$TMP/local-retry-project"
|
|
132
|
+
LOCAL_RETRY_ROOT="$LOCAL_RETRY_PROJECT/.kontourai/flow-agents"
|
|
133
|
+
mkdir -p "$LOCAL_RETRY_ROOT/local-retry"
|
|
134
|
+
printf 'Selected Work Item: local:local-retry\n' > "$LOCAL_RETRY_ROOT/local-retry/local-retry--pull-work.md"
|
|
135
|
+
printf 'not a run-store directory\n' >"$LOCAL_RETRY_PROJECT/.kontourai/flow"
|
|
136
|
+
set +e
|
|
137
|
+
(cd "$LOCAL_RETRY_PROJECT" && CODEX_SESSION_ID=public-workflow-eval npx --yes --package="file:$TARBALL" flow-agents-workflow-sidecar ensure-session \
|
|
138
|
+
--artifact-root "$LOCAL_RETRY_ROOT" --task-slug local-retry \
|
|
139
|
+
--title "Local retry" --summary "Resume the bound local workflow." --flow-id builder.build >/dev/null 2>&1)
|
|
140
|
+
LOCAL_SEED_RC=$?
|
|
141
|
+
set -e
|
|
142
|
+
[[ "$LOCAL_SEED_RC" -eq 0 && ! -e "$LOCAL_RETRY_PROJECT/.kontourai/flow/runs/local-retry/state.json" ]] || fail "private writer advanced canonical Flow instead of only seeding the public retry"
|
|
143
|
+
pass "private workflow writer cannot start or advance canonical Flow"
|
|
144
|
+
LOCAL_RETRY_COMMAND="$(node -p "JSON.parse(require('fs').readFileSync('$LOCAL_RETRY_ROOT/local-retry/state.json')).next_action.command")"
|
|
145
|
+
[[ "$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"
|
|
146
|
+
rm -f "$LOCAL_RETRY_PROJECT/.kontourai/flow"
|
|
147
|
+
FOREIGN_RETRY_CWD="$TMP/foreign-retry-cwd"
|
|
148
|
+
mkdir -p "$FOREIGN_RETRY_CWD"
|
|
149
|
+
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'")"
|
|
150
|
+
(cd "$FOREIGN_RETRY_CWD" && CODEX_SESSION_ID=public-workflow-eval eval "$EXECUTABLE_RETRY" >/dev/null)
|
|
151
|
+
[[ -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"
|
|
152
|
+
pass "emitted local retry executes from a foreign cwd against its exact originating store"
|
|
153
|
+
|
|
154
|
+
BEFORE_STATUS="$(snapshot_tree "$CONSUMER/.kontourai")"
|
|
155
|
+
STATUS_JSON="$(run_candidate status --session-dir "$RELEASE_SESSION" --json)"
|
|
156
|
+
AFTER_STATUS="$(snapshot_tree "$CONSUMER/.kontourai")"
|
|
157
|
+
[[ "$BEFORE_STATUS" == "$AFTER_STATUS" ]] || fail "workflow status mutated durable artifacts"
|
|
158
|
+
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"
|
|
159
|
+
pass "status is canonical and byte-read-only"
|
|
160
|
+
|
|
161
|
+
FLOW_MANIFEST="$CONSUMER/.kontourai/flow/runs/acme-widgets-101/evidence/manifest.json"
|
|
162
|
+
BEFORE_EVIDENCE="$(node -p "JSON.parse(require('fs').readFileSync('$FLOW_MANIFEST')).evidence.length")"
|
|
163
|
+
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
|
|
164
|
+
AFTER_EVIDENCE="$(node -p "JSON.parse(require('fs').readFileSync('$FLOW_MANIFEST')).evidence.length")"
|
|
165
|
+
[[ "$AFTER_EVIDENCE" -eq $((BEFORE_EVIDENCE + 1)) ]] || fail "evidence invocation did not attach exactly once"
|
|
166
|
+
pass "evidence records and synchronizes exactly once"
|
|
167
|
+
|
|
168
|
+
PULL_REPORT="$RELEASE_SESSION/$(basename "$RELEASE_SESSION")--pull-work.md"
|
|
169
|
+
PULL_REPORT_REF="{\"kind\":\"artifact\",\"file\":\"$PULL_REPORT\",\"summary\":\"Concrete selected-work and probe report.\"}"
|
|
170
|
+
run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation pickup-probe-readiness --status pass --summary "Complete the consumer readiness expectation after exercising NOT_VERIFIED." --evidence-ref-json "$PULL_REPORT_REF" --json >/dev/null
|
|
171
|
+
run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation probe-decisions-or-accepted-gaps --status pass --summary "Complete the consumer probe gate." --evidence-ref-json "$PULL_REPORT_REF" --json >/dev/null
|
|
172
|
+
seed_pull_work acme/widgets#104
|
|
173
|
+
run_candidate_as poison-pointer start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#104 --assignment-provider local-file --summary "Poison global pointer fixture" >/dev/null
|
|
174
|
+
node - "$ARTIFACT_ROOT" "$(basename "$RELEASE_SESSION")" <<'NODE'
|
|
175
|
+
const fs = require('node:fs');
|
|
176
|
+
const path = require('node:path');
|
|
177
|
+
const [root, releaseSlug] = process.argv.slice(2);
|
|
178
|
+
const actorRoot = path.join(root, 'current');
|
|
179
|
+
for (const name of fs.readdirSync(actorRoot)) {
|
|
180
|
+
if (!name.endsWith('.json')) continue;
|
|
181
|
+
const file = path.join(actorRoot, name);
|
|
182
|
+
const pointer = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
183
|
+
if (pointer.active_slug !== releaseSlug) continue;
|
|
184
|
+
pointer.active_slug = 'acme-widgets-104';
|
|
185
|
+
pointer.artifact_dir = 'acme-widgets-104';
|
|
186
|
+
pointer.active_step_id = 'design-probe';
|
|
187
|
+
fs.writeFileSync(file, `${JSON.stringify(pointer, null, 2)}\n`);
|
|
188
|
+
}
|
|
189
|
+
NODE
|
|
190
|
+
PLAN_REPORT="$RELEASE_SESSION/$(basename "$RELEASE_SESSION")--plan-work.md"
|
|
191
|
+
printf '# Plan Work\n\nReviewed fixture plan and acceptance mapping.\n' > "$PLAN_REPORT"
|
|
192
|
+
PLAN_REPORT_REF="{\"kind\":\"artifact\",\"file\":\"$PLAN_REPORT\",\"summary\":\"Reviewed implementation plan.\"}"
|
|
193
|
+
EXACT_SESSION_RESULT="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation implementation-plan --status pass --summary "Exact session binding must ignore the poisoned global pointer." --evidence-ref-json "$PLAN_REPORT_REF" --json)"
|
|
194
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.current_step!=="execute"||!String(r.next_action?.summary).includes("`execute`"))process.exit(1)' "$EXACT_SESSION_RESULT" || fail "public evidence followed a poisoned global pointer or returned a stale next action"
|
|
195
|
+
pass "evidence binds the exact session and returns the locked post-transition next action"
|
|
196
|
+
set +e
|
|
197
|
+
UNRELATED_EVIDENCE="$(run_candidate_as unrelated-caller evidence --session-dir "$RELEASE_SESSION" --expectation implementation-scope --status pass --summary rejected 2>&1)"
|
|
198
|
+
UNRELATED_EVIDENCE_RC=$?
|
|
199
|
+
set -e
|
|
200
|
+
[[ "$UNRELATED_EVIDENCE_RC" -ne 0 && "$UNRELATED_EVIDENCE" == *"active, matching assignment actor"* ]] || fail "public evidence allowed a non-holder to impersonate the assignment actor"
|
|
201
|
+
pass "evidence rejects callers that do not match the exact session assignment"
|
|
202
|
+
|
|
203
|
+
STALE_STATE="$TMP/stale-state.json"
|
|
204
|
+
cp "$RELEASE_SESSION/state.json" "$STALE_STATE"
|
|
205
|
+
node - "$RELEASE_SESSION/state.json" <<'NODE'
|
|
206
|
+
const fs = require('node:fs');
|
|
207
|
+
const file = process.argv[2];
|
|
208
|
+
const state = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
209
|
+
state.flow_run.current_step = 'pull-work';
|
|
210
|
+
state.next_action = { status: 'continue', summary: 'tampered projection' };
|
|
211
|
+
fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`);
|
|
212
|
+
NODE
|
|
213
|
+
DELIVER_REPORT="$RELEASE_SESSION/$(basename "$RELEASE_SESSION")--deliver.md"
|
|
214
|
+
DELIVER_REPORT_REF="{\"kind\":\"artifact\",\"file\":\"$DELIVER_REPORT\",\"summary\":\"Executed implementation scope report.\"}"
|
|
215
|
+
REPAIRED_EVIDENCE_RESULT="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation implementation-scope --status pass --summary "Repair stale projection before evidence." --evidence-ref-json "$DELIVER_REPORT_REF" --json)"
|
|
216
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.current_step!=="verify"||!String(r.next_action?.summary).includes("`verify`"))process.exit(1)' "$REPAIRED_EVIDENCE_RESULT" || fail "evidence did not return its locked post-transition report"
|
|
217
|
+
pass "evidence returns immutable postconditions captured while the subject lock is held"
|
|
218
|
+
FAILED_ROUTE_RESULT="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation tests-evidence --status fail --route-reason implementation_defect --summary "Failing fixture routes back to execution." --command false --json)"
|
|
219
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.current_step!=="execute"||!String(r.next_action?.summary).includes("implementation_defect"))process.exit(1)' "$FAILED_ROUTE_RESULT" || fail "failed command evidence did not select the declared canonical route-back"
|
|
220
|
+
REENTER_VERIFY_RESULT="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation implementation-scope --status pass --summary "Corrected implementation re-enters verification." --evidence-ref-json "$DELIVER_REPORT_REF" --json)"
|
|
221
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.current_step!=="verify")process.exit(1)' "$REENTER_VERIFY_RESULT" || fail "corrected implementation did not re-enter verification"
|
|
222
|
+
pass "public evidence records failing command observations and declared route-back reasons"
|
|
223
|
+
set +e
|
|
224
|
+
MISSING_TEST_COMMAND="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation tests-evidence --status pass --summary "Missing runnable test command." 2>&1)"
|
|
225
|
+
MISSING_TEST_COMMAND_RC=$?
|
|
226
|
+
FAILED_TEST_COMMAND="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation tests-evidence --status pass --summary "Failing test command." --command 'false' 2>&1)"
|
|
227
|
+
FAILED_TEST_COMMAND_RC=$?
|
|
228
|
+
VERSION_TEST_COMMAND="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation tests-evidence --status pass --summary "Version-only test command." --command 'node --version' 2>&1)"
|
|
229
|
+
VERSION_TEST_COMMAND_RC=$?
|
|
230
|
+
set -e
|
|
231
|
+
[[ "$MISSING_TEST_COMMAND_RC" -ne 0 && "$MISSING_TEST_COMMAND" == *"--command"* && "$FAILED_TEST_COMMAND_RC" -ne 0 && "$FAILED_TEST_COMMAND" == *"non-vacuous package script"* && "$VERSION_TEST_COMMAND_RC" -ne 0 && "$VERSION_TEST_COMMAND" == *"non-vacuous package script"* ]] || fail "tests-evidence did not require a real passing fixture assertion"
|
|
232
|
+
REJECTED_COMMAND_MARKER="$TMP/rejected-command-marker"
|
|
233
|
+
cp "$RELEASE_SESSION/state.json" "$TMP/rejected-command-state.clean.json"
|
|
234
|
+
node - "$RELEASE_SESSION/state.json" <<'NODE'
|
|
235
|
+
const fs = require('node:fs');
|
|
236
|
+
const file = process.argv[2];
|
|
237
|
+
const state = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
238
|
+
state.flow_run.current_step = 'pull-work';
|
|
239
|
+
state.next_action = { status: 'continue', summary: 'stale projection must remain untouched on rejection' };
|
|
240
|
+
fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`);
|
|
241
|
+
NODE
|
|
242
|
+
REJECTED_COMMAND_BEFORE="$(snapshot_tree "$RELEASE_SESSION")"
|
|
243
|
+
set +e
|
|
244
|
+
REJECTED_COMMAND="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation tests-evidence --status pass --summary "Rejected prose command must not run." --command "This prose command is invalid; touch '$REJECTED_COMMAND_MARKER'" 2>&1)"
|
|
245
|
+
REJECTED_COMMAND_RC=$?
|
|
246
|
+
set -e
|
|
247
|
+
REJECTED_COMMAND_AFTER="$(snapshot_tree "$RELEASE_SESSION")"
|
|
248
|
+
[[ "$REJECTED_COMMAND_RC" -ne 0 && "$REJECTED_COMMAND" == *"not a runnable shell command"* && ! -e "$REJECTED_COMMAND_MARKER" && "$REJECTED_COMMAND_BEFORE" == "$REJECTED_COMMAND_AFTER" ]] || fail "rejected evidence command executed or mutated durable artifacts"
|
|
249
|
+
mv "$TMP/rejected-command-state.clean.json" "$RELEASE_SESSION/state.json"
|
|
250
|
+
pass "rejected evidence commands have no process or durable-artifact side effects"
|
|
251
|
+
BEFORE_CRITIQUE_MANIFEST="$(shasum -a 256 "$FLOW_MANIFEST" | awk '{print $1}')"
|
|
252
|
+
BEFORE_CRITIQUE_BUNDLE="$(shasum -a 256 "$RELEASE_SESSION/trust.bundle" | awk '{print $1}')"
|
|
253
|
+
CODE_LANE="{\"id\":\"code\",\"status\":\"pass\",\"summary\":\"Code review covered the planned implementation.\",\"evidence_refs\":[{\"kind\":\"artifact\",\"file\":\"$PLAN_REPORT\",\"summary\":\"Reviewed implementation plan.\"}]}"
|
|
254
|
+
SECURITY_LANE="{\"id\":\"security\",\"status\":\"pass\",\"summary\":\"Security review covered the delivered scope.\",\"evidence_refs\":[{\"kind\":\"artifact\",\"file\":\"$DELIVER_REPORT\",\"summary\":\"Reviewed delivered scope.\"}]}"
|
|
255
|
+
set +e
|
|
256
|
+
SAME_ACTOR_CRITIQUE="$(run_candidate critique --session-dir "$RELEASE_SESSION" --id public-review --verdict pass --summary "Implementation actor cannot review its own work." --artifact-ref "$DELIVER_REPORT" --lane-json "$CODE_LANE" --lane-json "$SECURITY_LANE" 2>&1)"
|
|
257
|
+
SAME_ACTOR_CRITIQUE_RC=$?
|
|
258
|
+
set -e
|
|
259
|
+
[[ "$SAME_ACTOR_CRITIQUE_RC" -ne 0 && "$SAME_ACTOR_CRITIQUE" == *"reviewer identity distinct"* ]] || fail "public critique allowed the active implementation actor to self-review"
|
|
260
|
+
run_candidate_as public-reviewer critique --session-dir "$RELEASE_SESSION" --id public-review --verdict pass --summary "Public critique fixture." --artifact-ref "$DELIVER_REPORT" --lane-json "$CODE_LANE" --lane-json "$SECURITY_LANE" --json >/dev/null
|
|
261
|
+
AFTER_CRITIQUE_MANIFEST="$(shasum -a 256 "$FLOW_MANIFEST" | awk '{print $1}')"
|
|
262
|
+
AFTER_CRITIQUE_BUNDLE="$(shasum -a 256 "$RELEASE_SESSION/trust.bundle" | awk '{print $1}')"
|
|
263
|
+
[[ "$BEFORE_CRITIQUE_MANIFEST" == "$AFTER_CRITIQUE_MANIFEST" && "$BEFORE_CRITIQUE_BUNDLE" != "$AFTER_CRITIQUE_BUNDLE" ]] || fail "public critique attached to Flow or did not change trust.bundle"
|
|
264
|
+
pass "critique requires an active assignment but rejects self-review by its implementation actor"
|
|
265
|
+
CRITERION_ID="$(node -p "JSON.parse(require('fs').readFileSync('$RELEASE_SESSION/acceptance.json')).criteria[0].id")"
|
|
266
|
+
TEST_COMMAND="bash checks/check-public-workflow.sh .kontourai/flow-agents/$(basename "$RELEASE_SESSION")/state.json"
|
|
267
|
+
MULTI_COMMAND_ONE="$TMP/multi-command-one"
|
|
268
|
+
MULTI_COMMAND_TWO="$TMP/multi-command-two"
|
|
269
|
+
TEST_COMMAND_TWO="bash checks/check-multi-command.sh .kontourai/flow-agents/$(basename "$RELEASE_SESSION")/state.json '$MULTI_COMMAND_ONE'"
|
|
270
|
+
TEST_COMMAND_THREE="bash checks/check-multi-command.sh .kontourai/flow-agents/$(basename "$RELEASE_SESSION")/state.json '$MULTI_COMMAND_TWO'"
|
|
271
|
+
CRITERION_JSON="$(node - "$CRITERION_ID" "$TEST_COMMAND" "$TEST_COMMAND_TWO" "$TEST_COMMAND_THREE" <<'NODE'
|
|
272
|
+
const [id, ...commands] = process.argv.slice(2);
|
|
273
|
+
process.stdout.write(JSON.stringify({ id, status: 'pass', evidence_refs: commands.map((excerpt) => ({ kind: 'command', excerpt, summary: 'Asserts the bound session state exists.' })) }));
|
|
274
|
+
NODE
|
|
275
|
+
)"
|
|
276
|
+
COMMAND_REF="$(node - "$TEST_COMMAND" <<'NODE'
|
|
277
|
+
const command = process.argv[2];
|
|
278
|
+
process.stdout.write(JSON.stringify({ kind: 'command', excerpt: command, summary: 'Exact project-local public workflow check.' }));
|
|
279
|
+
NODE
|
|
280
|
+
)"
|
|
281
|
+
COMMAND_REF_TWO="$(node - "$TEST_COMMAND_TWO" <<'NODE'
|
|
282
|
+
const command = process.argv[2];
|
|
283
|
+
process.stdout.write(JSON.stringify({ kind: 'command', excerpt: command, summary: 'Exact first additional project-local check.' }));
|
|
284
|
+
NODE
|
|
285
|
+
)"
|
|
286
|
+
COMMAND_REF_THREE="$(node - "$TEST_COMMAND_THREE" <<'NODE'
|
|
287
|
+
const command = process.argv[2];
|
|
288
|
+
process.stdout.write(JSON.stringify({ kind: 'command', excerpt: command, summary: 'Exact second additional project-local check.' }));
|
|
289
|
+
NODE
|
|
290
|
+
)"
|
|
291
|
+
run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation tests-evidence --status pass --summary "Passing fixture assertion." --command "$TEST_COMMAND" --command "$TEST_COMMAND_TWO" --command "$TEST_COMMAND_THREE" --evidence-ref-json "$COMMAND_REF" --evidence-ref-json "$COMMAND_REF_TWO" --evidence-ref-json "$COMMAND_REF_THREE" --criterion-json "$CRITERION_JSON" --json >/dev/null
|
|
292
|
+
node - "$RELEASE_SESSION/trust.bundle" <<'NODE'
|
|
293
|
+
const fs = require('node:fs');
|
|
294
|
+
const bundle = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
295
|
+
const claim = bundle.claims.find((entry) => entry.metadata?.gate_claim?.expectation_id === 'tests-evidence');
|
|
296
|
+
if (!claim || claim.metadata?.output_digest?.algorithm !== 'sha256' || typeof claim.metadata.output_digest.hex !== 'string' || claim.metadata?.observed_commands?.length !== 3) process.exit(1);
|
|
297
|
+
NODE
|
|
298
|
+
[[ "$(wc -l < "$MULTI_COMMAND_ONE" | tr -d ' ')" == 1 && "$(wc -l < "$MULTI_COMMAND_TWO" | tr -d ' ')" == 1 ]] || fail "public evidence did not execute every repeated --command exactly once"
|
|
299
|
+
pass "tests-evidence executes every repeated command once and records matching observations"
|
|
300
|
+
|
|
301
|
+
ASSIGNMENT="$ARTIFACT_ROOT/assignment/$(basename "$RELEASE_SESSION").json"
|
|
302
|
+
ASSIGNMENT_TARGET="$TMP/assignment-target.json"
|
|
303
|
+
cp "$ASSIGNMENT" "$ASSIGNMENT_TARGET"
|
|
304
|
+
rm "$ASSIGNMENT"
|
|
305
|
+
ln -s "$ASSIGNMENT_TARGET" "$ASSIGNMENT"
|
|
306
|
+
set +e
|
|
307
|
+
SYMLINK_ASSIGNMENT="$(run_candidate critique --session-dir "$RELEASE_SESSION" --summary "Rejected assignment symlink." 2>&1)"
|
|
308
|
+
SYMLINK_ASSIGNMENT_RC=$?
|
|
309
|
+
set -e
|
|
310
|
+
rm "$ASSIGNMENT"
|
|
311
|
+
mv "$ASSIGNMENT_TARGET" "$ASSIGNMENT"
|
|
312
|
+
[[ "$SYMLINK_ASSIGNMENT_RC" -ne 0 && "$SYMLINK_ASSIGNMENT" == *"assignment must be a non-symlink regular file"* ]] || fail "public critique followed a symlinked assignment"
|
|
313
|
+
pass "public assignment readers reject symlinked assignment files"
|
|
314
|
+
node - "$RELEASE_SESSION/trust.bundle" "$RELEASE_SESSION" <<'NODE'
|
|
315
|
+
const fs = require('node:fs');
|
|
316
|
+
const [bundleFile, session] = process.argv.slice(2);
|
|
317
|
+
const bundle = JSON.parse(fs.readFileSync(bundleFile, 'utf8'));
|
|
318
|
+
const critique = bundle.claims.find((claim) => claim.metadata?.origin === 'critique');
|
|
319
|
+
if (!critique || critique.claimType !== 'workflow.critique.review' || critique.metadata?.lanes?.length !== 2 || fs.existsSync(`${session}/critique.json`) || fs.existsSync(`${session}/evidence.json`)) process.exit(1);
|
|
320
|
+
NODE
|
|
321
|
+
pass "public critique is report-only, forwards lanes, and writes only to trust.bundle"
|
|
322
|
+
|
|
323
|
+
cp "$RELEASE_SESSION/state.json" "$TMP/release-state.clean.json"
|
|
324
|
+
node - "$RELEASE_SESSION/state.json" <<'NODE'
|
|
325
|
+
const fs = require('node:fs');
|
|
326
|
+
const file = process.argv[2];
|
|
327
|
+
const state = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
328
|
+
state.task_slug = '../outside';
|
|
329
|
+
fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`);
|
|
330
|
+
NODE
|
|
331
|
+
set +e
|
|
332
|
+
TAMPERED_SLUG="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation policy-compliance --status not_verified --summary rejected 2>&1)"
|
|
333
|
+
TAMPERED_SLUG_RC=$?
|
|
334
|
+
set -e
|
|
335
|
+
mv "$TMP/release-state.clean.json" "$RELEASE_SESSION/state.json"
|
|
336
|
+
[[ "$TAMPERED_SLUG_RC" -ne 0 && "$TAMPERED_SLUG" == *"task_slug must exactly match"* ]] || fail "public evidence accepted a tampered task slug"
|
|
337
|
+
pass "public evidence rejects a mismatched or traversal task slug before assignment lookup"
|
|
338
|
+
|
|
339
|
+
OUTSIDE="$TMP/outside-session"
|
|
340
|
+
mkdir -p "$OUTSIDE"
|
|
341
|
+
printf '{"schema_version":"1.0","task_slug":"outside"}\n' >"$OUTSIDE/state.json"
|
|
342
|
+
ln -s "$OUTSIDE" "$ARTIFACT_ROOT/symlink-session"
|
|
343
|
+
OUTSIDE_BEFORE="$(snapshot_tree "$OUTSIDE")"
|
|
344
|
+
set +e
|
|
345
|
+
SYMLINK_EVIDENCE="$(run_candidate evidence --session-dir "$ARTIFACT_ROOT/symlink-session" --expectation pickup-probe-readiness --status not_verified --summary rejected 2>&1)"
|
|
346
|
+
SYMLINK_RC=$?
|
|
347
|
+
set -e
|
|
348
|
+
[[ "$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"
|
|
349
|
+
pass "evidence rejects symlinked session paths before mutation"
|
|
350
|
+
|
|
351
|
+
run_candidate pause --session-dir "$RELEASE_SESSION" --reason "consumer pause" >/dev/null
|
|
352
|
+
run_candidate resume --session-dir "$RELEASE_SESSION" --reason "consumer resume" >/dev/null
|
|
353
|
+
run_candidate release --session-dir "$RELEASE_SESSION" --reason "consumer release" >/dev/null
|
|
354
|
+
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"
|
|
355
|
+
pass "pause, resume, and release use the public command"
|
|
356
|
+
|
|
357
|
+
seed_pull_work acme/widgets#102
|
|
358
|
+
run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#102 --assignment-provider local-file --summary "Cancel fixture" >/dev/null
|
|
359
|
+
CANCEL_SESSION="$ARTIFACT_ROOT/acme-widgets-102"
|
|
360
|
+
node --input-type=module - "$CONSUMER" "$CANCEL_SESSION" <<'NODE'
|
|
361
|
+
import fs from 'node:fs';
|
|
362
|
+
import path from 'node:path';
|
|
363
|
+
import { generateKeyPairSync, sign } from 'node:crypto';
|
|
364
|
+
const [project, session] = process.argv.slice(2);
|
|
365
|
+
const slug = path.basename(session);
|
|
366
|
+
const assignment = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow-agents', 'assignment', `${slug}.json`), 'utf8'));
|
|
367
|
+
const state = JSON.parse(fs.readFileSync(path.join(session, 'state.json'), 'utf8'));
|
|
368
|
+
const keys = generateKeyPairSync('ed25519');
|
|
369
|
+
fs.mkdirSync(path.join(project, '.flow-agents'), { recursive: true });
|
|
370
|
+
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));
|
|
371
|
+
for (const operation of ['cancel', 'archive']) {
|
|
372
|
+
const requestedAt = new Date();
|
|
373
|
+
const unsigned = {
|
|
374
|
+
schema_version: '1.0', operation, run_id: slug, subject: state.work_item_refs[0],
|
|
375
|
+
assignment_actor_key: assignment.actor_key,
|
|
376
|
+
assignment_actor: { ...assignment.actor, human: assignment.actor.human ?? null },
|
|
377
|
+
nonce: `consumer-${operation}`,
|
|
378
|
+
expires_at: new Date(requestedAt.getTime() + 3600000).toISOString(),
|
|
379
|
+
request: { reason: `consumer ${operation}`, authority: { kind: 'user_request', actor: 'consumer-user', request_ref: `fixture://consumer/${operation}`, requested_at: requestedAt.toISOString() } },
|
|
380
|
+
};
|
|
381
|
+
const authorization = { ...unsigned, signature: { algorithm: 'ed25519', key_id: 'consumer', value: sign(null, Buffer.from(JSON.stringify(unsigned)), keys.privateKey).toString('base64') } };
|
|
382
|
+
fs.writeFileSync(path.join(project, `${operation}.authorization.json`), JSON.stringify(authorization, null, 2));
|
|
383
|
+
}
|
|
384
|
+
NODE
|
|
385
|
+
run_candidate cancel --session-dir "$CANCEL_SESSION" --authorization-file "$CONSUMER/cancel.authorization.json" >/dev/null
|
|
386
|
+
run_candidate archive --session-dir "$CANCEL_SESSION" --authorization-file "$CONSUMER/archive.authorization.json" >/dev/null
|
|
387
|
+
[[ -f "$ARTIFACT_ROOT/archive/acme-widgets-102/state.json" && ! -e "$CANCEL_SESSION" ]] || fail "cancel/archive did not retain archived session"
|
|
388
|
+
pass "signed cancel and archive execute through the public command"
|
|
389
|
+
|
|
390
|
+
seed_pull_work acme/widgets#106
|
|
391
|
+
run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#106 --assignment-provider local-file --summary "Command authority lock fixture" >/dev/null
|
|
392
|
+
LOCK_SESSION="$ARTIFACT_ROOT/acme-widgets-106"
|
|
393
|
+
LOCK_PULL_REPORT="$LOCK_SESSION/$(basename "$LOCK_SESSION")--pull-work.md"
|
|
394
|
+
LOCK_PULL_REF="{\"kind\":\"artifact\",\"file\":\"$LOCK_PULL_REPORT\",\"summary\":\"Lock fixture selected work report.\"}"
|
|
395
|
+
run_candidate evidence --session-dir "$LOCK_SESSION" --expectation pickup-probe-readiness --status pass --summary "Complete lock fixture probe." --evidence-ref-json "$LOCK_PULL_REF" --json >/dev/null
|
|
396
|
+
run_candidate evidence --session-dir "$LOCK_SESSION" --expectation probe-decisions-or-accepted-gaps --status pass --summary "Complete lock fixture decisions." --evidence-ref-json "$LOCK_PULL_REF" --json >/dev/null
|
|
397
|
+
LOCK_PLAN_REPORT="$LOCK_SESSION/$(basename "$LOCK_SESSION")--plan-work.md"
|
|
398
|
+
printf '# Lock Plan\n' > "$LOCK_PLAN_REPORT"
|
|
399
|
+
LOCK_PLAN_REF="{\"kind\":\"artifact\",\"file\":\"$LOCK_PLAN_REPORT\",\"summary\":\"Lock fixture plan.\"}"
|
|
400
|
+
run_candidate evidence --session-dir "$LOCK_SESSION" --expectation implementation-plan --status pass --summary "Complete lock fixture plan." --evidence-ref-json "$LOCK_PLAN_REF" --json >/dev/null
|
|
401
|
+
LOCK_DELIVER_REPORT="$LOCK_SESSION/$(basename "$LOCK_SESSION")--deliver.md"
|
|
402
|
+
LOCK_DELIVER_REF="{\"kind\":\"artifact\",\"file\":\"$LOCK_DELIVER_REPORT\",\"summary\":\"Lock fixture execution report.\"}"
|
|
403
|
+
LOCK_STARTED="$TMP/command-lock.started"
|
|
404
|
+
LOCK_COMMAND="bash checks/check-command-lock.sh '$LOCK_STARTED'"
|
|
405
|
+
run_candidate evidence --session-dir "$LOCK_SESSION" --expectation implementation-scope --status pass --summary "Long command retains authority lock." --evidence-ref-json "$LOCK_DELIVER_REF" --command "$LOCK_COMMAND" --json >"$TMP/command-lock-evidence.out" 2>&1 &
|
|
406
|
+
LOCK_EVIDENCE_PID=$!
|
|
407
|
+
for _ in $(seq 1 250); do [[ -f "$LOCK_STARTED" ]] && break; sleep 0.02; done
|
|
408
|
+
[[ -f "$LOCK_STARTED" ]] || fail "authority-lock fixture command did not start"
|
|
409
|
+
run_candidate release --session-dir "$LOCK_SESSION" --reason "release must wait for the observed command" >"$TMP/command-lock-release.out" 2>&1 &
|
|
410
|
+
LOCK_RELEASE_PID=$!
|
|
411
|
+
sleep 0.1
|
|
412
|
+
kill -0 "$LOCK_RELEASE_PID" 2>/dev/null || fail "lifecycle release bypassed the running command lock"
|
|
413
|
+
node -e 'const fs=require("fs");const r=JSON.parse(fs.readFileSync(process.argv[1]));if(r.status!=="claimed")process.exit(1)' "$ARTIFACT_ROOT/assignment/acme-widgets-106.json" || fail "assignment changed before the locked command completed"
|
|
414
|
+
wait "$LOCK_EVIDENCE_PID" || fail "locked evidence command failed"
|
|
415
|
+
wait "$LOCK_RELEASE_PID" || fail "blocked release failed after command completion"
|
|
416
|
+
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-106.json" || fail "release did not complete after the command lock was released"
|
|
417
|
+
pass "authority-bound commands retain the subject lock through lifecycle release"
|
|
418
|
+
|
|
419
|
+
seed_pull_work acme/widgets#107
|
|
420
|
+
run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#107 --assignment-provider local-file --summary "Process group timeout fixture" >/dev/null
|
|
421
|
+
TIMEOUT_SESSION="$ARTIFACT_ROOT/acme-widgets-107"
|
|
422
|
+
TIMEOUT_CHILD_PID="$TMP/command-timeout-child.pid"
|
|
423
|
+
TIMEOUT_MARKER="$TMP/command-timeout-marker"
|
|
424
|
+
set +e
|
|
425
|
+
(cd "$CONSUMER" && CODEX_SESSION_ID=public-workflow-eval FLOW_AGENTS_EVIDENCE_COMMAND_TIMEOUT_MS=1000 FLOW_AGENTS_EVIDENCE_COMMAND_KILL_GRACE_MS=250 npx --yes --package="file:$TARBALL" flow-agents workflow evidence --session-dir "$TIMEOUT_SESSION" --expectation pickup-probe-readiness --status not_verified --summary "Timeout fixture captures full process-group termination." --command "bash checks/check-command-timeout.sh '$TIMEOUT_CHILD_PID' '$TIMEOUT_MARKER'" --json) >"$TMP/command-timeout.out" 2>&1
|
|
426
|
+
TIMEOUT_RC=$?
|
|
427
|
+
set -e
|
|
428
|
+
[[ "$TIMEOUT_RC" -ne 0 && -s "$TIMEOUT_CHILD_PID" && ! -e "$TIMEOUT_MARKER" ]] || fail "timed-out evidence command did not complete its controlled capture"
|
|
429
|
+
TIMEOUT_CHILD="$(cat "$TIMEOUT_CHILD_PID")"
|
|
430
|
+
kill -0 "$TIMEOUT_CHILD" 2>/dev/null && fail "timed-out evidence command left a child process running"
|
|
431
|
+
pass "timed-out evidence commands terminate their complete process group"
|
|
432
|
+
|
|
433
|
+
BACKGROUND_CHILD_PID="$TMP/success-background-child.pid"
|
|
434
|
+
BACKGROUND_MARKER="$TMP/success-background-marker"
|
|
435
|
+
TIMEOUT_PULL_REPORT="$TIMEOUT_SESSION/$(basename "$TIMEOUT_SESSION")--pull-work.md"
|
|
436
|
+
(cd "$CONSUMER" && CODEX_SESSION_ID=public-workflow-eval FLOW_AGENTS_EVIDENCE_COMMAND_KILL_GRACE_MS=50 npx --yes --package="file:$TARBALL" flow-agents workflow evidence \
|
|
437
|
+
--session-dir "$TIMEOUT_SESSION" --expectation pickup-probe-readiness --status pass \
|
|
438
|
+
--summary "Successful evidence cleans up surviving background processes." \
|
|
439
|
+
--command "bash checks/check-success-background.sh '$BACKGROUND_CHILD_PID' '$BACKGROUND_MARKER'" \
|
|
440
|
+
--evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$TIMEOUT_PULL_REPORT\",\"summary\":\"Selected work for background cleanup fixture.\"}" --json >/dev/null)
|
|
441
|
+
[[ -s "$BACKGROUND_CHILD_PID" && ! -e "$BACKGROUND_MARKER" ]] || fail "successful evidence command did not expose a surviving background child"
|
|
442
|
+
BACKGROUND_CHILD="$(cat "$BACKGROUND_CHILD_PID")"
|
|
443
|
+
kill -0 "$BACKGROUND_CHILD" 2>/dev/null && fail "successful evidence command left a background process running"
|
|
444
|
+
pass "successful evidence commands terminate surviving process-group children before recording"
|
|
445
|
+
|
|
446
|
+
DOCTOR="$TMP/doctor-consumer"
|
|
447
|
+
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"
|
|
448
|
+
cat >"$DOCTOR/node_modules/@kontourai/flow-agents/package.json" <<'JSON'
|
|
449
|
+
{"name":"@kontourai/flow-agents","version":"3.4.3","bin":{"flow-agents":"bin.js"}}
|
|
450
|
+
JSON
|
|
451
|
+
cat >"$DOCTOR/node_modules/.bin/flow-agents" <<'SH'
|
|
452
|
+
#!/usr/bin/env bash
|
|
453
|
+
echo STALE_LOCAL_BINARY
|
|
454
|
+
SH
|
|
455
|
+
chmod +x "$DOCTOR/node_modules/.bin/flow-agents"
|
|
456
|
+
cat >"$DOCTOR/.flow-agents/install.json" <<'JSON'
|
|
457
|
+
{"version":"3.4.3","runtime":"codex","active_kit_ids":["builder"]}
|
|
458
|
+
JSON
|
|
459
|
+
cat >"$DOCTOR/kits/builder/kit.json" <<'JSON'
|
|
460
|
+
{"schema_version":"0.9","id":"builder"}
|
|
461
|
+
JSON
|
|
462
|
+
cat >"$DOCTOR/kits/builder/flows/build.flow.json" <<'JSON'
|
|
463
|
+
{"id":"builder.build","version":"0.9"}
|
|
464
|
+
JSON
|
|
465
|
+
cat >"$DOCTOR/.kontourai/flow-agents/current.json" <<'JSON'
|
|
466
|
+
{"active_slug":"doctor-session","artifact_dir":"doctor-session"}
|
|
467
|
+
JSON
|
|
468
|
+
cat >"$DOCTOR/.kontourai/flow-agents/doctor-session/state.json" <<'JSON'
|
|
469
|
+
{"schema_version":"0.9","task_slug":"doctor-session","flow_run":{"definition_id":"builder.build","definition_version":"0.9"}}
|
|
470
|
+
JSON
|
|
471
|
+
cat >"$DOCTOR/.kontourai/flow-agents/doctor-session/trust.bundle" <<'JSON'
|
|
472
|
+
{"schema_version":"0.9"}
|
|
473
|
+
JSON
|
|
474
|
+
set +e
|
|
475
|
+
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)"
|
|
476
|
+
DOCTOR_RC=$?
|
|
477
|
+
set -e
|
|
478
|
+
[[ "$DOCTOR_RC" -eq 2 ]] || fail "doctor should return 2 for incompatible consumer fixtures"
|
|
479
|
+
node - "$DOCTOR_JSON" "$VERSION" "$DOCTOR" <<'NODE'
|
|
480
|
+
const [reportText, version, root] = process.argv.slice(2);
|
|
481
|
+
const report = JSON.parse(reportText);
|
|
482
|
+
if (report.cli.version !== version || report.cli.workflow_contract_version !== '1.0') process.exit(1);
|
|
483
|
+
if (report.local_dependency.version !== '3.4.3' || report.local_dependency.selected !== false) process.exit(2);
|
|
484
|
+
if (!report.warnings.some((w) => w.includes('hook/writer version 3.4.3'))) process.exit(3);
|
|
485
|
+
if (!report.warnings.some((w) => w.includes('Builder Kit'))) process.exit(4);
|
|
486
|
+
if (!report.warnings.some((w) => w.includes('builder.build version 0.9'))) process.exit(5);
|
|
487
|
+
if (!report.warnings.some((w) => w.includes('Artifact schema 0.9'))) process.exit(6);
|
|
488
|
+
if (!report.warnings.some((w) => w.includes('Trust bundle schema 0.9'))) process.exit(7);
|
|
489
|
+
if (!report.warnings.some((w) => w.includes('hook/writer assets failed integrity'))) process.exit(10);
|
|
490
|
+
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);
|
|
491
|
+
if (report.cli.package_root.startsWith(root)) process.exit(9);
|
|
492
|
+
NODE
|
|
493
|
+
pass "doctor detects same-major hook/writer, Kit, Flow, and schema skew with exact remediation"
|
|
494
|
+
[[ "$DOCTOR_JSON" != *"STALE_LOCAL_BINARY"* ]] || fail "explicit package invocation selected stale local binary"
|
|
495
|
+
pass "explicit packed package wins over an old local dependency"
|
|
496
|
+
|
|
497
|
+
node - "$DOCTOR/node_modules/@kontourai/flow-agents/package.json" "$VERSION" <<'NODE'
|
|
498
|
+
const fs = require('node:fs');
|
|
499
|
+
const [file, version] = process.argv.slice(2);
|
|
500
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
501
|
+
value.version = version;
|
|
502
|
+
fs.writeFileSync(file, JSON.stringify(value));
|
|
503
|
+
NODE
|
|
504
|
+
SAME_VERSION_HELP="$(cd "$DOCTOR" && npx --yes --package="file:$TARBALL" flow-agents --help)"
|
|
505
|
+
[[ "$SAME_VERSION_HELP" == *"workflow"* && "$SAME_VERSION_HELP" != *"STALE_LOCAL_BINARY"* ]] || fail "explicit tarball selected a hostile same-version local binary"
|
|
506
|
+
pass "explicit tarball wins over a hostile same-version local binary"
|
|
507
|
+
|
|
508
|
+
PINNED_COMMAND_MODULE="pinned-cli-command.js"
|
|
509
|
+
ISOLATED_COMMAND="$(node --input-type=module - "$ROOT_DIR/build/src/lib/$PINNED_COMMAND_MODULE" "file:$TARBALL" <<'NODE'
|
|
510
|
+
import { pathToFileURL } from 'node:url';
|
|
511
|
+
const [modulePath, packageSpec] = process.argv.slice(2);
|
|
512
|
+
const { isolatedPackageCommand } = await import(pathToFileURL(modulePath));
|
|
513
|
+
console.log(isolatedPackageCommand(packageSpec, 'flow-agents', ['--help']));
|
|
514
|
+
NODE
|
|
515
|
+
)"
|
|
516
|
+
ISOLATED_HELP="$(cd "$DOCTOR" && eval "$ISOLATED_COMMAND")"
|
|
517
|
+
[[ "$ISOLATED_HELP" == *"workflow"* && "$ISOLATED_HELP" != *"STALE_LOCAL_BINARY"* ]] || fail "isolated package command selected a hostile same-version local binary"
|
|
518
|
+
pass "generated isolated package command defeats a hostile same-version local binary"
|
|
519
|
+
|
|
520
|
+
rm -f "$DOCTOR/kits/builder/kit.json" "$DOCTOR/kits/builder/flows/build.flow.json"
|
|
521
|
+
printf '{"version":"%s","runtime":"codex","active_kit_ids":["builder"]}\n' "$VERSION" >"$DOCTOR/.flow-agents/install.json"
|
|
522
|
+
set +e
|
|
523
|
+
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)"
|
|
524
|
+
MISSING_RC=$?
|
|
525
|
+
set -e
|
|
526
|
+
[[ "$MISSING_RC" -eq 2 ]] || fail "doctor should fail when an activated Kit is missing"
|
|
527
|
+
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"))||!r.warnings.some(w=>w.includes("Activated builder.shape definition is missing")))process.exit(1)' "$MISSING_JSON" || fail "doctor did not report both missing activated Builder definitions"
|
|
528
|
+
pass "doctor fails closed for missing activated Kit components"
|
|
529
|
+
|
|
530
|
+
HEALTHY="$TMP/healthy-install"
|
|
531
|
+
mkdir -p "$HEALTHY"
|
|
532
|
+
(cd "$HEALTHY" && npx --yes --package="file:$TARBALL" flow-agents init --runtime codex --dest "$HEALTHY" --activate-kit builder --yes >/dev/null)
|
|
533
|
+
HEALTHY_JSON="$(cd "$HEALTHY" && npx --yes --package="file:$TARBALL" flow-agents workflow doctor --project-root "$HEALTHY" --artifact-root "$HEALTHY/.kontourai/flow-agents" --json)"
|
|
534
|
+
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"
|
|
535
|
+
pass "real init converges to doctor PASS"
|
|
536
|
+
|
|
537
|
+
cp "$HEALTHY/build/src/cli/workflow.js" "$TMP/workflow.js.clean"
|
|
538
|
+
printf '\n// WORKFLOW_CONTRACT_VERSION = "1.0"\n' >>"$HEALTHY/build/src/cli/workflow.js"
|
|
539
|
+
set +e
|
|
540
|
+
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)"
|
|
541
|
+
TAMPERED_CLI_RC=$?
|
|
542
|
+
set -e
|
|
543
|
+
[[ "$TAMPERED_CLI_RC" -eq 2 && "$TAMPERED_CLI_JSON" == *"asset mismatch: build/src/cli/workflow.js"* ]] || fail "doctor accepted marker-preserving CLI tampering"
|
|
544
|
+
cp "$TMP/workflow.js.clean" "$HEALTHY/build/src/cli/workflow.js"
|
|
545
|
+
pass "doctor rejects marker-preserving CLI tampering"
|
|
546
|
+
|
|
547
|
+
cp "$HEALTHY/.codex/hooks.json" "$TMP/hooks.json.clean"
|
|
548
|
+
node - "$HEALTHY/.codex/hooks.json" <<'NODE'
|
|
549
|
+
const fs = require('node:fs');
|
|
550
|
+
const file = process.argv[2];
|
|
551
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
552
|
+
const group = value.hooks.SessionStart.find((entry) => JSON.stringify(entry).includes('workflow-steering'));
|
|
553
|
+
group.hooks[0].command += '; true';
|
|
554
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
555
|
+
NODE
|
|
556
|
+
set +e
|
|
557
|
+
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)"
|
|
558
|
+
TAMPERED_HOOK_RC=$?
|
|
559
|
+
set -e
|
|
560
|
+
[[ "$TAMPERED_HOOK_RC" -eq 2 && "$TAMPERED_HOOK_JSON" == *"does not contain the packaged managed hooks"* ]] || fail "doctor accepted name-preserving hook tampering"
|
|
561
|
+
cp "$TMP/hooks.json.clean" "$HEALTHY/.codex/hooks.json"
|
|
562
|
+
pass "doctor rejects name-preserving hook configuration tampering"
|
|
563
|
+
|
|
564
|
+
for RUNTIME in base claude-code opencode pi kiro; do
|
|
565
|
+
RUNTIME_ROOT="$TMP/runtime-$RUNTIME"
|
|
566
|
+
mkdir -p "$RUNTIME_ROOT"
|
|
567
|
+
(cd "$RUNTIME_ROOT" && npx --yes --package="file:$TARBALL" flow-agents init --runtime "$RUNTIME" --dest "$RUNTIME_ROOT" --activate-kit builder --yes >/dev/null)
|
|
568
|
+
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)"
|
|
569
|
+
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"
|
|
570
|
+
pass "doctor validates $RUNTIME runtime wiring"
|
|
571
|
+
done
|
|
572
|
+
|
|
573
|
+
printf 'public workflow CLI integration passed\n'
|