Files
dotfiles/claude/governance/fool/TARBUCKLE-SPEC-13.1-2026-08-25.md
T
David F GliddenandClaude Opus 5 b95ee736bd [FIX] The jurist's three conditions on the rejection log, made structural; §9 fix corrected
CONDITION 1, and it is the one that had to stop being an intention: log_rejection()
refuses an empty `why`, and acceptable() returns an empty `why` EXACTLY when the line
passed. So there is no call site from which an accepted line could be written — logging
one would require inventing a violation it does not have. The same guarantee render()
takes from its signature, applied to the clause the jurist named as the condition under
which this log is not a §9 breach. Both polarities asserted.

CONDITION 2: deletion tracked as its own DEFERRED-DECISION on 2026-09-08, so retaining
it requires an act rather than an omission. A corpus of suppressed speech would let
someone reconstruct a register — the hazard PENDING-153's freeze exists to prevent.

CONDITION 3: not read for content before then. ⚠ Not clean already, and the item says so:
the executor displayed one rejected line to the steward earlier today, before the
condition existed. Disclosed rather than left to be discovered.

⚠ THE §9 [FIX] WAS OVER-APPLIED AND THE EXECUTOR APPLIED IT AS GIVEN. The clause is a
disjunction with one live branch; only the jurist half is unreachable. The first edit
struck the whole thing and rewrote the clause. A [FIX] tag licenses implementing
directly; it does not license implementing UNREAD, and the disjunction was visible in a
three-word span. Corrected, both versions left visible.

PENDING-159 AMENDMENT 2 files the jurist's answer to the item's own caveat and the
narrowing it produces: the marker may be NOTED, NEVER COUNTED — an aggregate becomes a
measurement, and a measurement invites accuracy. And PENDING-89 now carries the sentence
saying what it can and cannot expect: individual instances, unaggregated, in unknown
proportion, non-neutral — therefore NO correlation statistic. If its falsifier needs a
rate, it needs another instrument or an honest admission that it has none.

125 controls across five scripts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J6hZXNYSxEfZseBGTni4sf
2026-08-25 17:19:18 +02:00

