diff --git a/PENDING.md b/PENDING.md index f954a47..f8dabc8 100644 --- a/PENDING.md +++ b/PENDING.md @@ -4906,7 +4906,14 @@ Link 1 to link 4 directly. **The trial built to test whether a fool could reach since: 2026-08-25 trigger: date 2026-09-08 owner: executor - discriminator: §8's own clause — "Report the observed mumble rate after two weeks." The + discriminator: ⚠ THE DENOMINATOR IS CONTAMINATED BEFORE 2026-08-25T17:53 and the + log carries a MARKER record at that boundary. The build session ran the status line + by hand and re-ran the seam (which resets the tick clock), so early ticks are test + artifacts indistinguishable from live ones by inspection. Found 2026-08-25 by + measuring the inter-tick gaps: 1.8, 4.7, 0.4 and 16.0 minutes against a 20-minute + interval, then 20.0, 21.0, 20.0 once the session stopped touching it. COUNT FROM + THE MARKER, and say in the report that the first 8 ticks were discarded and why. + §8's own clause — "Report the observed mumble rate after two weeks." The two-week clock starts the day the body is wired, which has not happened, so no date can honestly be written yet. `manual` was the correct recording rather than a proxy date, and this CONVERTED to `date 2026-09-08` on 2026-08-25, the day the body shipped diff --git a/scripts/tarbuckle-mumble.py b/scripts/tarbuckle-mumble.py index 60528f3..6d0f348 100755 --- a/scripts/tarbuckle-mumble.py +++ b/scripts/tarbuckle-mumble.py @@ -275,21 +275,25 @@ def acceptable(line: str, max_words: int = MAX_WORDS, def main() -> int: - if muted(): - return 1 kind = sys.argv[1] if len(sys.argv) > 1 else "aside" + if muted(): + log_event(kind, "muted") + return 1 transcript = sys.argv[2] if len(sys.argv) > 2 else "" register = soul_register() if not register: + log_event(kind, "no-soul") return 1 # no soul, no voice. Deliberately no fallback. material = session_material(transcript) if not material.strip(): + log_event(kind, "no-material") return 1 env = dict(os.environ, TARBUCKLE_CHILD="1") # precautionary; see body script try: r = subprocess.run(["claude", "-p", build_prompt(kind, register, material)], capture_output=True, text=True, timeout=120, env=env) except Exception: + log_event(kind, "generator-failed") return 1 line = (r.stdout or "").strip().strip('"').strip() ok, why = acceptable(line) @@ -306,6 +310,7 @@ def main() -> int: with open(SLOT, "w") as fh: json.dump({"utterance": line, "kind": kind, "written": int(time.time())}, fh) except Exception: + log_event(kind, "slot-write-failed") return 1 log_event(kind, "spoke") return 0 @@ -394,6 +399,18 @@ def selftest() -> int: not echoes_soul("Second surface you've found for the same voice.", _soul)) ck("A7n passes an ordinary short observation", not echoes_soul("Third one that's held together with a name.", _soul)) + # A9 — ⚠ EVERY EXIT FROM main() MUST REPORT ITSELF. Earned 2026-08-25: a live + # `aside` draw vanished with no record, because the muted/no-soul/no-material/ + # generator-failed/slot-write paths all returned silently. The rate report that + # REVIEWED-128 binds is computed from this log, so an unlogged exit does not just + # lose a datum — it makes the denominator wrong while looking complete. + _src = open(__file__, encoding="utf-8").read() + _main = _src[_src.index("def main() -> int:"):_src.index("def selftest")] + _returns = _main.count("return 1") + _logged = _main.count("log_event(kind,") + ck("A9 every silent exit reports itself", _logged >= _returns) + ck("A9n the predicate can fail", _returns > 0) + # A8 — THE JURIST'S CONDITION, structural. An accepted line has no `why`, so there # is no call from which it could be written. _r = REJECTS