@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,228 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_telemetry_usage_pipeline.sh — Layer 2: hermetic Stop-hook usage pipeline
|
|
3
|
+
#
|
|
4
|
+
# Proves the full telemetry.sh Stop path (add_stop_data_and_emit_usage) yields
|
|
5
|
+
# a session.usage event with real tokens, a concrete (non-"unknown") model,
|
|
6
|
+
# and a non-null estimated_cost_usd when a runtime transcript is supplied —
|
|
7
|
+
# and that tokens still survive (with estimated_cost_usd null) when the
|
|
8
|
+
# pricing registry is forced unavailable. Also guards the kiro-cli
|
|
9
|
+
# non-regression case: with no transcript, model still resolves via the
|
|
10
|
+
# existing usage_get_model() kiro lookup (unaffected by this fix).
|
|
11
|
+
#
|
|
12
|
+
# Uses the same TELEMETRY_DIR resolution convention as test_telemetry.sh
|
|
13
|
+
# (prefers context/scripts/telemetry when present) so this exercises the same
|
|
14
|
+
# copy CI actually runs, while explicitly pointing TELEMETRY_PRICING_FILE at
|
|
15
|
+
# the canonical bundled registry so pricing resolves regardless of which copy
|
|
16
|
+
# is under test (context/scripts/telemetry ships no bundled pricing.json).
|
|
17
|
+
set -uo pipefail
|
|
18
|
+
|
|
19
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
20
|
+
if [[ -d "$ROOT_DIR/context/scripts/telemetry" ]]; then
|
|
21
|
+
TELEMETRY_DIR="$ROOT_DIR/context/scripts/telemetry"
|
|
22
|
+
else
|
|
23
|
+
TELEMETRY_DIR="$HOME/.flow-agents/context/scripts/telemetry"
|
|
24
|
+
fi
|
|
25
|
+
TELEMETRY_SH="${TELEMETRY_DIR}/telemetry.sh"
|
|
26
|
+
PRICING_FILE="$ROOT_DIR/scripts/telemetry/pricing.json"
|
|
27
|
+
FIXTURE_TRANSCRIPT="$ROOT_DIR/evals/fixtures/telemetry/usage-transcript-sample.jsonl"
|
|
28
|
+
|
|
29
|
+
TMPDIR_EVAL=$(mktemp -d /tmp/eval-telemetry-usage-pipeline.XXXXXX)
|
|
30
|
+
TMPLOG="${TMPDIR_EVAL}/test-output.jsonl"
|
|
31
|
+
FAKE_HOME="${TMPDIR_EVAL}/home"
|
|
32
|
+
mkdir -p "$FAKE_HOME" "$TMPDIR_EVAL/sessions"
|
|
33
|
+
|
|
34
|
+
pass=0; fail=0
|
|
35
|
+
_pass() { echo " ✓ $1"; pass=$((pass + 1)); }
|
|
36
|
+
_fail() { echo " ✗ $1"; fail=$((fail + 1)); }
|
|
37
|
+
|
|
38
|
+
echo "=== Layer 2: Telemetry Usage Pipeline (hermetic fixture) ==="
|
|
39
|
+
echo ""
|
|
40
|
+
|
|
41
|
+
if [[ ! -f "$TELEMETRY_SH" ]]; then
|
|
42
|
+
_fail "telemetry.sh not found at $TELEMETRY_SH"
|
|
43
|
+
echo "Cannot continue without telemetry script"
|
|
44
|
+
rm -rf "$TMPDIR_EVAL"
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
if [[ ! -f "$FIXTURE_TRANSCRIPT" ]]; then
|
|
48
|
+
_fail "fixture transcript not found at $FIXTURE_TRANSCRIPT"
|
|
49
|
+
rm -rf "$TMPDIR_EVAL"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Wait for a new line to land in TMPLOG (telemetry.sh's Stop path emits
|
|
54
|
+
# asynchronously even in foreground mode's background-adjacent callers).
|
|
55
|
+
_wait_for_line() {
|
|
56
|
+
local before_lines="$1" i=0 current_lines
|
|
57
|
+
while [[ $i -lt 50 ]]; do
|
|
58
|
+
current_lines=$(wc -l < "$TMPLOG" 2>/dev/null | tr -d ' ')
|
|
59
|
+
[[ "${current_lines:-0}" -gt "$before_lines" ]] && break
|
|
60
|
+
sleep 0.1; i=$((i + 1))
|
|
61
|
+
done
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_wait_for_file_line() {
|
|
65
|
+
local file="$1" i=0 current_lines
|
|
66
|
+
while [[ $i -lt 50 ]]; do
|
|
67
|
+
current_lines=$(wc -l < "$file" 2>/dev/null | tr -d ' ')
|
|
68
|
+
[[ "${current_lines:-0}" -gt 0 ]] && break
|
|
69
|
+
sleep 0.1; i=$((i + 1))
|
|
70
|
+
done
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Run a real Stop event against a freshly-established session (agentSpawn
|
|
74
|
+
# first, matching real usage — Claude Code always sends SessionStart before
|
|
75
|
+
# Stop). Returns the emitted session.usage event (jq-compact, one line).
|
|
76
|
+
_run_stop() {
|
|
77
|
+
local input="$1"; shift
|
|
78
|
+
local extra_env_count="$#"
|
|
79
|
+
local extra_env=("$@")
|
|
80
|
+
local common_env=(
|
|
81
|
+
HOME="$FAKE_HOME"
|
|
82
|
+
TELEMETRY_ENABLED=true
|
|
83
|
+
TELEMETRY_CHANNELS=full
|
|
84
|
+
TELEMETRY_CHANNEL_FULL_LOG_FILE="$TMPLOG"
|
|
85
|
+
FLOW_AGENTS_TELEMETRY_FOREGROUND=true
|
|
86
|
+
TELEMETRY_CONFIG_FILE="$TMPDIR_EVAL/telemetry.conf"
|
|
87
|
+
TELEMETRY_DATA_DIR="$TMPDIR_EVAL"
|
|
88
|
+
TELEMETRY_SESSION_DIR="$TMPDIR_EVAL/sessions"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
local before_lines
|
|
92
|
+
touch "$TMPLOG"
|
|
93
|
+
before_lines=$(wc -l < "$TMPLOG" | tr -d ' ')
|
|
94
|
+
echo '{"cwd":"/tmp"}' | env "${common_env[@]}" bash "$TELEMETRY_SH" agentSpawn dev >/dev/null 2>&1
|
|
95
|
+
_wait_for_line "$before_lines"
|
|
96
|
+
|
|
97
|
+
before_lines=$(wc -l < "$TMPLOG" | tr -d ' ')
|
|
98
|
+
if [[ "$extra_env_count" -gt 0 ]]; then
|
|
99
|
+
echo "$input" | env "${common_env[@]}" TELEMETRY_USAGE_TRACKING=true "${extra_env[@]}" \
|
|
100
|
+
bash "$TELEMETRY_SH" Stop dev 2>/dev/null
|
|
101
|
+
else
|
|
102
|
+
echo "$input" | env "${common_env[@]}" TELEMETRY_USAGE_TRACKING=true \
|
|
103
|
+
bash "$TELEMETRY_SH" Stop dev 2>/dev/null
|
|
104
|
+
fi
|
|
105
|
+
_wait_for_line "$before_lines"
|
|
106
|
+
|
|
107
|
+
tail -n +"$((before_lines + 1))" "$TMPLOG" 2>/dev/null | jq -c 'select(.event_type=="session.usage")' | tail -1
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# --- 1. With transcript + pricing available: real tokens, cost, concrete model
|
|
111
|
+
echo "--- Fixture transcript, pricing available ---"
|
|
112
|
+
input1=$(jq -nc --arg tp "$FIXTURE_TRANSCRIPT" '{session_id:"pipeline-1",transcript_path:$tp,hook_event_name:"Stop"}')
|
|
113
|
+
out1=$(_run_stop "$input1" TELEMETRY_PRICING_FILE="$PRICING_FILE")
|
|
114
|
+
|
|
115
|
+
if [[ -n "$out1" ]]; then
|
|
116
|
+
model=$(echo "$out1" | jq -r '.usage.model')
|
|
117
|
+
it=$(echo "$out1" | jq -r '.usage.input_tokens')
|
|
118
|
+
ot=$(echo "$out1" | jq -r '.usage.output_tokens')
|
|
119
|
+
cost=$(echo "$out1" | jq -r '.usage.estimated_cost_usd')
|
|
120
|
+
by_model_len=$(echo "$out1" | jq -r '.usage.by_model | length')
|
|
121
|
+
|
|
122
|
+
[[ "$model" != "unknown" && "$model" != "null" && -n "$model" ]] && _pass "model is concrete (got: $model)" || _fail "model should not be unknown (got: $model)"
|
|
123
|
+
[[ "$model" == "claude-opus-4-8" ]] && _pass "model is the dominant-by-tokens model (claude-opus-4-8)" || _fail "expected dominant model claude-opus-4-8, got $model"
|
|
124
|
+
[[ "$it" != "null" && "$it" -gt 0 ]] && _pass "input_tokens is real and non-null (got: $it)" || _fail "input_tokens should be non-null/positive (got: $it)"
|
|
125
|
+
[[ "$ot" != "null" && "$ot" -gt 0 ]] && _pass "output_tokens is real and non-null (got: $ot)" || _fail "output_tokens should be non-null/positive (got: $ot)"
|
|
126
|
+
cost_positive=$(echo "$out1" | jq -r '(.usage.estimated_cost_usd // 0) > 0')
|
|
127
|
+
[[ "$cost" != "null" && "$cost_positive" == "true" ]] && _pass "estimated_cost_usd is real and non-null (got: $cost)" || _fail "estimated_cost_usd should be non-null/positive (got: $cost)"
|
|
128
|
+
[[ "$by_model_len" == "2" ]] && _pass "by_model has 2 entries" || _fail "expected 2 by_model entries, got $by_model_len"
|
|
129
|
+
else
|
|
130
|
+
_fail "no session.usage event emitted for fixture transcript"
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# --- 1b. Economics attribution: canonical current pointer beats stale legacy
|
|
134
|
+
echo ""
|
|
135
|
+
echo "--- Economics attribution uses canonical current pointer ---"
|
|
136
|
+
ATTR_CWD="${TMPDIR_EVAL}/workspace"
|
|
137
|
+
CANON_SLUG="canonical-task"
|
|
138
|
+
LEGACY_SLUG="legacy-task"
|
|
139
|
+
ECON_LOG="${TMPDIR_EVAL}/economics.jsonl"
|
|
140
|
+
mkdir -p "$ATTR_CWD/.kontourai/flow-agents/$CANON_SLUG" "$ATTR_CWD/.flow-agents/$LEGACY_SLUG"
|
|
141
|
+
: > "$ECON_LOG"
|
|
142
|
+
printf '%s\n' "{\"active_slug\":\"$CANON_SLUG\"}" > "$ATTR_CWD/.kontourai/flow-agents/current.json"
|
|
143
|
+
printf '%s\n' "{\"active_slug\":\"$LEGACY_SLUG\"}" > "$ATTR_CWD/.flow-agents/current.json"
|
|
144
|
+
printf '%s\n' '{"schema_version":"1.0","task_slug":"canonical-task","phase":"execution","verification_verdict":"PASS"}' > "$ATTR_CWD/.kontourai/flow-agents/$CANON_SLUG/state.json"
|
|
145
|
+
printf '%s\n' '{"schema_version":"1.0","task_slug":"legacy-task","phase":"execution","verification_verdict":"PASS"}' > "$ATTR_CWD/.flow-agents/$LEGACY_SLUG/state.json"
|
|
146
|
+
input_attr=$(jq -nc --arg tp "$FIXTURE_TRANSCRIPT" --arg cwd "$ATTR_CWD" '{session_id:"pipeline-attribution",transcript_path:$tp,hook_event_name:"Stop",cwd:$cwd}')
|
|
147
|
+
TELEMETRY_SH_SAVED="$TELEMETRY_SH"
|
|
148
|
+
TELEMETRY_SH="$ROOT_DIR/scripts/telemetry/telemetry.sh"
|
|
149
|
+
out_attr=$(_run_stop "$input_attr" TELEMETRY_PRICING_FILE="$PRICING_FILE" TELEMETRY_ECONOMICS_LOG_FILE="$ECON_LOG")
|
|
150
|
+
TELEMETRY_SH="$TELEMETRY_SH_SAVED"
|
|
151
|
+
_wait_for_file_line "$ECON_LOG"
|
|
152
|
+
|
|
153
|
+
if [[ -n "$out_attr" && -s "$ECON_LOG" ]]; then
|
|
154
|
+
attr_task=$(tail -1 "$ECON_LOG" | jq -r '.task_slug')
|
|
155
|
+
attr_phase=$(tail -1 "$ECON_LOG" | jq -r '.phases[0].phase')
|
|
156
|
+
[[ "$attr_task" == "$CANON_SLUG" ]] && _pass "economics record task_slug comes from canonical .kontourai current pointer" || _fail "expected canonical task_slug '$CANON_SLUG', got '$attr_task'"
|
|
157
|
+
[[ "$attr_phase" == "execution" ]] && _pass "economics record phase comes from canonical state.json" || _fail "expected canonical phase execution, got '$attr_phase'"
|
|
158
|
+
else
|
|
159
|
+
_fail "economics attribution record was not emitted for canonical-current test"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# --- 2. Pricing forced unavailable: tokens survive, cost is null ------------
|
|
163
|
+
echo ""
|
|
164
|
+
echo "--- Fixture transcript, pricing forced unavailable ---"
|
|
165
|
+
input2=$(jq -nc --arg tp "$FIXTURE_TRANSCRIPT" '{session_id:"pipeline-2",transcript_path:$tp,hook_event_name:"Stop"}')
|
|
166
|
+
out2=$(_run_stop "$input2" TELEMETRY_PRICING_FILE=/nonexistent/pricing.json TELEMETRY_PRICING_URL="" FLOW_AGENTS_PRICING_FILE="" FLOW_AGENTS_PRICING_URL="")
|
|
167
|
+
|
|
168
|
+
if [[ -n "$out2" ]]; then
|
|
169
|
+
it2=$(echo "$out2" | jq -r '.usage.input_tokens')
|
|
170
|
+
ot2=$(echo "$out2" | jq -r '.usage.output_tokens')
|
|
171
|
+
cost2=$(echo "$out2" | jq -r '.usage.estimated_cost_usd')
|
|
172
|
+
pv2=$(echo "$out2" | jq -r '.usage.pricing_version')
|
|
173
|
+
model2=$(echo "$out2" | jq -r '.usage.model')
|
|
174
|
+
|
|
175
|
+
[[ "$it2" != "null" && "$it2" -gt 0 ]] && _pass "tokens survive when pricing unavailable (input_tokens=$it2)" || _fail "tokens should survive when pricing unavailable (got input_tokens=$it2)"
|
|
176
|
+
[[ "$ot2" != "null" && "$ot2" -gt 0 ]] && _pass "output_tokens survive when pricing unavailable (got: $ot2)" || _fail "output_tokens should survive when pricing unavailable (got: $ot2)"
|
|
177
|
+
[[ "$cost2" == "null" ]] && _pass "estimated_cost_usd is null when pricing unavailable (cost degrades, tokens don't)" || _fail "estimated_cost_usd should be null when pricing unavailable (got: $cost2)"
|
|
178
|
+
[[ "$pv2" == "null" ]] && _pass "pricing_version is null when pricing unavailable" || _fail "pricing_version should be null when pricing unavailable (got: $pv2)"
|
|
179
|
+
[[ "$model2" == "claude-opus-4-8" ]] && _pass "model still resolves from transcript when pricing unavailable" || _fail "expected model claude-opus-4-8, got $model2"
|
|
180
|
+
else
|
|
181
|
+
_fail "no session.usage event emitted when pricing forced unavailable"
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# --- 3. No transcript (kiro-cli style): model still resolves via kiro fallback, no regression
|
|
185
|
+
echo ""
|
|
186
|
+
echo "--- No transcript (kiro-cli non-regression) ---"
|
|
187
|
+
input3='{"session_id":"pipeline-3","hook_event_name":"Stop"}'
|
|
188
|
+
out3=$(_run_stop "$input3")
|
|
189
|
+
|
|
190
|
+
if [[ -n "$out3" ]]; then
|
|
191
|
+
model3=$(echo "$out3" | jq -r '.usage.model')
|
|
192
|
+
it3=$(echo "$out3" | jq -r '.usage.input_tokens')
|
|
193
|
+
by_model3=$(echo "$out3" | jq -r '.usage.by_model')
|
|
194
|
+
|
|
195
|
+
# FAKE_HOME has no ~/.kiro/agents spec, so usage_get_model's kiro lookup
|
|
196
|
+
# falls through to "unknown" — this is the pre-existing, unfixed kiro path
|
|
197
|
+
# and must be untouched by the transcript-model override (transcript_usage
|
|
198
|
+
# is null here, so model is never overridden).
|
|
199
|
+
[[ "$model3" == "unknown" ]] && _pass "no-transcript path still resolves via usage_get_model kiro fallback (unknown, no regression)" || _fail "expected kiro fallback 'unknown' with no transcript, got $model3"
|
|
200
|
+
[[ "$it3" == "null" ]] && _pass "input_tokens is null with no transcript (expected)" || _fail "expected null input_tokens with no transcript, got $it3"
|
|
201
|
+
[[ "$by_model3" == "null" ]] && _pass "by_model is null with no transcript (expected)" || _fail "expected null by_model with no transcript, got $by_model3"
|
|
202
|
+
else
|
|
203
|
+
_fail "no session.usage event emitted for no-transcript case"
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# --- 4. Empty transcript + TELEMETRY_USAGE_DEBUG=1: debug reason is emitted -
|
|
207
|
+
echo ""
|
|
208
|
+
echo "--- Empty transcript, TELEMETRY_USAGE_DEBUG=1 (debug path) ---"
|
|
209
|
+
EMPTY_TRANSCRIPT="${TMPDIR_EVAL}/empty-transcript.jsonl"
|
|
210
|
+
: > "$EMPTY_TRANSCRIPT"
|
|
211
|
+
DEBUG_DRIFT_LOG="${TMPDIR_EVAL}/debug-drift.log"
|
|
212
|
+
input4=$(jq -nc --arg tp "$EMPTY_TRANSCRIPT" '{session_id:"pipeline-4",transcript_path:$tp,hook_event_name:"Stop"}')
|
|
213
|
+
out4=$(_run_stop "$input4" TELEMETRY_USAGE_DEBUG=1 TELEMETRY_DRIFT_LOG="$DEBUG_DRIFT_LOG")
|
|
214
|
+
|
|
215
|
+
if grep -q '\[telemetry\] usage_parse_transcript:' "$DEBUG_DRIFT_LOG" 2>/dev/null; then
|
|
216
|
+
_pass "debug reason line emitted for empty-transcript no-usage scenario"
|
|
217
|
+
else
|
|
218
|
+
_fail "expected a usage_parse_transcript debug reason line in $DEBUG_DRIFT_LOG"
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
model4=$(echo "$out4" | jq -r '.usage.model // "unknown"' 2>/dev/null)
|
|
222
|
+
[[ -z "$out4" || "$model4" == "unknown" ]] && _pass "empty-transcript path emits no real usage (no regression)" || _fail "expected no/unknown usage for empty transcript, got model=$model4"
|
|
223
|
+
|
|
224
|
+
rm -rf "$TMPDIR_EVAL"
|
|
225
|
+
|
|
226
|
+
echo ""
|
|
227
|
+
echo "Telemetry usage pipeline: $pass passed, $fail failed"
|
|
228
|
+
[ "$fail" -eq 0 ]
|
|
@@ -9,21 +9,23 @@
|
|
|
9
9
|
#
|
|
10
10
|
# 1. no-label-bypass (finding 1) → not-run: a test_output claim with no
|
|
11
11
|
# manifest-matched execution.label is a divergence, never session-local.
|
|
12
|
-
# 2.
|
|
13
|
-
#
|
|
12
|
+
# 2. mcp-degrade (issue 492) → not-run: an MCP-shaped execution.label is not a
|
|
13
|
+
# manifest command and can never be RECONCILED as a CI-verified command check.
|
|
14
|
+
# 3. skip-assumed-bypass (finding 2) → unwaived-assumed: 'assumed' alone is not a pass.
|
|
15
|
+
# 4. status-misassertion (finding 3) → status-misassertion: CI re-derives status; a
|
|
14
16
|
# self-reported status that does not match the bundle's own evidence is rejected.
|
|
15
|
-
#
|
|
17
|
+
# 5. waived-command-check (finding 4) → waiver-on-command-check: a command-backed
|
|
16
18
|
# (test_output) check cannot be waived.
|
|
17
|
-
#
|
|
19
|
+
# 6. ws3-old-style-bundle (AC6) → old all-test_output bundle FAILS the same way (exit 1,
|
|
18
20
|
# divergences) under the new reconciler as under the old one — no soundness regression.
|
|
19
|
-
#
|
|
21
|
+
# 7. fabricated-attestation (iteration-4, converged iteration-3 finding, both gates) → a
|
|
20
22
|
# fully self-consistent, hand-fabricated no-command 'security' claim+evidence+event
|
|
21
23
|
# triple (indistinguishable from a genuine attestation at the reconciler's own
|
|
22
24
|
# re-derivation layer) MUST still pass (exit 0 — blocking attestations at L0 would break
|
|
23
25
|
# every honest human-attestation use) BUT MUST be loudly, distinctly marked
|
|
24
26
|
# 'ATTESTED (not independently verifiable at L0)' plus the summary count line — never a
|
|
25
27
|
# quiet SESSION-LOCAL OK indistinguishable from a reconciled check. See ADR 0020 Residuals.
|
|
26
|
-
#
|
|
28
|
+
# 8. delivery/DECLARED marker regressions (ADR 0022 §1/§2, section 7 below) — bundle-absent
|
|
27
29
|
# fail-closed default, malformed/empty-array/missing-field diagnostics, and all four
|
|
28
30
|
# matchesScope() forms (ref:, commit: single + range, author:, branch-prefix:) including
|
|
29
31
|
# positive AND near-miss coverage, plus the compound (space-separated AND) scope form a
|
|
@@ -49,10 +51,12 @@ errors=0
|
|
|
49
51
|
_pass() { echo " PASS: $1"; }
|
|
50
52
|
_fail() { echo " FAIL: $1"; errors=$((errors + 1)); }
|
|
51
53
|
|
|
52
|
-
# run_case <label> <bundle> <needle>
|
|
53
|
-
# Asserts: reconciler exits non-zero
|
|
54
|
+
# run_case <label> <bundle> <needle> [forbidden]
|
|
55
|
+
# Asserts: reconciler exits non-zero, stdout/stderr contains <needle>, and optionally does
|
|
56
|
+
# NOT contain <forbidden>.
|
|
54
57
|
run_case() {
|
|
55
58
|
local label="$1" bundle="$2" needle="$3"
|
|
59
|
+
local forbidden="${4:-}"
|
|
56
60
|
echo "=== $label ==="
|
|
57
61
|
if [[ ! -f "$bundle" ]]; then _fail "$label: fixture not found at $bundle"; return; fi
|
|
58
62
|
local out code
|
|
@@ -69,25 +73,38 @@ run_case() {
|
|
|
69
73
|
else
|
|
70
74
|
_fail "$label: expected divergence \"$needle\" not found — output: $out"
|
|
71
75
|
fi
|
|
76
|
+
if [[ -n "$forbidden" ]]; then
|
|
77
|
+
if echo "$out" | grep -qF "$forbidden"; then
|
|
78
|
+
_fail "$label: must NOT emit \"$forbidden\" — output: $out"
|
|
79
|
+
else
|
|
80
|
+
_pass "$label: does not emit \"$forbidden\""
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
72
83
|
}
|
|
73
84
|
|
|
74
85
|
# 1. Reviewer's no-label exploit → not-run (test_output must reconcile against the manifest).
|
|
75
86
|
run_case "no-label-bypass (finding 1)" "$FX/no-label-bypass.json" \
|
|
76
87
|
"no manifest-matched execution.label"
|
|
77
88
|
|
|
78
|
-
# 2.
|
|
89
|
+
# 2. Issue #492: MCP-shaped self-reported pass → not-run (not manifest-matched), never
|
|
90
|
+
# RECONCILED as a CI-verified command check.
|
|
91
|
+
run_case "mcp-degrade (issue 492)" "$FX/mcp-degrade.json" \
|
|
92
|
+
"command is not in the reconcile manifest" \
|
|
93
|
+
"RECONCILED"
|
|
94
|
+
|
|
95
|
+
# 3. Verifier's skip->assumed exploit → unwaived-assumed.
|
|
79
96
|
run_case "skip-assumed-bypass (finding 2)" "$FX/skip-assumed-bypass.json" \
|
|
80
97
|
"[unwaived-assumed]"
|
|
81
98
|
|
|
82
|
-
#
|
|
99
|
+
# 4. Status-misassertion exploit → status re-derived CI-side; asserted != derived.
|
|
83
100
|
run_case "status-misassertion (finding 3)" "$FX/status-misassertion.json" \
|
|
84
101
|
"[status-misassertion]"
|
|
85
102
|
|
|
86
|
-
#
|
|
103
|
+
# 5. Waived command-backed check → waiver-on-command-check.
|
|
87
104
|
run_case "waived-command-check (finding 4)" "$FX/waived-command-check.json" \
|
|
88
105
|
"[waiver-on-command-check]"
|
|
89
106
|
|
|
90
|
-
#
|
|
107
|
+
# 6. AC6 backward-compat regression: the real ws3-kit-dependencies-namespacing/trust.bundle
|
|
91
108
|
# (an old-style all-test_output bundle whose commands are not manifest-matched) FAILS the
|
|
92
109
|
# same way under the new reconciler as under the old one — same FAIL verdict, divergences
|
|
93
110
|
# present, no silent pass introduced.
|
|
@@ -115,7 +132,7 @@ else
|
|
|
115
132
|
fi
|
|
116
133
|
fi
|
|
117
134
|
|
|
118
|
-
#
|
|
135
|
+
# 7. Fabricated-attestation (iteration-4): passes (exit 0) but MUST carry the loud ATTESTED
|
|
119
136
|
# marker and the summary count line — NOT the old quiet SESSION-LOCAL OK. This is a
|
|
120
137
|
# visibility assertion, not a divergence assertion (unlike run_case above): a fabricated
|
|
121
138
|
# self-consistent attestation bundle is, by construction, indistinguishable from a genuine
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_trust_reconcile_trailer_diagnostic.sh — Integration eval for scripts/ci/
|
|
3
|
+
# trust-reconcile.js's runtime-session commit-trailer diagnostic (issue #305, ADR 0022 §1).
|
|
4
|
+
#
|
|
5
|
+
# Proves the diagnostic added in findRuntimeSessionTrailers()/logRuntimeSessionTrailers()
|
|
6
|
+
# is audit-only:
|
|
7
|
+
# AC5 trailer-diagnostic-positive: a commit with a Claude-Session: trailer in the
|
|
8
|
+
# resolved range produces the exact line
|
|
9
|
+
# `[trust-reconcile] identified: runtime-session trailer 'Claude-Session' on <ref>`
|
|
10
|
+
# — the trailer KEY only, never the VALUE (session URL) — printed for the range's
|
|
11
|
+
# commit-with-trailer.
|
|
12
|
+
# AC6 trailer-diagnostic-negative: a commit range with no runtime-session trailer
|
|
13
|
+
# produces NO such diagnostic line, and the reconciler's exit code is IDENTICAL
|
|
14
|
+
# to the positive case (same canonical command, same bundle/DECLARED absence,
|
|
15
|
+
# differing only in trailer presence) — the diagnostic never changes the exit path.
|
|
16
|
+
# AC7 trailer-diagnostic-checkout-depth-verified: .github/workflows/trust-reconcile.yml
|
|
17
|
+
# already carries fetch-depth: 0 (no .yml edit needed for #305's depth ask).
|
|
18
|
+
# Base-ref-range vs narrower-fallback: both range-resolution paths
|
|
19
|
+
# (TRUST_RECONCILE_BASE_REF/GITHUB_BASE_REF resolvable vs unresolvable, falling back
|
|
20
|
+
# to just the head commit) are exercised directly against the exported
|
|
21
|
+
# findRuntimeSessionTrailers() helper (unit-style, no reconciler wrapper needed for
|
|
22
|
+
# this sub-case) as well as through the full reconciler CLI (integration-style).
|
|
23
|
+
#
|
|
24
|
+
# Eval-authoring hazards observed (per plan/goal constraints):
|
|
25
|
+
# - No self-recursive harness invocation: this eval shells out to trust-reconcile.js and
|
|
26
|
+
# to git only; it never invokes evals/run.sh or any other eval-suite entry point.
|
|
27
|
+
# - No legacy/deprecated-path literals: trailer fixture values are synthetic/generic
|
|
28
|
+
# (a https://claude.ai/code/session_test-fixture-only placeholder), never a copy of a
|
|
29
|
+
# real committed session URL or a legacy flat delivery/trust.bundle-style literal.
|
|
30
|
+
#
|
|
31
|
+
# Deterministic, no model spend, self-cleaning.
|
|
32
|
+
# Usage: bash evals/integration/test_trust_reconcile_trailer_diagnostic.sh
|
|
33
|
+
|
|
34
|
+
set -uo pipefail
|
|
35
|
+
|
|
36
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
37
|
+
RECONCILE="$ROOT/scripts/ci/trust-reconcile.js"
|
|
38
|
+
|
|
39
|
+
errors=0
|
|
40
|
+
TMP_DIR="$(mktemp -d)"
|
|
41
|
+
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
42
|
+
pass() { echo " ✓ $1"; }
|
|
43
|
+
fail() { echo " ✗ $1"; errors=$((errors + 1)); }
|
|
44
|
+
|
|
45
|
+
echo "=== trust-reconcile.js runtime-session trailer diagnostic — integration eval ==="
|
|
46
|
+
|
|
47
|
+
# Synthetic-only trailer value — never a real/legacy committed session URL.
|
|
48
|
+
TRAILER_VALUE="https://claude.ai/code/session_test-fixture-only"
|
|
49
|
+
RECONCILE_CMD="node -e 'process.exit(0)'"
|
|
50
|
+
|
|
51
|
+
# ─── Fixture: a real scratch git repo with two commits (base, then head) ─────────
|
|
52
|
+
build_repo() {
|
|
53
|
+
local repo_dir="$1"
|
|
54
|
+
local with_trailer="$2" # "true" or "false"
|
|
55
|
+
mkdir -p "$repo_dir"
|
|
56
|
+
git init -q "$repo_dir"
|
|
57
|
+
git -C "$repo_dir" config user.email "eval@local"
|
|
58
|
+
git -C "$repo_dir" config user.name "eval"
|
|
59
|
+
echo "base" > "$repo_dir/f.txt"
|
|
60
|
+
git -C "$repo_dir" add -A
|
|
61
|
+
git -C "$repo_dir" commit -qm "base commit"
|
|
62
|
+
echo "change" >> "$repo_dir/f.txt"
|
|
63
|
+
git -C "$repo_dir" add -A
|
|
64
|
+
if [[ "$with_trailer" == "true" ]]; then
|
|
65
|
+
git -C "$repo_dir" commit -qm "$(printf 'feat: trailer test commit\n\nClaude-Session: %s\n' "$TRAILER_VALUE")"
|
|
66
|
+
else
|
|
67
|
+
git -C "$repo_dir" commit -qm "feat: no-trailer test commit"
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
POS_REPO="$TMP_DIR/positive-repo"
|
|
72
|
+
NEG_REPO="$TMP_DIR/negative-repo"
|
|
73
|
+
build_repo "$POS_REPO" "true"
|
|
74
|
+
build_repo "$NEG_REPO" "false"
|
|
75
|
+
|
|
76
|
+
POS_BASE="$(git -C "$POS_REPO" rev-list --max-parents=0 HEAD)"
|
|
77
|
+
POS_HEAD="$(git -C "$POS_REPO" rev-parse HEAD)"
|
|
78
|
+
NEG_BASE="$(git -C "$NEG_REPO" rev-list --max-parents=0 HEAD)"
|
|
79
|
+
NEG_HEAD="$(git -C "$NEG_REPO" rev-parse HEAD)"
|
|
80
|
+
|
|
81
|
+
# A THIRD repo where the range-distinguishing case actually differs from POS_REPO/NEG_REPO:
|
|
82
|
+
# the trailer lives on a MIDDLE commit (between base and a later, trailer-free head commit)
|
|
83
|
+
# -- reachable ONLY via the ranged `git log <base>..<head>` walk, never via the narrower
|
|
84
|
+
# head-commit-only fallback. This is the genuine base-ref-range-vs-fallback distinguisher;
|
|
85
|
+
# POS_REPO/NEG_REPO above (trailer-or-not on the head commit itself) would pass under
|
|
86
|
+
# EITHER code path and do not by themselves prove the ranged branch is exercised.
|
|
87
|
+
RANGE_REPO="$TMP_DIR/range-only-repo"
|
|
88
|
+
mkdir -p "$RANGE_REPO"
|
|
89
|
+
git init -q "$RANGE_REPO"
|
|
90
|
+
git -C "$RANGE_REPO" config user.email "eval@local"
|
|
91
|
+
git -C "$RANGE_REPO" config user.name "eval"
|
|
92
|
+
echo "base" > "$RANGE_REPO/f.txt"
|
|
93
|
+
git -C "$RANGE_REPO" add -A
|
|
94
|
+
git -C "$RANGE_REPO" commit -qm "base commit"
|
|
95
|
+
RANGE_BASE="$(git -C "$RANGE_REPO" rev-parse HEAD)"
|
|
96
|
+
echo "middle" >> "$RANGE_REPO/f.txt"
|
|
97
|
+
git -C "$RANGE_REPO" add -A
|
|
98
|
+
git -C "$RANGE_REPO" commit -qm "$(printf 'feat: middle commit carries the trailer
|
|
99
|
+
|
|
100
|
+
Claude-Session: %s
|
|
101
|
+
' "$TRAILER_VALUE")"
|
|
102
|
+
echo "head" >> "$RANGE_REPO/f.txt"
|
|
103
|
+
git -C "$RANGE_REPO" add -A
|
|
104
|
+
git -C "$RANGE_REPO" commit -qm "feat: head commit itself has no trailer"
|
|
105
|
+
RANGE_HEAD="$(git -C "$RANGE_REPO" rev-parse HEAD)"
|
|
106
|
+
|
|
107
|
+
# --- AC5: positive case — exact diagnostic line, key only, no value leak --------
|
|
108
|
+
echo "--- AC5: positive — Claude-Session trailer present ---"
|
|
109
|
+
pos_out="$(TRUST_RECONCILE_BASE_REF="$POS_BASE" TRUST_RECONCILE_SHA="$POS_HEAD" TRUST_RECONCILE_REF="test-branch" \
|
|
110
|
+
TRUST_RECONCILE_COMMANDS="$RECONCILE_CMD" node "$RECONCILE" --repo-root "$POS_REPO" 2>&1)"
|
|
111
|
+
pos_code=$?
|
|
112
|
+
|
|
113
|
+
if echo "$pos_out" | grep -qF "[trust-reconcile] identified: runtime-session trailer 'Claude-Session' on test-branch"; then
|
|
114
|
+
pass "AC5: exact diagnostic line printed for the Claude-Session trailer"
|
|
115
|
+
else
|
|
116
|
+
fail "AC5: expected diagnostic line not found — output: $pos_out"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
if echo "$pos_out" | grep -qF "$TRAILER_VALUE"; then
|
|
120
|
+
fail "AC5: trailer VALUE ($TRAILER_VALUE) leaked into stdout — diagnostic must log the KEY only"
|
|
121
|
+
else
|
|
122
|
+
pass "AC5: trailer VALUE does not appear anywhere in stdout (key-only logging, per ADR 0022 §1's exact quoted format)"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# --- AC6: negative case — no trailer, no line, same canonical command/DECLARED state --
|
|
126
|
+
echo "--- AC6: negative — no runtime-session trailer present ---"
|
|
127
|
+
neg_out="$(TRUST_RECONCILE_BASE_REF="$NEG_BASE" TRUST_RECONCILE_SHA="$NEG_HEAD" TRUST_RECONCILE_REF="test-branch" \
|
|
128
|
+
TRUST_RECONCILE_COMMANDS="$RECONCILE_CMD" node "$RECONCILE" --repo-root "$NEG_REPO" 2>&1)"
|
|
129
|
+
neg_code=$?
|
|
130
|
+
|
|
131
|
+
if echo "$neg_out" | grep -q "identified: runtime-session trailer"; then
|
|
132
|
+
fail "AC6: an 'identified: runtime-session trailer' line was printed with no trailer present"
|
|
133
|
+
else
|
|
134
|
+
pass "AC6: no 'identified: runtime-session trailer' line printed (no trailer present)"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if [[ "$pos_code" == "$neg_code" ]]; then
|
|
138
|
+
pass "AC6: reconciler exit code is IDENTICAL between the trailer-present and no-trailer cases (both: $pos_code) — diagnostic never changes the exit path"
|
|
139
|
+
else
|
|
140
|
+
fail "AC6: reconciler exit code DIVERGED between trailer-present ($pos_code) and no-trailer ($neg_code) cases — the diagnostic must never affect the exit path"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# --- Base-ref-range vs narrower-fallback, exercised directly on the exported helper --
|
|
144
|
+
# RANGE_REPO's trailer lives on a MIDDLE commit, absent from the head commit itself — this
|
|
145
|
+
# genuinely distinguishes the two code paths (see RANGE_REPO fixture comment above).
|
|
146
|
+
echo "--- base-ref-range vs narrower-fallback (direct helper exercise, genuinely distinguishing fixture) ---"
|
|
147
|
+
range_result="$(TRUST_RECONCILE_BASE_REF="$RANGE_BASE" node -e "
|
|
148
|
+
const { findRuntimeSessionTrailers } = require('$RECONCILE');
|
|
149
|
+
const found = findRuntimeSessionTrailers('$RANGE_REPO', { sha: '$RANGE_HEAD', ref: 'test-branch' });
|
|
150
|
+
console.log(JSON.stringify(found));
|
|
151
|
+
" 2>&1)"
|
|
152
|
+
if echo "$range_result" | grep -q '"trailerName":"Claude-Session"'; then
|
|
153
|
+
pass "base-ref-range path: findRuntimeSessionTrailers finds a trailer on a MIDDLE commit (base..head range) that is absent from the head commit itself, when TRUST_RECONCILE_BASE_REF resolves"
|
|
154
|
+
else
|
|
155
|
+
fail "base-ref-range path: findRuntimeSessionTrailers did not find the middle-commit trailer via the ranged walk — output: $range_result"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# Narrower fallback: no base ref env var set at all (helper's own internal base-ref
|
|
159
|
+
# resolution reads process.env directly, so unset both env vars for this call) — against
|
|
160
|
+
# the SAME range-only repo, this must NOT find the middle-commit trailer (it is invisible
|
|
161
|
+
# to a head-commit-only scan), proving the fallback is genuinely narrower, not just an
|
|
162
|
+
# equivalent restatement of the ranged path.
|
|
163
|
+
fallback_result="$(env -u TRUST_RECONCILE_BASE_REF -u GITHUB_BASE_REF node -e "
|
|
164
|
+
const { findRuntimeSessionTrailers } = require('$RECONCILE');
|
|
165
|
+
const found = findRuntimeSessionTrailers('$RANGE_REPO', { sha: '$RANGE_HEAD', ref: 'test-branch' });
|
|
166
|
+
console.log(JSON.stringify(found));
|
|
167
|
+
" 2>&1)"
|
|
168
|
+
if [[ "$fallback_result" == "[]" ]]; then
|
|
169
|
+
pass "narrower-fallback path: findRuntimeSessionTrailers does NOT find the middle-commit-only trailer when no base ref is resolvable (head-commit-only scan is genuinely narrower)"
|
|
170
|
+
else
|
|
171
|
+
fail "narrower-fallback path: expected an empty array (middle-commit trailer invisible to head-only scan), got: $fallback_result"
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# Narrower fallback, positive: against POS_REPO (trailer ON the head commit itself), the
|
|
175
|
+
# no-base-ref fallback still finds it — proving the fallback is not simply "always empty".
|
|
176
|
+
fallback_pos_result="$(env -u TRUST_RECONCILE_BASE_REF -u GITHUB_BASE_REF node -e "
|
|
177
|
+
const { findRuntimeSessionTrailers } = require('$RECONCILE');
|
|
178
|
+
const found = findRuntimeSessionTrailers('$POS_REPO', { sha: '$POS_HEAD', ref: 'test-branch' });
|
|
179
|
+
console.log(JSON.stringify(found));
|
|
180
|
+
" 2>&1)"
|
|
181
|
+
if echo "$fallback_pos_result" | grep -q '"trailerName":"Claude-Session"'; then
|
|
182
|
+
pass "narrower-fallback path: still finds a trailer that IS on the head commit itself (fallback scans the head commit, not nothing)"
|
|
183
|
+
else
|
|
184
|
+
fail "narrower-fallback path: did not find the head-commit trailer with no base ref set — output: $fallback_pos_result"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
# Narrower-fallback negative: no base ref, no trailer on the head commit itself (NEG_REPO).
|
|
188
|
+
fallback_neg_result="$(env -u TRUST_RECONCILE_BASE_REF -u GITHUB_BASE_REF node -e "
|
|
189
|
+
const { findRuntimeSessionTrailers } = require('$RECONCILE');
|
|
190
|
+
const found = findRuntimeSessionTrailers('$NEG_REPO', { sha: '$NEG_HEAD', ref: 'test-branch' });
|
|
191
|
+
console.log(JSON.stringify(found));
|
|
192
|
+
" 2>&1)"
|
|
193
|
+
if [[ "$fallback_neg_result" == "[]" ]]; then
|
|
194
|
+
pass "narrower-fallback path: findRuntimeSessionTrailers returns an empty array when the head commit itself has no trailer and no base ref is resolvable"
|
|
195
|
+
else
|
|
196
|
+
fail "narrower-fallback path: expected an empty array, got: $fallback_neg_result"
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
# --- No-crash: unresolvable sha degrades to empty array, never throws -----------
|
|
200
|
+
degrade_result="$(node -e "
|
|
201
|
+
const { findRuntimeSessionTrailers } = require('$RECONCILE');
|
|
202
|
+
const found = findRuntimeSessionTrailers('$POS_REPO', { sha: '', ref: 'test-branch' });
|
|
203
|
+
console.log(JSON.stringify(found));
|
|
204
|
+
" 2>&1)"
|
|
205
|
+
if [[ "$degrade_result" == "[]" ]]; then
|
|
206
|
+
pass "empty sha degrades to an empty array (never throws, never crashes the caller)"
|
|
207
|
+
else
|
|
208
|
+
fail "empty sha did not degrade cleanly — got: $degrade_result"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# --- AC7: checkout-depth already verified (no .yml edit needed) -----------------
|
|
212
|
+
echo "--- AC7: .github/workflows/trust-reconcile.yml already carries fetch-depth: 0 ---"
|
|
213
|
+
WORKFLOW_FILE="$ROOT/.github/workflows/trust-reconcile.yml"
|
|
214
|
+
if grep -n "fetch-depth: 0" "$WORKFLOW_FILE" >/dev/null 2>&1; then
|
|
215
|
+
pass "AC7: fetch-depth: 0 already present in trust-reconcile.yml (Addendum part 3) — no .yml change needed for #305's depth ask"
|
|
216
|
+
else
|
|
217
|
+
fail "AC7: fetch-depth: 0 not found in trust-reconcile.yml — expected it to already be present"
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
if command -v python3 >/dev/null 2>&1 && python3 -c "import yaml" 2>/dev/null; then
|
|
221
|
+
if python3 - "$WORKFLOW_FILE" << 'PY' 2>/dev/null
|
|
222
|
+
import sys, yaml
|
|
223
|
+
try:
|
|
224
|
+
yaml.safe_load(open(sys.argv[1]).read())
|
|
225
|
+
sys.exit(0)
|
|
226
|
+
except yaml.YAMLError:
|
|
227
|
+
sys.exit(1)
|
|
228
|
+
PY
|
|
229
|
+
then
|
|
230
|
+
pass "AC7: trust-reconcile.yml parses as valid YAML (python3 yaml)"
|
|
231
|
+
else
|
|
232
|
+
fail "AC7: trust-reconcile.yml failed python3 yaml parse"
|
|
233
|
+
fi
|
|
234
|
+
else
|
|
235
|
+
grep -q "^name:" "$WORKFLOW_FILE" && grep -q "trust-reconcile" "$WORKFLOW_FILE" && \
|
|
236
|
+
pass "AC7: trust-reconcile.yml has expected structural fields (yaml parser unavailable, structural fallback)" || \
|
|
237
|
+
fail "AC7: trust-reconcile.yml missing expected structural fields"
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
echo ""
|
|
241
|
+
if [[ "$errors" -eq 0 ]]; then
|
|
242
|
+
echo "PASS: trust-reconcile.js runtime-session trailer diagnostic (audit-only, key-only, exit-code-identical)"
|
|
243
|
+
exit 0
|
|
244
|
+
else
|
|
245
|
+
echo "FAIL: $errors check(s) failed"
|
|
246
|
+
exit 1
|
|
247
|
+
fi
|