@kontourai/flow-agents 3.6.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/build/src/builder-flow-run-adapter.d.ts +22 -2
- package/build/src/builder-flow-run-adapter.js +93 -28
- package/build/src/builder-flow-runtime.d.ts +8 -3
- package/build/src/builder-flow-runtime.js +407 -51
- package/build/src/cli/assignment-provider.d.ts +4 -7
- package/build/src/cli/assignment-provider.js +80 -14
- package/build/src/cli/builder-run.js +14 -18
- package/build/src/cli/workflow-sidecar.d.ts +28 -4
- package/build/src/cli/workflow-sidecar.js +825 -103
- package/build/src/cli/workflow.js +301 -43
- package/build/src/flow-kit/validate.d.ts +17 -0
- package/build/src/flow-kit/validate.js +340 -2
- package/build/src/index.js +5 -5
- package/build/src/lib/observed-command.d.ts +7 -0
- package/build/src/lib/observed-command.js +100 -0
- package/build/src/tools/build-universal-bundles.js +53 -3
- package/build/src/tools/generate-context-map.js +5 -3
- package/context/scripts/hooks/lib/kit-catalog.js +1 -1
- package/context/scripts/hooks/stop-goal-fit.js +78 -9
- package/docs/context-map.md +22 -20
- package/docs/developer-architecture.md +1 -1
- package/docs/getting-started.md +9 -1
- package/docs/public-workflow-cli.md +76 -7
- package/docs/skills-map.md +51 -27
- package/docs/spec/builder-flow-runtime.md +75 -40
- package/docs/workflow-usage-guide.md +109 -42
- package/evals/fixtures/hook-influence/cases.json +2 -2
- package/evals/integration/test_builder_entry_enforcement.sh +52 -41
- package/evals/integration/test_builder_step_producers.sh +330 -6
- package/evals/integration/test_bundle_install.sh +318 -65
- package/evals/integration/test_bundle_lifecycle.sh +4 -6
- package/evals/integration/test_critique_supersession_roundtrip.sh +21 -8
- package/evals/integration/test_current_json_per_actor.sh +12 -0
- package/evals/integration/test_dual_emit_flow_step.sh +62 -43
- package/evals/integration/test_flowdef_session_activation.sh +145 -25
- package/evals/integration/test_flowdef_session_history_preservation.sh +23 -21
- package/evals/integration/test_gate_lockdown.sh +3 -5
- package/evals/integration/test_goal_fit_hook.sh +60 -2
- package/evals/integration/test_install_merge.sh +18 -8
- package/evals/integration/test_liveness_verdict.sh +14 -17
- package/evals/integration/test_phase_map_and_gate_claim.sh +63 -38
- package/evals/integration/test_public_workflow_cli.sh +334 -14
- package/evals/integration/test_pull_work_liveness_preflight.sh +22 -66
- package/evals/integration/test_sidecar_field_preservation.sh +36 -11
- package/evals/integration/test_workflow_sidecar_writer.sh +277 -44
- package/evals/integration/test_workflow_steering_hook.sh +15 -38
- package/evals/run.sh +2 -0
- package/evals/static/test_builder_skill_coherence.sh +247 -0
- package/evals/static/test_library_exports.sh +5 -2
- package/evals/static/test_universal_bundles.sh +44 -1
- package/evals/static/test_workflow_skills.sh +13 -325
- package/kits/builder/flows/build.flow.json +22 -0
- package/kits/builder/flows/shape.flow.json +9 -9
- package/kits/builder/kit.json +70 -16
- package/kits/builder/skills/builder-shape/SKILL.md +75 -75
- package/kits/builder/skills/continue-work/SKILL.md +45 -106
- package/kits/builder/skills/deliver/SKILL.md +96 -442
- package/kits/builder/skills/design-probe/SKILL.md +40 -139
- package/kits/builder/skills/evidence-gate/SKILL.md +59 -201
- package/kits/builder/skills/execute-plan/SKILL.md +54 -125
- package/kits/builder/skills/fix-bug/SKILL.md +42 -132
- package/kits/builder/skills/gate-review/SKILL.md +60 -211
- package/kits/builder/skills/idea-to-backlog/SKILL.md +63 -244
- package/kits/builder/skills/learning-review/SKILL.md +63 -170
- package/kits/builder/skills/pickup-probe/SKILL.md +54 -111
- package/kits/builder/skills/plan-work/SKILL.md +51 -185
- package/kits/builder/skills/pull-work/SKILL.md +136 -485
- package/kits/builder/skills/release-readiness/SKILL.md +66 -107
- package/kits/builder/skills/review-work/SKILL.md +89 -176
- package/kits/builder/skills/tdd-workflow/SKILL.md +53 -147
- package/kits/builder/skills/verify-work/SKILL.md +101 -113
- package/package.json +2 -2
- package/packaging/README.md +1 -1
- package/scripts/README.md +1 -1
- package/scripts/hooks/lib/kit-catalog.js +1 -1
- package/scripts/hooks/stop-goal-fit.js +78 -9
- package/scripts/install-codex-home.sh +63 -23
- package/scripts/install-owned-files.js +18 -2
- package/src/builder-flow-run-adapter.ts +118 -32
- package/src/builder-flow-runtime.ts +426 -64
- package/src/cli/assignment-provider-lock.test.mjs +83 -0
- package/src/cli/assignment-provider.ts +75 -14
- package/src/cli/builder-flow-run-adapter.test.mjs +4 -2
- package/src/cli/builder-flow-runtime.test.mjs +436 -18
- package/src/cli/builder-run.ts +3 -9
- package/src/cli/kit-metadata-security.test.mjs +232 -6
- package/src/cli/public-api.test.mjs +15 -0
- package/src/cli/workflow-sidecar-execution-proof.test.mjs +90 -0
- package/src/cli/workflow-sidecar.ts +746 -103
- package/src/cli/workflow.ts +288 -41
- package/src/flow-kit/validate.ts +320 -2
- package/src/index.ts +6 -5
- package/src/lib/observed-command.ts +96 -0
- package/src/tools/build-universal-bundles.ts +51 -3
- package/src/tools/generate-context-map.ts +5 -3
|
@@ -18,23 +18,111 @@ VERSION="$(node -p "require('./package.json').version")"
|
|
|
18
18
|
CONSUMER="$TMP/consumer"
|
|
19
19
|
ARTIFACT_ROOT="$CONSUMER/.kontourai/flow-agents"
|
|
20
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"
|
|
21
32
|
|
|
22
33
|
run_candidate() {
|
|
23
34
|
(cd "$CONSUMER" && CODEX_SESSION_ID=public-workflow-eval npx --yes --package="file:$TARBALL" flow-agents workflow "$@")
|
|
24
35
|
}
|
|
25
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
|
+
|
|
26
48
|
PRIMARY_HELP="$(cd "$CONSUMER" && npx --yes --package="file:$TARBALL" flow-agents --help)"
|
|
27
49
|
WORKFLOW_HELP="$(run_candidate --help)"
|
|
28
50
|
[[ "$PRIMARY_HELP" == *"workflow"* && "$WORKFLOW_HELP" != *"workflow-sidecar"* && "$WORKFLOW_HELP" != *"npm run workflow:sidecar"* ]] || fail "public help exposes internal writer terminology or omits workflow"
|
|
29
51
|
pass "primary help exposes the public workflow command without internal writer terminology"
|
|
30
52
|
|
|
31
|
-
|
|
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
|
|
32
63
|
RELEASE_SESSION="$ARTIFACT_ROOT/acme-widgets-101"
|
|
33
64
|
[[ -f "$RELEASE_SESSION/state.json" ]] || fail "packed start did not create a session"
|
|
34
65
|
[[ ! -e "$CONSUMER/package.json" ]] || fail "consumer unexpectedly gained package.json"
|
|
35
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
|
|
36
93
|
set +e
|
|
37
|
-
|
|
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)"
|
|
38
126
|
UNSAFE_START_RC=$?
|
|
39
127
|
set -e
|
|
40
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"
|
|
@@ -42,7 +130,8 @@ pass "public start rejects internal authority flags before mutation"
|
|
|
42
130
|
|
|
43
131
|
LOCAL_RETRY_PROJECT="$TMP/local-retry-project"
|
|
44
132
|
LOCAL_RETRY_ROOT="$LOCAL_RETRY_PROJECT/.kontourai/flow-agents"
|
|
45
|
-
mkdir -p "$
|
|
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"
|
|
46
135
|
printf 'not a run-store directory\n' >"$LOCAL_RETRY_PROJECT/.kontourai/flow"
|
|
47
136
|
set +e
|
|
48
137
|
(cd "$LOCAL_RETRY_PROJECT" && CODEX_SESSION_ID=public-workflow-eval npx --yes --package="file:$TARBALL" flow-agents-workflow-sidecar ensure-session \
|
|
@@ -50,7 +139,8 @@ set +e
|
|
|
50
139
|
--title "Local retry" --summary "Resume the bound local workflow." --flow-id builder.build >/dev/null 2>&1)
|
|
51
140
|
LOCAL_SEED_RC=$?
|
|
52
141
|
set -e
|
|
53
|
-
[[ "$LOCAL_SEED_RC" -
|
|
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"
|
|
54
144
|
LOCAL_RETRY_COMMAND="$(node -p "JSON.parse(require('fs').readFileSync('$LOCAL_RETRY_ROOT/local-retry/state.json')).next_action.command")"
|
|
55
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"
|
|
56
146
|
rm -f "$LOCAL_RETRY_PROJECT/.kontourai/flow"
|
|
@@ -61,12 +151,8 @@ EXECUTABLE_RETRY="$(node -e 'process.stdout.write(process.argv[1].replace(proces
|
|
|
61
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"
|
|
62
152
|
pass "emitted local retry executes from a foreign cwd against its exact originating store"
|
|
63
153
|
|
|
64
|
-
snapshot_tree() {
|
|
65
|
-
local root="$1"
|
|
66
|
-
find "$root" -type f -print0 | sort -z | xargs -0 shasum -a 256
|
|
67
|
-
}
|
|
68
154
|
BEFORE_STATUS="$(snapshot_tree "$CONSUMER/.kontourai")"
|
|
69
|
-
STATUS_JSON="$(run_candidate status --
|
|
155
|
+
STATUS_JSON="$(run_candidate status --session-dir "$RELEASE_SESSION" --json)"
|
|
70
156
|
AFTER_STATUS="$(snapshot_tree "$CONSUMER/.kontourai")"
|
|
71
157
|
[[ "$BEFORE_STATUS" == "$AFTER_STATUS" ]] || fail "workflow status mutated durable artifacts"
|
|
72
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"
|
|
@@ -74,10 +160,187 @@ pass "status is canonical and byte-read-only"
|
|
|
74
160
|
|
|
75
161
|
FLOW_MANIFEST="$CONSUMER/.kontourai/flow/runs/acme-widgets-101/evidence/manifest.json"
|
|
76
162
|
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
|
|
163
|
+
PARTIAL_EVIDENCE="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation pickup-probe-readiness --status not_verified --summary "Consumer fixture intentionally leaves this claim unverified." --json)"
|
|
78
164
|
AFTER_EVIDENCE="$(node -p "JSON.parse(require('fs').readFileSync('$FLOW_MANIFEST')).evidence.length")"
|
|
79
|
-
[[ "$AFTER_EVIDENCE" -eq $
|
|
80
|
-
|
|
165
|
+
[[ "$AFTER_EVIDENCE" -eq "$BEFORE_EVIDENCE" ]] || fail "partial evidence changed the canonical manifest"
|
|
166
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.attached!==false||r.awaiting_evidence!==true||r.current_step!=="design-probe")process.exit(1)' "$PARTIAL_EVIDENCE" || fail "partial evidence did not report an explicit awaiting-evidence result"
|
|
167
|
+
pass "partial evidence records locally without evaluation or canonical attachment"
|
|
168
|
+
|
|
169
|
+
PULL_REPORT="$RELEASE_SESSION/$(basename "$RELEASE_SESSION")--pull-work.md"
|
|
170
|
+
PULL_REPORT_REF="{\"kind\":\"artifact\",\"file\":\"$PULL_REPORT\",\"summary\":\"Concrete selected-work and probe report.\"}"
|
|
171
|
+
READINESS_PARTIAL="$(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)"
|
|
172
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.attached!==false||r.awaiting_evidence!==true||r.current_step!=="design-probe")process.exit(1)' "$READINESS_PARTIAL" || fail "first passing member of a multi-expectation gate did not remain pending"
|
|
173
|
+
PROBE_COMPLETE="$(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)"
|
|
174
|
+
node -e 'const r=JSON.parse(process.argv[1]);if(r.attached!==true||r.awaiting_evidence!==false||r.current_step!=="plan")process.exit(1)' "$PROBE_COMPLETE" || fail "complete multi-expectation gate did not attach and advance exactly once"
|
|
175
|
+
AFTER_COMPLETE_EVIDENCE="$(node -p "JSON.parse(require('fs').readFileSync('$FLOW_MANIFEST')).evidence.length")"
|
|
176
|
+
[[ "$AFTER_COMPLETE_EVIDENCE" -eq $((BEFORE_EVIDENCE + 1)) ]] || fail "complete multi-expectation gate did not attach exactly once"
|
|
177
|
+
pass "multi-expectation evidence attaches atomically only when complete"
|
|
178
|
+
seed_pull_work acme/widgets#104
|
|
179
|
+
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
|
|
180
|
+
node - "$ARTIFACT_ROOT" "$(basename "$RELEASE_SESSION")" <<'NODE'
|
|
181
|
+
const fs = require('node:fs');
|
|
182
|
+
const path = require('node:path');
|
|
183
|
+
const [root, releaseSlug] = process.argv.slice(2);
|
|
184
|
+
const actorRoot = path.join(root, 'current');
|
|
185
|
+
for (const name of fs.readdirSync(actorRoot)) {
|
|
186
|
+
if (!name.endsWith('.json')) continue;
|
|
187
|
+
const file = path.join(actorRoot, name);
|
|
188
|
+
const pointer = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
189
|
+
if (pointer.active_slug !== releaseSlug) continue;
|
|
190
|
+
pointer.active_slug = 'acme-widgets-104';
|
|
191
|
+
pointer.artifact_dir = 'acme-widgets-104';
|
|
192
|
+
pointer.active_step_id = 'design-probe';
|
|
193
|
+
fs.writeFileSync(file, `${JSON.stringify(pointer, null, 2)}\n`);
|
|
194
|
+
}
|
|
195
|
+
NODE
|
|
196
|
+
PLAN_REPORT="$RELEASE_SESSION/$(basename "$RELEASE_SESSION")--plan-work.md"
|
|
197
|
+
printf '# Plan Work\n\nReviewed fixture plan and acceptance mapping.\n' > "$PLAN_REPORT"
|
|
198
|
+
PLAN_REPORT_REF="{\"kind\":\"artifact\",\"file\":\"$PLAN_REPORT\",\"summary\":\"Reviewed implementation plan.\"}"
|
|
199
|
+
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)"
|
|
200
|
+
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"
|
|
201
|
+
pass "evidence binds the exact session and returns the locked post-transition next action"
|
|
202
|
+
set +e
|
|
203
|
+
UNRELATED_EVIDENCE="$(run_candidate_as unrelated-caller evidence --session-dir "$RELEASE_SESSION" --expectation implementation-scope --status pass --summary rejected 2>&1)"
|
|
204
|
+
UNRELATED_EVIDENCE_RC=$?
|
|
205
|
+
set -e
|
|
206
|
+
[[ "$UNRELATED_EVIDENCE_RC" -ne 0 && "$UNRELATED_EVIDENCE" == *"active, matching assignment actor"* ]] || fail "public evidence allowed a non-holder to impersonate the assignment actor"
|
|
207
|
+
pass "evidence rejects callers that do not match the exact session assignment"
|
|
208
|
+
|
|
209
|
+
STALE_STATE="$TMP/stale-state.json"
|
|
210
|
+
cp "$RELEASE_SESSION/state.json" "$STALE_STATE"
|
|
211
|
+
node - "$RELEASE_SESSION/state.json" <<'NODE'
|
|
212
|
+
const fs = require('node:fs');
|
|
213
|
+
const file = process.argv[2];
|
|
214
|
+
const state = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
215
|
+
state.flow_run.current_step = 'pull-work';
|
|
216
|
+
state.next_action = { status: 'continue', summary: 'tampered projection' };
|
|
217
|
+
fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`);
|
|
218
|
+
NODE
|
|
219
|
+
DELIVER_REPORT="$RELEASE_SESSION/$(basename "$RELEASE_SESSION")--deliver.md"
|
|
220
|
+
DELIVER_REPORT_REF="{\"kind\":\"artifact\",\"file\":\"$DELIVER_REPORT\",\"summary\":\"Executed implementation scope report.\"}"
|
|
221
|
+
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)"
|
|
222
|
+
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"
|
|
223
|
+
pass "evidence returns immutable postconditions captured while the subject lock is held"
|
|
224
|
+
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)"
|
|
225
|
+
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"
|
|
226
|
+
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)"
|
|
227
|
+
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"
|
|
228
|
+
pass "public evidence records failing command observations and declared route-back reasons"
|
|
229
|
+
set +e
|
|
230
|
+
MISSING_TEST_COMMAND="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation tests-evidence --status pass --summary "Missing runnable test command." 2>&1)"
|
|
231
|
+
MISSING_TEST_COMMAND_RC=$?
|
|
232
|
+
FAILED_TEST_COMMAND="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation tests-evidence --status pass --summary "Failing test command." --command 'false' 2>&1)"
|
|
233
|
+
FAILED_TEST_COMMAND_RC=$?
|
|
234
|
+
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)"
|
|
235
|
+
VERSION_TEST_COMMAND_RC=$?
|
|
236
|
+
set -e
|
|
237
|
+
[[ "$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"
|
|
238
|
+
REJECTED_COMMAND_MARKER="$TMP/rejected-command-marker"
|
|
239
|
+
cp "$RELEASE_SESSION/state.json" "$TMP/rejected-command-state.clean.json"
|
|
240
|
+
node - "$RELEASE_SESSION/state.json" <<'NODE'
|
|
241
|
+
const fs = require('node:fs');
|
|
242
|
+
const file = process.argv[2];
|
|
243
|
+
const state = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
244
|
+
state.flow_run.current_step = 'pull-work';
|
|
245
|
+
state.next_action = { status: 'continue', summary: 'stale projection must remain untouched on rejection' };
|
|
246
|
+
fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`);
|
|
247
|
+
NODE
|
|
248
|
+
REJECTED_COMMAND_BEFORE="$(snapshot_tree "$RELEASE_SESSION")"
|
|
249
|
+
set +e
|
|
250
|
+
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)"
|
|
251
|
+
REJECTED_COMMAND_RC=$?
|
|
252
|
+
set -e
|
|
253
|
+
REJECTED_COMMAND_AFTER="$(snapshot_tree "$RELEASE_SESSION")"
|
|
254
|
+
[[ "$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"
|
|
255
|
+
mv "$TMP/rejected-command-state.clean.json" "$RELEASE_SESSION/state.json"
|
|
256
|
+
pass "rejected evidence commands have no process or durable-artifact side effects"
|
|
257
|
+
BEFORE_CRITIQUE_MANIFEST="$(shasum -a 256 "$FLOW_MANIFEST" | awk '{print $1}')"
|
|
258
|
+
BEFORE_CRITIQUE_BUNDLE="$(shasum -a 256 "$RELEASE_SESSION/trust.bundle" | awk '{print $1}')"
|
|
259
|
+
CODE_LANE="{\"id\":\"code\",\"status\":\"pass\",\"summary\":\"Code review covered the planned implementation.\",\"evidence_refs\":[{\"kind\":\"artifact\",\"file\":\"$PLAN_REPORT\",\"summary\":\"Reviewed implementation plan.\"}]}"
|
|
260
|
+
SECURITY_LANE="{\"id\":\"security\",\"status\":\"pass\",\"summary\":\"Security review covered the delivered scope.\",\"evidence_refs\":[{\"kind\":\"artifact\",\"file\":\"$DELIVER_REPORT\",\"summary\":\"Reviewed delivered scope.\"}]}"
|
|
261
|
+
set +e
|
|
262
|
+
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)"
|
|
263
|
+
SAME_ACTOR_CRITIQUE_RC=$?
|
|
264
|
+
set -e
|
|
265
|
+
[[ "$SAME_ACTOR_CRITIQUE_RC" -ne 0 && "$SAME_ACTOR_CRITIQUE" == *"reviewer identity distinct"* ]] || fail "public critique allowed the active implementation actor to self-review"
|
|
266
|
+
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
|
|
267
|
+
AFTER_CRITIQUE_MANIFEST="$(shasum -a 256 "$FLOW_MANIFEST" | awk '{print $1}')"
|
|
268
|
+
AFTER_CRITIQUE_BUNDLE="$(shasum -a 256 "$RELEASE_SESSION/trust.bundle" | awk '{print $1}')"
|
|
269
|
+
[[ "$BEFORE_CRITIQUE_MANIFEST" == "$AFTER_CRITIQUE_MANIFEST" && "$BEFORE_CRITIQUE_BUNDLE" != "$AFTER_CRITIQUE_BUNDLE" ]] || fail "public critique attached to Flow or did not change trust.bundle"
|
|
270
|
+
pass "critique requires an active assignment but rejects self-review by its implementation actor"
|
|
271
|
+
CRITERION_ID="$(node -p "JSON.parse(require('fs').readFileSync('$RELEASE_SESSION/acceptance.json')).criteria[0].id")"
|
|
272
|
+
TEST_COMMAND="bash checks/check-public-workflow.sh .kontourai/flow-agents/$(basename "$RELEASE_SESSION")/state.json"
|
|
273
|
+
MULTI_COMMAND_ONE="$TMP/multi-command-one"
|
|
274
|
+
MULTI_COMMAND_TWO="$TMP/multi-command-two"
|
|
275
|
+
TEST_COMMAND_TWO="bash checks/check-multi-command.sh .kontourai/flow-agents/$(basename "$RELEASE_SESSION")/state.json '$MULTI_COMMAND_ONE'"
|
|
276
|
+
TEST_COMMAND_THREE="bash checks/check-multi-command.sh .kontourai/flow-agents/$(basename "$RELEASE_SESSION")/state.json '$MULTI_COMMAND_TWO'"
|
|
277
|
+
CRITERION_JSON="$(node - "$CRITERION_ID" "$TEST_COMMAND" "$TEST_COMMAND_TWO" "$TEST_COMMAND_THREE" <<'NODE'
|
|
278
|
+
const [id, ...commands] = process.argv.slice(2);
|
|
279
|
+
process.stdout.write(JSON.stringify({ id, status: 'pass', evidence_refs: commands.map((excerpt) => ({ kind: 'command', excerpt, summary: 'Asserts the bound session state exists.' })) }));
|
|
280
|
+
NODE
|
|
281
|
+
)"
|
|
282
|
+
COMMAND_REF="$(node - "$TEST_COMMAND" <<'NODE'
|
|
283
|
+
const command = process.argv[2];
|
|
284
|
+
process.stdout.write(JSON.stringify({ kind: 'command', excerpt: command, summary: 'Exact project-local public workflow check.' }));
|
|
285
|
+
NODE
|
|
286
|
+
)"
|
|
287
|
+
COMMAND_REF_TWO="$(node - "$TEST_COMMAND_TWO" <<'NODE'
|
|
288
|
+
const command = process.argv[2];
|
|
289
|
+
process.stdout.write(JSON.stringify({ kind: 'command', excerpt: command, summary: 'Exact first additional project-local check.' }));
|
|
290
|
+
NODE
|
|
291
|
+
)"
|
|
292
|
+
COMMAND_REF_THREE="$(node - "$TEST_COMMAND_THREE" <<'NODE'
|
|
293
|
+
const command = process.argv[2];
|
|
294
|
+
process.stdout.write(JSON.stringify({ kind: 'command', excerpt: command, summary: 'Exact second additional project-local check.' }));
|
|
295
|
+
NODE
|
|
296
|
+
)"
|
|
297
|
+
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
|
|
298
|
+
node - "$RELEASE_SESSION/trust.bundle" <<'NODE'
|
|
299
|
+
const fs = require('node:fs');
|
|
300
|
+
const bundle = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
301
|
+
const claim = bundle.claims.find((entry) => entry.metadata?.gate_claim?.expectation_id === 'tests-evidence');
|
|
302
|
+
if (!claim || claim.metadata?.output_digest?.algorithm !== 'sha256' || typeof claim.metadata.output_digest.hex !== 'string' || claim.metadata?.observed_commands?.length !== 3) process.exit(1);
|
|
303
|
+
NODE
|
|
304
|
+
[[ "$(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"
|
|
305
|
+
pass "tests-evidence executes every repeated command once and records matching observations"
|
|
306
|
+
|
|
307
|
+
ASSIGNMENT="$ARTIFACT_ROOT/assignment/$(basename "$RELEASE_SESSION").json"
|
|
308
|
+
ASSIGNMENT_TARGET="$TMP/assignment-target.json"
|
|
309
|
+
cp "$ASSIGNMENT" "$ASSIGNMENT_TARGET"
|
|
310
|
+
rm "$ASSIGNMENT"
|
|
311
|
+
ln -s "$ASSIGNMENT_TARGET" "$ASSIGNMENT"
|
|
312
|
+
set +e
|
|
313
|
+
SYMLINK_ASSIGNMENT="$(run_candidate critique --session-dir "$RELEASE_SESSION" --summary "Rejected assignment symlink." 2>&1)"
|
|
314
|
+
SYMLINK_ASSIGNMENT_RC=$?
|
|
315
|
+
set -e
|
|
316
|
+
rm "$ASSIGNMENT"
|
|
317
|
+
mv "$ASSIGNMENT_TARGET" "$ASSIGNMENT"
|
|
318
|
+
[[ "$SYMLINK_ASSIGNMENT_RC" -ne 0 && "$SYMLINK_ASSIGNMENT" == *"assignment must be a non-symlink regular file"* ]] || fail "public critique followed a symlinked assignment"
|
|
319
|
+
pass "public assignment readers reject symlinked assignment files"
|
|
320
|
+
node - "$RELEASE_SESSION/trust.bundle" "$RELEASE_SESSION" <<'NODE'
|
|
321
|
+
const fs = require('node:fs');
|
|
322
|
+
const [bundleFile, session] = process.argv.slice(2);
|
|
323
|
+
const bundle = JSON.parse(fs.readFileSync(bundleFile, 'utf8'));
|
|
324
|
+
const critique = bundle.claims.find((claim) => claim.metadata?.origin === 'critique');
|
|
325
|
+
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);
|
|
326
|
+
NODE
|
|
327
|
+
pass "public critique is report-only, forwards lanes, and writes only to trust.bundle"
|
|
328
|
+
|
|
329
|
+
cp "$RELEASE_SESSION/state.json" "$TMP/release-state.clean.json"
|
|
330
|
+
node - "$RELEASE_SESSION/state.json" <<'NODE'
|
|
331
|
+
const fs = require('node:fs');
|
|
332
|
+
const file = process.argv[2];
|
|
333
|
+
const state = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
334
|
+
state.task_slug = '../outside';
|
|
335
|
+
fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`);
|
|
336
|
+
NODE
|
|
337
|
+
set +e
|
|
338
|
+
TAMPERED_SLUG="$(run_candidate evidence --session-dir "$RELEASE_SESSION" --expectation policy-compliance --status not_verified --summary rejected 2>&1)"
|
|
339
|
+
TAMPERED_SLUG_RC=$?
|
|
340
|
+
set -e
|
|
341
|
+
mv "$TMP/release-state.clean.json" "$RELEASE_SESSION/state.json"
|
|
342
|
+
[[ "$TAMPERED_SLUG_RC" -ne 0 && "$TAMPERED_SLUG" == *"task_slug must exactly match"* ]] || fail "public evidence accepted a tampered task slug"
|
|
343
|
+
pass "public evidence rejects a mismatched or traversal task slug before assignment lookup"
|
|
81
344
|
|
|
82
345
|
OUTSIDE="$TMP/outside-session"
|
|
83
346
|
mkdir -p "$OUTSIDE"
|
|
@@ -97,7 +360,8 @@ run_candidate release --session-dir "$RELEASE_SESSION" --reason "consumer releas
|
|
|
97
360
|
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
361
|
pass "pause, resume, and release use the public command"
|
|
99
362
|
|
|
100
|
-
|
|
363
|
+
seed_pull_work acme/widgets#102
|
|
364
|
+
run_candidate start --artifact-root "$ARTIFACT_ROOT" --flow builder.build --work-item acme/widgets#102 --assignment-provider local-file --summary "Cancel fixture" >/dev/null
|
|
101
365
|
CANCEL_SESSION="$ARTIFACT_ROOT/acme-widgets-102"
|
|
102
366
|
node --input-type=module - "$CONSUMER" "$CANCEL_SESSION" <<'NODE'
|
|
103
367
|
import fs from 'node:fs';
|
|
@@ -129,6 +393,62 @@ run_candidate archive --session-dir "$CANCEL_SESSION" --authorization-file "$CON
|
|
|
129
393
|
[[ -f "$ARTIFACT_ROOT/archive/acme-widgets-102/state.json" && ! -e "$CANCEL_SESSION" ]] || fail "cancel/archive did not retain archived session"
|
|
130
394
|
pass "signed cancel and archive execute through the public command"
|
|
131
395
|
|
|
396
|
+
seed_pull_work acme/widgets#106
|
|
397
|
+
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
|
|
398
|
+
LOCK_SESSION="$ARTIFACT_ROOT/acme-widgets-106"
|
|
399
|
+
LOCK_PULL_REPORT="$LOCK_SESSION/$(basename "$LOCK_SESSION")--pull-work.md"
|
|
400
|
+
LOCK_PULL_REF="{\"kind\":\"artifact\",\"file\":\"$LOCK_PULL_REPORT\",\"summary\":\"Lock fixture selected work report.\"}"
|
|
401
|
+
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
|
|
402
|
+
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
|
|
403
|
+
LOCK_PLAN_REPORT="$LOCK_SESSION/$(basename "$LOCK_SESSION")--plan-work.md"
|
|
404
|
+
printf '# Lock Plan\n' > "$LOCK_PLAN_REPORT"
|
|
405
|
+
LOCK_PLAN_REF="{\"kind\":\"artifact\",\"file\":\"$LOCK_PLAN_REPORT\",\"summary\":\"Lock fixture plan.\"}"
|
|
406
|
+
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
|
|
407
|
+
LOCK_DELIVER_REPORT="$LOCK_SESSION/$(basename "$LOCK_SESSION")--deliver.md"
|
|
408
|
+
LOCK_DELIVER_REF="{\"kind\":\"artifact\",\"file\":\"$LOCK_DELIVER_REPORT\",\"summary\":\"Lock fixture execution report.\"}"
|
|
409
|
+
LOCK_STARTED="$TMP/command-lock.started"
|
|
410
|
+
LOCK_COMMAND="bash checks/check-command-lock.sh '$LOCK_STARTED'"
|
|
411
|
+
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 &
|
|
412
|
+
LOCK_EVIDENCE_PID=$!
|
|
413
|
+
for _ in $(seq 1 250); do [[ -f "$LOCK_STARTED" ]] && break; sleep 0.02; done
|
|
414
|
+
[[ -f "$LOCK_STARTED" ]] || fail "authority-lock fixture command did not start"
|
|
415
|
+
run_candidate release --session-dir "$LOCK_SESSION" --reason "release must wait for the observed command" >"$TMP/command-lock-release.out" 2>&1 &
|
|
416
|
+
LOCK_RELEASE_PID=$!
|
|
417
|
+
sleep 0.1
|
|
418
|
+
kill -0 "$LOCK_RELEASE_PID" 2>/dev/null || fail "lifecycle release bypassed the running command lock"
|
|
419
|
+
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"
|
|
420
|
+
wait "$LOCK_EVIDENCE_PID" || fail "locked evidence command failed"
|
|
421
|
+
wait "$LOCK_RELEASE_PID" || fail "blocked release failed after command completion"
|
|
422
|
+
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"
|
|
423
|
+
pass "authority-bound commands retain the subject lock through lifecycle release"
|
|
424
|
+
|
|
425
|
+
seed_pull_work acme/widgets#107
|
|
426
|
+
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
|
|
427
|
+
TIMEOUT_SESSION="$ARTIFACT_ROOT/acme-widgets-107"
|
|
428
|
+
TIMEOUT_CHILD_PID="$TMP/command-timeout-child.pid"
|
|
429
|
+
TIMEOUT_MARKER="$TMP/command-timeout-marker"
|
|
430
|
+
set +e
|
|
431
|
+
(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
|
|
432
|
+
TIMEOUT_RC=$?
|
|
433
|
+
set -e
|
|
434
|
+
[[ "$TIMEOUT_RC" -ne 0 && -s "$TIMEOUT_CHILD_PID" && ! -e "$TIMEOUT_MARKER" ]] || fail "timed-out evidence command did not complete its controlled capture"
|
|
435
|
+
TIMEOUT_CHILD="$(cat "$TIMEOUT_CHILD_PID")"
|
|
436
|
+
kill -0 "$TIMEOUT_CHILD" 2>/dev/null && fail "timed-out evidence command left a child process running"
|
|
437
|
+
pass "timed-out evidence commands terminate their complete process group"
|
|
438
|
+
|
|
439
|
+
BACKGROUND_CHILD_PID="$TMP/success-background-child.pid"
|
|
440
|
+
BACKGROUND_MARKER="$TMP/success-background-marker"
|
|
441
|
+
TIMEOUT_PULL_REPORT="$TIMEOUT_SESSION/$(basename "$TIMEOUT_SESSION")--pull-work.md"
|
|
442
|
+
(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 \
|
|
443
|
+
--session-dir "$TIMEOUT_SESSION" --expectation pickup-probe-readiness --status pass \
|
|
444
|
+
--summary "Successful evidence cleans up surviving background processes." \
|
|
445
|
+
--command "bash checks/check-success-background.sh '$BACKGROUND_CHILD_PID' '$BACKGROUND_MARKER'" \
|
|
446
|
+
--evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$TIMEOUT_PULL_REPORT\",\"summary\":\"Selected work for background cleanup fixture.\"}" --json >/dev/null)
|
|
447
|
+
[[ -s "$BACKGROUND_CHILD_PID" && ! -e "$BACKGROUND_MARKER" ]] || fail "successful evidence command did not expose a surviving background child"
|
|
448
|
+
BACKGROUND_CHILD="$(cat "$BACKGROUND_CHILD_PID")"
|
|
449
|
+
kill -0 "$BACKGROUND_CHILD" 2>/dev/null && fail "successful evidence command left a background process running"
|
|
450
|
+
pass "successful evidence commands terminate surviving process-group children before recording"
|
|
451
|
+
|
|
132
452
|
DOCTOR="$TMP/doctor-consumer"
|
|
133
453
|
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
454
|
cat >"$DOCTOR/node_modules/@kontourai/flow-agents/package.json" <<'JSON'
|
|
@@ -210,7 +530,7 @@ MISSING_JSON="$(cd "$DOCTOR" && npx --yes --package="file:$TARBALL" flow-agents
|
|
|
210
530
|
MISSING_RC=$?
|
|
211
531
|
set -e
|
|
212
532
|
[[ "$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
|
|
533
|
+
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"
|
|
214
534
|
pass "doctor fails closed for missing activated Kit components"
|
|
215
535
|
|
|
216
536
|
HEALTHY="$TMP/healthy-install"
|
|
@@ -126,7 +126,7 @@ fi
|
|
|
126
126
|
# duplicate validation logic was introduced for resolve-slug.
|
|
127
127
|
if flow_agents_node "$WRITER" resolve-slug 'owner/repo' >"$TMPDIR_EVAL/a6.out" 2>"$TMPDIR_EVAL/a6.err"; then
|
|
128
128
|
_fail "resolve-slug should reject a ref with no # separator"
|
|
129
|
-
elif rg -q -- 'owner/repo#id
|
|
129
|
+
elif rg -q -- 'provider-neutral provider:id ref or owner/repo#numeric-id' "$TMPDIR_EVAL/a6.err"; then
|
|
130
130
|
_pass "resolve-slug rejects a ref with no # separator using workItemSlug()'s existing message (AC7)"
|
|
131
131
|
else
|
|
132
132
|
_fail "resolve-slug malformed-ref rejection message mismatch: $(cat "$TMPDIR_EVAL/a6.out" "$TMPDIR_EVAL/a6.err")"
|
|
@@ -323,71 +323,27 @@ else
|
|
|
323
323
|
_fail "liveness status unexpectedly did not acquire the lock — F1's bypass may have leaked beyond the whoami action: out=$(cat "$TMPDIR_EVAL/e3.out") err=$(cat "$TMPDIR_EVAL/e3.err")"
|
|
324
324
|
fi
|
|
325
325
|
|
|
326
|
-
# ─── D.
|
|
327
|
-
echo "--- D.
|
|
328
|
-
|
|
329
|
-
require_text "$PULL" '
|
|
330
|
-
require_text "$PULL" '
|
|
331
|
-
require_text "$PULL" '
|
|
332
|
-
require_text "$PULL" '
|
|
333
|
-
require_text "$PULL" '
|
|
334
|
-
require_text "$PULL" '
|
|
335
|
-
require_text "$PULL" '
|
|
336
|
-
require_text "$PULL" '
|
|
337
|
-
require_text "$PULL" '
|
|
338
|
-
require_text "$PULL" '
|
|
339
|
-
require_text "$PULL" '
|
|
340
|
-
require_text "$PULL" '
|
|
341
|
-
require_text "$PULL" '
|
|
342
|
-
require_text "$PULL" '
|
|
343
|
-
require_text "$PULL" '
|
|
344
|
-
require_text "$PULL" '
|
|
345
|
-
require_text "$PULL" '
|
|
346
|
-
require_text "$
|
|
347
|
-
require_text "$PULL" 'this is not\s*$|this is not a provider mutation' "pull-work explicitly states the liveness claim is not a provider mutation (AC6, AC8)"
|
|
348
|
-
require_text "$PULL" '#290' "pull-work names #290 as the deferred assignment-provider seam (AC6, AC8)"
|
|
349
|
-
require_text "$PULL" 'liveness_preflight' "pull-work artifact contract records liveness_preflight (AC1, AC8)"
|
|
350
|
-
require_text "$PULL" 'liveness_claim' "pull-work artifact contract records liveness_claim (AC4, AC8)"
|
|
351
|
-
require_text "$PICKUP_PROBE" 'flags the selected item .reclaimable., treat the recorded opt-in as a decision to re-confirm' "pickup-probe re-confirms (not silently accepts) a reclaimable opt-in on drift (AC3, AC8)"
|
|
352
|
-
|
|
353
|
-
# ─── F2-F6 (fix-plan iteration 1) static assertions ─────────────────────────
|
|
354
|
-
require_text "$PULL" 'group all rows for that .subjectId. by subject' "pull-work classifies per-subject (grouped rows), not per-row (F2, AC1, AC8)"
|
|
355
|
-
require_text "$PULL" 'classify in this precedence order' "pull-work states the classification is an ordered precedence rule (F2, AC8)"
|
|
356
|
-
# F2's original numbered precedence list was liveness-only (#166); #290 (Wave 3) replaced it with
|
|
357
|
-
# the full ADR 0021 §1 assignment ⋈ liveness join, computed via `assignment-provider status`'s
|
|
358
|
-
# `effective_state`/`reason` fields. These assertions are updated to match the new five-row list
|
|
359
|
-
# (mine/held/reclaimable/human-held/free) rather than the old four-row liveness-only list.
|
|
360
|
-
require_text "$PULL" '^1\. .effective_state: .held.. with .reason: .self_is_holder..' "pull-work's precedence rule step 1 (mine, via self_is_holder) is numbered verbatim (F2, #290, AC8)"
|
|
361
|
-
require_text "$PULL" '^2\. else .effective_state: .held..' "pull-work's precedence rule step 2 (held, excluded) is numbered verbatim (F2, #290, AC8)"
|
|
362
|
-
require_text "$PULL" '^3\. else .effective_state: .reclaimable..' "pull-work's precedence rule step 3 (reclaimable) is numbered verbatim (F2, #290, AC8)"
|
|
363
|
-
require_text "$PULL" '^4\. else .effective_state: .human-held..' "pull-work's precedence rule step 4 (human-held) is numbered verbatim (F2, #290, AC11, AC8)"
|
|
364
|
-
require_text "$PULL" '^5\. else .effective_state: .free..' "pull-work's precedence rule step 5 (free) is numbered verbatim (F2, #290, AC8)"
|
|
365
|
-
require_text "$PULL" 'double-hold' "pull-work names the own-actor + other-actor co-existing row combination a double-hold (F2, AC8)"
|
|
366
|
-
require_text "$PULL" 'explicit conflict warning \(per ADR 0012 §4 detection' "pull-work cites ADR 0012 §4 detection for the double-hold conflict warning (F2, AC8)"
|
|
367
|
-
require_text "$PULL" 'never silently resolve it to .mine.' "pull-work states a double-hold is never silently resolved to mine (F2, AC8)"
|
|
368
|
-
|
|
369
|
-
require_text "$PULL" 'pin it for reuse' "pull-work pins self_actor for reuse across the pass (F3, AC8)"
|
|
370
|
-
require_text "$PULL" 'Capture the returned .actor. value as .self_actor.' "pull-work captures self_actor exactly once per pass from whoami (F3, AC8)"
|
|
371
|
-
require_text "$PULL" 'liveness claim <subjectId> --actor <self_actor>' "pull-work's claim-on-selection command passes --actor <self_actor> explicitly (F3, AC4, AC8)"
|
|
372
|
-
require_text "$PULL" 'always pass .--actor <self_actor>. explicitly on every claim in this pass' "pull-work requires --actor <self_actor> on every claim in the pass, not just the first (F3, AC8)"
|
|
373
|
-
require_text "$PULL" 'never a fresh derivation per claim call' "pull-work states the actor is never re-derived per claim call (F3, AC8)"
|
|
374
|
-
|
|
375
|
-
require_text "$PULL" 'On any claim-emit failure \(non-zero exit, unresolved actor, or liveness disabled/unavailable\)' "pull-work names the precise claim-emit failure modes covered by fail-open (F4, AC8)"
|
|
376
|
-
require_text "$PULL" '\{skipped: <stderr reason>\}' "pull-work's fail-open shape records {skipped: <stderr reason>} (F4, AC8)"
|
|
377
|
-
require_text "$PULL" 'also surface that skip reason in pull-work.s user-facing output' "pull-work requires the skip reason to be surfaced in user-facing output, never silent (F4, AC8)"
|
|
378
|
-
require_text "$PULL" 'unresolved-actor failure.? additionally names? the remediation' "pull-work requires unresolved-actor failures to additionally name the remediation (F4, AC8)"
|
|
379
|
-
|
|
380
|
-
require_text "$PULL" '.reclaimable_override.: recorded only when a .reclaimable.' "pull-work's Artifact Contract declares the reclaimable_override field (F5, AC8)"
|
|
381
|
-
|
|
382
|
-
require_text "$PULL" '### Post-Claim Conflict Re-check' "pull-work documents the Post-Claim Conflict Re-check subsection (F6, AC8)"
|
|
383
|
-
require_text "$PULL" 'coexists with this session.s own just-emitted claim on the same subject' "pull-work's post-claim re-check detects a double-hold against the just-emitted claim (F6, AC8)"
|
|
384
|
-
require_text "$PULL" 'surface the conflict prominently in the user-facing output and instruct the user to coordinate before proceeding' "pull-work instructs surfacing the post-claim conflict prominently and coordinating before proceeding, not silently continuing (F6, AC8)"
|
|
385
|
-
require_text "$PULL" 'does not provide true mutual exclusion across the read-then-write race window itself' "pull-work's honesty note states the exclusion guarantee still does not provide true mutual exclusion across the read-then-write race window (F6, AC8; updated #320 wording — see also test_liveness_verdict.sh)"
|
|
386
|
-
require_text "$PULL" '.#290.\x27s provider assignment lease closes this gap for the .{0,2}local-file.{0,2} provider only' "pull-work scopes true mutual exclusion to the local-file provider only, via #290's lock (fix-plan iteration 1, F5, AC8)"
|
|
387
|
-
require_text "$PULL" 'two concurrent local-file .claim. calls on the same subject genuinely cannot both win' "pull-work states local-file claim/claim races genuinely cannot both win under #290's lock (fix-plan iteration 1, F5, AC8)"
|
|
388
|
-
require_text "$PULL" 'The .{0,2}GitHub.{0,2} provider \(assignee/label/claim-comment\) remains advisory/last-writer' "pull-work does NOT overclaim true mutual exclusion for the GitHub provider — advisory/last-writer, detect not prevent (fix-plan iteration 1, F5, AC8)"
|
|
389
|
-
require_text "$PULL" 'Do not read .provider assignment lease. as closing the GitHub race; only the .verify-hold. publish gate \(#293\)' "pull-work explicitly warns not to read #290 as closing the GitHub race — only #293's verify-hold gate would (fix-plan iteration 1, F5, AC8)"
|
|
390
|
-
|
|
326
|
+
# ─── D. Provider-neutral skill contract assertions ─────────────────────────
|
|
327
|
+
echo "--- D. Provider-neutral skill contract assertions ---"
|
|
328
|
+
|
|
329
|
+
require_text "$PULL" 'Assignment And Liveness Selection Preflight' "pull-work documents assignment/liveness preflight"
|
|
330
|
+
require_text "$PULL" 'AssignmentProvider\.status' "pull-work consumes assignment status through the provider"
|
|
331
|
+
require_text "$PULL" 'classify the subject, not individual observation rows' "pull-work classifies grouped subject state"
|
|
332
|
+
require_text "$PULL" 'in this precedence' "pull-work documents ordered ownership states"
|
|
333
|
+
require_text "$PULL" 'raw provider status' "pull-work consumes raw status instead of display labels"
|
|
334
|
+
require_text "$PULL" 'double-hold conflict' "pull-work detects own/other live conflicts"
|
|
335
|
+
require_text "$PULL" 'Exclude it by default' "pull-work excludes held work by default"
|
|
336
|
+
require_text "$PULL" 'explicit recorded opt-in' "pull-work requires explicit reclaimable takeover"
|
|
337
|
+
require_text "$PULL" 'reclaimable_override' "pull-work records reclaimable overrides"
|
|
338
|
+
require_text "$PULL" 'takeover is resumption, not restart' "pull-work preserves incumbent continuation"
|
|
339
|
+
require_text "$PULL" 'AssignmentProvider\.claim' "pull-work claims through the provider boundary"
|
|
340
|
+
require_text "$PULL" 'After .AssignmentProvider\.claim' "pull-work rechecks ownership after claim"
|
|
341
|
+
require_text "$PULL" 'post-claim' "pull-work records post-claim conflicts"
|
|
342
|
+
require_text "$PULL" 'does not imply universal mutual exclusion' "pull-work does not overclaim race prevention"
|
|
343
|
+
require_text "$PULL" 'local-file provider' "pull-work scopes serialized claims to capable providers"
|
|
344
|
+
require_text "$PULL" 'without compare-and-swap' "pull-work treats non-atomic remote providers honestly"
|
|
345
|
+
require_text "$PULL" 'unrelated provider state' "pull-work limits provider mutation"
|
|
346
|
+
require_text "$PICKUP_PROBE" 're-confirm the recorded takeover opt-in' "pickup-probe re-confirms reclaimable takeover after drift"
|
|
391
347
|
|
|
392
348
|
echo ""
|
|
393
349
|
if [[ "$errors" -eq 0 ]]; then
|