[HARDENING] The mumble discriminator fails its own must-detect gate; repairs stopped at the gate (PENDING-179)

The 2026-09-03 repair plan rested on one claim: that human_turns() is an
existing, controlled discriminator, and wiring it into four transcript
consumer sites was plumbing rather than classifier-building.

Tested against ground truth taken from the fool's own prompt text — not from
the function under test — the claim is false. 24 known mumbles, 41 known
non-mumbles, out of 65 transcripts:

  must-detect    22/24  two mumbles read as human-attended
  must-not-flag  35/41  and one "failure" is correct — b7e7eb39 is the
                        unattended session of 2026-08-31, which genuinely
                        has no human turn

human_turns() == 0 never meant "mumble". It means "nobody spoke", which is
equally true of an unattended real session. A mumble embeds the previous
session's text and so inherits its slash-command markers; it is excluded
only when the session it quoted happened to contain one. The two leaks are
exactly the two marker-free mumbles. Coincidence, not design.

No repair was made at any site, and no replacement discriminator was built.
Three controls passed and a fourth broke: all three test the question the
function was built for, none could see the question it was being reused for.
A successor written now inherits whatever made the first set look sufficient.

Also here, per the 2026-09-03 handover:
  - step 0 preservation ran: 76 transcripts, read-back PASS, 11 of them
    already pruned at source and surviving only in the archive
  - gate 2b: the composition claim in PENDING-178 stands (11 mumbles,
    ~a fifth, on 08-31). Two terms do not close and are reported as an open
    disagreement, not a correction — the reconstruction is a demonstrated
    lower bound and -178 enumerated live
  - gate 2c: five sites in four files; -178's [HARDENING] scope holds
  - MEMORY.md record-only arithmetic correction: N-now 44 -> 65 measured,
    composition 41 real + 24 mumble added, direction reversed (it is rising,
    not shedding), stale :331 pointer corrected to :513

Filed as an item rather than a PENDING-178 addendum on PENDING-145's
mechanism: a ruling claims a number, so an addendum would be suppressed the
moment -178 is ruled. The undecided filing moratorium is disclosed inside
the item.