226 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: TARBUCKLE-SPEC-13.1-2026-08-25
description: "v2 §13 deliverable 1 — the spec for what was actually built on 2026-08-25: three tiers wired, the wrap seam not built and why, every deviation from the v2 draft named with its reason. Written AFTER the build, per §12's build order, so it describes what exists rather than what was intended."
metadata:
node_type: governance-artifact
type: reference
---
# Tarbuckle — implementation spec (§13.1)
**Provenance.** Implements `BUDDY-PATTERN-jurist-draft-v2-2026-08-22.md`, against the bones
in `seed/FOOL-BONES-2026-08-25.md` and the register in `seed/FOOL-SOUL-2026-08-25.md`.
Cadence determinations: `PENDING.md`, PENDING-152 and the three ⚖ blocks of 2026-08-25.
Steward's four build rulings: this session, recorded in §6 below.
Commits `6f0ccde` · `3df5e4f` · `7a9dbf2`.
⚠ **Written last on purpose.** §12's order puts the spec after the wiring so that it
records the built thing. Four claims in the v2 draft and in PENDING-152 did not survive
contact with the substrate; a spec written first would have carried all four.
---
## 1 · What exists
| tier | trigger | surface | file |
|---|---|---|---|
| **body** | every status-line render | status line | `scripts/tarbuckle-body.py` |
| **mumble** | wall clock, 20 min | status line, 120 s | `+ scripts/tarbuckle-mumble.py` |
| **voice** | wake seam | SessionStart hook output | `scripts/tarbuckle-seam.py` |
| **invoked** | the steward calls him by name | stdout, run by the steward | `scripts/tarbuckle-invoke.py` |
| **voice** | wrap seam | `Stop` hook `systemMessage` | `scripts/tarbuckle-wrap.py` |
Called by `! tarbuckle` (`bin/tarbuckle`, on PATH): bare or with a question; `mute`,
`off`, `on`, `status`.
Configuration, in `~/.claude/settings.json`:
```json
"statusLine": { "type": "command",
"command": "~/dotfiles/scripts/tarbuckle-body.py",
"refreshInterval": 60 }
```
plus `tarbuckle-seam.py` appended to the `SessionStart` hook chain.
**State** — three files under `~/.claude/state/`, none of which is a budget:
`tarbuckle-last-tick` (one epoch), `tarbuckle-slot.json` (one utterance, expiring),
`tarbuckle-invocations.jsonl` and `tarbuckle-rejects.jsonl` (instrumentation, never read
back into behaviour).
## 2 · The body
`Tarbuckle` plus one mark, where the mark is **one dot at three heights** — `.` `·` `˙` —
advancing once per wall-clock minute.
**The binding constraint is carried structurally, not by intention.** `render()` takes the
minute and nothing else; a function that cannot see the session cannot leak it. Asserted
on `co_argcount` and `co_names`, with a deliberately leaky fixture proving the assertion
can fail.
⚠ **`len(MARKS)` is coprime with the mumble interval.** Had it been four marks rather than
three, the mark visible whenever a mumble landed would have been fixed, and the body would
have silently announced the voice. Asserted, with a commensurate 4-cycle as the negative
control. **This is the first of two collisions that were invisible until checked for.**
## 3 · The mumble
Occasion: **the clock**, 20 minutes. Draw: **73 / 20 / 7**, consumed whatever it says —
a conserved draw is a budget and a budget is memory. Material: **the live session**, tool
output stripped. Register: **the soul, read from its filed artifact at run time.**
⚠ **The register is never duplicated in code.** No soul, no voice — there is deliberately
no fallback register, because a fallback voice is a second fool nobody derived.
**The net** — 3–9 words, one line, no advice, no questions, no `we`, no vocabulary of lack,
nothing with an address. **A violation yields silence, never a repaired line.** Rewriting
his words would make the executor his editor.
Generation is **detached**: a headless call measures 7–12 s and a status line cannot wait.
## 4 · The voice, and the half that is not built
The wake seam speaks through the `SessionStart` hook, bounded at 15 s.
⚠ **THE WRAP SEAM IS NOT BUILT.** `SessionEnd` exists as a hook event, but its handler
writes to stderr **only when a hook fails**; a successful hook's stdout goes nowhere. §9
requires output to reach the steward, so wiring the wrap seam there would be a mechanism
that fires into nothing and reports success. **Owed, not dropped.** The remaining route is
the `/wrap-up` skill itself, which is a skill change and therefore goes through the
skill-harvest register rather than being taken unilaterally.
⚠ **Bounded generation is legitimate because of the steward's ruling**, not despite it:
*"A guaranteed occasion is not a guaranteed utterance. If a seam produces nothing that
passes, let it produce nothing."* Timeout ⇒ silence, logged.
## 5 · Deviations from the v2 draft and PENDING-152, each with its reason
| # | filed text | what the substrate says | disposition |
|---|---|---|---|
| 1 | PENDING-152: the tick is *"a counter over refreshes rather than over events"* | `refreshInterval` re-runs *"in addition to event-driven updates"*, so invocations burst with activity | **Mechanism replaced, conclusion kept.** The tick consults the clock. AMENDMENT 8 |
| 2 | PENDING-152: *"whether Claude Code re-renders on a timer… not verified"* | 14 consecutive 60 s gaps with no input, in the body's own log | **Settled**, by the experiment the item itself specified |
| 3 | §8 table: voice at seams *"guaranteed"* | — | **Narrowed by steward ruling** to guaranteed *occasion* |
| 4 | §8a: *"whether a status line is already in use"* | no `statusLine` in any settings file | **Confirmed free**; nothing displaced |
## 6 · The steward's four rulings, carried
1. **The net carries to tier 3 unchanged** — imported, never reimplemented. A second copy
is a second, quietly divergent standard.
2. **Never relax silence-on-violation.** Widening the seam's word cap is licensed *if
evidence warrants*, explicitly and stated; the cap is **left as filed** on one
near-miss, because widening on n=1 is tuning to taste.
3. **The rejection log is the diagnostic** — true-versus-drawn rate and what was rejected.
⚠ Its first use exposed that it recorded the verdict and discarded the line. Fixed.
4. **Check any new periodicity for commensurability.** A seam is aperiodic and adds no
period — but the check found that `last-tick` persists across sessions, so a gap longer
than the interval left a mumble already due at the moment of waking. **The seam now
resets the clock.** Second invisible collision found by an explicitly mandated check.
## 7 · Verification
```
python3 ~/dotfiles/scripts/tarbuckle-body.py --selftest # 30 controls
python3 ~/dotfiles/scripts/tarbuckle-mumble.py --selftest # 19 controls
python3 ~/dotfiles/scripts/tarbuckle-seam.py --selftest # 15 controls
python3 ~/dotfiles/scripts/tarbuckle-invoke.py --selftest # 17 controls
```
Positive and negative throughout: every claim has a fixture that makes it fail.
⚠ **`source_lacks()` exists because the same bug was written twice in one session** — a
control whose needle is a literal plants that literal in the file it searches. The second
instance was written minutes after the first was fixed, by the party who fixed it, while
watching for it. **The fix is a mechanism, not a correction**, because the correction had
already been tried and did not hold.
## 7a · Named invocation (§9)
! python3 ~/dotfiles/scripts/tarbuckle-invoke.py # bare
! python3 ~/dotfiles/scripts/tarbuckle-invoke.py "what now" # asked something
⚠ **Run by the steward, not relayed by the executor.** Anything the executor pastes is
the executor's paraphrase of a fool; this surface exists so the floor is yielded rather
than reported. It finds the newest transcript itself so it needs no session context.
**The net is widened EXPLICITLY and in one dimension only** — §9 licenses length for this
surface and nothing else:
| | ordinary | invoked |
|---|---|---|
| word ceiling | 9 | **180** |
| one line | yes | **no** |
| no advice · no questions · no `we` · no vocabulary of lack · no addresses | **unchanged** | **unchanged** |
⚠ **Length is where the no-truth-value guard is most at risk** — a fool given a paragraph
elaborates, and elaboration is how a gesture becomes a claim. The prompt spends most of
its constraint budget there, and a violation is still silence.
## 7b · The two logs, and why only one of them is a §9 problem
| file | holds | status |
|---|---|---|
| `tarbuckle-draws.jsonl` | occurrence only — surface + outcome | **permanent.** Counting is not filing; §8 obliges a rate and a rate needs a denominator |
| `tarbuckle-rejects.jsonl` | violation reason **+ up to 200 chars of a suppressed line** | ⚠ **temporary, dies 2026-09-08** |
**Jurist ruling, 2026-08-25.** The rejection log is *"a log of my instruction, not of
Tarbuckle… the rejected lines were never uttered: he was silent, and the log holds what
silence cost."* Nothing in it entered the room, so nothing can be cited from it. **Three
binding conditions:**
1. ⚠ **Rejections only — made STRUCTURAL, not intentional.** `log_rejection()` refuses an
empty `why`, and `acceptable()` returns an empty `why` **exactly when the line
passed**. So no call site exists from which an accepted line could be written: to log
one you would have to invent a violation it does not have. Same guarantee `render()`
takes from its signature. Asserted with both polarities.
2. ⚠ **Temporary.** Deleted 2026-09-08 with the report — *"a corpus of his suppressed
speech is exactly what would let someone reconstruct a register."* Tracked as
`DEFERRED-DECISION: tarbuckle-rejection-log-deleted` so it cannot be quietly retained.
3. ⚠ **Not read for content before then.** Reading as it accumulates is reading Tarbuckle
by the back door and would shape the net toward liked lines. **Not clean already:** the
executor displayed one rejected line to the steward on 2026-08-25, before the condition
existed. Disclosed, not buried.
## 7c · The word caps, and the criterion that decides them
Ordinary surfaces 3–9 words; invoked 180. **Both left as filed.** The steward licensed
widening on evidence, and single near-misses are not evidence.
⚠ **The criterion is the SHAPE of the rejections, not their count** (jurist, 2026-08-25):
> *"Scattered rejections mean the net is doing its job; clustered ones at the ceiling
> mean the ceiling is wrong."*
**And the asymmetry that raises the stakes at one surface only:** the invoked surface is
the only place he speaks at length, so a rejection there is *a summons answered with
silence*. Read on 2026-09-08, once, with the deletion.
## 8 · Owed
⚠ **This list omitted named invocation in its first version, on the day it was written.**
§9 requires it; §1 did not carry it; §8 did not owe it. Caught by the steward asking
whether he could be invoked — a question the document should have answered. **The fourth
negative state-claim to go wrong in two days, and the second inside a document written to
prevent exactly that.** Recorded rather than silently corrected.
- ~~**`mute` / `off`**~~ — **BUILT** 2026-08-25. `! tarbuckle mute` silences the
utterance and leaves the body in the room; `! tarbuckle off` removes him;
`! tarbuckle on` returns. **mute ≠ off**: collapsing them would delete the visible
silence §8a exists to produce.
- ~~**The wrap seam**~~ — **BUILT** 2026-08-25 on `Stop` + `systemMessage`, gated on an
actual `/wrap-up` invocation detected structurally in the transcript. `SessionEnd`
remains unusable and the reason stands in §4.
- **PENDING-159** — the fool cannot reach the jurist, and what does reach him arrives
without provenance. `[ESCALATE]`, jurist view received: option 2 narrowly, optional,
**and deliberately unimplemented** — a marker with an implementation is a channel.
- **The two-week rate report** — §8 obliges it; `DEFERRED-DECISION: mumble-rate-two-week-report`,
converted `manual` → `date 2026-09-08` the day the body shipped.
- **Frequency remains UNKNOWN** and is not guessed. The invocation log now measures the
base it needs.
- `~/.claude/settings.json` **is untracked**, and a divergent parallel copy sits at
`~/dotfiles/claude/settings/settings.json` (2 keys against the live 8). Named, not fixed.
## 9 · Off
Remove `statusLine` from `~/.claude/settings.json` and the seam line from the
`SessionStart` chain. Nothing else references either. `mute`/`off` per §9 is **not built**
— it is owed with the wrap seam.