@kontourai/flow-agents 3.3.0 → 3.4.1
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/add-to-project.yml +15 -0
- package/.github/workflows/ci.yml +161 -0
- package/CHANGELOG.md +48 -0
- package/CONTEXT.md +5 -1
- package/README.md +19 -8
- package/build/src/builder-flow-run-adapter.d.ts +80 -0
- package/build/src/builder-flow-run-adapter.js +241 -0
- package/build/src/builder-flow-runtime.d.ts +16 -0
- package/build/src/builder-flow-runtime.js +290 -0
- package/build/src/cli/builder-run.d.ts +1 -0
- package/build/src/cli/builder-run.js +27 -0
- package/build/src/cli/effective-backlog-settings.js +70 -2
- package/build/src/cli/init.d.ts +34 -0
- package/build/src/cli/init.js +341 -61
- package/build/src/cli/kit.js +55 -12
- package/build/src/cli/pull-work-provider.js +346 -5
- package/build/src/cli/skill-drift-check.d.ts +1 -0
- package/build/src/cli/skill-drift-check.js +165 -0
- package/build/src/cli/telemetry-doctor.d.ts +37 -0
- package/build/src/cli/telemetry-doctor.js +53 -6
- package/build/src/cli/validate-hook-influence.js +37 -7
- package/build/src/cli/workflow-sidecar.d.ts +93 -8
- package/build/src/cli/workflow-sidecar.js +1175 -158
- package/build/src/cli.js +5 -0
- package/build/src/flow-kit/validate.d.ts +54 -34
- package/build/src/flow-kit/validate.js +237 -26
- package/build/src/index.d.ts +2 -0
- package/build/src/index.js +1 -0
- package/build/src/lib/console-connect-options.d.ts +97 -0
- package/build/src/lib/console-connect-options.js +199 -0
- package/build/src/lib/console-telemetry-validate.d.ts +49 -0
- package/build/src/lib/console-telemetry-validate.js +91 -0
- package/build/src/lib/flow-resolver.d.ts +56 -3
- package/build/src/lib/flow-resolver.js +151 -11
- package/build/src/lib/fs.d.ts +17 -0
- package/build/src/lib/fs.js +172 -0
- package/build/src/lib/local-artifact-root.d.ts +44 -1
- package/build/src/lib/local-artifact-root.js +131 -3
- package/build/src/runtime-adapters.d.ts +39 -3
- package/build/src/runtime-adapters.js +77 -31
- package/build/src/tools/build-universal-bundles.js +40 -2
- package/build/src/tools/codex-agent-routing.d.ts +2 -0
- package/build/src/tools/codex-agent-routing.js +49 -0
- package/build/src/tools/generate-context-map.js +1 -0
- package/build/src/tools/validate-source-tree.js +27 -1
- package/context/scripts/hooks/lib/kit-catalog.js +235 -0
- package/context/scripts/hooks/lib/runnable-command.js +177 -0
- package/context/scripts/hooks/stop-goal-fit.js +278 -48
- package/context/scripts/hooks/workflow-steering.js +121 -21
- package/context/scripts/package.json +3 -0
- package/context/scripts/telemetry/install-console-config.sh +25 -4
- package/context/scripts/telemetry/lib/config.sh +102 -12
- package/context/scripts/telemetry/lib/pricing.sh +50 -0
- package/context/scripts/telemetry/lib/session.sh +3 -0
- package/context/scripts/telemetry/lib/transport.sh +87 -0
- package/context/scripts/telemetry/lib/usage.sh +205 -4
- package/context/scripts/telemetry/telemetry.conf +6 -0
- package/context/scripts/telemetry/telemetry.sh +48 -0
- package/context/settings/workspace-backlog-provider-settings.example.json +48 -0
- package/docs/agent-usage-feedback-loop.md +35 -0
- package/docs/architecture-engine-and-kits.md +110 -0
- package/docs/context-map.md +2 -0
- package/docs/decisions/embeddable-engine.md +152 -0
- package/docs/decisions/index.md +3 -1
- package/docs/decisions/trust-ledger-retention.md +88 -0
- package/docs/decisions/workflow-enforcement.md +31 -9
- package/docs/fixture-ownership.md +3 -0
- package/docs/implementing-trust-reconciliation.md +129 -0
- package/docs/index.md +19 -9
- package/docs/integrations/flow-agents-console.md +167 -0
- package/docs/kit-authoring-guide.md +52 -21
- package/docs/spec/builder-flow-runtime.md +80 -0
- package/docs/spec/runtime-hook-surface.md +45 -1
- package/docs/specs/economics-record-contract.md +270 -0
- package/docs/specs/harness-capability-matrix.md +74 -0
- package/docs/specs/learning-review-proposals-contract.md +340 -0
- package/docs/specs/routing-efficiency-review.md +59 -0
- package/docs/verifiable-trust.md +74 -25
- package/docs/workflow-usage-guide.md +10 -0
- package/evals/acceptance/prove-capture-teeth.sh +132 -0
- package/evals/ci/antigaming-suite.sh +1 -0
- package/evals/ci/run-baseline.sh +72 -4
- package/evals/fixtures/economics/acceptance.json +12 -0
- package/evals/fixtures/economics/agents/tool-worker-1/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-2/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-3/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-4/events.jsonl +1 -0
- package/evals/fixtures/economics/agents/tool-worker-5/events.jsonl +2 -0
- package/evals/fixtures/economics/critique.json +22 -0
- package/evals/fixtures/economics/expected-record.json +71 -0
- package/evals/fixtures/economics/session-usage-event.json +1 -0
- package/evals/fixtures/economics/state.json +11 -0
- package/evals/fixtures/economics/transcript.jsonl +3 -0
- package/evals/fixtures/hook-influence/cases.json +7 -7
- package/evals/fixtures/learning-review-proposals/balanced/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/economics.jsonl +5 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-1/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-2/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-3/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-4/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-5/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json +30 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.md +66 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-5/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-6/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/repeat-window/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/under-threshold/economics.jsonl +3 -0
- package/evals/fixtures/telemetry/usage-transcript-sample.jsonl +4 -0
- package/evals/fixtures/trust-reconcile-exploits/mcp-degrade.json +42 -0
- package/evals/integration/test_builder_entry_enforcement.sh +241 -0
- package/evals/integration/test_builder_step_producers.sh +18 -10
- package/evals/integration/test_bundle_install.sh +172 -0
- package/evals/integration/test_console_tenant_isolation.sh +167 -0
- package/evals/integration/test_critique_supersession_roundtrip.sh +4 -1
- package/evals/integration/test_dual_emit_flow_step.sh +10 -4
- package/evals/integration/test_economics_record.sh +674 -0
- package/evals/integration/test_effective_backlog_settings.sh +1 -1
- package/evals/integration/test_evidence_capture_hook.sh +17 -2
- package/evals/integration/test_exemption_usage_review.sh +198 -0
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_flow_kit_install_git.sh +83 -0
- package/evals/integration/test_flowdef_session_activation.sh +0 -1
- package/evals/integration/test_flowdef_session_history_preservation.sh +13 -3
- package/evals/integration/test_gate_lockdown.sh +7 -0
- package/evals/integration/test_gate_review_inquiry_records.sh +9 -1
- package/evals/integration/test_goal_fit_hook.sh +2031 -0
- package/evals/integration/test_hook_category_behaviors.sh +8 -1
- package/evals/integration/test_hook_influence_cases.sh +25 -1
- package/evals/integration/test_install_merge.sh +227 -2
- package/evals/integration/test_kit_conformance_levels.sh +6 -6
- package/evals/integration/test_learning_review_proposals.sh +329 -0
- package/evals/integration/test_liveness_conflict_injection.sh +26 -22
- package/evals/integration/test_liveness_console_relay.sh +166 -0
- package/evals/integration/test_liveness_heartbeat.sh +17 -17
- package/evals/integration/test_liveness_worktree_root.sh +575 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +6 -1
- package/evals/integration/test_publish_delivery.sh +331 -1
- package/evals/integration/test_pull_work_board.sh +200 -0
- package/evals/integration/test_pull_work_provider.sh +1 -1
- package/evals/integration/test_record_check.sh +378 -0
- package/evals/integration/test_routing_efficiency.sh +71 -0
- package/evals/integration/test_runtime_adapter_activation.sh +28 -0
- package/evals/integration/test_session_resume_roundtrip.sh +16 -19
- package/evals/integration/test_skill_drift_check.sh +870 -0
- package/evals/integration/test_telemetry.sh +445 -0
- package/evals/integration/test_telemetry_doctor.sh +66 -0
- package/evals/integration/test_telemetry_usage_pipeline.sh +228 -0
- package/evals/integration/test_trust_reconcile_negatives.sh +30 -13
- package/evals/integration/test_trust_reconcile_trailer_diagnostic.sh +247 -0
- package/evals/integration/test_usage_cost.sh +61 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +1395 -0
- package/evals/integration/test_workflow_steering_hook.sh +157 -16
- package/evals/integration/test_workspace_settings.sh +176 -0
- package/evals/lib/env.sh +26 -0
- package/evals/lib/node.sh +8 -0
- package/evals/run.sh +29 -0
- package/evals/static/test_ci_integration_coverage.sh +115 -0
- package/evals/static/test_declared_scope_forms_documented.sh +114 -0
- package/evals/static/test_universal_bundles.sh +34 -0
- package/evals/static/test_validate_source_kit_asset_scope.sh +259 -0
- package/evals/static/test_workflow_skills.sh +1 -1
- package/kits/builder/flows/build.flow.json +9 -18
- package/kits/builder/flows/publish-learn.flow.json +5 -1
- package/kits/builder/kit.json +120 -0
- package/kits/builder/skills/deliver/SKILL.md +42 -0
- package/kits/builder/skills/evidence-gate/SKILL.md +12 -0
- package/kits/builder/skills/execute-plan/SKILL.md +9 -0
- package/kits/builder/skills/learning-review/SKILL.md +51 -0
- package/kits/builder/skills/plan-work/SKILL.md +17 -20
- package/kits/builder/skills/pull-work/SKILL.md +21 -0
- package/kits/builder/skills/release-readiness/SKILL.md +12 -0
- package/kits/knowledge/kit.json +9 -0
- package/kits/veritas-governance/docs/README.md +35 -7
- package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
- package/kits/veritas-governance/kit.json +14 -0
- package/kits/veritas-governance/skills/exemption-usage-review/SKILL.md +128 -0
- package/kits/veritas-governance/skills/exemption-usage-review/review-exemptions.mjs +231 -0
- package/package.json +2 -2
- package/packaging/manifest.json +29 -0
- package/schemas/backlog-provider-settings.schema.json +13 -0
- package/schemas/workflow-state.schema.json +44 -0
- package/scripts/README.md +4 -0
- package/scripts/check-content-boundary.cjs +8 -1
- package/scripts/ci/trust-reconcile.js +136 -0
- package/scripts/hooks/codex-hook-adapter.js +77 -2
- package/scripts/hooks/evidence-capture.js +38 -5
- package/scripts/hooks/lib/codex-exit-code.js +316 -0
- package/scripts/hooks/lib/kit-catalog.js +235 -0
- package/scripts/hooks/lib/liveness-write.js +28 -1
- package/scripts/hooks/lib/local-artifact-paths.js +97 -1
- package/scripts/hooks/lib/runnable-command.js +177 -0
- package/scripts/hooks/lib/skill-drift.js +350 -0
- package/scripts/hooks/stop-goal-fit.js +278 -48
- package/scripts/hooks/workflow-steering.js +121 -21
- package/scripts/install-codex-home.sh +97 -47
- package/scripts/install-merge.js +72 -14
- package/scripts/install-owned-files.js +178 -0
- package/scripts/liveness/relay.sh +84 -0
- package/scripts/telemetry/economics-record.schema.json +145 -0
- package/scripts/telemetry/economics-record.sh +331 -0
- package/scripts/telemetry/install-console-config.sh +25 -4
- package/scripts/telemetry/learning-review-decide.sh +124 -0
- package/scripts/telemetry/learning-review-proposals.schema.json +161 -0
- package/scripts/telemetry/learning-review-proposals.sh +484 -0
- package/scripts/telemetry/lib/config.sh +102 -12
- package/scripts/telemetry/lib/pricing.sh +14 -6
- package/scripts/telemetry/lib/session.sh +3 -0
- package/scripts/telemetry/lib/transport.sh +133 -15
- package/scripts/telemetry/lib/usage.sh +121 -28
- package/scripts/telemetry/routing-efficiency.sh +0 -0
- package/scripts/telemetry/telemetry.conf +6 -0
- package/scripts/telemetry/telemetry.sh +48 -0
- package/src/builder-flow-run-adapter.ts +357 -0
- package/src/builder-flow-runtime.ts +348 -0
- package/src/cli/builder-flow-run-adapter.test.mjs +495 -0
- package/src/cli/builder-flow-runtime.test.mjs +213 -0
- package/src/cli/builder-run.ts +28 -0
- package/src/cli/codex-agent-routing.test.mjs +44 -0
- package/src/cli/codex-exit-code.test.mjs +207 -0
- package/src/cli/console-connect-options.test.mjs +329 -0
- package/src/cli/console-telemetry-validate.test.mjs +157 -0
- package/src/cli/effective-backlog-settings.ts +68 -2
- package/src/cli/flow-resolver-composition.test.mjs +101 -0
- package/src/cli/init.test.mjs +161 -0
- package/src/cli/init.ts +407 -62
- package/src/cli/kit-metadata-security.test.mjs +443 -0
- package/src/cli/kit.ts +50 -12
- package/src/cli/pull-work-provider.ts +377 -3
- package/src/cli/sidecar-pure-helpers.test.mjs +64 -0
- package/src/cli/skill-drift-check.ts +196 -0
- package/src/cli/telemetry-doctor.test.mjs +53 -0
- package/src/cli/telemetry-doctor.ts +50 -7
- package/src/cli/validate-hook-influence.ts +37 -6
- package/src/cli/workflow-sidecar.ts +1150 -151
- package/src/cli.ts +5 -0
- package/src/flow-kit/validate.ts +277 -38
- package/src/index.ts +19 -0
- package/src/lib/console-connect-options.ts +261 -0
- package/src/lib/console-telemetry-validate.ts +88 -0
- package/src/lib/flow-resolver.ts +153 -10
- package/src/lib/fs.ts +160 -0
- package/src/lib/local-artifact-root.ts +129 -3
- package/src/runtime-adapters.ts +113 -33
- package/src/tools/build-universal-bundles.ts +36 -2
- package/src/tools/codex-agent-routing.ts +48 -0
- package/src/tools/generate-context-map.ts +1 -0
- package/src/tools/validate-source-tree.ts +26 -1
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_record_check.sh — integration eval for `workflow-sidecar record-check` (#380, AC7).
|
|
3
|
+
#
|
|
4
|
+
# Proves:
|
|
5
|
+
# 1. `record-check <dir> -- <command>` RUNS the command and records a kind:"command" check
|
|
6
|
+
# with a real execution.label, composed losslessly into the trust.bundle (through the same
|
|
7
|
+
# readBundleState + mergeChecksById + writeTrustBundle path every other writer uses).
|
|
8
|
+
# 2. A passing command → exit 0, claim status "verified" (Surface deriveClaimStatus).
|
|
9
|
+
# 3. A failing command → exit NONZERO from record-check itself (loud, not silently swallowed)
|
|
10
|
+
# AND a claim status "disputed" is still recorded (the failure is captured, not dropped).
|
|
11
|
+
# 4. A prior check recorded by record-evidence survives a subsequent record-check call
|
|
12
|
+
# (compose-safe, not a parallel bundle-writing implementation — #298/#270 class).
|
|
13
|
+
# 5. `--command "<shell string>"` is accepted as an alternative to `-- <command...>` and is
|
|
14
|
+
# rejected when the string is not a runnable shell command (#412 parity with
|
|
15
|
+
# record-gate-claim --command).
|
|
16
|
+
# 6. record-check is reachable via the REAL CLI entry point (main()'s switch), not just as an
|
|
17
|
+
# exported function — this guards against the "added the function but forgot to wire it
|
|
18
|
+
# into the switch" stop-short risk called out in the delivery plan.
|
|
19
|
+
#
|
|
20
|
+
# Deterministic, no model spend, self-cleaning.
|
|
21
|
+
# Usage: bash evals/integration/test_record_check.sh
|
|
22
|
+
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
26
|
+
source "$ROOT/evals/lib/node.sh"
|
|
27
|
+
|
|
28
|
+
WRITER="workflow-sidecar"
|
|
29
|
+
TMPDIR_EVAL="$(mktemp -d)"
|
|
30
|
+
errors=0
|
|
31
|
+
|
|
32
|
+
cleanup() { rm -rf "$TMPDIR_EVAL"; }
|
|
33
|
+
trap cleanup EXIT
|
|
34
|
+
|
|
35
|
+
_pass() { echo " ✓ $1"; }
|
|
36
|
+
_fail() { echo " ✗ $1"; errors=$((errors + 1)); }
|
|
37
|
+
|
|
38
|
+
SESSION_ROOT="$TMPDIR_EVAL/repo/.kontourai/flow-agents"
|
|
39
|
+
SLUG="record-check-eval"
|
|
40
|
+
SESSION_DIR="$SESSION_ROOT/$SLUG"
|
|
41
|
+
mkdir -p "$SESSION_ROOT"
|
|
42
|
+
|
|
43
|
+
flow_agents_node "$WRITER" ensure-session \
|
|
44
|
+
--artifact-root "$SESSION_ROOT" \
|
|
45
|
+
--task-slug "$SLUG" \
|
|
46
|
+
--title "record-check eval" \
|
|
47
|
+
--source-request "Integration eval for record-check (#380)." \
|
|
48
|
+
--summary "Seed session for record-check assertions." \
|
|
49
|
+
--criterion "record-check runs a command and records a capture-backed check" \
|
|
50
|
+
--timestamp "2026-07-05T09:00:00Z" >"$TMPDIR_EVAL/seed.out" 2>"$TMPDIR_EVAL/seed.err"
|
|
51
|
+
|
|
52
|
+
if [[ ! -d "$SESSION_DIR" ]]; then
|
|
53
|
+
_fail "eval setup: ensure-session did not create the session dir: $(cat "$TMPDIR_EVAL/seed.out" "$TMPDIR_EVAL/seed.err")"
|
|
54
|
+
echo ""
|
|
55
|
+
echo "$errors check(s) failed."
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# ─── 1/2/6: record-check <dir> -- <command> (pass path), via the REAL CLI entry point ────────
|
|
60
|
+
if flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:01:00Z" -- echo record-check-eval-ok \
|
|
61
|
+
>"$TMPDIR_EVAL/rc-pass.out" 2>"$TMPDIR_EVAL/rc-pass.err"; then
|
|
62
|
+
_pass "record-check (pass path) exits 0 for a succeeding command, via the real CLI switch dispatch"
|
|
63
|
+
else
|
|
64
|
+
_fail "record-check (pass path) unexpectedly failed: $(cat "$TMPDIR_EVAL/rc-pass.out" "$TMPDIR_EVAL/rc-pass.err")"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
if [[ -f "$SESSION_DIR/trust.bundle" ]] && node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/rc-pass-assert.err"
|
|
68
|
+
import { readFileSync } from 'node:fs';
|
|
69
|
+
const bundle = JSON.parse(readFileSync('${SESSION_DIR}/trust.bundle', 'utf8'));
|
|
70
|
+
const claim = bundle.claims.find((c) => (c.metadata || {}).check_kind === 'command' && c.fieldOrBehavior && c.fieldOrBehavior.includes('echo record-check-eval-ok'));
|
|
71
|
+
if (!claim) { process.stderr.write('no kind:"command" claim found for the echo command\n'); process.exit(1); }
|
|
72
|
+
if (claim.status !== 'verified') { process.stderr.write('expected claim status verified for a passing command, got ' + claim.status + '\n'); process.exit(1); }
|
|
73
|
+
const ev = bundle.evidence.find((e) => e.claimId === claim.id);
|
|
74
|
+
if (!ev || !ev.execution || ev.execution.label !== 'echo record-check-eval-ok') { process.stderr.write('execution.label was not set to the executed command: ' + JSON.stringify(ev && ev.execution) + '\n'); process.exit(1); }
|
|
75
|
+
if (ev.execution.isError !== false) { process.stderr.write('execution.isError should be false for a passing command\n'); process.exit(1); }
|
|
76
|
+
NODEOF
|
|
77
|
+
then
|
|
78
|
+
_pass "AC7: record-check (pass) records a kind:\"command\" check with real execution.label and claim status verified"
|
|
79
|
+
else
|
|
80
|
+
_fail "AC7: record-check (pass) bundle assertion failed: $(cat "$TMPDIR_EVAL/rc-pass-assert.err")"
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# ─── #270/#380 MEDIUM fix: record-check persists a REAL sha256 digest of the captured output ──
|
|
84
|
+
# onto claim.metadata.output_digest, never the raw output text (secret-safe by construction).
|
|
85
|
+
# The executed command's TEXT ("rc-digest-output-8f2a1c9b3d" piped through base64) is deliberately DIFFERENT
|
|
86
|
+
# from its OUTPUT TEXT ("cmMtZGlnZXN0LW91dHB1dC04ZjJhMWM5YjNk") so that asserting the output text is absent from the
|
|
87
|
+
# bundle cannot be confused with the (legitimate, expected) presence of the command text itself
|
|
88
|
+
# in execution.label/fieldOrBehavior.
|
|
89
|
+
if flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:01:30Z" --id digest-check --command "printf 'rc-digest-output-8f2a1c9b3d' | base64" \
|
|
90
|
+
>"$TMPDIR_EVAL/rc-digest.out" 2>"$TMPDIR_EVAL/rc-digest.err"; then
|
|
91
|
+
_pass "record-check (digest eval command) exits 0"
|
|
92
|
+
else
|
|
93
|
+
_fail "record-check (digest eval command) unexpectedly failed: $(cat "$TMPDIR_EVAL/rc-digest.out" "$TMPDIR_EVAL/rc-digest.err")"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
if [[ -f "$SESSION_DIR/trust.bundle" ]] && node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/rc-digest-assert.err"
|
|
97
|
+
import { readFileSync } from 'node:fs';
|
|
98
|
+
const bundleText = readFileSync('${SESSION_DIR}/trust.bundle', 'utf8');
|
|
99
|
+
const bundle = JSON.parse(bundleText);
|
|
100
|
+
const claim = bundle.claims.find((c) => c.subjectId && c.subjectId.endsWith('/digest-check'));
|
|
101
|
+
if (!claim) { process.stderr.write('no claim found for the digest-check command\n'); process.exit(1); }
|
|
102
|
+
const od = (claim.metadata || {}).output_digest;
|
|
103
|
+
if (!od || typeof od !== 'object') { process.stderr.write('claim.metadata.output_digest is missing: ' + JSON.stringify(claim.metadata) + '\n'); process.exit(1); }
|
|
104
|
+
if (od.algorithm !== 'sha256') { process.stderr.write('expected algorithm sha256, got ' + od.algorithm + '\n'); process.exit(1); }
|
|
105
|
+
if (typeof od.hex !== 'string' || !/^[0-9a-f]{64}$/.test(od.hex)) { process.stderr.write('output_digest.hex is not a 64-char lowercase hex sha256 digest: ' + JSON.stringify(od.hex) + '\n'); process.exit(1); }
|
|
106
|
+
const expectedHex = '163efc60e68c74a22225e8844f8a9f8b7d9ed30b2424bdbf939a3e41085348fe';
|
|
107
|
+
if (od.hex !== expectedHex) { process.stderr.write('output_digest.hex does not match the expected sha256 of the captured output: got ' + od.hex + ' expected ' + expectedHex + '\n'); process.exit(1); }
|
|
108
|
+
// Secret-safety: the RAW output text must never appear anywhere in the persisted bundle text —
|
|
109
|
+
// only its digest may. (The command TEXT itself legitimately appears in execution.label/summary;
|
|
110
|
+
// that is NOT what this assertion is checking.)
|
|
111
|
+
if (bundleText.includes('cmMtZGlnZXN0LW91dHB1dC04ZjJhMWM5YjNk')) { process.stderr.write('SECURITY REGRESSION: the raw captured output text was found in trust.bundle — output_digest must be a hash only, never raw output\n'); process.exit(1); }
|
|
112
|
+
NODEOF
|
|
113
|
+
then
|
|
114
|
+
_pass "#270/#380: record-check persists a real 64-hex sha256 digest of the captured output onto claim.metadata.output_digest, and the raw output text is never persisted (secret-safe by construction)"
|
|
115
|
+
else
|
|
116
|
+
_fail "#270/#380: record-check digest assertion failed: $(cat "$TMPDIR_EVAL/rc-digest-assert.err")"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# ─── 3: record-check <dir> -- <command> (fail path) ──────────────────────────────────────────
|
|
120
|
+
if flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:02:00Z" -- false \
|
|
121
|
+
>"$TMPDIR_EVAL/rc-fail.out" 2>"$TMPDIR_EVAL/rc-fail.err"; then
|
|
122
|
+
_fail "record-check (fail path) should exit nonzero for a failing command, but exited 0"
|
|
123
|
+
else
|
|
124
|
+
_pass "record-check (fail path) exits nonzero (loud) for a failing command, never silently swallowed"
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
if [[ -f "$SESSION_DIR/trust.bundle" ]] && node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/rc-fail-assert.err"
|
|
128
|
+
import { readFileSync } from 'node:fs';
|
|
129
|
+
const bundle = JSON.parse(readFileSync('${SESSION_DIR}/trust.bundle', 'utf8'));
|
|
130
|
+
const claim = bundle.claims.find((c) => (c.metadata || {}).check_kind === 'command' && c.fieldOrBehavior && c.fieldOrBehavior.includes('false'));
|
|
131
|
+
if (!claim) { process.stderr.write('no kind:"command" claim found for the false command\n'); process.exit(1); }
|
|
132
|
+
if (claim.status !== 'disputed') { process.stderr.write('expected claim status disputed for a failing command, got ' + claim.status + '\n'); process.exit(1); }
|
|
133
|
+
const ev = bundle.evidence.find((e) => e.claimId === claim.id);
|
|
134
|
+
if (!ev || !ev.execution || ev.execution.isError !== true) { process.stderr.write('execution.isError should be true for a failing command\n'); process.exit(1); }
|
|
135
|
+
NODEOF
|
|
136
|
+
then
|
|
137
|
+
_pass "AC7: record-check (fail) records a kind:\"command\" check with status fail/disputed — the failure is captured, not dropped"
|
|
138
|
+
else
|
|
139
|
+
_fail "AC7: record-check (fail) bundle assertion failed: $(cat "$TMPDIR_EVAL/rc-fail-assert.err")"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# ─── 4: a PRIOR check (recorded by record-evidence) survives a SUBSEQUENT record-check call ───
|
|
143
|
+
if flow_agents_node "$WRITER" record-evidence "$SESSION_DIR" \
|
|
144
|
+
--verdict pass \
|
|
145
|
+
--check-json '{"id":"prior-evidence-check","kind":"test","status":"pass","summary":"Prior evidence check passed."}' \
|
|
146
|
+
--timestamp "2026-07-05T09:03:00Z" >"$TMPDIR_EVAL/prior-evidence.out" 2>"$TMPDIR_EVAL/prior-evidence.err"; then
|
|
147
|
+
_pass "eval setup: record-evidence seeds a prior check before the compose-safety assertion"
|
|
148
|
+
else
|
|
149
|
+
_fail "eval setup: record-evidence failed: $(cat "$TMPDIR_EVAL/prior-evidence.out" "$TMPDIR_EVAL/prior-evidence.err")"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
if flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:04:00Z" -- echo second-record-check-call \
|
|
153
|
+
>"$TMPDIR_EVAL/rc-second.out" 2>"$TMPDIR_EVAL/rc-second.err"; then
|
|
154
|
+
_pass "record-check (second call) succeeds after a prior record-evidence call"
|
|
155
|
+
else
|
|
156
|
+
_fail "record-check (second call) unexpectedly failed: $(cat "$TMPDIR_EVAL/rc-second.out" "$TMPDIR_EVAL/rc-second.err")"
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
if node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/rc-compose-assert.err"
|
|
160
|
+
import { readFileSync } from 'node:fs';
|
|
161
|
+
const bundle = JSON.parse(readFileSync('${SESSION_DIR}/trust.bundle', 'utf8'));
|
|
162
|
+
const claims = bundle.claims;
|
|
163
|
+
const priorCheck = claims.find((c) => c.subjectId && c.subjectId.endsWith('/prior-evidence-check'));
|
|
164
|
+
const passCheck = claims.find((c) => (c.metadata || {}).check_kind === 'command' && c.fieldOrBehavior && c.fieldOrBehavior.includes('echo record-check-eval-ok'));
|
|
165
|
+
const failCheck = claims.find((c) => (c.metadata || {}).check_kind === 'command' && c.fieldOrBehavior && c.fieldOrBehavior.includes('false'));
|
|
166
|
+
const secondCheck = claims.find((c) => (c.metadata || {}).check_kind === 'command' && c.fieldOrBehavior && c.fieldOrBehavior.includes('echo second-record-check-call'));
|
|
167
|
+
if (!priorCheck) { process.stderr.write('prior record-evidence check was LOST after a subsequent record-check call (#298/#270 compose-safety regression)\n'); process.exit(1); }
|
|
168
|
+
if (!passCheck || !failCheck || !secondCheck) { process.stderr.write('an earlier record-check claim was lost after a later record-check call\n'); process.exit(1); }
|
|
169
|
+
NODEOF
|
|
170
|
+
then
|
|
171
|
+
_pass "record-check is compose-safe: a prior record-evidence check AND earlier record-check claims all survive a subsequent record-check call"
|
|
172
|
+
else
|
|
173
|
+
_fail "record-check compose-safety assertion failed: $(cat "$TMPDIR_EVAL/rc-compose-assert.err")"
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
# ─── 5: --command "<shell string>" parity flag; rejects prose (#412) ─────────────────────────
|
|
177
|
+
if flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:05:00Z" --command "git rev-parse HEAD" \
|
|
178
|
+
>"$TMPDIR_EVAL/rc-command-flag.out" 2>"$TMPDIR_EVAL/rc-command-flag.err"; then
|
|
179
|
+
_pass "record-check --command \"<shell string>\" (parity flag with record-gate-claim) succeeds"
|
|
180
|
+
else
|
|
181
|
+
_fail "record-check --command failed: $(cat "$TMPDIR_EVAL/rc-command-flag.out" "$TMPDIR_EVAL/rc-command-flag.err")"
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
if flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:06:00Z" --command "Manually confirmed the output looks correct." \
|
|
185
|
+
>"$TMPDIR_EVAL/rc-command-prose.out" 2>"$TMPDIR_EVAL/rc-command-prose.err"; then
|
|
186
|
+
_fail "record-check --command should reject prose, but accepted it"
|
|
187
|
+
elif grep -qi "not a runnable shell command" "$TMPDIR_EVAL/rc-command-prose.out" "$TMPDIR_EVAL/rc-command-prose.err"; then
|
|
188
|
+
_pass "record-check --command rejects a prose value at record time (#412 parity with record-gate-claim --command)"
|
|
189
|
+
else
|
|
190
|
+
_fail "record-check --command prose rejection message was unexpected: $(cat "$TMPDIR_EVAL/rc-command-prose.out" "$TMPDIR_EVAL/rc-command-prose.err")"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# ─── Usage error: neither `-- <command>` nor --command supplied ─────────────────────────────
|
|
194
|
+
if flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:07:00Z" \
|
|
195
|
+
>"$TMPDIR_EVAL/rc-no-command.out" 2>"$TMPDIR_EVAL/rc-no-command.err"; then
|
|
196
|
+
_fail "record-check with no command at all should die with a usage error, but exited 0"
|
|
197
|
+
elif grep -qi "requires a command" "$TMPDIR_EVAL/rc-no-command.out" "$TMPDIR_EVAL/rc-no-command.err"; then
|
|
198
|
+
_pass "record-check with neither -- <command> nor --command dies with an actionable usage error"
|
|
199
|
+
else
|
|
200
|
+
_fail "record-check no-command error message was unexpected: $(cat "$TMPDIR_EVAL/rc-no-command.out" "$TMPDIR_EVAL/rc-no-command.err")"
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# ─── #362 AC6/AC7: record-check's ambiguous-absence-command classification ──────────────────
|
|
204
|
+
# A bare (non-negated, non-count-asserted, non-chained) grep/diff invocation whose exit code is
|
|
205
|
+
# exactly 1 is exit-code-ambiguous (zero matches could be the author's intended PASS for an
|
|
206
|
+
# absence check, or an unintended miss for a presence check) — record-check stamps a distinct
|
|
207
|
+
# "not_verified" status (never silently pass, never silently fail) and surfaces a loud, actionable
|
|
208
|
+
# stderr note, but does NOT itself exit nonzero (the command DID run; this is not a tool error).
|
|
209
|
+
FIXTURE_FILE="$TMPDIR_EVAL/repo/ac6-fixture.txt"
|
|
210
|
+
echo "some content with no matching pattern here" >"$FIXTURE_FILE"
|
|
211
|
+
|
|
212
|
+
# AC6: bare `grep` exit 1 (pattern genuinely absent) → recorded status not_verified + loud stderr
|
|
213
|
+
# note, record-check itself exits 0 (never hard-fail on an ambiguous, not a tool-error, outcome).
|
|
214
|
+
if flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:08:00Z" --id ac6-ambiguous-grep -- grep -E 'this-pattern-does-not-exist-anywhere' "$FIXTURE_FILE" >"$TMPDIR_EVAL/rc-ambiguous.out" 2>"$TMPDIR_EVAL/rc-ambiguous.err"; then
|
|
215
|
+
_pass "AC6: record-check with a bare grep exit-1 (ambiguous absence) exits 0, never hard-fails"
|
|
216
|
+
else
|
|
217
|
+
_fail "AC6 REGRESSION: record-check with a bare grep exit-1 (ambiguous absence) should exit 0 (never a tool error, never hard-fail), but exited nonzero: $(cat "$TMPDIR_EVAL/rc-ambiguous.out" "$TMPDIR_EVAL/rc-ambiguous.err")"
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
if grep -qi "ambiguous" "$TMPDIR_EVAL/rc-ambiguous.err" && grep -qi "not_verified" "$TMPDIR_EVAL/rc-ambiguous.err"; then
|
|
221
|
+
_pass "AC6: record-check emits a loud stderr note naming both 'ambiguous' and the recorded 'not_verified' status for a bare grep exit-1"
|
|
222
|
+
else
|
|
223
|
+
_fail "AC6: record-check's ambiguous stderr note was missing expected substrings ('ambiguous' and 'not_verified'): $(cat "$TMPDIR_EVAL/rc-ambiguous.err")"
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
# AC7: the same stderr note nudges toward a self-asserting rewrite ('! grep ...' / 'grep -c ... | grep -qx 0').
|
|
227
|
+
if grep -qi "self-asserting" "$TMPDIR_EVAL/rc-ambiguous.err"; then
|
|
228
|
+
_pass "AC7: record-check's ambiguous stderr note nudges toward a self-asserting recorded-command form"
|
|
229
|
+
else
|
|
230
|
+
_fail "AC7: record-check's ambiguous stderr note did not mention 'self-asserting': $(cat "$TMPDIR_EVAL/rc-ambiguous.err")"
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
if node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/rc-ambiguous-assert.err"
|
|
234
|
+
import { readFileSync } from 'node:fs';
|
|
235
|
+
const bundle = JSON.parse(readFileSync('${SESSION_DIR}/trust.bundle', 'utf8'));
|
|
236
|
+
const claim = bundle.claims.find((c) => c.subjectId && c.subjectId.endsWith('/ac6-ambiguous-grep'));
|
|
237
|
+
if (!claim) { process.stderr.write('no claim found for the ac6-ambiguous-grep check\n'); process.exit(1); }
|
|
238
|
+
if (claim.value !== 'not_verified') { process.stderr.write('expected claim value not_verified for an ambiguous bare-grep exit-1, got ' + claim.value + '\n'); process.exit(1); }
|
|
239
|
+
NODEOF
|
|
240
|
+
then
|
|
241
|
+
_pass "AC6: record-check stamps check status not_verified (not a new 'ambiguous' enum value — mapped onto the existing not_verified status per the plan/ADR 0008/0010) for a bare grep exit-1"
|
|
242
|
+
else
|
|
243
|
+
_fail "AC6: record-check ambiguous-status bundle assertion failed: $(cat "$TMPDIR_EVAL/rc-ambiguous-assert.err")"
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
# Regression guard: a SELF-ASSERTING form (`! grep ...`, negation flips intent into the command's
|
|
247
|
+
# own exit code) exiting 0 records a clean pass — never misclassified ambiguous just because the
|
|
248
|
+
# underlying binary is grep. Uses the NATURAL `--command '! grep ...'` form (not a `bash -lc`
|
|
249
|
+
# workaround) — isRunnableCommandText now strips a leading `!` before evaluating runnability
|
|
250
|
+
# (coherence fix: the ambiguous-absence advisory's own suggested remediation must not itself be
|
|
251
|
+
# rejected by the #412 runnability guard), so `--command` accepts this literal negated form.
|
|
252
|
+
if flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:08:30Z" --id ac6-self-asserting-negation --command "! grep -E 'this-pattern-does-not-exist-anywhere' $FIXTURE_FILE" \
|
|
253
|
+
>"$TMPDIR_EVAL/rc-negation.out" 2>"$TMPDIR_EVAL/rc-negation.err"; then
|
|
254
|
+
_pass "record-check --command '! grep ...' (natural self-asserting negated form) exits 0, as expected for a plain pass"
|
|
255
|
+
else
|
|
256
|
+
_fail "record-check --command '! grep ...' unexpectedly failed: $(cat "$TMPDIR_EVAL/rc-negation.out" "$TMPDIR_EVAL/rc-negation.err")"
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
if grep -qi "not a runnable shell command" "$TMPDIR_EVAL/rc-negation.out" "$TMPDIR_EVAL/rc-negation.err"; then
|
|
260
|
+
_fail "REGRESSION: record-check --command '! grep ...' was rejected as non-runnable — isRunnableCommandText's leading-'!' strip is not working"
|
|
261
|
+
else
|
|
262
|
+
_pass "record-check --command '! grep ...' is ACCEPTED by the runnability guard (leading '!' is stripped before evaluation)"
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
if node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/rc-negation-assert.err"
|
|
266
|
+
import { readFileSync } from 'node:fs';
|
|
267
|
+
const bundle = JSON.parse(readFileSync('${SESSION_DIR}/trust.bundle', 'utf8'));
|
|
268
|
+
const claim = bundle.claims.find((c) => c.subjectId && c.subjectId.endsWith('/ac6-self-asserting-negation'));
|
|
269
|
+
if (!claim) { process.stderr.write('no claim found for the ac6-self-asserting-negation check\n'); process.exit(1); }
|
|
270
|
+
if (claim.value !== 'pass') { process.stderr.write('expected claim value pass for a self-asserting negated grep, got ' + claim.value + '\n'); process.exit(1); }
|
|
271
|
+
NODEOF
|
|
272
|
+
then
|
|
273
|
+
_pass "regression guard: a self-asserting '! grep ...' form records a clean pass, never misclassified ambiguous"
|
|
274
|
+
else
|
|
275
|
+
_fail "regression guard assertion failed: $(cat "$TMPDIR_EVAL/rc-negation-assert.err")"
|
|
276
|
+
fi
|
|
277
|
+
|
|
278
|
+
# AC5-parity regression guard: bare grep exit >= 2 (a real tool error — missing file) still hard
|
|
279
|
+
# FAILs (nonzero record-check exit), proving the exit-1-only carve-out is narrowly scoped.
|
|
280
|
+
if flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:09:00Z" --id ac6-exit2-hard-fail -- grep -E 'pattern' "$TMPDIR_EVAL/this-file-does-not-exist-ac6" >"$TMPDIR_EVAL/rc-exit2.out" 2>"$TMPDIR_EVAL/rc-exit2.err"; then
|
|
281
|
+
_fail "REGRESSION: record-check with a bare grep exit >= 2 (missing file, a real tool error) should hard-fail (nonzero exit), but exited 0"
|
|
282
|
+
else
|
|
283
|
+
_pass "record-check with a bare grep exit >= 2 (missing file) still hard-fails (nonzero exit) — the exit-1-only carve-out does not weaken real tool-error detection"
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
if node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/rc-exit2-assert.err"
|
|
287
|
+
import { readFileSync } from 'node:fs';
|
|
288
|
+
const bundle = JSON.parse(readFileSync('${SESSION_DIR}/trust.bundle', 'utf8'));
|
|
289
|
+
const claim = bundle.claims.find((c) => c.subjectId && c.subjectId.endsWith('/ac6-exit2-hard-fail'));
|
|
290
|
+
if (!claim) { process.stderr.write('no claim found for the ac6-exit2-hard-fail check\n'); process.exit(1); }
|
|
291
|
+
if (claim.value !== 'fail') { process.stderr.write('expected claim value fail for a bare grep exit >= 2, got ' + claim.value + '\n'); process.exit(1); }
|
|
292
|
+
NODEOF
|
|
293
|
+
then
|
|
294
|
+
_pass "record-check with a bare grep exit >= 2 records a hard fail claim value (not ambiguous, not_verified)"
|
|
295
|
+
else
|
|
296
|
+
_fail "exit>=2 hard-fail claim-value assertion failed: $(cat "$TMPDIR_EVAL/rc-exit2-assert.err")"
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
# ─── Mutation test (#362 AC6 recordCheck ambiguity branch): temporarily disable recordCheck's ──
|
|
300
|
+
# `ambiguous` computation in a SCRATCH COPY of the compiled build/ output, confirm the AC6 bare-
|
|
301
|
+
# grep-exit-1 fixture above now records status "fail" (not "not_verified") against that mutated
|
|
302
|
+
# binary (eval "goes red" without the branch), then restore the original compiled file
|
|
303
|
+
# immediately. Proves the eval is actually exercising this specific classification branch, not
|
|
304
|
+
# passing vacuously for an unrelated reason.
|
|
305
|
+
DIST_SIDECAR="$ROOT/build/src/cli/workflow-sidecar.js"
|
|
306
|
+
AMBIGUOUS_SCRATCH="$TMPDIR_EVAL/ambiguous-mutation-scratch"
|
|
307
|
+
mkdir -p "$AMBIGUOUS_SCRATCH"
|
|
308
|
+
|
|
309
|
+
if [[ -f "$DIST_SIDECAR" ]]; then
|
|
310
|
+
cp "$DIST_SIDECAR" "$AMBIGUOUS_SCRATCH/workflow-sidecar.orig.js"
|
|
311
|
+
node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/ambiguous-mutation-patch.err"
|
|
312
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
313
|
+
const file = '${AMBIGUOUS_SCRATCH}/workflow-sidecar.orig.js';
|
|
314
|
+
let src = readFileSync(file, 'utf8');
|
|
315
|
+
const needle = 'const ambiguous = exitCode === 1 && isAmbiguousAbsenceCommand(displayCommand ?? "");';
|
|
316
|
+
if (!src.includes(needle)) { process.stderr.write('mutation: recordCheck ambiguous-branch text not found — source pattern drifted, cannot mutation-test\n'); process.exit(1); }
|
|
317
|
+
src = src.split(needle).join('const ambiguous = false;');
|
|
318
|
+
writeFileSync('${AMBIGUOUS_SCRATCH}/workflow-sidecar.mutated.js', src);
|
|
319
|
+
NODEOF
|
|
320
|
+
|
|
321
|
+
if [[ -s "$TMPDIR_EVAL/ambiguous-mutation-patch.err" ]]; then
|
|
322
|
+
_fail "mutation-test setup failed (recordCheck ambiguous-branch source pattern did not match compiled output): $(cat "$TMPDIR_EVAL/ambiguous-mutation-patch.err")"
|
|
323
|
+
else
|
|
324
|
+
if node --check "$AMBIGUOUS_SCRATCH/workflow-sidecar.mutated.js" 2>"$TMPDIR_EVAL/ambiguous-mutation-syntax.err"; then
|
|
325
|
+
cp "$AMBIGUOUS_SCRATCH/workflow-sidecar.mutated.js" "$DIST_SIDECAR"
|
|
326
|
+
|
|
327
|
+
flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:10:00Z" --id ac6-mutation-check -- grep -E 'this-pattern-does-not-exist-anywhere' "$FIXTURE_FILE" >"$TMPDIR_EVAL/rc-mutation.out" 2>"$TMPDIR_EVAL/rc-mutation.err"
|
|
328
|
+
MUTATION_EXIT=$?
|
|
329
|
+
|
|
330
|
+
if node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/rc-mutation-assert.err"
|
|
331
|
+
import { readFileSync } from 'node:fs';
|
|
332
|
+
const bundle = JSON.parse(readFileSync('${SESSION_DIR}/trust.bundle', 'utf8'));
|
|
333
|
+
const claim = bundle.claims.find((c) => c.subjectId && c.subjectId.endsWith('/ac6-mutation-check'));
|
|
334
|
+
if (!claim) { process.stderr.write('no claim found for the ac6-mutation-check check\n'); process.exit(1); }
|
|
335
|
+
if (claim.value !== 'fail') { process.stderr.write('expected the mutated (ambiguity-disabled) binary to record claim value fail (not not_verified), got ' + claim.value + '\n'); process.exit(1); }
|
|
336
|
+
NODEOF
|
|
337
|
+
then
|
|
338
|
+
if [[ "$MUTATION_EXIT" -eq 0 ]]; then
|
|
339
|
+
_fail "mutation-test: with the ambiguous branch neutered, record-check should hard-fail (nonzero exit) on the same bare-grep exit-1 fixture, but it exited 0"
|
|
340
|
+
else
|
|
341
|
+
_pass "mutation-test: with the ambiguous branch neutered, the same bare-grep exit-1 fixture now records a hard FAIL and exits nonzero (eval correctly goes red without the branch, proving the eval exercises it)"
|
|
342
|
+
fi
|
|
343
|
+
else
|
|
344
|
+
_fail "mutation-test: bare-grep exit-1 fixture did not flip to a hard fail claim value even with the ambiguous branch neutered — the eval may not be exercising the intended branch: $(cat "$TMPDIR_EVAL/rc-mutation-assert.err")"
|
|
345
|
+
fi
|
|
346
|
+
else
|
|
347
|
+
_fail "mutation-test setup: mutated workflow-sidecar.js (recordCheck ambiguous branch) failed a syntax check, refusing to run it: $(cat "$TMPDIR_EVAL/ambiguous-mutation-syntax.err")"
|
|
348
|
+
fi
|
|
349
|
+
|
|
350
|
+
# Restore the real compiled branch immediately — never leave the mutated binary in place —
|
|
351
|
+
# and re-run the same fixture to confirm the restored binary classifies it ambiguous again.
|
|
352
|
+
cp "$AMBIGUOUS_SCRATCH/workflow-sidecar.orig.js" "$DIST_SIDECAR"
|
|
353
|
+
flow_agents_node "$WRITER" record-check "$SESSION_DIR" --timestamp "2026-07-05T09:10:30Z" --id ac6-restore-check -- grep -E 'this-pattern-does-not-exist-anywhere' "$FIXTURE_FILE" >"$TMPDIR_EVAL/rc-restore.out" 2>"$TMPDIR_EVAL/rc-restore.err"
|
|
354
|
+
RESTORE_EXIT=$?
|
|
355
|
+
if node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/rc-restore-assert.err"
|
|
356
|
+
import { readFileSync } from 'node:fs';
|
|
357
|
+
const bundle = JSON.parse(readFileSync('${SESSION_DIR}/trust.bundle', 'utf8'));
|
|
358
|
+
const claim = bundle.claims.find((c) => c.subjectId && c.subjectId.endsWith('/ac6-restore-check'));
|
|
359
|
+
if (!claim) { process.stderr.write('no claim found for the ac6-restore-check check\n'); process.exit(1); }
|
|
360
|
+
if (claim.value !== 'not_verified' || "$RESTORE_EXIT" != "0") { process.stderr.write('expected the restored binary to record claim value not_verified and exit 0, got value=' + claim.value + ' exit=$RESTORE_EXIT\n'); process.exit(1); }
|
|
361
|
+
NODEOF
|
|
362
|
+
then
|
|
363
|
+
_pass "mutation-test cleanup: the real compiled ambiguous branch is restored and classifies a bare-grep exit-1 as not_verified (exit 0) again"
|
|
364
|
+
else
|
|
365
|
+
_fail "mutation-test cleanup REGRESSION: the ambiguous branch did not come back correctly after restoring the original compiled file: $(cat "$TMPDIR_EVAL/rc-restore-assert.err")"
|
|
366
|
+
fi
|
|
367
|
+
fi
|
|
368
|
+
else
|
|
369
|
+
_fail "mutation-test setup: could not locate the compiled build/src/cli/workflow-sidecar.js to mutate for the recordCheck ambiguous branch (ran 'npm run build' first?)"
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
if [[ "$errors" -eq 0 ]]; then
|
|
373
|
+
echo "record-check integration passed."
|
|
374
|
+
exit 0
|
|
375
|
+
fi
|
|
376
|
+
|
|
377
|
+
echo "record-check integration failed: $errors issue(s)."
|
|
378
|
+
exit 1
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# shellcheck disable=SC2015
|
|
3
|
+
# ^ the `<cond> && pass ... || fail ...` assert idiom is deliberate (pass always returns 0).
|
|
4
|
+
# test_routing_efficiency.sh — routing-efficiency proposals from delegation outcomes (#415).
|
|
5
|
+
#
|
|
6
|
+
# Proves scripts/telemetry/routing-efficiency.sh turns real economics.jsonl delegation outcomes into
|
|
7
|
+
# ADVISORY per-(role,model) proposals, honestly: unavailable outcomes are excluded from every rate, a
|
|
8
|
+
# thin sample yields insufficient-signal (not a confident call), and the analyzer never writes config.
|
|
9
|
+
set -uo pipefail
|
|
10
|
+
|
|
11
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
12
|
+
ANALYZER="$ROOT/scripts/telemetry/routing-efficiency.sh"
|
|
13
|
+
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
|
14
|
+
errors=0
|
|
15
|
+
pass() { echo " [PASS] $1"; }
|
|
16
|
+
fail() { echo " [FAIL] $1"; errors=$((errors + 1)); }
|
|
17
|
+
command -v jq >/dev/null 2>&1 || { echo "jq unavailable; skipping"; exit 0; }
|
|
18
|
+
|
|
19
|
+
echo "=== routing-efficiency (#415) ==="
|
|
20
|
+
|
|
21
|
+
# Fixture: mechanical is troubled (3 of 4 measurable reworked/diverged/failed), design is clean (3/3
|
|
22
|
+
# accepted), implementation is all-unavailable (no measurable signal on this harness).
|
|
23
|
+
LOG="$TMP/economics.jsonl"
|
|
24
|
+
cat > "$LOG" <<'EOF'
|
|
25
|
+
{"schema":"kontour.console.economics","version":"0.1","run_id":"r1","cost":{},"time":{},"iterations":{},"defects":{},"delegations":[{"role":"delegate-mechanical","resolved_model":"claude-haiku-4-5@anthropic","outcome":"rework"},{"role":"delegate-design","resolved_model":"claude-opus-4-8@anthropic","outcome":"accepted"}]}
|
|
26
|
+
{"schema":"kontour.console.economics","version":"0.1","run_id":"r2","cost":{},"time":{},"iterations":{},"defects":{},"delegations":[{"role":"delegate-mechanical","resolved_model":"claude-haiku-4-5@anthropic","outcome":"failed"},{"role":"delegate-design","resolved_model":"claude-opus-4-8@anthropic","outcome":"accepted"}]}
|
|
27
|
+
{"schema":"kontour.console.economics","version":"0.1","run_id":"r3","cost":{},"time":{},"iterations":{},"defects":{},"delegations":[{"role":"delegate-mechanical","resolved_model":"claude-haiku-4-5@anthropic","outcome":"diverged"},{"role":"delegate-design","resolved_model":"claude-opus-4-8@anthropic","outcome":"accepted"},{"role":"delegate-implementation","resolved_model":"claude-sonnet-5@anthropic","outcome":"unavailable"}]}
|
|
28
|
+
{"schema":"kontour.console.economics","version":"0.1","run_id":"r4","cost":{},"time":{},"iterations":{},"defects":{},"delegations":[{"role":"delegate-mechanical","resolved_model":"claude-haiku-4-5@anthropic","outcome":"accepted"},{"role":"delegate-implementation","resolved_model":"claude-sonnet-5@anthropic","outcome":"unavailable"}]}
|
|
29
|
+
EOF
|
|
30
|
+
OUT="$(bash "$ANALYZER" "$LOG")"
|
|
31
|
+
aeq() { # <label> <jq-expr> <expected>
|
|
32
|
+
local got; got="$(printf '%s' "$OUT" | jq -c "$2" 2>/dev/null)"
|
|
33
|
+
[[ "$got" == "$3" ]] && pass "$1 ($got)" || fail "$1: expected $3 got $got"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# AC: at least one real proposal (the #415 acceptance) — the troubled cheap tier flagged for escalation.
|
|
37
|
+
aeq "troubled cheap tier → escalate-minimum-tier proposal" \
|
|
38
|
+
'.proposals[] | select(.role=="delegate-mechanical") | .kind' '"escalate-minimum-tier"'
|
|
39
|
+
aeq "clean expensive tier → keep-tier proposal" \
|
|
40
|
+
'.proposals[] | select(.role=="delegate-design") | .kind' '"keep-tier"'
|
|
41
|
+
# AC: unavailable excluded from rates → thin measurable sample is insufficient-signal, NOT a confident call.
|
|
42
|
+
aeq "all-unavailable role → insufficient-signal (not a confident verdict)" \
|
|
43
|
+
'.proposals[] | select(.role=="delegate-implementation") | .kind' '"insufficient-signal"'
|
|
44
|
+
aeq "unavailable EXCLUDED from measurable (mechanical measurable == 4)" \
|
|
45
|
+
'.by_role_model[] | select(.role=="delegate-mechanical") | .measurable' '4'
|
|
46
|
+
aeq "implementation unavailable counted separately (== 2), measurable == 0" \
|
|
47
|
+
'.by_role_model[] | select(.role=="delegate-implementation") | [.unavailable,.measurable]' '[2,0]'
|
|
48
|
+
aeq "trouble_rate excludes unavailable (mechanical 3/4 == 0.75)" \
|
|
49
|
+
'.by_role_model[] | select(.role=="delegate-mechanical") | .trouble_rate' '0.75'
|
|
50
|
+
aeq "every proposal is advisory (never auto-applied)" \
|
|
51
|
+
'[.proposals[] | select(.severity != "advisory")] | length' '0'
|
|
52
|
+
# a real, human-readable rationale exists on the escalation proposal
|
|
53
|
+
HAS_RAT="$(printf '%s' "$OUT" | jq -r '.proposals[] | select(.role=="delegate-mechanical") | .rationale' 2>/dev/null)"
|
|
54
|
+
[[ "$HAS_RAT" == *"under-routed"* ]] && pass "escalation proposal carries a human rationale" || fail "no rationale: $HAS_RAT"
|
|
55
|
+
|
|
56
|
+
# empty / no-delegations input → empty proposals, never a fabricated call
|
|
57
|
+
EMPTY="$TMP/empty.jsonl"; : > "$EMPTY"
|
|
58
|
+
EOUT="$(bash "$ANALYZER" "$EMPTY")"
|
|
59
|
+
[[ "$(printf '%s' "$EOUT" | jq -c '.proposals')" == "[]" ]] && pass "empty input → no proposals (no fabrication)" || fail "empty input produced proposals"
|
|
60
|
+
[[ "$(printf '%s' "$EOUT" | jq -r '.schema')" == "kontour.routing-efficiency" ]] && pass "empty input still emits a valid envelope" || fail "empty envelope malformed"
|
|
61
|
+
|
|
62
|
+
# honesty guard: the analyzer must NOT write .datum/config.json (advisory only)
|
|
63
|
+
if grep -qE '\.datum/config\.json' "$ANALYZER" && ! grep -qE 'never (edit|auto-appl)' "$ANALYZER"; then
|
|
64
|
+
fail "analyzer references .datum/config.json outside a comment — must not write routing config"
|
|
65
|
+
else
|
|
66
|
+
pass "analyzer never writes .datum/config.json (advisory only, human-ratified)"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
echo ""
|
|
70
|
+
if [[ "$errors" -eq 0 ]]; then echo "test_routing_efficiency: all checks passed."; exit 0
|
|
71
|
+
else echo "test_routing_efficiency: $errors check(s) failed."; exit 1; fi
|
|
@@ -116,6 +116,34 @@ else
|
|
|
116
116
|
fail "source kits/catalog.json changed during activation"
|
|
117
117
|
fi
|
|
118
118
|
|
|
119
|
+
SYMLINK_DEST="$TMP_DIR/symlink-activation-dest"
|
|
120
|
+
SYMLINK_OUTSIDE="$TMP_DIR/symlink-activation-outside"
|
|
121
|
+
mkdir -p "$SYMLINK_DEST" "$SYMLINK_OUTSIDE"
|
|
122
|
+
flow_agents_node "$CLI" install "$MIXED_SRC" --dest "$SYMLINK_DEST" >/dev/null 2>&1
|
|
123
|
+
SYMLINK_OUTPUT="$SYMLINK_DEST/.kontourai/flow-agents/projections/codex/flows/mixed-runtime-kit/mixed.runtime.flow.json"
|
|
124
|
+
mkdir -p "$(dirname "$SYMLINK_OUTPUT")"
|
|
125
|
+
printf 'unchanged\n' > "$SYMLINK_OUTSIDE/output"
|
|
126
|
+
ln -s "$SYMLINK_OUTSIDE/output" "$SYMLINK_OUTPUT"
|
|
127
|
+
if flow_agents_node "$CLI" activate --dest "$SYMLINK_DEST" --source-root "$ROOT" >"$TMP_DIR/symlink-output.out" 2>&1; then
|
|
128
|
+
fail "activation output symlink should fail closed"
|
|
129
|
+
elif [[ "$(cat "$SYMLINK_OUTSIDE/output")" == "unchanged" ]]; then
|
|
130
|
+
pass "activation output symlink fails closed without outside write"
|
|
131
|
+
else
|
|
132
|
+
fail "activation output symlink modified outside file"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
rm -rf "$SYMLINK_DEST/.kontourai"
|
|
136
|
+
mkdir -p "$SYMLINK_DEST/.kontourai/flow-agents/projections/codex"
|
|
137
|
+
printf 'unchanged\n' > "$SYMLINK_OUTSIDE/manifest"
|
|
138
|
+
ln -s "$SYMLINK_OUTSIDE/manifest" "$SYMLINK_DEST/.kontourai/flow-agents/projections/codex/activation.json"
|
|
139
|
+
if flow_agents_node "$CLI" activate --dest "$SYMLINK_DEST" --source-root "$ROOT" >"$TMP_DIR/symlink-manifest.out" 2>&1; then
|
|
140
|
+
fail "activation manifest symlink should fail closed"
|
|
141
|
+
elif [[ "$(cat "$SYMLINK_OUTSIDE/manifest")" == "unchanged" ]]; then
|
|
142
|
+
pass "activation manifest symlink fails closed without outside write"
|
|
143
|
+
else
|
|
144
|
+
fail "activation manifest symlink modified outside file"
|
|
145
|
+
fi
|
|
146
|
+
|
|
119
147
|
if flow_agents_node "$CLI" activate --dest "$DEST" --source-root "$ROOT" --adapter unknown --format json >"$UNKNOWN_OUT" 2>&1; then
|
|
120
148
|
fail "unknown adapter should fail"
|
|
121
149
|
sed -n '1,120p' "$UNKNOWN_OUT"
|
|
@@ -37,9 +37,9 @@ sha256_file() {
|
|
|
37
37
|
# ─── Seed fixture ─────────────────────────────────────────────────────────────
|
|
38
38
|
REPO="$TMPDIR_EVAL/repo"
|
|
39
39
|
SLUG="test-slug-153"
|
|
40
|
-
TASK_DIR="$REPO/.flow-agents/$SLUG"
|
|
40
|
+
TASK_DIR="$REPO/.kontourai/flow-agents/$SLUG"
|
|
41
41
|
mkdir -p "$TASK_DIR"
|
|
42
|
-
mkdir -p "$REPO/.flow-agents/liveness"
|
|
42
|
+
mkdir -p "$REPO/.kontourai/flow-agents/liveness"
|
|
43
43
|
mkdir -p "$REPO/docs"
|
|
44
44
|
|
|
45
45
|
printf '# Test Repo\n' > "$REPO/AGENTS.md"
|
|
@@ -98,7 +98,7 @@ cat > "$TASK_DIR/trust.bundle" << 'JSON'
|
|
|
98
98
|
JSON
|
|
99
99
|
|
|
100
100
|
# install.json — initial version
|
|
101
|
-
cat > "$REPO/.flow-agents/install.json" << 'JSON'
|
|
101
|
+
cat > "$REPO/.kontourai/flow-agents/install.json" << 'JSON'
|
|
102
102
|
{
|
|
103
103
|
"version": "v0.0.1",
|
|
104
104
|
"installedAt": "2026-06-25T00:00:00Z",
|
|
@@ -107,10 +107,10 @@ cat > "$REPO/.flow-agents/install.json" << 'JSON'
|
|
|
107
107
|
JSON
|
|
108
108
|
|
|
109
109
|
# Liveness stream: fresh other-agent event (5 min ago, within 1800 s TTL)
|
|
110
|
-
# and a self
|
|
110
|
+
# and a self event — self should NOT trigger a warning
|
|
111
111
|
FIVE_MIN_AGO="$(node -e "process.stdout.write(new Date(Date.now()-300000).toISOString().replace(/\\.\\d{3}Z$/,'Z'))")"
|
|
112
|
-
printf '{"type":"claim","subjectId":"test-slug-153","actor":"other-agent","at":"%s","ttlSeconds":1800}\n' "$FIVE_MIN_AGO" > "$REPO/.flow-agents/liveness/events.jsonl"
|
|
113
|
-
printf '{"type":"heartbeat","subjectId":"test-slug-153","actor":"
|
|
112
|
+
printf '{"type":"claim","subjectId":"test-slug-153","actor":"other-agent","at":"%s","ttlSeconds":1800}\n' "$FIVE_MIN_AGO" > "$REPO/.kontourai/flow-agents/liveness/events.jsonl"
|
|
113
|
+
printf '{"type":"heartbeat","subjectId":"test-slug-153","actor":"self-actor","at":"%s"}\n' "$FIVE_MIN_AGO" >> "$REPO/.kontourai/flow-agents/liveness/events.jsonl"
|
|
114
114
|
|
|
115
115
|
# ─── Snapshot checksums before hook run ───────────────────────────────────────
|
|
116
116
|
CKSUM_STATE_BEFORE="$(sha256_file "$TASK_DIR/state.json")"
|
|
@@ -120,7 +120,7 @@ CKSUM_TRUST_BEFORE="$(sha256_file "$TASK_DIR/trust.bundle")"
|
|
|
120
120
|
# ─── Hot-upgrade simulation: bump install.json version ───────────────────────
|
|
121
121
|
node -e "
|
|
122
122
|
const fs = require('fs');
|
|
123
|
-
const f = '$REPO/.flow-agents/install.json';
|
|
123
|
+
const f = '$REPO/.kontourai/flow-agents/install.json';
|
|
124
124
|
const obj = JSON.parse(fs.readFileSync(f,'utf8'));
|
|
125
125
|
obj.version = 'v0.0.2';
|
|
126
126
|
fs.writeFileSync(f, JSON.stringify(obj, null, 2) + '\n');
|
|
@@ -128,7 +128,7 @@ fs.writeFileSync(f, JSON.stringify(obj, null, 2) + '\n');
|
|
|
128
128
|
|
|
129
129
|
# ─── Run hook with SessionStart ───────────────────────────────────────────────
|
|
130
130
|
if echo "{\"hook_event_name\":\"SessionStart\",\"cwd\":\"$REPO\"}" | \
|
|
131
|
-
FLOW_AGENTS_ACTOR="
|
|
131
|
+
FLOW_AGENTS_ACTOR="self-actor" node "$ROOT/scripts/hooks/workflow-steering.js" > "$TMPDIR_EVAL/resume.out" 2>&1; then
|
|
132
132
|
_pass "hook exits 0 for SessionStart"
|
|
133
133
|
else
|
|
134
134
|
_fail "hook should exit 0 for SessionStart (exit $?)"
|
|
@@ -208,9 +208,9 @@ else
|
|
|
208
208
|
_fail "other-agent actor missing from liveness warning"
|
|
209
209
|
fi
|
|
210
210
|
|
|
211
|
-
# Self
|
|
212
|
-
if ! grep -q "LIVENESS WARNING.*
|
|
213
|
-
_pass "self-actor
|
|
211
|
+
# Self actor should NOT appear as a liveness warning
|
|
212
|
+
if ! grep -q "LIVENESS WARNING.*self-actor\|self-actor.*LIVENESS WARNING" "$TMPDIR_EVAL/resume.out"; then
|
|
213
|
+
_pass "self-actor correctly excluded from liveness warning"
|
|
214
214
|
else
|
|
215
215
|
_fail "self-actor should not be warned in liveness advisory"
|
|
216
216
|
fi
|
|
@@ -240,7 +240,7 @@ fi
|
|
|
240
240
|
|
|
241
241
|
# ─── Negative: UserPromptSubmit should produce NO RESUME block ────────────────
|
|
242
242
|
echo "{\"hook_event_name\":\"UserPromptSubmit\",\"cwd\":\"$REPO\",\"prompt\":\"continue\"}" | \
|
|
243
|
-
FLOW_AGENTS_ACTOR="
|
|
243
|
+
FLOW_AGENTS_ACTOR="self-actor" node "$ROOT/scripts/hooks/workflow-steering.js" > "$TMPDIR_EVAL/prompt.out" 2>&1
|
|
244
244
|
|
|
245
245
|
if ! grep -q "RESUME:" "$TMPDIR_EVAL/prompt.out"; then
|
|
246
246
|
_pass "RESUME block absent for UserPromptSubmit (negative case)"
|
|
@@ -250,7 +250,7 @@ fi
|
|
|
250
250
|
|
|
251
251
|
# ─── Negative: Empty liveness stream → no LIVENESS WARNING ───────────────────
|
|
252
252
|
REPO2="$TMPDIR_EVAL/repo2"
|
|
253
|
-
TASK_DIR2="$REPO2/.flow-agents/$SLUG"
|
|
253
|
+
TASK_DIR2="$REPO2/.kontourai/flow-agents/$SLUG"
|
|
254
254
|
mkdir -p "$TASK_DIR2"
|
|
255
255
|
mkdir -p "$REPO2/docs"
|
|
256
256
|
printf '# Test Repo 2\n' > "$REPO2/AGENTS.md"
|
|
@@ -262,7 +262,7 @@ printf 'test-slug-153--plan-work.md stub\n' > "$TASK_DIR2/test-slug-153--plan-wo
|
|
|
262
262
|
# No liveness directory → empty stream
|
|
263
263
|
|
|
264
264
|
echo "{\"hook_event_name\":\"SessionStart\",\"cwd\":\"$REPO2\"}" | \
|
|
265
|
-
FLOW_AGENTS_ACTOR="
|
|
265
|
+
FLOW_AGENTS_ACTOR="self-actor" node "$ROOT/scripts/hooks/workflow-steering.js" > "$TMPDIR_EVAL/nolive.out" 2>&1
|
|
266
266
|
|
|
267
267
|
if grep -q "RESUME:" "$TMPDIR_EVAL/nolive.out"; then
|
|
268
268
|
_pass "RESUME block present when no liveness stream (absence case)"
|
|
@@ -284,12 +284,9 @@ fi
|
|
|
284
284
|
# env, and assert that derived actor is excluded from LIVENESS WARNING while a
|
|
285
285
|
# genuinely different actor is still reported.
|
|
286
286
|
#
|
|
287
|
-
#
|
|
288
|
-
# ".flow-agents/<slug>" as the REPO/REPO2 fixtures above do), because
|
|
287
|
+
# This and the fixtures above seed under ".kontourai/flow-agents/<slug>", because
|
|
289
288
|
# scripts/hooks/lib/local-artifact-paths.js's flowAgentsArtifactRootsForRead()
|
|
290
|
-
#
|
|
291
|
-
# fixtures above, unrelated to #287, out of scope for this task (see plan
|
|
292
|
-
# artifact task 3.2 investigation notes).
|
|
289
|
+
# reads the runtime artifact root, not durable ".flow-agents" config.
|
|
293
290
|
REPO3="$TMPDIR_EVAL/repo3"
|
|
294
291
|
SLUG3="test-slug-153-derived"
|
|
295
292
|
TASK_DIR3="$REPO3/.kontourai/flow-agents/$SLUG3"
|