Files
claude-plugin-inference-arb…/tests/memory.test.sh
T
Oleks 6b634fd9f8 audit-snapshot: fix wrong decline reason for a stability-downgraded candidate
decline_reason() only ever read a candidate's own grade, so a candidate
boundary-classify graded high/complete-triple -- meaning it cleared FR-4.2
fine -- fell through to the generic 'did not clear the filing threshold'
line when stability-classify downgraded it afterward. That's the opposite of
what happened, and it directly contradicted the correct FR-4.5 section
rendered a few lines below it in the same judgment-calls drawer, poisoning
the next audit's step-2b recall with a false reason.

Found live: real audit of anxious (2026-07-30) downgraded
agent-wip/release-policy-derivation at 11.85% of audited spend -- the one
gate it DID clear -- and the drawer said it didn't. Filed and now fixed as
kotkan/claude-plugin-inference-arbitrage#16.

Fix keeps FR-4.2's own check order first (a candidate can coincidentally be
named in an unrelated stability finding without having actually cleared
those checks) and only replaces the final fallback, since a candidate that
reaches it cleared every earlier check on its own grade -- it cannot have
also failed the filing threshold on its own merits.

New regression case (h) in tests/memory.test.sh. Suite: all tests pass.
2026-07-30 04:35:42 +03:00

323 lines
16 KiB
Bash
Executable File

