Files
dotfiles/claude/governance/tarbuckle-fortnight/normalise.py
T
David F GliddenandClaude Opus 5 3d45e3abb2 [FIX] The fortnight read, banked content-free; the corpus and its writer both retired (REVIEWED-136)
REVIEWED-136 and AMENDMENT 1. The 2026-09-08 obligation, run a day late because
the steward could not reach the machine.

Why the writer was stopped and not just the file removed. `log_rejection()` opens
in append mode, which recreates the log on the next rejection. Deleting the file
alone would have retired 101 entries into a successor accumulating under no
condition — condition 2's rationale defeated the moment it was honoured, at the
W2 rate within hours. `REJECT_LOGGING_ENABLED = False` makes the write path inert
at the single shared call site; the four surfaces reach it through one import and
a symlink. What replaces it is NOT ruled: condition G files the mechanism question
open, and restoring the path needs a ruling, not a constant flip.

The A8 controls are kept, not adjusted to pass. Condition G suspends the jurist's
structural guarantee; it does not repeal it. A8/A8n now run under a temporarily
enabled flag, where they double as the positive control proving the new G check
can observe a write at all. G fails correctly when the constant is flipped —
verified against a probe copy.

What was banked before deletion, because none of it can be recovered after:
per-day word-count histograms (the scattered/clustered judgment is temporal, and
two windows could not carry it), per-surface counts, rate blocks by window, and
the normalisation map. Residue 0 of 101 against must-not-classify controls, so the
zero is not vacuous. `why` is not content-free by construction — `echoes_soul()`
returns a literal 4- or 6-word run from the suppressed line — so recital payloads
are discarded unconditionally.

The rejects snapshot is deleted, not committed. It was a verbatim corpus copy;
committing it would have defeated condition 2 permanently in git history, where it
cannot be undone without a rewrite. Leak-gated while the corpus still existed to
test against: 100 hits on the snapshot as positive control, 0 on all five
committed artifacts.

Scope: `tarbuckle-rejects.jsonl` and its snapshot only. `tarbuckle-draws.jsonl`
and `tarbuckle-invocations.jsonl` are untouched — they are not under condition 2
and they hold the input-distribution confound the verdicts sitting needs.

No verdicts offered. Rate, distribution and shape are figures; scattered-versus-
clustered, the 6.7 s question and any cap consequence are reserved to the jurist
and steward.

Selftests 39/15/25/21, all four surfaces green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbXpZup4GGCbLBbRJ79KpM
2026-09-09 17:28:26 +02:00

75 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Normalisation map for `tarbuckle-rejects.jsonl` `why` values.
REVIEWED-136 condition A. Fixed and recorded BEFORE the fortnight read, because
after REVIEWED-128 condition 2 deletes the corpus this map is the only thing that
makes the banked tallies auditable.
⚠ WHY THIS EXISTS. `why` is not content-free by construction. `echoes_soul()`
returns `sorted(hit)[0]` — a literal n-gram lifted from the suppressed line, at a
4-word threshold against the sample lines and a 6-word threshold against the soul's
prose. Every recital-class reason therefore embeds verbatim text of a line that was
never uttered. Normalisation for that class is UNCONDITIONAL and discards the
payload; it is not applied on inspection of the values that happen to be present,
because the next recital rejection written would leak again.
"""
import re
RE_WORDS = re.compile(r"^(\d+) words$")
RE_RECITAL = re.compile(r"^recited the soul: '.*'$", re.S)
RE_BANNED = re.compile(r"^banned \\b(\w+)\\b$")
def normalise(why: str):
"""(category, payload) or None if the shape is unknown.
Returning None on an unknown shape is the point: a classifier that absorbs
everything reports zero residue vacuously.
"""
if why is None:
return None
m = RE_WORDS.match(why)
if m:
# A count is content-free: it says how long the line was, never what it said.
return ("word-count", int(m.group(1)))
if RE_RECITAL.match(why):
# PAYLOAD DISCARDED. The fragment is the suppressed line, verbatim.
return ("recital", None)
m = RE_BANNED.match(why)
if m:
# ⚠ THE ONE JUDGMENT IN THIS MAP, AND THE EXECUTOR DOES NOT MAKE IT.
# The token is a member of a fixed ban list — a property of the RULE that
# fired, not a distinctive phrase composed by the line. Retained on that
# reading. Condition A scopes unconditional normalisation to the recital
# class and does not reach this one. Flagged for the jurist: if the
# reading is rejected, change the payload below to None and re-bank.
return ("banned-token", m.group(1))
return None
def selftest() -> bool:
ck = lambda name, ok: (print((" ok " if ok else " FAIL ") + name), ok)[1]
r = []
# must-classify — every shape observed in the corpus
r.append(ck("14 words -> word-count/14", normalise("14 words") == ("word-count", 14)))
r.append(ck("196 words -> word-count/196", normalise("196 words") == ("word-count", 196)))
r.append(ck("banned -> banned-token/must", normalise(r"banned \bmust\b") == ("banned-token", "must")))
r.append(ck("recital -> recital/None", normalise("recited the soul: 'a b c d'") == ("recital", None)))
# must-DISCARD — the payload may never carry the fragment through
got = normalise("recited the soul: 'is standing about here'")
r.append(ck("recital payload is discarded, not passed through", got == ("recital", None)))
r.append(ck("recital fragment absent from repr(result)", "standing" not in repr(got)))
# must-NOT-classify — without this, residue 0 is vacuous
r.append(ck("unknown shape -> None", normalise("something nobody wrote") is None))
r.append(ck("empty -> None", normalise("") is None))
r.append(ck("None -> None", normalise(None) is None))
r.append(ck("near-miss 'words' plural bare -> None", normalise("words") is None))
r.append(ck("near-miss unanchored -> None", normalise("about 14 words long") is None))
return all(r)
if __name__ == "__main__":
import sys
print("normalise.py selftest")
sys.exit(0 if selftest() else 1)