@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,329 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# shellcheck disable=SC2015
|
|
3
|
+
# ^ the `<cond> && pass ... || fail ...` assert idiom is deliberate (pass always returns 0).
|
|
4
|
+
# test_learning_review_proposals.sh — kit/gate tuning proposals from economics records (#352).
|
|
5
|
+
#
|
|
6
|
+
# Proves scripts/telemetry/learning-review-proposals.sh turns a window of real
|
|
7
|
+
# kontour.console.economics records into ADVISORY, evidence-cited, per-kit/per-gate proposals —
|
|
8
|
+
# generalizing scripts/telemetry/routing-efficiency.sh (#415) from per-(role,model) to per-kit/
|
|
9
|
+
# per-gate — and that scripts/telemetry/learning-review-decide.sh records human ratify/reject/
|
|
10
|
+
# defer decisions in place, never auto-applying anything.
|
|
11
|
+
#
|
|
12
|
+
# Coverage -> ACs (see docs/specs/learning-review-proposals-contract.md):
|
|
13
|
+
# AC1 (R1) pattern-present fixture's by_kit[]/by_gate[] aggregates match hand-computed values
|
|
14
|
+
# (evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json,
|
|
15
|
+
# arithmetic shown in expected-aggregates.md) at exact 4-decimal precision.
|
|
16
|
+
# AC2 (R2) pattern-present yields >=1 evidence-cited proposal (cost+defect paired); balanced
|
|
17
|
+
# fixture (proportional cost/findings movement) yields zero proposals.
|
|
18
|
+
# AC3 (R3) analyzer + decide script make zero mutation to kits/**/.datum/config.json; decide
|
|
19
|
+
# refuses --follow-on-ref without --ratify.
|
|
20
|
+
# AC4 (R4) running twice over an identical window de-dupes (no duplicate proposal_id in the
|
|
21
|
+
# ledger, second run marks already_proposed:true); under-threshold record count
|
|
22
|
+
# yields an explicit "insufficient-data" outcome, never a confident thin call.
|
|
23
|
+
# AC5 (R5) a ratified proposal is linked to a follow_on_ref; a later window's pass over the
|
|
24
|
+
# same target fills in effect_observed (metric/before/after/moved).
|
|
25
|
+
# schema emitted output validates against learning-review-proposals.schema.json; a doctored
|
|
26
|
+
# cost-only (no defect) proposal FAILS validation (Goodhart-guard co-requirement).
|
|
27
|
+
#
|
|
28
|
+
# Deterministic (no network, no model spend); fixtures live under
|
|
29
|
+
# evals/fixtures/learning-review-proposals/**. Each case uses its own mktemp-sandboxed ledger.
|
|
30
|
+
set -uo pipefail
|
|
31
|
+
|
|
32
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
33
|
+
TELEMETRY="$ROOT/scripts/telemetry"
|
|
34
|
+
ANALYZER="$TELEMETRY/learning-review-proposals.sh"
|
|
35
|
+
DECIDER="$TELEMETRY/learning-review-decide.sh"
|
|
36
|
+
SCHEMA="$TELEMETRY/learning-review-proposals.schema.json"
|
|
37
|
+
FIX="$ROOT/evals/fixtures/learning-review-proposals"
|
|
38
|
+
|
|
39
|
+
TMP="$(mktemp -d)"
|
|
40
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
41
|
+
errors=0
|
|
42
|
+
pass() { echo " [PASS] $1"; }
|
|
43
|
+
fail() { echo " [FAIL] $1"; errors=$((errors + 1)); }
|
|
44
|
+
|
|
45
|
+
command -v jq >/dev/null 2>&1 || { echo "jq unavailable; skipping"; exit 0; }
|
|
46
|
+
|
|
47
|
+
echo "=== learning-review-proposals (#352) ==="
|
|
48
|
+
|
|
49
|
+
# ── AC1/AC2: pattern-present — hand-computed aggregates + evidence-cited proposals ─────────────────
|
|
50
|
+
echo "--- AC1/AC2: pattern-present fixture — hand-computed aggregates match; evidence-cited proposals fire ---"
|
|
51
|
+
PP="$FIX/pattern-present"
|
|
52
|
+
LEDGER_PP="$TMP/ledger-pattern-present.jsonl"
|
|
53
|
+
OUT_PP="$(bash "$ANALYZER" --sessions-root "$PP/sessions" --ledger "$LEDGER_PP" "$PP/economics.jsonl")"
|
|
54
|
+
|
|
55
|
+
aeq() { # <out-json> <label> <jq-expr> <expected>
|
|
56
|
+
local out="$1" label="$2" expr="$3" exp="$4"
|
|
57
|
+
local got; got="$(printf '%s' "$out" | jq -c "$expr" 2>/dev/null)"
|
|
58
|
+
[[ "$got" == "$exp" ]] && pass "$label ($got)" || fail "$label: expected $exp got $got"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
aeq "$OUT_PP" "records_considered == 6" '.records_considered' '6'
|
|
62
|
+
aeq "$OUT_PP" "outcome == ok" '.outcome' '"ok"'
|
|
63
|
+
|
|
64
|
+
# by_kit[] — exact 4-decimal-rounded values, hand-computed in expected-aggregates.md
|
|
65
|
+
BYKIT='.aggregates.by_kit[] | select(.kit_id=="builder")'
|
|
66
|
+
aeq "$OUT_PP" "by_kit[builder].runs == 6" "$BYKIT | .runs" '6'
|
|
67
|
+
aeq "$OUT_PP" "by_kit[builder].first_half_avg_cost_usd == 0.1000" "$BYKIT | .first_half_avg_cost_usd" '0.1'
|
|
68
|
+
aeq "$OUT_PP" "by_kit[builder].second_half_avg_cost_usd == 0.2000" "$BYKIT | .second_half_avg_cost_usd" '0.2'
|
|
69
|
+
aeq "$OUT_PP" "by_kit[builder].cost_trend_pct == 100.0000" "$BYKIT | .cost_trend_pct" '100'
|
|
70
|
+
aeq "$OUT_PP" "by_kit[builder].first_half_findings_total == 6" "$BYKIT | .first_half_findings_total" '6'
|
|
71
|
+
aeq "$OUT_PP" "by_kit[builder].second_half_findings_total == 6" "$BYKIT | .second_half_findings_total" '6'
|
|
72
|
+
aeq "$OUT_PP" "by_kit[builder].findings_delta_pct == 0.0000" "$BYKIT | .findings_delta_pct" '0'
|
|
73
|
+
aeq "$OUT_PP" "by_kit[builder].avg_wall_clock_s == 100.0000" "$BYKIT | .avg_wall_clock_s" '100'
|
|
74
|
+
aeq "$OUT_PP" "by_kit[builder].avg_human_wait_s == 10.0000" "$BYKIT | .avg_human_wait_s" '10'
|
|
75
|
+
aeq "$OUT_PP" "by_kit[builder].route_back_rate == 0.5000" "$BYKIT | .route_back_rate" '0.5'
|
|
76
|
+
aeq "$OUT_PP" "by_kit[builder].caught_false_completions_total == 6" "$BYKIT | .caught_false_completions_total" '6'
|
|
77
|
+
|
|
78
|
+
# by_gate[] — exact 4-decimal-rounded values
|
|
79
|
+
BYGATE='.aggregates.by_gate[] | select(.gate_id=="unit tests pass")'
|
|
80
|
+
aeq "$OUT_PP" "by_gate[unit tests pass].fire_count == 4" "$BYGATE | .fire_count" '4'
|
|
81
|
+
aeq "$OUT_PP" "by_gate[unit tests pass].correct_count == 1" "$BYGATE | .correct_count" '1'
|
|
82
|
+
aeq "$OUT_PP" "by_gate[unit tests pass].false_block_count == 3" "$BYGATE | .false_block_count" '3'
|
|
83
|
+
aeq "$OUT_PP" "by_gate[unit tests pass].missed_block_count == 0" "$BYGATE | .missed_block_count" '0'
|
|
84
|
+
aeq "$OUT_PP" "by_gate[unit tests pass].false_block_rate == 0.7500" "$BYGATE | .false_block_rate" '0.75'
|
|
85
|
+
aeq "$OUT_PP" "by_gate[unit tests pass].avg_wall_clock_s_when_fired == 100.0000" "$BYGATE | .avg_wall_clock_s_when_fired" '100'
|
|
86
|
+
aeq "$OUT_PP" "by_gate[unit tests pass].avg_human_wait_s_when_fired == 10.0000" "$BYGATE | .avg_human_wait_s_when_fired" '10'
|
|
87
|
+
|
|
88
|
+
# whole-document by_kit/by_gate equal the independently hand-computed expected-aggregates.json exactly.
|
|
89
|
+
EXPECTED_AGG="$(cat "$PP/expected-aggregates.json")"
|
|
90
|
+
GOT_BYKIT="$(printf '%s' "$OUT_PP" | jq -c '.aggregates.by_kit')"
|
|
91
|
+
EXP_BYKIT="$(printf '%s' "$EXPECTED_AGG" | jq -c '.by_kit')"
|
|
92
|
+
[[ "$GOT_BYKIT" == "$EXP_BYKIT" ]] && pass "by_kit[] equals expected-aggregates.json exactly" || fail "by_kit[] drifted from expected-aggregates.json: got=$GOT_BYKIT want=$EXP_BYKIT"
|
|
93
|
+
GOT_BYGATE="$(printf '%s' "$OUT_PP" | jq -c '.aggregates.by_gate')"
|
|
94
|
+
EXP_BYGATE="$(printf '%s' "$EXPECTED_AGG" | jq -c '.by_gate')"
|
|
95
|
+
[[ "$GOT_BYGATE" == "$EXP_BYGATE" ]] && pass "by_gate[] equals expected-aggregates.json exactly" || fail "by_gate[] drifted from expected-aggregates.json: got=$GOT_BYGATE want=$EXP_BYGATE"
|
|
96
|
+
|
|
97
|
+
# AC2: at least one evidence-cited kit-review-cost-inflation proposal, cost+defect paired.
|
|
98
|
+
KIT_PROP="$(printf '%s' "$OUT_PP" | jq -c '.proposals[] | select(.pattern=="kit-review-cost-inflation" and .target.id=="builder")')"
|
|
99
|
+
[[ -n "$KIT_PROP" ]] && pass "pattern-present yields a kit-review-cost-inflation proposal for builder" || fail "no kit-review-cost-inflation proposal found"
|
|
100
|
+
aeq "$OUT_PP" "kit proposal evidence.cost non-null" '.proposals[] | select(.pattern=="kit-review-cost-inflation") | (.evidence.cost != null)' 'true'
|
|
101
|
+
aeq "$OUT_PP" "kit proposal evidence.defect non-null" '.proposals[] | select(.pattern=="kit-review-cost-inflation") | (.evidence.defect != null)' 'true'
|
|
102
|
+
aeq "$OUT_PP" "kit proposal evidence.cost.cost_trend_pct == 100" '.proposals[] | select(.pattern=="kit-review-cost-inflation") | .evidence.cost.cost_trend_pct' '100'
|
|
103
|
+
aeq "$OUT_PP" "kit proposal evidence.defect.findings_delta_pct == 0" '.proposals[] | select(.pattern=="kit-review-cost-inflation") | .evidence.defect.findings_delta_pct' '0'
|
|
104
|
+
|
|
105
|
+
# AC2: evidence-cited gate-false-block-review proposal, cost+defect paired.
|
|
106
|
+
GATE_PROP="$(printf '%s' "$OUT_PP" | jq -c '.proposals[] | select(.pattern=="gate-false-block-review" and .target.id=="unit tests pass")')"
|
|
107
|
+
[[ -n "$GATE_PROP" ]] && pass "pattern-present yields a gate-false-block-review proposal" || fail "no gate-false-block-review proposal found"
|
|
108
|
+
aeq "$OUT_PP" "gate proposal evidence.cost non-null" '.proposals[] | select(.pattern=="gate-false-block-review") | (.evidence.cost != null)' 'true'
|
|
109
|
+
aeq "$OUT_PP" "gate proposal evidence.defect non-null" '.proposals[] | select(.pattern=="gate-false-block-review") | (.evidence.defect != null)' 'true'
|
|
110
|
+
aeq "$OUT_PP" "gate proposal evidence.defect.false_block_count == 3" '.proposals[] | select(.pattern=="gate-false-block-review") | .evidence.defect.false_block_count' '3'
|
|
111
|
+
aeq "$OUT_PP" "every proposal is severity advisory" '[.proposals[] | select(.severity != "advisory")] | length' '0'
|
|
112
|
+
|
|
113
|
+
# ── AC2: balanced — proportional cost+findings movement -> zero proposals ─────────────────────────
|
|
114
|
+
echo "--- AC2: balanced fixture — proportional cost+findings rise (30%/30%) yields zero proposals ---"
|
|
115
|
+
BAL="$FIX/balanced"
|
|
116
|
+
LEDGER_BAL="$TMP/ledger-balanced.jsonl"
|
|
117
|
+
OUT_BAL="$(bash "$ANALYZER" --ledger "$LEDGER_BAL" "$BAL/economics.jsonl")"
|
|
118
|
+
aeq "$OUT_BAL" "balanced: outcome == ok (enough samples, just no pattern)" '.outcome' '"ok"'
|
|
119
|
+
aeq "$OUT_BAL" "balanced: proposals == []" '.proposals' '[]'
|
|
120
|
+
aeq "$OUT_BAL" "balanced: by_kit cost_trend_pct == 30 (engineered, not a threshold accident)" '.aggregates.by_kit[0].cost_trend_pct' '30'
|
|
121
|
+
aeq "$OUT_BAL" "balanced: by_kit findings_delta_pct == 30 (rising in step with cost -> guarded)" '.aggregates.by_kit[0].findings_delta_pct' '30'
|
|
122
|
+
|
|
123
|
+
# ── AC4: under-threshold — below LR_MIN_WINDOW_SAMPLE -> explicit insufficient-data ────────────────
|
|
124
|
+
echo "--- AC4: under-threshold fixture — below LR_MIN_WINDOW_SAMPLE -> insufficient-data, not a thin confident call ---"
|
|
125
|
+
UT="$FIX/under-threshold"
|
|
126
|
+
LEDGER_UT="$TMP/ledger-under-threshold.jsonl"
|
|
127
|
+
OUT_UT="$(bash "$ANALYZER" --ledger "$LEDGER_UT" "$UT/economics.jsonl")"
|
|
128
|
+
aeq "$OUT_UT" "under-threshold: outcome == insufficient-data" '.outcome' '"insufficient-data"'
|
|
129
|
+
aeq "$OUT_UT" "under-threshold: proposals == []" '.proposals' '[]'
|
|
130
|
+
aeq "$OUT_UT" "under-threshold: aggregates.partial == true" '.aggregates.partial' 'true'
|
|
131
|
+
aeq "$OUT_UT" "under-threshold: records_considered == 3 (< default 5)" '.records_considered' '3'
|
|
132
|
+
|
|
133
|
+
# ── AC4: repeat-window — identical window run twice -> no duplicate proposal_id, already_proposed ─
|
|
134
|
+
echo "--- AC4: repeat-window fixture — running twice over the identical window de-dupes ---"
|
|
135
|
+
RW="$FIX/repeat-window"
|
|
136
|
+
LEDGER_RW="$TMP/ledger-repeat-window.jsonl"
|
|
137
|
+
OUT_RW1="$(bash "$ANALYZER" --ledger "$LEDGER_RW" "$RW/economics.jsonl")"
|
|
138
|
+
aeq "$OUT_RW1" "repeat-window run1: yields a real proposal to de-dupe" '[.proposals[] | select(.pattern=="kit-review-cost-inflation")] | length' '1'
|
|
139
|
+
aeq "$OUT_RW1" "repeat-window run1: already_proposed == false (first time)" '.proposals[] | select(.pattern=="kit-review-cost-inflation") | .already_proposed' 'false'
|
|
140
|
+
LINES_AFTER_RUN1=$(wc -l < "$LEDGER_RW" | tr -d ' ')
|
|
141
|
+
OUT_RW2="$(bash "$ANALYZER" --ledger "$LEDGER_RW" "$RW/economics.jsonl")"
|
|
142
|
+
aeq "$OUT_RW2" "repeat-window run2: already_proposed == true (de-duped)" '.proposals[] | select(.pattern=="kit-review-cost-inflation") | .already_proposed' 'true'
|
|
143
|
+
LINES_AFTER_RUN2=$(wc -l < "$LEDGER_RW" | tr -d ' ')
|
|
144
|
+
[[ "$LINES_AFTER_RUN1" == "$LINES_AFTER_RUN2" ]] && pass "ledger line count unchanged across the second run ($LINES_AFTER_RUN2 lines)" || fail "ledger grew on the second run: $LINES_AFTER_RUN1 -> $LINES_AFTER_RUN2"
|
|
145
|
+
UNIQUE_IDS=$(jq -r '.proposal_id' "$LEDGER_RW" | sort -u | wc -l | tr -d ' ')
|
|
146
|
+
TOTAL_IDS=$(jq -r '.proposal_id' "$LEDGER_RW" | wc -l | tr -d ' ')
|
|
147
|
+
[[ "$UNIQUE_IDS" == "$TOTAL_IDS" ]] && pass "no duplicate proposal_id in the ledger ($TOTAL_IDS line(s), $UNIQUE_IDS unique)" || fail "duplicate proposal_id found: $TOTAL_IDS lines but $UNIQUE_IDS unique"
|
|
148
|
+
|
|
149
|
+
# ── AC3: zero mutation to kit/gate/flow config; decide.sh refuses --follow-on-ref w/o --ratify ────
|
|
150
|
+
echo "--- AC3: analyzer + decide.sh make zero mutation to kits/**/.datum/config.json ---"
|
|
151
|
+
_snapshot() { find "$ROOT/kits" "$ROOT/.datum" -type f 2>/dev/null | sort | xargs shasum 2>/dev/null | shasum | awk '{print $1}'; }
|
|
152
|
+
BEFORE_HASH="$(_snapshot)"
|
|
153
|
+
# Re-run the analyzer over every fixture (already exercised above) plus a decide.sh call, all
|
|
154
|
+
# pointed at TMP-sandboxed ledgers/paths only — never at anything under kits/ or .datum/.
|
|
155
|
+
: "$(bash "$ANALYZER" --sessions-root "$PP/sessions" --ledger "$TMP/ledger-ac3-scratch.jsonl" "$PP/economics.jsonl")"
|
|
156
|
+
SCRATCH_PID="$(jq -r '.proposal_id' "$TMP/ledger-ac3-scratch.jsonl" | head -1)"
|
|
157
|
+
: "$(bash "$DECIDER" "$TMP/ledger-ac3-scratch.jsonl" "$SCRATCH_PID" --ratify --decided-by tester --follow-on-ref "https://example.invalid/1" 2>/dev/null)"
|
|
158
|
+
AFTER_HASH="$(_snapshot)"
|
|
159
|
+
[[ "$BEFORE_HASH" == "$AFTER_HASH" ]] && pass "kits/ and .datum/ content hash unchanged after analyzer + decide.sh runs" || fail "kits/ or .datum/ content changed (mutation detected!)"
|
|
160
|
+
|
|
161
|
+
# decide.sh refuses --follow-on-ref without --ratify (checksum-verified no-write).
|
|
162
|
+
DECIDE_LEDGER="$TMP/ledger-decide-guard.jsonl"
|
|
163
|
+
cp "$LEDGER_RW" "$DECIDE_LEDGER"
|
|
164
|
+
GUARD_PID="$(jq -r '.proposal_id' "$DECIDE_LEDGER" | head -1)"
|
|
165
|
+
CHECKSUM_BEFORE="$(shasum "$DECIDE_LEDGER" | awk '{print $1}')"
|
|
166
|
+
bash "$DECIDER" "$DECIDE_LEDGER" "$GUARD_PID" --reject --decided-by tester --follow-on-ref "https://example.invalid/2" >/dev/null 2>&1
|
|
167
|
+
GUARD_RC=$?
|
|
168
|
+
CHECKSUM_AFTER="$(shasum "$DECIDE_LEDGER" | awk '{print $1}')"
|
|
169
|
+
[[ "$GUARD_RC" -ne 0 ]] && pass "decide.sh exits non-zero for --follow-on-ref without --ratify" || fail "decide.sh should have refused --follow-on-ref without --ratify"
|
|
170
|
+
[[ "$CHECKSUM_BEFORE" == "$CHECKSUM_AFTER" ]] && pass "decide.sh wrote nothing when refusing --follow-on-ref without --ratify" || fail "decide.sh wrote despite refusing"
|
|
171
|
+
|
|
172
|
+
# decide.sh also refuses an unknown proposal-id, and more than one of --ratify/--reject/--defer.
|
|
173
|
+
if bash "$DECIDER" "$DECIDE_LEDGER" "no-such-proposal-id" --ratify --decided-by tester >/dev/null 2>&1; then
|
|
174
|
+
fail "decide.sh should refuse an unknown proposal-id"
|
|
175
|
+
else
|
|
176
|
+
pass "decide.sh refuses an unknown proposal-id"
|
|
177
|
+
fi
|
|
178
|
+
if bash "$DECIDER" "$DECIDE_LEDGER" "$GUARD_PID" --ratify --reject --decided-by tester >/dev/null 2>&1; then
|
|
179
|
+
fail "decide.sh should refuse conflicting decision flags"
|
|
180
|
+
else
|
|
181
|
+
pass "decide.sh refuses more than one of --ratify/--reject/--defer"
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# ── AC5: ratify a pattern-present proposal, then a later window fills in effect_observed ──────────
|
|
185
|
+
echo "--- AC5: ratify pattern-present proposal -> follow_on_ref set; later window fills effect_observed ---"
|
|
186
|
+
EF="$FIX/effect-follow-up"
|
|
187
|
+
LEDGER_AC5="$TMP/ledger-ac5.jsonl"
|
|
188
|
+
OUT_AC5_1="$(bash "$ANALYZER" --sessions-root "$PP/sessions" --ledger "$LEDGER_AC5" "$PP/economics.jsonl")"
|
|
189
|
+
AC5_PID="$(printf '%s' "$OUT_AC5_1" | jq -r '.proposals[] | select(.pattern=="kit-review-cost-inflation") | .proposal_id')"
|
|
190
|
+
FOLLOW_ON_URL="https://github.com/kontourai/flow-agents/issues/99999"
|
|
191
|
+
bash "$DECIDER" "$LEDGER_AC5" "$AC5_PID" --ratify --decided-by "eval-tester" --follow-on-ref "$FOLLOW_ON_URL" >/dev/null
|
|
192
|
+
RATIFIED_ENTRY="$(jq -c --arg pid "$AC5_PID" 'select(.proposal_id == $pid)' "$LEDGER_AC5")"
|
|
193
|
+
[[ "$(printf '%s' "$RATIFIED_ENTRY" | jq -r '.decision.status')" == "ratified" ]] && pass "ledger entry decision.status == ratified after decide.sh --ratify" || fail "decision.status not ratified"
|
|
194
|
+
[[ "$(printf '%s' "$RATIFIED_ENTRY" | jq -r '.follow_on_ref')" == "$FOLLOW_ON_URL" ]] && pass "ledger entry follow_on_ref set to the fixture issue URL" || fail "follow_on_ref not set"
|
|
195
|
+
[[ "$(printf '%s' "$RATIFIED_ENTRY" | jq -c '.effect_observed')" == "null" ]] && pass "effect_observed still null before the later window pass" || fail "effect_observed should still be null before a later pass"
|
|
196
|
+
|
|
197
|
+
OUT_AC5_2="$(bash "$ANALYZER" --sessions-root "$EF/sessions" --ledger "$LEDGER_AC5" "$EF/economics.jsonl")"
|
|
198
|
+
RATIFIED_ENTRY_2="$(jq -c --arg pid "$AC5_PID" 'select(.proposal_id == $pid)' "$LEDGER_AC5")"
|
|
199
|
+
EFFECT="$(printf '%s' "$RATIFIED_ENTRY_2" | jq -c '.effect_observed')"
|
|
200
|
+
[[ "$EFFECT" != "null" ]] && pass "effect_observed populated after the later effect-follow-up window ($EFFECT)" || fail "effect_observed still null after the later window pass"
|
|
201
|
+
aeq "$RATIFIED_ENTRY_2" "effect_observed.metric == avg_cost_usd" '.effect_observed.metric' '"avg_cost_usd"'
|
|
202
|
+
aeq "$RATIFIED_ENTRY_2" "effect_observed.before == 0.2 (original evidence.cost.second_half_avg_cost_usd)" '.effect_observed.before' '0.2'
|
|
203
|
+
aeq "$RATIFIED_ENTRY_2" "effect_observed.after == 0.05 (effect-follow-up's second_half_avg_cost_usd)" '.effect_observed.after' '0.05'
|
|
204
|
+
aeq "$RATIFIED_ENTRY_2" "effect_observed.moved == improved (cost fell, expected_effect.direction was decrease)" '.effect_observed.moved' '"improved"'
|
|
205
|
+
: "$OUT_AC5_2"
|
|
206
|
+
|
|
207
|
+
# ── regression: --since/--until ISO-8601 (date-only, Z, +00:00) all parse to a real windowed pass ──
|
|
208
|
+
# Code-review iteration 2 finding [HIGH]: to_epoch_ms's old `sub("Z$"; "+00:00")` direction broke
|
|
209
|
+
# every ISO-8601 input (jq's fromdateiso8601 only understands a literal "Z", never "+00:00"), so a
|
|
210
|
+
# genuinely parseable --since/--until silently produced a FALSE "insufficient-data"/fallback-envelope
|
|
211
|
+
# outcome instead of a real windowed analysis. pattern-present's fixture .at values all fall on
|
|
212
|
+
# 2025-07-02 (23:46:40-45 UTC) — cover date-only, full-ISO-with-Z, and full-ISO-with-+00:00 bounds.
|
|
213
|
+
echo "--- regression: ISO-8601 --since/--until (date-only, Z, +00:00) parse correctly (not a false insufficient-data) ---"
|
|
214
|
+
OUT_ISO_DATEONLY="$(bash "$ANALYZER" --since 2025-07-02 --until 2025-07-03 --ledger "$TMP/ledger-iso-dateonly.jsonl" "$PP/economics.jsonl")"
|
|
215
|
+
aeq "$OUT_ISO_DATEONLY" "date-only --since/--until: outcome == ok (real windowed pass, not a parse-failure fallback)" '.outcome' '"ok"'
|
|
216
|
+
aeq "$OUT_ISO_DATEONLY" "date-only --since/--until: records_considered == 6" '.records_considered' '6'
|
|
217
|
+
|
|
218
|
+
OUT_ISO_Z="$(bash "$ANALYZER" --since 2025-07-02T00:00:00Z --until 2025-07-03T00:00:00Z --ledger "$TMP/ledger-iso-z.jsonl" "$PP/economics.jsonl")"
|
|
219
|
+
aeq "$OUT_ISO_Z" "full ISO-8601 with Z: outcome == ok" '.outcome' '"ok"'
|
|
220
|
+
aeq "$OUT_ISO_Z" "full ISO-8601 with Z: records_considered == 6" '.records_considered' '6'
|
|
221
|
+
|
|
222
|
+
OUT_ISO_OFFSET="$(bash "$ANALYZER" --since 2025-07-02T00:00:00+00:00 --until 2025-07-03T00:00:00+00:00 --ledger "$TMP/ledger-iso-offset.jsonl" "$PP/economics.jsonl")"
|
|
223
|
+
aeq "$OUT_ISO_OFFSET" "full ISO-8601 with +00:00 offset: outcome == ok" '.outcome' '"ok"'
|
|
224
|
+
aeq "$OUT_ISO_OFFSET" "full ISO-8601 with +00:00 offset: records_considered == 6" '.records_considered' '6'
|
|
225
|
+
|
|
226
|
+
# all three forms resolve to byte-identical effective window bounds.
|
|
227
|
+
WIN_DATEONLY="$(printf '%s' "$OUT_ISO_DATEONLY" | jq -c '.window')"
|
|
228
|
+
WIN_Z="$(printf '%s' "$OUT_ISO_Z" | jq -c '.window')"
|
|
229
|
+
WIN_OFFSET="$(printf '%s' "$OUT_ISO_OFFSET" | jq -c '.window')"
|
|
230
|
+
[[ "$WIN_DATEONLY" == "$WIN_Z" && "$WIN_Z" == "$WIN_OFFSET" ]] && pass "date-only / Z / +00:00 all resolve to the identical effective window ($WIN_Z)" || fail "window bounds diverged: dateonly=$WIN_DATEONLY z=$WIN_Z offset=$WIN_OFFSET"
|
|
231
|
+
|
|
232
|
+
# a record's own ISO .at (not just --since/--until) also parses: reuse balanced fixture's epoch .at
|
|
233
|
+
# values by re-deriving an equivalent ISO-stamped record set on the fly (no new fixture file needed).
|
|
234
|
+
ISO_AT_LOG="$TMP/iso-at.jsonl"
|
|
235
|
+
jq -c '.at |= ((tonumber / 1000) | todateiso8601)' "$PP/economics.jsonl" > "$ISO_AT_LOG"
|
|
236
|
+
OUT_ISO_AT="$(bash "$ANALYZER" --ledger "$TMP/ledger-iso-at.jsonl" "$ISO_AT_LOG")"
|
|
237
|
+
aeq "$OUT_ISO_AT" "record .at as full ISO-8601 (Z) parses: outcome == ok" '.outcome' '"ok"'
|
|
238
|
+
aeq "$OUT_ISO_AT" "record .at as full ISO-8601 (Z) parses: records_considered == 6" '.records_considered' '6'
|
|
239
|
+
|
|
240
|
+
# ── regression: fractional-second ISO-8601 (.sssZ / .sss+00:00) parses to a real windowed pass ─────
|
|
241
|
+
# Code-review iteration 3 finding [MEDIUM]: a JS Date.toISOString()-style fractional-second
|
|
242
|
+
# timestamp (e.g. 2025-07-01T00:00:00.123Z) used to hit the old `test("Z$")` branch and throw
|
|
243
|
+
# inside fromdateiso8601 (which only understands whole seconds), silently degrading to a false
|
|
244
|
+
# insufficient-data/fallback-envelope outcome instead of a real windowed analysis.
|
|
245
|
+
echo "--- regression: fractional-second ISO-8601 (.sssZ / .sss+00:00) --since/--until parse correctly ---"
|
|
246
|
+
OUT_FRAC_Z="$(bash "$ANALYZER" --since "2025-07-02T00:00:00.000Z" --until "2025-07-03T00:00:00.999Z" --ledger "$TMP/ledger-frac-z.jsonl" "$PP/economics.jsonl")"
|
|
247
|
+
aeq "$OUT_FRAC_Z" "fractional-second ISO with Z: outcome == ok" '.outcome' '"ok"'
|
|
248
|
+
aeq "$OUT_FRAC_Z" "fractional-second ISO with Z: records_considered == 6" '.records_considered' '6'
|
|
249
|
+
|
|
250
|
+
OUT_FRAC_OFFSET="$(bash "$ANALYZER" --since "2025-07-02T00:00:00.000+00:00" --until "2025-07-03T00:00:00.999+00:00" --ledger "$TMP/ledger-frac-offset.jsonl" "$PP/economics.jsonl")"
|
|
251
|
+
aeq "$OUT_FRAC_OFFSET" "fractional-second ISO with +00:00 offset: outcome == ok" '.outcome' '"ok"'
|
|
252
|
+
aeq "$OUT_FRAC_OFFSET" "fractional-second ISO with +00:00 offset: records_considered == 6" '.records_considered' '6'
|
|
253
|
+
|
|
254
|
+
# ── regression: a garbage --since/--until FAILS LOUD (never masquerades as insufficient-data) ──────
|
|
255
|
+
# Code-review iteration 3 finding [MEDIUM] item 2: an unrecognized --since/--until value is
|
|
256
|
+
# operator-correctable CLI input and must exit non-zero with a stderr message — NEVER silently
|
|
257
|
+
# look like an honest "insufficient-data" result (which would hide a usage error as a data gap).
|
|
258
|
+
echo "--- regression: garbage --since exits non-zero with a stderr message, NOT insufficient-data ---"
|
|
259
|
+
GARBAGE_STDOUT="$TMP/garbage-since-stdout.txt"
|
|
260
|
+
GARBAGE_STDERR="$TMP/garbage-since-stderr.txt"
|
|
261
|
+
LEDGER_GARBAGE="$TMP/ledger-garbage-since.jsonl"
|
|
262
|
+
bash "$ANALYZER" --since "notadate" --ledger "$LEDGER_GARBAGE" "$PP/economics.jsonl" >"$GARBAGE_STDOUT" 2>"$GARBAGE_STDERR"
|
|
263
|
+
GARBAGE_RC=$?
|
|
264
|
+
[[ "$GARBAGE_RC" -ne 0 ]] && pass "garbage --since exits non-zero (rc=$GARBAGE_RC)" || fail "garbage --since should have exited non-zero (rc=$GARBAGE_RC)"
|
|
265
|
+
[[ ! -s "$GARBAGE_STDOUT" ]] && pass "garbage --since prints no stdout document (never a fabricated insufficient-data envelope)" || fail "garbage --since printed a document: $(cat "$GARBAGE_STDOUT")"
|
|
266
|
+
grep -qi "invalid --since" "$GARBAGE_STDERR" && pass "garbage --since prints a clear stderr diagnostic" || fail "garbage --since produced no clear stderr diagnostic: $(cat "$GARBAGE_STDERR")"
|
|
267
|
+
[[ ! -e "$LEDGER_GARBAGE" ]] && pass "garbage --since writes no ledger file" || fail "garbage --since unexpectedly created a ledger file"
|
|
268
|
+
|
|
269
|
+
# ── regression: a corrupted ledger line FAILS LOUD, writes NOTHING, valid entries survive ──────────
|
|
270
|
+
# Code-review iteration 2 finding [CRITICAL]: `jq -s` on a malformed ledger line used to fail
|
|
271
|
+
# silently, fall back to "[]", and the later mv would overwrite the ledger — silently destroying
|
|
272
|
+
# every recorded human ratify/reject/defer decision. Must now exit non-zero with zero bytes written.
|
|
273
|
+
echo "--- regression: malformed ledger line -> analyzer exits non-zero, writes nothing, valid entry survives ---"
|
|
274
|
+
CORRUPT_LEDGER="$TMP/ledger-corrupt.jsonl"
|
|
275
|
+
cat > "$CORRUPT_LEDGER" <<'EOF2'
|
|
276
|
+
{"proposal_id":"valid-1","target":{"kind":"kit","id":"builder"},"pattern":"kit-review-cost-inflation","proposed_change":"x","severity":"advisory","evidence":{"cost":{},"defect":{}},"expected_effect":{"metric":"avg_cost_usd","direction":"decrease","description":"x"},"decision":{"status":"ratified","decided_by":"tester","decided_at":"2026-01-01T00:00:00Z","rationale":"r"},"follow_on_ref":"https://example.invalid/1","effect_observed":null,"window":{"since":1000,"until":2000}}
|
|
277
|
+
{this line is not valid json at all
|
|
278
|
+
EOF2
|
|
279
|
+
CORRUPT_BEFORE_HASH="$(shasum "$CORRUPT_LEDGER" | awk '{print $1}')"
|
|
280
|
+
EMPTY_LOG="$TMP/empty-for-corrupt.jsonl"
|
|
281
|
+
: > "$EMPTY_LOG"
|
|
282
|
+
CORRUPT_STDOUT="$(bash "$ANALYZER" --ledger "$CORRUPT_LEDGER" "$EMPTY_LOG" 2>"$TMP/corrupt-stderr.txt")"
|
|
283
|
+
CORRUPT_RC=$?
|
|
284
|
+
CORRUPT_AFTER_HASH="$(shasum "$CORRUPT_LEDGER" | awk '{print $1}')"
|
|
285
|
+
[[ "$CORRUPT_RC" -ne 0 ]] && pass "analyzer exits non-zero on a corrupted ledger (rc=$CORRUPT_RC)" || fail "analyzer should have exited non-zero on a corrupted ledger (rc=$CORRUPT_RC)"
|
|
286
|
+
[[ -z "$CORRUPT_STDOUT" ]] && pass "analyzer prints no stdout document on a corrupted ledger (fail loud, not a silent fallback envelope)" || fail "analyzer printed a document despite a corrupted ledger: $CORRUPT_STDOUT"
|
|
287
|
+
[[ -s "$TMP/corrupt-stderr.txt" ]] && pass "analyzer prints a stderr diagnostic on a corrupted ledger" || fail "analyzer produced no stderr diagnostic"
|
|
288
|
+
[[ "$CORRUPT_BEFORE_HASH" == "$CORRUPT_AFTER_HASH" ]] && pass "corrupted ledger file is byte-identical after the run (zero bytes written)" || fail "corrupted ledger file was mutated (data-loss regression!)"
|
|
289
|
+
grep -q '"proposal_id":"valid-1"' "$CORRUPT_LEDGER" && pass "the valid ledger entry (with its ratified decision) survives untouched" || fail "the valid ledger entry was lost"
|
|
290
|
+
|
|
291
|
+
# ── Schema validation (Ajv, mirroring test_economics_record.sh's approach) ─────────────────────────
|
|
292
|
+
echo "--- schema: pattern-present output validates; a doctored cost-only proposal FAILS (Goodhart guard) ---"
|
|
293
|
+
if node -e "require.resolve('ajv')" >/dev/null 2>&1; then
|
|
294
|
+
SCHEMA_CHECK="$(node -e '
|
|
295
|
+
const Ajv=require("ajv");
|
|
296
|
+
const a=new Ajv({allErrors:true,strict:false});
|
|
297
|
+
const v=a.compile(require(process.argv[1]));
|
|
298
|
+
const doc=JSON.parse(process.argv[2]);
|
|
299
|
+
const docOk=v(doc);
|
|
300
|
+
const bad=JSON.parse(JSON.stringify(doc));
|
|
301
|
+
if (bad.proposals && bad.proposals.length > 0) { delete bad.proposals[0].evidence.defect; }
|
|
302
|
+
const badOk=v(bad);
|
|
303
|
+
console.log(JSON.stringify({docOk, badOk, errors: v.errors}));
|
|
304
|
+
' "$SCHEMA" "$OUT_PP" 2>&1)"
|
|
305
|
+
echo "$SCHEMA_CHECK" | grep -q '"docOk":true' && pass "pattern-present output validates against learning-review-proposals.schema.json" || fail "pattern-present output did NOT validate: $SCHEMA_CHECK"
|
|
306
|
+
echo "$SCHEMA_CHECK" | grep -q '"badOk":false' && pass "cost-only proposal (defect deleted) FAILS validation — Goodhart co-required guard holds" || fail "cost-only proposal wrongly validated: $SCHEMA_CHECK"
|
|
307
|
+
else
|
|
308
|
+
echo " NOT_VERIFIED: ajv module unavailable (no node_modules/ajv) — schema validation skipped. Run \`npm ci\` to enable this check."
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
# ── static guard: neither script writes to kits/** or .datum/config.json (mirrors #415's guard) ───
|
|
312
|
+
echo "--- static guard: neither script references kits/**/.datum/config.json outside a 'never' disclaimer ---"
|
|
313
|
+
for f in "$ANALYZER" "$DECIDER"; do
|
|
314
|
+
name="$(basename "$f")"
|
|
315
|
+
if grep -qE '\.datum/config\.json' "$f" && ! grep -qE 'never (writes?|edit|auto-appl)' "$f"; then
|
|
316
|
+
fail "$name references .datum/config.json outside a 'never write' disclaimer"
|
|
317
|
+
else
|
|
318
|
+
pass "$name never writes .datum/config.json (advisory only, human-ratified)"
|
|
319
|
+
fi
|
|
320
|
+
if grep -qE 'kits/\*\*|kits/<' "$f" && ! grep -qE 'never (writes?|edit|auto-appl)' "$f"; then
|
|
321
|
+
fail "$name references kits/** outside a 'never write' disclaimer"
|
|
322
|
+
else
|
|
323
|
+
pass "$name never writes kits/** (advisory only, human-ratified)"
|
|
324
|
+
fi
|
|
325
|
+
done
|
|
326
|
+
|
|
327
|
+
echo ""
|
|
328
|
+
if [[ "$errors" -eq 0 ]]; then echo "test_learning_review_proposals: all checks passed."; exit 0
|
|
329
|
+
else echo "test_learning_review_proposals: $errors check(s) failed."; exit 1; fi
|
|
@@ -124,17 +124,13 @@ NODE
|
|
|
124
124
|
|
|
125
125
|
# is_valid_json <string> — true if the given string parses as JSON.
|
|
126
126
|
is_valid_json() {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
process.
|
|
130
|
-
process.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
} catch {
|
|
135
|
-
process.exit(1);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
127
|
+
JSON_ARG="$1" node - <<'NODE'
|
|
128
|
+
try {
|
|
129
|
+
JSON.parse(process.env.JSON_ARG);
|
|
130
|
+
process.exit(0);
|
|
131
|
+
} catch {
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
138
134
|
NODE
|
|
139
135
|
}
|
|
140
136
|
|
|
@@ -358,6 +354,10 @@ echo "--- D. Wrapper-level conflict injection (Claude Code, Codex) ---"
|
|
|
358
354
|
|
|
359
355
|
POST_TOOL_USE_PAYLOAD='{"hook_event_name":"PostToolUse","tool_name":"Bash","tool_input":{"command":"echo hi"},"tool_response":{"stdout":"hi"}}'
|
|
360
356
|
CODEX_PAYLOAD='{"hook_event_name":"PostToolUse","tool_name":"shell","tool_input":{"command":"echo hi"}}'
|
|
357
|
+
POST_TOOL_USE_PAYLOAD_FILE="$TMPDIR_EVAL/post-tool-use-payload.json"
|
|
358
|
+
CODEX_PAYLOAD_FILE="$TMPDIR_EVAL/codex-payload.json"
|
|
359
|
+
printf '%s' "$POST_TOOL_USE_PAYLOAD" >"$POST_TOOL_USE_PAYLOAD_FILE"
|
|
360
|
+
printf '%s' "$CODEX_PAYLOAD" >"$CODEX_PAYLOAD_FILE"
|
|
361
361
|
|
|
362
362
|
# seed_conflict_fixture <root> <slug> <self_actor> <other_actor>
|
|
363
363
|
# Our own claim is well past the throttle window (real clock); the other actor's claim is
|
|
@@ -375,7 +375,7 @@ seed_conflict_fixture() {
|
|
|
375
375
|
D1_ROOT="$(new_scratch)"
|
|
376
376
|
seed_conflict_fixture "$D1_ROOT" "d1-subj" "agent-hb" "agent-conflict-claude"
|
|
377
377
|
D1_OUT="$(cd "$D1_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
|
|
378
|
-
node "$CLAUDE_HOOK" PostToolUse dev
|
|
378
|
+
node "$CLAUDE_HOOK" PostToolUse dev <"$POST_TOOL_USE_PAYLOAD_FILE")"
|
|
379
379
|
D1_STATUS=$?
|
|
380
380
|
D1_CONTEXT="$(field_of "$D1_OUT" hookSpecificOutput.additionalContext)"
|
|
381
381
|
if [[ "$D1_STATUS" -eq 0 ]] && is_valid_json "$D1_OUT" \
|
|
@@ -390,7 +390,7 @@ fi
|
|
|
390
390
|
D2_ROOT="$(new_scratch)"
|
|
391
391
|
seed_conflict_fixture "$D2_ROOT" "d2-subj" "agent-hb" "agent-conflict-codex"
|
|
392
392
|
D2_OUT="$(cd "$D2_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
|
|
393
|
-
node "$CODEX_HOOK" PostToolUse dev
|
|
393
|
+
node "$CODEX_HOOK" PostToolUse dev <"$CODEX_PAYLOAD_FILE")"
|
|
394
394
|
D2_STATUS=$?
|
|
395
395
|
D2_CONTEXT="$(field_of "$D2_OUT" hookSpecificOutput.additionalContext)"
|
|
396
396
|
if [[ "$D2_STATUS" -eq 0 ]] && is_valid_json "$D2_OUT" \
|
|
@@ -406,6 +406,10 @@ echo "--- E. Degraded-runtime proof (opencode, pi) ---"
|
|
|
406
406
|
|
|
407
407
|
OPENCODE_PAYLOAD='{"hook_event_name":"tool.execute.after"}'
|
|
408
408
|
PI_PAYLOAD='{"hook_event_name":"tool_result"}'
|
|
409
|
+
OPENCODE_PAYLOAD_FILE="$TMPDIR_EVAL/opencode-payload.json"
|
|
410
|
+
PI_PAYLOAD_FILE="$TMPDIR_EVAL/pi-payload.json"
|
|
411
|
+
printf '%s' "$OPENCODE_PAYLOAD" >"$OPENCODE_PAYLOAD_FILE"
|
|
412
|
+
printf '%s' "$PI_PAYLOAD" >"$PI_PAYLOAD_FILE"
|
|
409
413
|
|
|
410
414
|
# E1: opencode-telemetry-hook.js — no stdout context-injection field (stdout byte-empty), but a
|
|
411
415
|
# stderr diagnostic naming the conflicting actor (the plugin does not consume this wrapper's
|
|
@@ -413,7 +417,7 @@ PI_PAYLOAD='{"hook_event_name":"tool_result"}'
|
|
|
413
417
|
E1_ROOT="$(new_scratch)"
|
|
414
418
|
seed_conflict_fixture "$E1_ROOT" "e1-subj" "agent-hb" "agent-conflict-opencode"
|
|
415
419
|
E1_OUT="$(cd "$E1_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
|
|
416
|
-
node "$OPENCODE_HOOK" 'tool.execute.after' dev
|
|
420
|
+
node "$OPENCODE_HOOK" 'tool.execute.after' dev <"$OPENCODE_PAYLOAD_FILE" 2>"$TMPDIR_EVAL/e1.err")"
|
|
417
421
|
E1_STATUS=$?
|
|
418
422
|
E1_ERR="$(cat "$TMPDIR_EVAL/e1.err")"
|
|
419
423
|
if [[ "$E1_STATUS" -eq 0 ]] && [[ -z "$E1_OUT" ]] \
|
|
@@ -427,7 +431,7 @@ fi
|
|
|
427
431
|
E2_ROOT="$(new_scratch)"
|
|
428
432
|
seed_conflict_fixture "$E2_ROOT" "e2-subj" "agent-hb" "agent-conflict-pi"
|
|
429
433
|
E2_OUT="$(cd "$E2_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
|
|
430
|
-
node "$PI_HOOK" 'tool_result' dev
|
|
434
|
+
node "$PI_HOOK" 'tool_result' dev <"$PI_PAYLOAD_FILE" 2>"$TMPDIR_EVAL/e2.err")"
|
|
431
435
|
E2_STATUS=$?
|
|
432
436
|
E2_ERR="$(cat "$TMPDIR_EVAL/e2.err")"
|
|
433
437
|
if [[ "$E2_STATUS" -eq 0 ]] && [[ -z "$E2_OUT" ]] \
|
|
@@ -452,7 +456,7 @@ seed_solo_fixture() {
|
|
|
452
456
|
F1_ROOT="$(new_scratch)"
|
|
453
457
|
seed_solo_fixture "$F1_ROOT" "f1-subj" "agent-hb"
|
|
454
458
|
F1_OUT="$(cd "$F1_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
|
|
455
|
-
node "$CLAUDE_HOOK" PostToolUse dev
|
|
459
|
+
node "$CLAUDE_HOOK" PostToolUse dev <"$POST_TOOL_USE_PAYLOAD_FILE")"
|
|
456
460
|
if [[ "$F1_OUT" == '{"continue":true,"suppressOutput":true}' ]]; then
|
|
457
461
|
_pass "claude-telemetry-hook.js no-conflict PostToolUse output is byte-stable vs the pre-existing fixed shape (AC4)"
|
|
458
462
|
else
|
|
@@ -462,7 +466,7 @@ fi
|
|
|
462
466
|
F2_ROOT="$(new_scratch)"
|
|
463
467
|
seed_solo_fixture "$F2_ROOT" "f2-subj" "agent-hb"
|
|
464
468
|
F2_OUT="$(cd "$F2_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
|
|
465
|
-
node "$CODEX_HOOK" PostToolUse dev
|
|
469
|
+
node "$CODEX_HOOK" PostToolUse dev <"$CODEX_PAYLOAD_FILE")"
|
|
466
470
|
if [[ -z "$F2_OUT" ]]; then
|
|
467
471
|
_pass "codex-telemetry-hook.js no-conflict PostToolUse output is byte-stable (empty stdout, unchanged) vs the pre-existing fixed shape (AC4)"
|
|
468
472
|
else
|
|
@@ -472,7 +476,7 @@ fi
|
|
|
472
476
|
F3_ROOT="$(new_scratch)"
|
|
473
477
|
seed_solo_fixture "$F3_ROOT" "f3-subj" "agent-hb"
|
|
474
478
|
F3_OUT="$(cd "$F3_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
|
|
475
|
-
node "$OPENCODE_HOOK" 'tool.execute.after' dev
|
|
479
|
+
node "$OPENCODE_HOOK" 'tool.execute.after' dev <"$OPENCODE_PAYLOAD_FILE" 2>"$TMPDIR_EVAL/f3.err")"
|
|
476
480
|
F3_ERR="$(cat "$TMPDIR_EVAL/f3.err")"
|
|
477
481
|
if [[ -z "$F3_OUT" ]] && [[ "$F3_ERR" != *"liveness conflict"* ]]; then
|
|
478
482
|
_pass "opencode-telemetry-hook.js no-conflict output is byte-stable (empty stdout, no diagnostic) vs the pre-existing fixed shape (AC4)"
|
|
@@ -483,7 +487,7 @@ fi
|
|
|
483
487
|
F4_ROOT="$(new_scratch)"
|
|
484
488
|
seed_solo_fixture "$F4_ROOT" "f4-subj" "agent-hb"
|
|
485
489
|
F4_OUT="$(cd "$F4_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
|
|
486
|
-
node "$PI_HOOK" 'tool_result' dev
|
|
490
|
+
node "$PI_HOOK" 'tool_result' dev <"$PI_PAYLOAD_FILE" 2>"$TMPDIR_EVAL/f4.err")"
|
|
487
491
|
F4_ERR="$(cat "$TMPDIR_EVAL/f4.err")"
|
|
488
492
|
if [[ -z "$F4_OUT" ]] && [[ "$F4_ERR" != *"liveness conflict"* ]]; then
|
|
489
493
|
_pass "pi-telemetry-hook.js no-conflict output is byte-stable (empty stdout, no diagnostic) vs the pre-existing fixed shape (AC4)"
|
|
@@ -498,7 +502,7 @@ G_ROOT="$(new_scratch)"
|
|
|
498
502
|
mkdir -p "$G_ROOT/.kontourai/flow-agents/liveness"
|
|
499
503
|
printf '{not valid json' >"$G_ROOT/.kontourai/flow-agents/$SNAPSHOT_FILENAME"
|
|
500
504
|
G_OUT="$(cd "$G_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
|
|
501
|
-
node "$CLAUDE_HOOK" PostToolUse dev
|
|
505
|
+
node "$CLAUDE_HOOK" PostToolUse dev <"$POST_TOOL_USE_PAYLOAD_FILE" 2>"$TMPDIR_EVAL/g.err")"
|
|
502
506
|
G_STATUS=$?
|
|
503
507
|
if [[ "$G_STATUS" -eq 0 ]] && is_valid_json "$G_OUT" && [[ "$(stream_line_count "$G_ROOT")" -eq 0 ]]; then
|
|
504
508
|
_pass "claude-telemetry-hook.js fails open on a corrupted sidecar snapshot during the conflict check: exit 0, normal JSON contract, no heartbeat fabricated (AC8)"
|
|
@@ -546,7 +550,7 @@ H1_ROOT="$(new_scratch)"
|
|
|
546
550
|
seed_claim "$H1_ROOT" "h1-subj" "agent-hb" "$(iso_offset_ms -300000)" 1800
|
|
547
551
|
append_hostile_claim "$H1_ROOT" "h1-subj" "$HOSTILE_ACTOR" "$(iso_offset_ms -10000)" 1800
|
|
548
552
|
H1_OUT="$(cd "$H1_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
|
|
549
|
-
node "$CLAUDE_HOOK" PostToolUse dev
|
|
553
|
+
node "$CLAUDE_HOOK" PostToolUse dev <"$POST_TOOL_USE_PAYLOAD_FILE")"
|
|
550
554
|
H1_STATUS=$?
|
|
551
555
|
H1_CONTEXT="$(field_of "$H1_OUT" hookSpecificOutput.additionalContext)"
|
|
552
556
|
if [[ "$H1_STATUS" -eq 0 ]] && is_valid_json "$H1_OUT" \
|
|
@@ -564,7 +568,7 @@ H2_ROOT="$(new_scratch)"
|
|
|
564
568
|
seed_claim "$H2_ROOT" "h2-subj" "agent-hb" "$(iso_offset_ms -300000)" 1800
|
|
565
569
|
append_hostile_claim "$H2_ROOT" "h2-subj" "$HOSTILE_ACTOR" "$(iso_offset_ms -10000)" 1800
|
|
566
570
|
H2_OUT="$(cd "$H2_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
|
|
567
|
-
node "$OPENCODE_HOOK" 'tool.execute.after' dev
|
|
571
|
+
node "$OPENCODE_HOOK" 'tool.execute.after' dev <"$OPENCODE_PAYLOAD_FILE" 2>"$TMPDIR_EVAL/h2.err")"
|
|
568
572
|
H2_STATUS=$?
|
|
569
573
|
H2_ERR="$(cat "$TMPDIR_EVAL/h2.err")"
|
|
570
574
|
if [[ "$H2_STATUS" -eq 0 ]] && [[ -z "$H2_OUT" ]] \
|