#!/usr/bin/env bash
# bin/audit-snapshot memory-queries / memory-notes: the FR-9 mempalace bridge.
# Everything deterministic is asserted here — wing/room resolution, the query
# packing, which drawers exist for which run shape, the rendered content, and
# the redaction allowlist.
#
# NOT tested here, and not fakeable in this harness: the MCP calls themselves.
# `add_drawer`/`kg_add`/`search` run in the caller's session, not in a shell,
# so no assertion below proves a drawer landed. That is deliberate — the whole
# reason the payload is computed by a script is to make the untestable part one
# verbatim tool call with no judgment in it. The round trip was verified once,
# by hand, against the live palace (design/tasks.md 7.9.5).
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
BIN=../bin/audit-snapshot
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
fail=0
check() {
local what=$1 got=$2 want=$3
if [ "$got" = "$want" ]; then
echo " ok $what = $want"
else
echo " FAIL $what = $got (want $want)" >&2
fail=1
fi
}
py() { python3 -c "$@"; }
# drawer <notes.json> <kind> <field>
drawer() {
py "
import json,sys
d=json.load(open('$1'))
x=[k for k in d['drawers'] if k['kind']=='$2']
print(x[0]['$3'] if x else 'ABSENT')"
}
# --------------------------------------------------------------------------
# Fixtures — one fake target 'synth' with three candidates: one filed, one
# boundary question, one no-offload (whose reason prose reaches snapshot notes).
# --------------------------------------------------------------------------
# The strings that must never reach a drawer. Each stands for a different
# free-text path into the snapshot: an analyst reason, and a caller --note.
LEAK_REASON="the user said deploy the staging key AKIAIOSFODNN7EXAMPLE to prod"
LEAK_NOTE="filing: unavailable — grep -rn 'password=hunter2' /home/oleks/secrets"
inventory() {
py "
import json
json.dump({'plugin': {'name': 'synth', 'version': '1.2.0', 'source': 'oleks-local'},
'skills': [{'name': 'synth:alpha'}, {'name': 'synth:beta'}],
'agents': [{'name': 'synth:gamma'}]}, open('$1','w'))"
}
scan() {
py "
import json
json.dump({'window': {'since': '2026-07-01', 'until': '2026-07-29', 'days': 28},
'plugin': 'synth', 'tool_versions': {'cc_tokens_path': '/synthetic/cc-tokens'},
'coverage': {'attributed_turns': 400, 'candidate_turns': 20000, 'ratio': 0.02},
'totals': {'weighted_tokens': 10000000, 'invocations': 30,
'mechanical_share': 0.41},
'skills': [{'skill': 'synth:alpha', 'invocations': 20, 'offload_waste': 2000000,
'ngrams': [{'sig': ['list_issues', 'issue_read'], 'recurrences': 20,
'invocations': 20, 'waste': 2000000}]},
{'skill': 'synth:beta', 'invocations': 10, 'offload_waste': 400000,
'ngrams': [{'sig': ['glob', 'read'], 'recurrences': 10,
'invocations': 10, 'waste': 400000}]}],
'agents': []}, open('$1','w'))"
}
# classified <file> [--only-filed]
classified() {
py "
import json,sys
only = '${2:-}' == '--only-filed'
def cand(cid, skill, value, share, verdict, conf, overrule, t3=True):
return {'candidate_id': cid, 'skill': skill, 'position': 'llm-over-script-digest',
'determinism_tests': {t: True for t in ('T1','T2','T4','T5')} | {'T3': t3},
'falsifiability_triple': {'signature': True, 'examples': True,
'overrule_case': overrule},
'boundary_confidence': conf, 'measurement_strength': 'measured',
'share_of_audited_spend': share, 'offload_value': value,
'escalation_path': 'escalate', 'digest_schema': 'typed rows',
'verdict': verdict, 'reasons': ['synthetic']}
cands = [cand('synth/alpha/dispatch', 'synth:alpha', 2000000, 0.20, 'file', 'high', True)]
if not only:
cands.append(cand('synth/beta/triage', 'synth:beta', 400000, 0.04,
'boundary-question', 'medium', False))
nono = cand('synth/beta/prose', 'synth:beta', 0, 0.0, 'no-offload', 'low', False, t3=False)
nono['reasons'] = ['''$LEAK_REASON''']
cands.append(nono)
json.dump({'rubric_version': '1.0.0', 'target': 'synth', 'candidates': cands}, open('$1','w'))"
}
# stable <file> — one stability finding, the FR-4.5 downgrade shape
stable() {
py "
import json
json.dump({'stability_findings': [{
'gate': 'FR-4.5', 'candidate_id': 'synth/beta/triage', 'target': 'synth',
'skill': 'synth:beta', 'matched_by': 'slug',
'prior': {'run_id': '2026-07-01T00-00-00Z', 'window': {'since': '2026-06-01'},
'measurement_strength': 'thin', 'share_of_audited_spend': 0.0},
'current': {'window': {'since': '2026-07-01'}, 'measurement_strength': 'measured',
'share_of_audited_spend': 0.04},
'flips': [{'field': 'measurement_strength', 'prior': 'thin', 'current': 'measured'}],
'self_report': {'repo': 'kotkan/claude-plugin-inference-arbitrage',
'marker': '<!-- ia-stability: synth/beta/triage -->'}}]},
open('$1','w'))"
}
inventory "$tmp/inv.json"
scan "$tmp/scan.json"
classified "$tmp/cls.json"
stable "$tmp/stable.json"
store=$tmp/store
$BIN --store "$store" write --target synth --from "$tmp/inv.json" "$tmp/scan.json" \
--classified "$tmp/cls.json" --repo oleks/claude-plugin-synth \
--run-id 2026-07-29T12-00-00Z --note "$LEAK_NOTE" >/dev/null
# --------------------------------------------------------------------------
echo "== (a) memory-queries: wing/room resolution and query packing =="
$BIN --store "$store" memory-queries --target synth --inventory "$tmp/inv.json" \
>"$tmp/q.json"
check "target wing" "$(py "import json;print(json.load(open('$tmp/q.json'))['wings']['target'])")" \
"synth"
check "cross-target wing" \
"$(py "import json;print(json.load(open('$tmp/q.json'))['wings']['cross_target'])")" \
"claude-plugins"
check "target room" "$(py "import json;print(json.load(open('$tmp/q.json'))['rooms']['target'])")" \
"inference-arbitrage-audits"
check "cross-target room" \
"$(py "import json;print(json.load(open('$tmp/q.json'))['rooms']['cross_target'])")" \
"inference-arbitrage-lessons"
py "
import json
d=json.load(open('$tmp/q.json'))
qs=d['queries']
assert all(len(q['query'])<=250 for q in qs), [len(q['query']) for q in qs]
assert {q['wing'] for q in qs}=={'synth','claude-plugins'}, {q['wing'] for q in qs}
joined=' '.join(q['query'] for q in qs)
for n in ('synth:alpha','synth:beta','synth:gamma'):
assert n in joined, n
print(' ok every query fits 250 chars and names every skill and agent')" || fail=1
py "
import json
d=json.load(open('$tmp/q.json'))
a=d['advisory']
assert 'never override' in a and 'FR-4.2' in a and 'FR-4.5' in a, a
print(' ok advisory says results never override the mechanical gates')" || fail=1
# A cold store with no inventory still resolves wings — the read step must not
# depend on a pass that has not run yet.
$BIN --store "$tmp/cold" memory-queries --target other >"$tmp/q2.json"
check "cold-store target wing" \
"$(py "import json;print(json.load(open('$tmp/q2.json'))['wings']['target'])")" "other"
# --------------------------------------------------------------------------
echo "== (b) memory-notes: three drawers, correct wings and rooms =="
$BIN --store "$store" memory-notes --target synth --stable "$tmp/stable.json" >"$tmp/n.json"
check "drawer count" "$(py "import json;print(len(json.load(open('$tmp/n.json'))['drawers']))")" "3"
check "run-summary wing" "$(drawer "$tmp/n.json" run-summary wing)" "synth"
check "run-summary room" "$(drawer "$tmp/n.json" run-summary room)" "inference-arbitrage-audits"
check "judgment-calls wing" "$(drawer "$tmp/n.json" judgment-calls wing)" "synth"
check "lessons wing" "$(drawer "$tmp/n.json" cross-target-lessons wing)" "claude-plugins"
check "lessons room" "$(drawer "$tmp/n.json" cross-target-lessons room)" \
"inference-arbitrage-lessons"
check "added_by" "$(drawer "$tmp/n.json" run-summary added_by)" "inference-arbitrage"
check "source_file" "$(drawer "$tmp/n.json" run-summary source_file)" \
"inference-arbitrage/Audits/synth/2026-07-29.md"
# --------------------------------------------------------------------------
echo "== (c) rendered content carries the numbers, framed as the plugin frames them =="
py "
import json
c=[d for d in json.load(open('$tmp/n.json'))['drawers'] if d['kind']=='run-summary'][0]['content']
assert 'synth/alpha/dispatch' in c
assert 'oleks/claude-plugin-synth#' not in c # no issue filed in this fixture
assert '20.00%' in c, 'share of spend missing'
assert '41.0%' in c, 'mechanical share missing'
assert 'dilution' in c and 'not completeness' in c, 'coverage must be framed as dilution'
assert 'llm-over-script-digest' in c
print(' ok run summary carries share, mechanical share, position, dilution framing')" || fail=1
py "
import json
c=[d for d in json.load(open('$tmp/n.json'))['drawers'] if d['kind']=='judgment-calls'][0]['content']
assert 'synth/beta/triage' in c
assert 'no overrule case' in c, 'decline reason must come off the triple'
assert 'T3 FAIL' not in c, 'triage passes T3; a wrong test line would be worse than none'
assert 'Evidence-stability downgrades' in c
assert '<!-- ia-stability: synth/beta/triage -->' in c
assert 'kotkan/claude-plugin-inference-arbitrage' in c
print(' ok judgment drawer names the decline reason and the stability downgrade')" || fail=1
py "
import json
c=[d for d in json.load(open('$tmp/n.json'))['drawers']
if d['kind']=='cross-target-lessons'][0]['content']
assert 'llm-over-script-digest 2' in c, 'positions must be counted across both tiers'
assert 'measured 2' in c
assert '1 downgrade(s)' in c
assert 'not a failed scan' in c, '2% coverage must be explained, not reported bare'
print(' ok lessons drawer counts positions across tiers and explains low coverage')" || fail=1
# --------------------------------------------------------------------------
echo "== (d) redaction: free-text snapshot fields never reach a drawer (FR-3.5) =="
# `notes` holds a caller --note and analyst reason prose. Both are in the
# snapshot on disk; neither may cross into a wing shared with other projects.
py "
import json
snap=[json.loads(l) for l in open('$store/Data/synth/snapshots.jsonl')][-1]
joined=' '.join(snap['notes'])
assert '''$LEAK_NOTE''' in joined, 'fixture is not exercising the leak path'
assert 'AKIAIOSFODNN7EXAMPLE' in joined, 'fixture is not exercising the leak path'
print(' ok the snapshot on disk really does contain the free text')" || fail=1
py "
import json
d=json.load(open('$tmp/n.json'))
blob=json.dumps(d)
for needle in ('AKIAIOSFODNN7EXAMPLE', 'hunter2', 'grep -rn', 'the user said',
'filing: unavailable'):
assert needle not in blob, needle
print(' ok no snapshot note, reason, or command argument appears in any drawer or fact')" ||
fail=1
py "
import json
d=json.load(open('$tmp/n.json'))
blob=json.dumps(d)
assert 'FR-3.5' in blob, 'each drawer states the constraint it was rendered under'
print(' ok drawers carry the redaction note themselves')" || fail=1
# --------------------------------------------------------------------------
echo "== (e) knowledge-graph facts mirror the verdicts =="
py "
import json
facts=json.load(open('$tmp/n.json'))['facts']
t={(f['subject'],f['predicate'],f['object']) for f in facts}
assert ('synth','audited_by','inference-arbitrage') in t
assert ('synth/alpha/dispatch','classified_as','llm-over-script-digest') in t
assert ('synth/beta/triage','declined_as','boundary-question') in t
assert ('synth/beta/triage','downgraded_by','evidence-stability gate FR-4.5') in t
assert all(f['valid_from']=='2026-07-29' for f in facts), facts
assert not any(f['predicate']=='filed_as' for f in facts), 'nothing was filed in this fixture'
print(' ok facts cover audited/classified/declined/downgraded, dated by run')" || fail=1
# --------------------------------------------------------------------------
echo "== (f) a clean run: no judgment drawer, but the lessons drawer still writes =="
# The cross-target record accumulates on every run — a 'nothing to offload'
# result is exactly the calibration evidence worth keeping.
classified "$tmp/cls2.json" --only-filed
store2=$tmp/store2
$BIN --store "$store2" write --target synth --from "$tmp/inv.json" "$tmp/scan.json" \
--classified "$tmp/cls2.json" --run-id 2026-07-29T12-00-00Z >/dev/null
$BIN --store "$store2" memory-notes --target synth >"$tmp/n2.json"
check "drawer count" "$(py "import json;print(len(json.load(open('$tmp/n2.json'))['drawers']))")" "2"
check "judgment drawer" "$(drawer "$tmp/n2.json" judgment-calls wing)" "ABSENT"
check "lessons drawer still present" "$(drawer "$tmp/n2.json" cross-target-lessons wing)" \
"claude-plugins"
# --------------------------------------------------------------------------
echo "== (g) memory-notes refuses a target with no history, rather than writing nothing =="
if $BIN --store "$tmp/empty" memory-notes --target synth >/dev/null 2>&1; then
echo " FAIL memory-notes succeeded with no snapshots" >&2
fail=1
else
echo " ok memory-notes exits non-zero with no snapshots recorded"
fi
# --------------------------------------------------------------------------
echo "== (h) regression #16: a stability-downgraded candidate gets its real reason =="
# The bug: decline_reason() only ever looked at a candidate's own grade, so a
# candidate boundary-classify graded high/complete-triple (it cleared FR-4.2
# fine) but that stability-classify then downgraded fell through to the
# generic "did not clear the filing threshold" line -- the opposite of what
# happened, and a direct contradiction of the FR-4.5 section rendered a few
# lines below it in the same drawer.
py "
import json
cand = {'candidate_id': 'synth/gamma/flip', 'skill': 'synth:gamma',
'position': 'llm-over-script-digest',
'determinism_tests': {t: True for t in ('T1','T2','T3','T4','T5')},
'falsifiability_triple': {'signature': True, 'examples': True, 'overrule_case': True},
'boundary_confidence': 'high', 'measurement_strength': 'thin',
'share_of_audited_spend': 0.0, 'offload_value': 0,
'escalation_path': 'escalate', 'digest_schema': 'typed rows',
# rewritten by stability-classify in real use -- 'file' cleared, then downgraded
'verdict': 'boundary-question', 'reasons': ['downgraded by stability-classify']}
json.dump({'rubric_version': '1.0.0', 'target': 'synth', 'candidates': [cand]},
open('$tmp/cls3.json','w'))
json.dump({'stability_findings': [{
'gate': 'FR-4.5', 'candidate_id': 'synth/gamma/flip', 'target': 'synth',
'skill': 'synth:gamma', 'matched_by': 'slug',
'prior': {'run_id': '2026-07-01T00-00-00Z', 'window': {'since': '2026-06-01'},
'measurement_strength': 'thin', 'share_of_audited_spend': 0.0},
'current': {'window': {'since': '2026-07-01'}, 'measurement_strength': 'measured',
'share_of_audited_spend': 0.1185},
'flips': [{'field': 'measurement_strength', 'prior': 'thin', 'current': 'measured'}],
'self_report': {'repo': 'kotkan/claude-plugin-inference-arbitrage',
'marker': '<!-- ia-stability: synth/gamma/flip -->'}}]},
open('$tmp/stable3.json','w'))
"
store3=$tmp/store3
$BIN --store "$store3" write --target synth --from "$tmp/inv.json" "$tmp/scan.json" \
--classified "$tmp/cls3.json" --run-id 2026-07-29T12-00-00Z >/dev/null
$BIN --store "$store3" memory-notes --target synth --stable "$tmp/stable3.json" >"$tmp/n3.json"
py "
import json
c=[d for d in json.load(open('$tmp/n3.json'))['drawers'] if d['kind']=='judgment-calls'][0]['content']
assert 'did not clear the filing threshold' not in c, \
'regression: a candidate stability downgraded was never actually rejected by the threshold'
assert 'cleared boundary-classify' in c and 'FR-4.5' in c, \
'the decline reason must point at the real cause'
assert 'no overrule case' not in c, 'this candidate has a complete triple, that reason is wrong too'
print(' ok stability-downgraded candidate reports the real reason, not the filing-threshold line')" ||
fail=1
exit "$fail"