governance-drift-check.py:513 excluded on receipt and not examined.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3L79vgAnt1x2kxvf23Qt7
This commit is contained in:
David F Glidden
2026-09-04 10:22:23 +02:00
co-authored by Claude Opus 5
parent e5db321d6f
commit c150bdff17
4 changed files with 167 additions and 1 deletions
+37
View File
@@ -6914,3 +6914,40 @@ That is a separate decidable unit; merging it here would reproduce the CLASS E d
**Files affected:** `scripts/governance-drift-check.py` (`TRANSCRIPTS`, `trigger_fired`); whichever surface a monotonic counter is written from under (d).
**Awaiting:** Steward authorization.
---
## PENDING-179 — `human_turns()` is not a mumble discriminator: it detects slash-command markers, and the four sites were about to be repaired with it
**Date:** 2026-09-03
**Tag:** [HARDENING]
**Summary:** The repair plan inherited from 2026-09-03 ADDENDUM 1 rests on one claim — *"the discriminator already exists and is controlled, `wake-digest.py:932 human_turns()`; this is wiring a tested function into four call sites, not building a classifier."* **That claim is false.** Run as a must-detect gate against ground truth taken from Tarbuckle's own prompt text rather than from the function under test, `human_turns()` misclassifies in **both** directions. The repair was gated on this check and did not proceed.
**Filed as an item, not as `PENDING-178 ADDENDUM 1`, for the reason PENDING-145 documents.** These returns are expected to become -178's conditions for reconsideration. `ruled_pendings` claims a *number*, so the moment -178 is ruled DEFERRED every record under that number is suppressed on arrival — the demonstrated PENDING-131 ADDENDUM 4 case. An addendum here would be invisible exactly when it is needed. ⚠ **The parent relation is therefore carried in prose and not by numbering: this item is -178's evidence, and disposing of -178 without it would dispose of the reason.** ⚠ **The new-item moratorium is proposed and undecided.** This filing is instructed by the 2026-09-03 handover, not executor initiative; the 2026-09-03 wrap declined to file a related finding precisely to avoid the executor voting on its own moratorium, and that reasoning is not disturbed here. If the steward would rather this were an addendum, it converts cheaply.
**GATE 2a — FAIL in both directions.** Ground truth: a transcript is a mumble iff its first `user` record begins with a Tarbuckle generation prompt. That predicate is written by `tarbuckle-*.py`, not by the discriminator, so it is independent of what is being tested. Over all 65 transcripts in `~/.claude/projects/-Users-davidglidden` on 2026-09-03: **24 known mumbles, 41 known non-mumbles.**
· **must-detect: 22 of 24.** Two known mumbles return `human_turns == 1` and read as human-attended.
· **must-not-flag: 35 of 41.** Six known non-mumbles return `0`. ⚠ **At least one of these is CORRECT and that is the deeper point:** `b7e7eb39` is the unattended executor session of 2026-08-31 (PENDING-172), which genuinely has no human turn. `human_turns == 0` means *"no human spoke"*, which is true of mumbles **and** of unattended real sessions. It was never a mumble predicate and does not become one by being used as one.
**The mechanism, confirmed exactly rather than inferred.** `human_turns()` skips any `user` record containing one of seven `NONHUMAN` markers (`<command-name>`, `<local-command-`, `=== WAKE DIGEST`, …). A mumble prompt embeds the *previous session's* material, so it inherits those markers **only when the session it was mumbling about happened to contain a slash command.** Of the 24 known mumbles, 22 embed at least one marker and are excluded; **2 embed none — and those 2 are precisely the 2 that leak.** The correlation is content-dependent coincidence, not design. The function's own docstring says it answers *"did an executor just run unattended?"*; that is what its three controls test, and it does that job correctly.
**⚠ The finding is about the control set, not only the function — which is why no replacement was built in this session.** Three controls passed (`hook-only fixture -> 0`, `real human turn -> 1`, `unreadable -> None`) and a fourth broke. All three were written against the question the function was *for*; none could see the question it was being *reused* for. A replacement discriminator written under the momentum of that break would inherit whatever made the first set look sufficient. Per the handover, sites 2-4 are gated shut and stay shut.
**GATE 2b — the composition claim STANDS; two terms do not close, and the disagreement is reported rather than resolved.** Reconstructed from `~/_Dev/claude-transcript-archive` (76 files) by the same ground-truth signature:
· **2026-08-31: N=52, real=41, mumble=11 — 21.2%.** The mumble term and the *"roughly a fifth"* proportion close exactly against -178 as filed.
· **2026-09-01: N=50 = 39 real + 11 mumble**, against -178's enumerated **54 = 43 + 11**. The mumble term is identical; the whole 4-file gap sits in the real-session term.
· ⚠ **The gap is not asserted against -178.** My reconstruction models the harness prune as a 30-day window over `source_mtime` and is a **lower bound**: preservation has run **twice only** (2026-08-26, 2026-09-03), so anything created and pruned between runs is invisible to it. -178 enumerated live on the day. **Where they disagree, -178's method is the better-positioned one and this item does not overturn it.**
· **Contradicted, and this is the record-only correction:** `MEMORY.md` carried **"N-now 44/84 as of 2026-08-31, DOWN 7 from 51 on 08-25"**. 44 is below the lower bound for that date (52) and the 08-25 figure sits below the archive's floor. **Measured live 2026-09-03: N=65 = 41 real + 24 mumble (36.9%).** The index has been corrected to the measured figure only.
**GATE 2c — five sites in four files; -178's `[HARDENING]` scope HOLDS.** Census run with its controls named before execution (three must-find consumers, one must-not-find noise term). Files that *enumerate* the directory: `governance-drift-check.py` (426, 513) · `wake-digest.py` (354, and the selftest sample) · `tarbuckle-invoke.py` (37, 44) · `preserve-transcripts.py` (45, 126, 172). **No additional consumers found.** The other fool surfaces are not consumers: `tarbuckle-wrap.py` and `tarbuckle-seam.py` receive `transcript_path` from the hook payload and never list the directory.
**⚠ WHAT THE CENSUS CANNOT SEE — stated as output, not as a caveat.** It is blind to (i) a consumer that builds the path by component join *and* never enumerates `*.jsonl` on a matching line — **demonstrated, not hypothesised: `wake-digest.py` builds `os.path.join(HOME, ".claude", "projects", …)` and escaped a literal-fragment grep over the whole fleet**; (ii) any consumer reaching the population through a hook-supplied `transcript_path`; (iii) anything outside the swept roots. **(i) is PENDING-171's predicate class at a further site, and it applies retroactively to the four-site census in the 2026-09-03 ADDENDUM 1, which was grep-derived by exactly the method shown here to be blind.** The site list should be read as *at least* these, never as *these and no others*.
**Options:**
- **(a) Nothing.** Leave `human_turns()` in place at its one correct site and abandon the reuse. The four sites keep counting mumbles as sessions.
- **(b) A shape predicate at each site** — first `user` record begins with a fool prompt. This is what ground truth used here, so it is known to work today; its predicate is the fool's own prompt text, which drifts, and it is -178 option (b) at four sites instead of one.
- **(c) Have the fool mark its own transcripts** — an explicit field written at generation, so the discriminator stops inferring from content. Touches the fool's wiring on behalf of governance instruments (the PENDING-152 coupling), but it is the only option where the signal is *declared* rather than *recovered*.
- **(d) -178 option (d): a monotonic counter maintained by wake/wrap.** Fixes unit and window together for the counting site; does **not** by itself fix `previous_transcript`, the selftest sample, or `newest_transcript()`, which need a per-transcript predicate regardless.
**Recommendation: (c) for the predicate, and it does not substitute for -178's own ruling.** (b) is available immediately and is what any repair would use in the interim; (c) is the durable form because a mumble that must announce itself cannot be misread by a marker coincidence. ⚠ **Neither is proposed for building now** — the same reasoning that stopped the replacement discriminator applies to its successor: the control set that missed this is the thing to fix first, and that is a design question, not a patch.
**Files affected:** none changed. `~/dotfiles/scripts/wake-digest.py` (`human_turns`, `previous_transcript`, selftest sample), `~/dotfiles/scripts/tarbuckle-invoke.py` (`newest_transcript`) are the sites a ruling would touch; `governance-drift-check.py:513` is **excluded on receipt** by the handover and was not examined for repair.
**Awaiting:** Steward authorization. ⚠ **Nothing is blocked on this:** step 0 (preservation) ran and the evidence is safe; the repairs are correctly stopped, not stalled.