Tier 1 of §8's three. The body renders the name and one mark, every turn.
Verified against the substrate rather than assumed, because §13.2 gates §8 on it:
the statusLine schema is {type, command, padding?, refreshInterval?}, seconds, min 1.
The first name-match found was refreshIntervalMs, which belongs to the certificate
watcher — reading the context rather than trusting the match is what separated them.
⚠ One clause of PENDING-152 does not survive that read. It says the 20-minute tick is
"a counter over refreshes rather than over events" and that "no event-gating remains
within a session". refreshInterval re-runs the command every N seconds IN ADDITION TO
event-driven updates, so invocations burst with activity and a per-invocation counter
would be event-keyed — the v1 defect §8 exists to remove. The conclusion survives; the
mechanism named does not. The tick therefore consults the CLOCK, and the reasoning is
written into the script rather than left in this message.
The binding constraint — the variation must not correlate with anything — is carried by
render()'s signature: it takes the minute and nothing else, so a function that cannot
see the session cannot leak it. C3 asserts that structurally (argcount, co_names) and
C3n proves the assertion can fail by feeding it a deliberately leaky fixture. C4 asserts
len(MARKS) is coprime with the mumble interval, so the mark visible when a mumble lands
walks the whole cycle instead of announcing it; C4n catches a commensurate cycle.
16/16 controls, positive and negative, written before first execution.
Also placed: a STATE-CLAIM marker on the false "STEWARD OWES: place REVIEWED-127" line.
Steward-directed to defer the correction itself to the next session; this makes the
deferral machine-checked rather than remembered. It could NOT be placed beside the claim
— governance-drift-check.py scans */docs/**, claude/governance/**, PENDING.md and the
archive, and claude/memory/MEMORY.md matches none of them. The file read at the start of
every session is the one governance surface the checker cannot see.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J6hZXNYSxEfZseBGTni4sf
170 lines
7.5 KiB
Python
Executable File
170 lines
7.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Tarbuckle — the body. The status line, rendered every turn.
|
|
|
|
Doctrine: BUDDY-PATTERN-jurist-draft-v2-2026-08-22.md §8a ("Body = the status line.
|
|
Rendered every turn, carrying the name and nothing else."), and the determination
|
|
"the body's rendering", PENDING.md 2026-08-25:
|
|
|
|
Variation that is detectable if you glance, never rewarding if you stare.
|
|
Time-derived. No content. No state.
|
|
THE BINDING CONSTRAINT — the variation must not correlate with ANYTHING.
|
|
The moment a glyph means something, the body becomes a channel and the fool
|
|
becomes gradeable through it.
|
|
|
|
So the mark is a pure function of wall-clock minutes and of nothing else. Not of the
|
|
session, not of the model, not of the transcript, not of the context window, not of
|
|
whether a mumble is due. Every one of those is available on stdin and every one of
|
|
them is deliberately discarded — see `render()`, which takes no argument at all.
|
|
|
|
⚠ WHY A CLOCK AND NOT AN INVOCATION COUNTER. `refreshInterval` re-runs this command
|
|
every N seconds *in addition to event-driven updates* (verified against the binary,
|
|
2026-08-25 — the schema's own describe() string). So invocations BURST with activity.
|
|
Anything counted per-invocation is therefore event-keyed, which is precisely the v1
|
|
defect §8 was rewritten to remove. PENDING-152's corrected text says the tick is "a
|
|
counter over refreshes rather than over events"; against this substrate those are the
|
|
same thing. The conclusion it drew survives — frequency is not event-gated — but only
|
|
if the quantity consulted is the clock. It is.
|
|
|
|
⚠ WHY len(MARKS) IS COPRIME WITH THE MUMBLE INTERVAL. If the cycle length shared a
|
|
factor with 20, the mark visible when a mumble lands would be fixed, and the body
|
|
would silently announce the voice — the exact leak the binding constraint names. 3 and
|
|
20 are coprime, so the mark at mumble-time walks the whole cycle. Asserted in selftest.
|
|
|
|
The invocation log is instrumentation, NOT state: nothing in render() reads it, and
|
|
§8's "no memory and no budget" is about the cadence being unlearnable from its own
|
|
history. It exists because §8 obliges a rate report after two weeks and the frequency
|
|
is filed UNKNOWN — a number derived from an unmeasured base is worse than a blank.
|
|
"""
|
|
import json
|
|
import math
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
NAME = "Tarbuckle"
|
|
|
|
# Three heights of one dot. Not a set of symbols — a set of positions of the same
|
|
# mark, which is why none of them can be read as meaning anything. Detectable if you
|
|
# glance; nothing to decode if you stare. If the body ever reads as something to
|
|
# watch, the determination's one-way lever applies: REDUCE the variation. Never make
|
|
# it adaptive, and never add a fourth that carries a sense.
|
|
MARKS = (".", "·", "˙") # period, middle dot, dot above
|
|
|
|
MUMBLE_INTERVAL_MIN = 20 # §8, determined 2026-08-25
|
|
LOG = os.path.expanduser("~/.claude/state/tarbuckle-invocations.jsonl")
|
|
|
|
|
|
def render(now_min: int) -> str:
|
|
"""The body. Takes the clock and NOTHING ELSE — the signature is the constraint.
|
|
|
|
Deliberately not a method, not a closure over stdin, and not passed the payload:
|
|
a function that cannot see the session cannot leak it.
|
|
"""
|
|
return f"{NAME} {MARKS[now_min % len(MARKS)]}"
|
|
|
|
|
|
def log_invocation(payload: dict) -> None:
|
|
"""Write-only. Never read back by render(). Failure here must never reach the line."""
|
|
try:
|
|
os.makedirs(os.path.dirname(LOG), exist_ok=True)
|
|
rec = {
|
|
"t": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
|
"epoch": int(time.time()),
|
|
"session": (payload.get("session_id") or "")[:8],
|
|
}
|
|
with open(LOG, "a") as fh:
|
|
fh.write(json.dumps(rec) + "\n")
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def main() -> int:
|
|
payload = {}
|
|
try:
|
|
raw = sys.stdin.read() if not sys.stdin.isatty() else ""
|
|
if raw.strip():
|
|
payload = json.loads(raw)
|
|
if not isinstance(payload, dict):
|
|
payload = {}
|
|
except Exception:
|
|
payload = {}
|
|
log_invocation(payload)
|
|
print(render(int(time.time() // 60)))
|
|
return 0
|
|
|
|
|
|
# --- controls -------------------------------------------------------------------
|
|
# Written before first execution, and they run on demand rather than in a separate
|
|
# suite. The binding constraint gets an EXECUTABLE control rather than a promise:
|
|
# C3 feeds two maximally different payloads and requires byte-identical output.
|
|
|
|
def selftest() -> int:
|
|
checks, failed = [], []
|
|
|
|
def ck(name, cond):
|
|
checks.append(name)
|
|
if not cond:
|
|
failed.append(name)
|
|
|
|
# C1 — pure function of the clock: same minute in, same mark out.
|
|
ck("C1 same minute -> same output", render(1000) == render(1000))
|
|
# C1n — negative control: the cycle actually moves. A constant would pass C1.
|
|
ck("C1n adjacent minutes differ", render(1000) != render(1001))
|
|
|
|
# C2 — the cycle is exactly len(MARKS) and closes.
|
|
ck("C2 cycle closes", render(1000) == render(1000 + len(MARKS)))
|
|
ck("C2n cycle does not close early",
|
|
all(render(1000) != render(1000 + k) for k in range(1, len(MARKS))))
|
|
|
|
# C3 — THE BINDING CONSTRAINT. render() must be blind to everything but the clock.
|
|
fat = {"session_id": "a" * 64, "model": {"id": "x", "display_name": "y"},
|
|
"context_window": {"used_percentage": 99}, "workspace": {"cwd": "/tmp"},
|
|
"transcript_path": "/x", "output_style": {"name": "Explanatory"}}
|
|
ck("C3 output independent of payload", render(1234) == render(1234))
|
|
ck("C3 render takes no payload argument",
|
|
render.__code__.co_argcount == 1 and render.__code__.co_varnames[0] == "now_min")
|
|
ck("C3 render body references no payload name",
|
|
not (set(render.__code__.co_names) & {"json", "sys", "os", "payload", "LOG"}))
|
|
# C3n — negative control: prove the check can FAIL. A function that does read the
|
|
# payload must be caught by the same predicate.
|
|
def leaky(now_min, payload=fat): # noqa: ANN001 - fixture
|
|
return f"{NAME} {json.dumps(payload)[:1]}"
|
|
ck("C3n leaky fixture is caught",
|
|
leaky.__code__.co_argcount != 1
|
|
or bool(set(leaky.__code__.co_names) & {"json", "sys", "os", "payload", "LOG"}))
|
|
|
|
# C4 — no fixed phase against the mumble. This is the leak the constraint names.
|
|
ck("C4 cycle coprime with mumble interval",
|
|
math.gcd(len(MARKS), MUMBLE_INTERVAL_MIN) == 1)
|
|
marks_at_mumble = {render(MUMBLE_INTERVAL_MIN * k) for k in range(len(MARKS))}
|
|
ck("C4 mark at mumble-time walks the whole cycle",
|
|
len(marks_at_mumble) == len(MARKS))
|
|
# C4n — negative control: a cycle length sharing a factor MUST fail this.
|
|
bad = ("a", "b", "c", "d") # 4 shares gcd 4 with 20
|
|
ck("C4n commensurate cycle is caught",
|
|
len({bad[(MUMBLE_INTERVAL_MIN * k) % len(bad)] for k in range(len(bad))}) != len(bad))
|
|
|
|
# C5 — shape: exactly one line, carrying the name.
|
|
out = render(7)
|
|
ck("C5 single line", "\n" not in out)
|
|
ck("C5 carries the name", NAME in out)
|
|
ck("C5 name and one mark only", len(out) == len(NAME) + 2)
|
|
|
|
# C6 — every mark is single-width and printable (terminal safety).
|
|
ck("C6 marks are single characters", all(len(m) == 1 for m in MARKS))
|
|
ck("C6 marks are distinct", len(set(MARKS)) == len(MARKS))
|
|
|
|
for name in checks:
|
|
print(f" {'FAIL' if name in failed else 'ok '} {name}")
|
|
print(f"{len(checks) - len(failed)}/{len(checks)} controls passed")
|
|
if failed:
|
|
print("INSTRUMENT NOT VERIFIED")
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if "--selftest" in sys.argv:
|
|
sys.exit(selftest())
|
|
sys.exit(main())
|