From 2472ed8e880da98d555be8f6bd3eab6de1a52825 Mon Sep 17 00:00:00 2001 From: David F Glidden Date: Sat, 8 Aug 2026 18:15:20 +0200 Subject: [PATCH] =?UTF-8?q?[HARDENING]=20PENDING-126=20+=20PENDING-122=20A?= =?UTF-8?q?mendment=202=20=E2=80=94=20what=20inducing=20red=20revealed,=20?= =?UTF-8?q?and=20REVIEWED-102..105=20placed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three findings came from contact while building a red fixture for the REVIEWED-103 acceptance. None was sought; the search for a control that worked is what exposed them. The fleet already violates the condition REVIEWED-104 attached to the NEW live-binding assertion, on a dependency the ruling did not consider. Three suites crash on a gitignored corpus/index.db with a raw sqlite traceback, and run-fleet reports FLEET RED indistinguishably from a code defect — while store.py rebuilds that file in 0.628 seconds and the clone then runs 7/7 green. So the condition is retroactive, not prospective. And test_retrieve.py already detects the absence and skips with a named reason, which makes PENDING-124 recommendation (d) concrete: the honest third state exists in this fleet, in one suite, and three others lack it. R0's section_end bound is unguarded. Removing it leaves 31/31 passing. That is the rule R0 was created to establish after two consumers disagreed on 3 of 253 patterns with neither right — asserted in prose, correct-but-inert on the live corpus, and therefore invisible to every test. test_navigate crashes with StopIteration rather than naming a failure. The exit code was always right; the legibility is missing — REVIEWED-100's own distinction, recurring where its fix does not reach. Also commits REVIEWED-102 through -105, placed by the steward and left uncommitted. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01A35wiD55yRHj5U1ECZAX4t --- PENDING.md | 56 +++++++++ REVIEWED.md | 127 ++++++++++++++++++++- claude/memory/session-ledger-2026-08-08.md | 33 +++++- 3 files changed, 214 insertions(+), 2 deletions(-) diff --git a/PENDING.md b/PENDING.md index b1ad18a..726f1b9 100644 --- a/PENDING.md +++ b/PENDING.md @@ -1897,6 +1897,32 @@ The `0 of 17` → `0 of 14` figure the jurist saw is a **secondary** paragraph o --- +### AMENDMENT 2 — 2026-08-08, the condition is ALREADY VIOLATED, by a dependency the ruling did not consider + +*Found by contact while running REVIEWED-103's acceptance in a fresh clone — not sought.* + +**REVIEWED-104 §1 conditioned the NEW live-binding assertion on three states**, reasoning that *"red on absent trains people to discount fleet red, which is the worst possible outcome for this particular thread."* **That outcome is already the present state**, on a different dependency, with nothing to do with `chamber-library`. + +**Measured 2026-08-08 in a fresh `git clone`:** + +| suite | with `corpus/index.db` absent | +|---|---| +| `test_ground.py` | **crashes** — raw `sqlite3.OperationalError: unable to open database file` | +| `test_navigate.py` | **crashes** — same | +| `test_reading_index.py` | **crashes** — same | +| `test_retrieve.py` | ✅ **skips, with a named reason** | +| `test_fidelity_v3` · `test_ingest_gate` · `test_verify_quote` | pass (no dependency) | + +`run-fleet.sh` reports **FLEET RED**, indistinguishable from a code defect. + +**`corpus/index.db` is gitignored on purpose** — the engine's first law is that *the files are authoritative; every index is derived, subordinate, and disposable.* And the disposal is real: **`python3 engine/store.py build` rebuilt it in 0.628 s**, after which the clone ran **7/7 green**. So this red is a **0.6-second-avoidable environment condition, reported as a failure.** + +**Three consequences.** **(1)** The condition ruled here is **retroactive, not prospective** — three suites need `bound`/`drifted`/`cannot-assess` today, before any live-binding assertion exists. **(2)** ⚠ **The honest third state ALREADY EXISTS IN THIS FLEET, in one suite:** `test_retrieve.py` detects the absence and skips with a named reason. **That is PENDING-124 recommendation (d) with a live in-repo precedent** — generalize what is implemented rather than mint doctrine beside it. **(3)** **A crash is not a third state.** REVIEWED-100 made every suite name its failures in the summary; an uncaught traceback bypasses that, so these three are invisible to the improvement meant to cover them. + +**Files affected (revised):** three suites gain the detect-and-report shape `test_retrieve.py` already has; `scripts/run-fleet.sh` must render `cannot-assess` distinguishably from red. + +--- + ## PENDING-123 — The pre-commit hook cannot distinguish "nothing to check" from "I am disarmed" **Date:** 2026-08-08 @@ -2046,3 +2072,33 @@ Three cases, all discriminated: a rule ran → existing output already says so, **Awaiting:** Steward authorization (D-1 lane). --- + +## PENDING-126 — Two holes in the fleet, found by inducing red against it: an untested load-bearing rule, and a suite that crashes instead of failing + +**Date:** 2026-08-08 +**Tag:** [HARDENING] +**Related:** REVIEWED-103 (whose acceptance surfaced both) · REVIEWED-100 (the failure-naming improvement hole 2 bypasses) · `studium-engine/docs/spec/r0-reading-index-contract.md` §3 · PENDING-122 Amendment 2 (same act, third finding). +**Provenance:** neither was sought. Both surfaced while trying to build a red fixture the fleet would catch — **the search for a working control is what exposed them**, the discrimination gate doing its job one level out. + +**Hole 1 — R0's `section_end` bound is not covered by any test, and it is the rule R0 exists for.** `engine/reading_index.py:123` reads `it["line_end"] = min(nxt, section_end) if end is None else min(end, section_end)`. **Removing the `section_end` bound entirely leaves `tests/test_reading_index.py` at 31/31 passing** and the whole fleet green. That bound is not incidental: R0 was created because `measure_rerank.py` and `navigate.py` had each grown their own reader and **disagreed on 3 of 253 Alexander patterns with neither right** — one ran a pattern into the next group, the other into ACKNOWLEDGMENTS. The derived rule *"end = min(next sibling's start − 1, containing section's end)"* is the fix. **It is asserted in prose and unguarded in code.** ⚠ Likely cause: the live corpus never exercises the branch, so the bound is **correct-but-inert**, and a regression would surface only on a corpus shape we do not yet hold. + +**Hole 2 — `test_navigate.py` crashes rather than naming a failure.** Forcing `citable = False` at `engine/navigate.py:189` produces an uncaught `StopIteration` at `tests/test_navigate.py:116`. Exit is non-zero, so the fleet correctly goes red and the commit is correctly refused — **but the failure is a traceback, not a named check.** REVIEWED-100's improvement was that *"all seven suites now name failures in the summary"*; a crash bypasses the summary entirely. ⚠ **The exit code was always right; the legibility is what is missing** — the same distinction REVIEWED-100 drew, recurring where its fix does not reach. + +**Rationale.** Both holes are invisible to a green fleet by construction, and the trigger landed today makes the fleet the gate on every `engine/` and `tests/` commit. **A gate is only as good as the suites behind it**, and these are two measured ways those suites say less than their green implies — the PENDING-96 family, now inside the fleet rather than around it. + +**Options.** +- **(a) Fix both.** A fixture exercising the `section_end` bound (necessarily synthetic — the branch has no live instance), and a guarded lookup in `test_navigate.py` that fails by name instead of raising. +- **(b) Fix hole 2 only.** Cheaper; leaves a load-bearing derived rule unguarded. +- **(c) Census first.** ⚠ Neither hole was sought, so **the base rate is unknown** — how many other asserted-in-prose rules are unguarded, and how many suites crash rather than name? + +**Recommendation: (a), then (c) as a bounded sweep.** (a) closes what is measured; (c) is the honest follow-on because **two holes found without looking is not a base rate**, and the census is bounded (7 suites; the contracts are enumerable). + +**Check that it worked — both directions required.** Hole 1: the new fixture must go **red** with the bound removed and **green** with it restored — the removal is already proven invisible, so that is the discriminating negative, real and available. Hole 2: the induced citability break must produce a **named** failure in the summary and still exit non-zero; the restore must return 34/34. + +**⚠ What this does not establish.** Fixing these two says nothing about the class (option c). And hole 1's fixture is necessarily **synthetic** — the live corpus has no instance of the shape, which is exactly why the gap survived. + +**Files affected:** `~/_Dev/studium-engine/tests/test_reading_index.py`, `~/_Dev/studium-engine/tests/test_navigate.py`. + +**Awaiting:** Steward authorization (D-1 lane). + +--- diff --git a/REVIEWED.md b/REVIEWED.md index 9e71dfc..aebef04 100644 --- a/REVIEWED.md +++ b/REVIEWED.md @@ -1168,4 +1168,129 @@ data in the ratified spec, thin consumer in dotfiles — the pattern REVIEWED-10 → (a) → (c). Tag commits with REVIEWED-101. The spec amendment gates (a), not (e): (e) reads the engine's own manifest and sidecars and needs no cross-repo enumeration. Each stage carries a both-directions control — drift reported when a binding is stale, clean reported on a -genuinely clean corpus. \ No newline at end of file +genuinely clean corpus. + +## REVIEWED-102 — PENDING-119 — REVIEWED-101 condition 6 placed (e)'s consumer in dotfiles, on reasoning the same ruling says (e) does not engage +**Date:** 2026-08-08 +**Decision:** AUTHORIZED — option (i), with condition 6 of REVIEWED-101 explicitly narrowed on the record, and one condition on (i). + +**Notes:** The ambiguity is mine. Condition 6 was drafted flat, under a heading that read "Placement", and left its scope to be inferred from a +sentence four lines below it. That is a jurist defect, not an executor misreading. The item is right that the two texts admit both readings, and +right that the narrow reading is the one the reasoning supports: condition 6's argument is ownership of a cross-repo invariant, and (e) does not +enumerate one. It follows the engine's own declared pointers into the chamber; the surface list that (a) needs is precisely what (e) does not need. +That was already the stated basis for severing (e) from the spec amendment, and the same severance carries the placement. + +1. NARROWING, recorded as a ruling and not as a charitable reading: condition 6 of REVIEWED-101 governs consumers that must ENUMERATE the +cross-repo binding surface — (a) and (c). It does not govern (e). Left unrecorded, the next reader relitigates this. + +2. The recorded reason for rejecting (ii) is that it inverts the condition it honours. A ~/dotfiles/scripts/check-source-binding.sh whose body is +one exec of engine/ingest_gate.py --check-only puts an engine path and an engine flag into the global layer. That is the coupling REVIEWED-100 +rejected, reintroduced in the name of a condition written to prevent coupling. A rule that produces the outcome it exists to forbid is being read +at the wrong grain. + +3. Kept in view, because the reflex runs the other way: the placement question only became live because the executor delegated to ingest_gate +instead of writing a fresh sha-comparing script. Had it duplicated the logic, condition 6 would have been straightforwardly correct. The better +implementation is what made the condition misfit — a rule's misfit is not automatically an implementation error. + +4. The fleet-census finding is SPLIT OUT as PENDING-122. "No fleet suite validates live binding" is not evidence for a placement dispute; it is a +standing correction to what fleet-green certifies, owed to anyone reading a green fleet. Filed inside PENDING-119 it dies if PENDING-119 is +deferred. Same objection made at REVIEWED-101 condition 5; consistency requires making it again. It belongs with PENDING-96 as one family — a green +that attests less than its surface suggests. + +5. CONDITION on (i): 0.218 s over 14 sources is honest about being burst-sized, and the check is unconditional on every commit, scaling with +sources × file size. State the threshold now with its action — when it exceeds ~1 s, (e) re-scopes or hands off to (a)'s scheduled job. A +per-commit cost that grows unremarked converts a tripwire into a --no-verify habit, which is this thread's own failure class arriving by the back +door. + +**If AUTHORIZED:** Proceed with (i) — one line in the engine's .precommit-triggers. Record the condition-5 threshold beside it. Tag commits with +REVIEWED-102. + +## REVIEWED-103 — PENDING-120 — The fleet trigger covers corpus/ but not the engine code the fleet exists to test +**Date:** 2026-08-08 +**Decision:** AUTHORIZED — option (a), with the scope framing corrected and the acceptance fixture decomposed. + +**Notes:** The demonstration is the strongest part: eecc8bb touched the gate and the gate's own test floor, and the hook printed nothing. First +real non-probe commit since the trigger landed, and it ran nothing. That is a measured failure, not a reasoned risk — the same standard +REVIEWED-100 credited PENDING-116 for. + +1. CORRECTION to the scope-honesty note. The item says REVIEWED-100 "did not rule the pathspec". As to the ruling, that is accurate — REVIEWED-100 +authorizes the mechanism and the both-halves acceptance and says nothing about paths. But PENDING-116's own Costs section does: "the trigger paths +must be scoped tightly (corpus/, corpus/sidecars/) so ordinary docs commits do not pay it." So the pathspec was not silence — it was a cost +commitment in the authorized item. This does not change the outcome; it changes the bar. The widening must be SHOWN to preserve the discrimination +that commitment bought, which is exactly why (b) is correctly rejected and (a) is not. Record it as revising a stated cost-control with its +justification intact, not as filling a gap. In-scope repair is the more comfortable framing and the less accurate one. + +2. The acceptance test DECOMPOSES; one fixture cannot meet "neither may be a synthetic probe if a real one is available". (i) Fires on a real +engine change — replay eecc8bb against the widened pathspec; genuinely real, genuinely available, and it is the commit that demonstrated the gap — +but eecc8bb was green, so it proves firing only. (ii) Refuses on red — requires an induced red unless history holds a real red engine/ commit; if +one exists use it, and if not, say the fixture is synthetic rather than letting "real fixture" cover both halves. (iii) Docs-only still runs +nothing — unchanged, and the half that proves discrimination. + +3. The adjacent gap was checked and the answer is NO; filed as PENDING-123. The hook does not distinguish "no trigger path matched" from "the +declaration is malformed or its command is missing". Five disarming faults were tested against a positive control, each staging a real corpus/ +change the hook must catch: pathspec typo, no separator, empty command, comments-only, empty file. All five silent, all exit 0. A typo disarms the +gate permanently and invisibly. This is also why eecc8bb running nothing went unremarked — its output is byte-identical to a fully disarmed hook's. + +4. INTERACTION with PENDING-119. If both land, .precommit-triggers carries two lines with overlapping paths and engine commits pay ~2 s (fleet) + +0.218 s (binding). Declare the order in the file so a red is attributable to one check without reading both. + +**If AUTHORIZED:** Proceed with (a) — corpus/ engine/ tests/ scripts/run-fleet.sh. Land the three-part acceptance separately, labelling the +induced-red fixture synthetic if no real red engine/ commit exists. Tag commits with REVIEWED-103. + +## REVIEWED-104 — PENDING-122 — What a green fleet certifies, and what it does not: no suite validates live binding +**Date:** 2026-08-08 +**Decision:** AUTHORIZED — option (a), with a required third result state. + +**Notes:** A live-corpus assertion makes one suite depend on chamber-library being present and reachable. Every other suite builds under tmp and is +portable; this one will not be. The item does not say what happens on a fresh clone with no chamber beside it, and both obvious answers are wrong. +Red on absent is a suite going red for reasons unrelated to the code under test, which trains people to discount fleet red — the worst possible +outcome for this particular thread. Skip on absent is the silent net, in the very assertion added to correct an overstatement. + +1. CONDITION: three states — bound / drifted / cannot-assess — and cannot-assess must be distinguishable in the fleet summary, never folded into +green. A green fleet that includes an unassessed binding case is the same overstatement one layer along. + +2. The REVIEWED-83 A1 leg of the analogy was challenged as uncorroborated and has been verified against ~/REVIEWED.md, which is authoritative. +REVIEWED-83 Amendment 1 (2026-08-01) IS the classifier layer-error: "The classifier's controls exercise its decision rule … They cannot test +whether three signals are enough … In both cases the control was correct and sat at the wrong layer." The 0 of 17 → 0 of 14 figure visible in +e341242 is a secondary paragraph of that same amendment, labelled "Consequential correction, routed not applied." Third subsystem stands on checked +ground, and the amendment itself names the first two as one shape. + +3. Does not prejudge PENDING-121, confirmed from both sides. Option (a) closes the distance between "the gate works" and "the corpus is bound" at +whole-file granularity only; anchor correctness is 121's gate and the two land independently. Recorded with it: REVIEWED-101 §C's "fourth surface — +the reading index carries no hash" was wrong, and 121 corrects it — the runbook binds the index outward by source_sha256, and the real gap is +finer and worse. + +4. The three-state requirement is raised to doctrine as PENDING-124 rather than conditioned again per item. + +**If AUTHORIZED:** Proceed with (a), three-valued. Tag commits with REVIEWED-104. Land after REVIEWED-105 if both are authorized. + +## REVIEWED-105 — PENDING-123 — The pre-commit hook cannot distinguish "nothing to check" from "I am disarmed" +**Date:** 2026-08-08 +**Decision:** AUTHORIZED — (b) now, plus (e) in place of a flag; (a)'s full listing retained on --verbose. + +**Notes:** The strongest item of this set. Positive control first, six rows, mechanism read from the hook's own source down to the two lines that +swallow the difference — `[ -n "$cmd" ] || continue` and the `2>/dev/null` that erases "git could not resolve this pathspec". And it answers +PENDING-120 §C: "Running pre-commit checks…" with nothing after it is what a fully disarmed hook prints, which is why eecc8bb went unremarked. + +1. Instead of (a) behind a flag — a flag nobody sets is a capability nobody has — option (e): print the per-rule line ONLY when a +.precommit-triggers file exists and no rule matched. A rule ran, existing output already says so; nothing matched, one line naming the declared +rules and the staged paths; no triggers file, print nothing, so no noise in any other repo. Zero cost in the normal case, and the line appears in +exactly the ambiguous case. It also partly closes the fifth silence: a typo'd corpuss/ shows as a declared rule that did not match on a commit that +touched corpus/, catchable at the moment the reader is already looking — PENDING-98's mitigation shape, not a log. Keep (a)'s full per-rule +listing on --verbose for the never-yet-matched rule, which stays honestly unknown. + +2. The item's own standard, turned on the item: the summary claimed silence when the declaration is "malformed, mis-typed, empty, or absent", and +the table had no absent row, nor one for the hook itself missing or core.hooksPath unset. Rows added. Measuring them found something stronger than +the claim — with the hook file missing, the commit produces ZERO output, not an ambiguous silence — and found the executor wrong in the other +direction: unsetting core.hooksPath locally falls back to an armed global, so that row is a robustness property, not a fault, and is recorded as +one. + +3. Blast radius of (b). The hook is global, so a refusal on malformed declarations arms in every repo carrying a triggers file, present and future. +Censused 2026-08-08: exactly one exists today (studium-engine), across ten repos under the global hooksPath. Today's radius is one repo; the +condition is about the future and stands. Required: the refusal message names file, line number and fault, and states --no-verify. A gate that +blocks without saying why is replaced by habit within a week. + +4. SEQUENCING across the four open items: land 123 before 119(i) and 120(a). Both add lines to .precommit-triggers; a validator that catches a +malformed line should exist before the file grows. In the other order, the first thing to test the new declarations is the declarations themselves. + +**If AUTHORIZED:** Proceed with (b) + (e). Build order: REVIEWED-105 → REVIEWED-102 (i) → REVIEWED-103 (a). Tag commits with REVIEWED-105. \ No newline at end of file diff --git a/claude/memory/session-ledger-2026-08-08.md b/claude/memory/session-ledger-2026-08-08.md index e06dc00..cb66cda 100644 --- a/claude/memory/session-ledger-2026-08-08.md +++ b/claude/memory/session-ledger-2026-08-08.md @@ -5,7 +5,7 @@ metadata: node_type: memory type: feedback originSessionId: 7d08dad4-626a-484c-870b-8f1a9674db7a - modified: 2026-08-08T13:30:54.198Z + modified: 2026-08-08T14:45:17.679Z --- # Session Ledger — 2026-08-08 @@ -180,6 +180,37 @@ the omission is itself the finding above.* work; it is that I reach for a check whose vocabulary I have not verified against the thing it must match** — the substrate-vs-description class, pointed at my own instruments. +### ⎯ SESSION 3 authorization moves ⎯ + +- **REVIEWED-101 (e) — engine half BUILT** (`eecc8bb`), wiring deliberately **NOT** placed. + Built as a **delegation** to `ingest_gate.py`, which already enforced §1.1 on both + named surfaces. Suite 24 → **41**; fleet **221/221, 7/7 exit 0**. +- **PENDING-119** filed `[PROPOSAL]` — condition 6 vs the ruling's own "(e) needs no + cross-repo enumeration". **Steward directed: file, do not wire.** +- **PENDING-120** filed `[HARDENING]` — the trigger's pathspec misses `engine/`+`tests/`. + **Steward directed: file separately.** +- **PENDING-117 AMENDMENT 2** appended — pointer only, nothing above altered. +- Commits: engine `eecc8bb`, `f1bb227`; dotfiles `02c6875`. **Neither repo pushed** — + engine `ahead 2`, dotfiles `ahead 1`; the push belongs to the wrap. + +- **T~15:5x — the day's error class has a mechanical root, found at last.** `ls` is + **aliased** in this profile (eza-like; it rejects a bare path as an `--icons` value). + Instance (7) above was never a missing file — the alias ate the argument and returned + exit 2. Switched to `command ls`. Worth keeping: **an exit code from an aliased command + is evidence about the alias, not about the world.** Two of the day's seven check-errors + reduce to this one shell fact. +- **T~16:0x — an eighth, caught and not narrated:** read `${PIPESTATUS[0]}` after an + intervening `echo` had already clobbered it, printing `exit=` empty. Re-ran clean. + Same family: **a check whose vocabulary I had not verified against its own mechanics.** + +- **What held, this session.** Every claim that entered a commit message or a governance + filing was substrate-checked first: the fixture-only census by enumerating all six + invocations rather than grepping; the delegation argument by reading L128–131/L150–153; + the write hazard by reasoning about index-vs-worktree and then *not* asserting it + untested; byte-identity of the refactored write by an empty `git diff`; the red + direction on the **real** manifest with a `cmp`-verified restoration. **The errors + stayed in throwaway checks; nothing false reached a durable surface.** + ## Sub-agent dialogues ## Bypasses