A canon is only urged on a shape that could be it, the ledger can say what looks wrong, and the agent is the judge #173
Merged
bvandeusen
merged 7 commits from 2026-09-20 23:07:04 -04:00
dev into main
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e87bcfa48c |
fix(guidance): the index had two characters of headroom, and I spent 391
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 44s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / Python tests (push) Successful in 1m37s
CI & Build / Build & push image (push) Successful in 25s
CI 7098: unit tests red, everything else green. `_INSTRUCTIONS` was 2439 against a 2000 budget. WHAT I DID NOT CHECK. That block is capped because Claude Code injects only the first ~2,048 characters of a server's instructions and cuts the rest mid-word (#2562, observed live — a 20k version delivered ~10% of itself and the Systems guidance never reached a session). The cap is stated in a comment directly above the literal I edited. It was at 1998/2000 before this batch: a shared, nearly-exhausted resource, and I added a six-line entry to it. THE JUDGE LINE STAYS, and paying for it is the decision rather than dropping it. A client with no Agent Skills support receives this index and nothing else, so of everything here, "you are the judge of record" is among the least safe to leave past the fold — an agent that never learns it defers every call to an operator who was never going to make them. So the line is earned by compressing prose AROUND the existing markers, not by removing anyone's entry: RULES loses a clause, RECORD and REPORT lose trailing restatement, PLAN drops a sentence the two markers already imply, and the opening paragraph tightens. Every index marker the ownership registry requires survives verbatim — that is what test_the_index_names_each_reflex_it_points_at checks, and it passes. Back to 1998/2000: the same headroom as before, with one more reflex indexed. The next addition pays the same way. Three guidance modules run green locally (21 tests) — they read files and need no database, so this one did not have to go to CI to be known. Plugin version re-minted; the previous mint is on a commit that never went green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy |
||
|
|
76bf21633e |
feat(guidance): the agent is the judge — stated in the product, not in a rule
CI & Build / Python lint (push) Successful in 8s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 56s
CI & Build / Python tests (push) Failing after 1m10s
CI & Build / Build & push image (push) Skipped
I recorded this as project rule 174 first. That was wrong twice over, and the second reason is the one that matters. RULE 119 SAYS THIS EXACTLY: guidance about how an agent should behave with Scribe belongs in `_INSTRUCTIONS`, `plugin/skills/*` or the adapter's static context, never in the corpus. I read 119 while writing the rule, decided it was "about authority rather than about using Scribe", and wrote it anyway — which is the reasoning preference 29 exists to catch, performed in full. THE REASON THAT MATTERS: a rule in the corpus is true on ONE install. If the agent being the judge is how Scribe works, every install gets it or none does. Baked in, it ships. As a rule it was one operator's private note about a product stance. WHAT IT SAYS. The agent is the judge of record for the work — what a shape is, whether a finding holds, whether something is done. Surfacing a finding for the operator to rule on is the judgment NOT made, however well written up: it reads as diligence and functions as a backlog. Escalate the acts that are genuinely theirs — their money, their infrastructure, anything hard to reverse or facing outward — and keep the decisions. A hard call is still yours; an irreversible act is still theirs. And the half that keeps this from becoming the previous defect: JUDGING IS ATTENDED. An agent reading evidence and recording why is judgment; a threshold or a sweep reclassifying in bulk with nobody reading is the thing that fills a ledger with confident nonsense (#4208, and Portal's 35 rows). When the fix for bad unattended writes is another unattended write, stop. THE PRODUCT WAS TEACHING THE OPPOSITE. reporting-back's Finding row read "Symptom · Cause · Size of the fix · **Offer to fix it**". So the behaviour I was corrected for is the behaviour the skill prescribed — which is the better argument for fixing it here than any rule could be. Three surfaces, per 119 and the ownership registry (#4027): `_INSTRUCTIONS` gets a one-line JUDGE index entry; using-scribe owns the authority and the attended/unattended distinction; reporting-back owns the report shape. Two topics rather than one, registered separately in test_guidance_ownership so trimming one cannot quietly take the other. Plugin version minted — skills only reach a session when the manifest moves (#2209). Rule 174 deleted (trash 074434a2, recoverable). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy |
||
|
|
2be17828a9 |
fix(ledger): live_rows_for called access with nothing in scope (#4208)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 45s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / Python tests (push) Successful in 1m39s
CI & Build / Build & push image (push) Successful in 29s
Lint caught an F821 that would have been a NameError the first time `stamps_to_review` was called: `access` is imported locally inside each of the seven functions in this module that need it — services/access reaches back here, so a module-level import closes a cycle — and the new function used it without one. I wrote the function by pattern-matching its neighbours and did not check what those neighbours do to make themselves work. Same shape as the tuple unpack two commits ago (#4207): the mistake is not in the logic I was thinking about, it is in the surrounding contract I did not read. Unit and integration were both green on the failing run (7095); only lint was red. Worth recording because the lane that caught it is the cheapest one and I had read its command as covering tests — `ruff check src/ scripts/` does not look at tests/ at all, so a clean test suite says nothing about it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy |
||
|
|
400253d039 |
feat(ledger): the ledger can say "these look wrong" without acting on it (#4208)
CI & Build / Python lint (push) Failing after 3s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / integration (push) Successful in 1m6s
CI & Build / Python tests (push) Successful in 1m49s
CI & Build / Build & push image (push) Skipped
THE HALF THAT WAS MISSING. #4204 put a floor under what the write-path hook may assert. A floor only guards new writes; every row already stored stands (lesson #4202). Measured after that fix shipped: Portal carried 32 rows under one canon and 3 under another, all stamped on scores of 0.69-0.77 — below the 0.80 floor, so none of them could be written today, and all of them were still there. Scribe's own ledger carries 334 under #2860. `stamps_to_review` reports two things and changes nothing: weak — rows the hook stamped on a resemblance below the current floor, each with its score, signature and derived form. incoherent — canons whose own judged rows do not agree on a form. A canon claims some shapes are the same sort of thing; when its members are a class, three getters and a dozen tests, that claim has stopped being true and every base-rate reading built on it is reading noise. `canon_form` already made such a canon fall silent — nothing made it VISIBLE. IT DELIBERATELY CANNOT FIX ANYTHING, and that is the design, not an omission. The first version of this commit was an automatic sweep that reset rows by score. That is the original defect pointed the other way: what harmed the ledger was not one wrong score, it was a machine recording permanent classifications unattended. Un-recording them unattended is the same act with a wider blast radius. An agent reads the evidence, judges, and records the judgment under its own name through `classify_shapes`. `test_the_service_carries_no_machinery_for_bulk_withdrawal` asserts that structurally, so the next person to reach for an auto-retire has the argument again on purpose rather than in a diff nobody reads. A JUDGMENT IS NEVER LISTED AS WEAK, whatever its age. This is the measured correction to an assumption I nearly shipped: of Scribe's 334 rows under #2860, 302 are in `services/` — the canon's own home — and the ones sampled there are `classified_by="audit"` with no score at all. The legitimate bulk of that canon was never scored; it was judged by an agent in batch. Listing those as weak would invite an agent to withdraw the only real judgments in the ledger. An agent's decision is a different KIND of evidence, not a worse one. THE SCORE NOW HAS A PARSER. It lived only inside a prose sentence, so nothing could ask how strong the evidence for a row was without re-deriving it — which is how 32 rows sat unexamined for nineteen days. Format and reader are one constant apart (`_RESEMBLE_REASON` / `stamp_score`), with a round-trip test and a test pinned to reason strings taken verbatim from the two poisoned ledgers. `live_rows_for` is `live_rows` behind the project read gate, for callers that arrive from outside rather than from a job that already knows who is asking. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy |
||
|
|
d5b46ffc45 |
fix(ledger): the in-play tuple widened and one consumer kept reading four (#4204)
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Python tests (push) Successful in 1m42s
CI & Build / Build & push image (push) Successful in 26s
[s_id for rank, _at, s_id, _why in bucket if rank == 2]
ValueError: too many values to unpack (expected 4, got 5)
`in_play` gained the canon's form as a fifth element so the stamp could be
decided per shape rather than per file. The `record_uses` call eighty lines
below still destructured four, and every stamp that reached it raised.
Both integration failures on runs 7090 and 7091 are this one line —
`test_write_path_stamp_is_evidence_that_yields_to_judgment` and
`test_a_brand_new_shape_gets_a_provisional_row_the_sync_settles`.
Now indexed rather than destructured, matching the candidate scan above it, so
the next widening cannot break it positionally.
WHY THE UNIT LANE STAYED GREEN THROUGH TWO PUSHES. `record_uses` is only
reached once a stamp is actually written, which needs a real snippet, a real
ledger row and the write ACL — so no unit test crosses that line. 139 local
assertions and the whole unit suite passed on code that raised on every
successful stamp. The integration lane was the only thing that could say so,
which is the case for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
|
||
|
|
a4883c8ac1 |
fix(ledger): divergence asks at family level — the first gate was inverted (#4204)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Failing after 56s
CI & Build / Python tests (push) Successful in 1m29s
CI & Build / Build & push image (push) Successful in 28s
CI run 7090 caught this; the unit suite could not. Integration job 25763 — `test_a_second_confirm_dialog_is_detected_and_named`, the acceptance case of milestone #2793. WHAT I GOT WRONG. The previous commit gated BOTH halves of the ledger on `forms_agree`. That is right for stamping and backwards for divergence, because the two assert opposite things: STAMPING says "this IS that canon". Agreement in form is evidence FOR the claim, so demanding it is correct. DIVERGENCE says "this is NOT the canon that dominates here — did you mean to?" A form MISMATCH is the PREMISE of that prompt. Requiring the candidate to match the canon silences the check precisely where it belongs. So #2793's case stopped firing: a hand-rolled sync `confirmDanger` in a directory where an async confirm helper is canon read as `fn` against `async-fn`, disagreed, and was dropped. `flag_divergence` returned 0 where the test demands 1, and the write-time check returned nothing where it must name the canon. That is a real flag the milestone exists to produce, and my change removed it. THE FIX. Divergence now gates at FAMILY level — callable {fn, async-fn}, type, value, css — and only on contradiction. A sync function beside an async one is still a fair question. A frozen dataclass told to build from an async service function is not a question at all. WHAT THIS DOES NOT FIX, asserted rather than commented so it fails the day it changes (`test_how_many_of_the_five_the_divergence_gate_actually_silences`): of #4204's five false prompts this silences ONE. `Point` is a type against a callable canon. `_p`, `get_point`, `is_registered` and `sources_expected_to_emit` are callables like the canon and still ask — and at the signature level they are indistinguishable from the #2793 case above, so nothing readable here can separate them. That needs #4204 option 2 (widen `kind` past `css | sym`) or a comparison of meaning rather than form. The stamping half — `_RESEMBLE_MIN` 0.80 and the graded burden — is unchanged and unaffected by this failure. It is also the half that matters more: loose stamping is what manufactures the density the divergence check reads as authority. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy |
||
|
|
947203fa44 |
fix(ledger): a canon is only urged on a shape that could be it (#4204)
CI & Build / integration (push) Failing after 51s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Python tests (push) Successful in 1m33s
CI & Build / Build & push image (push) Successful in 25s
Two halves of one defect, found while writing #3431 and measured on a second project. THE DIVERGENCE CHECK WAS A BASE RATE. `dominant_canon` answers "what is most common in this directory" and never "is this that" — the candidate's signature was not examined at all. With `kind` carrying only `css | sym`, a frozen dataclass, a module constant, a sync predicate, a class and an async service function are all siblings, so the prior was not merely the best signal, it was the only one. Writing a registry module of pure helpers produced five prompts to build them from the `async_session` service canon. THE AUTO-STAMP HAD NO FLOOR. `elif sid in resembles` took any score at all: 0.69 asserted as confidently as 0.95, and the number went into the reason line without ever being compared to anything. Worse, the score is computed against the WHOLE PAYLOAD, so one number spoke for every symbol in the file. On Portal that recorded `class SessionAbsent`, `def build_channel`, `async def attach` and a dozen test functions as instances of one snippet — 17 rows under #3283, which then made that directory "canon-dense" and started instructing every later writer in it. The two compound: loose stamping manufactures the density the divergence check reads as authority. Both are fixed by one primitive. `shape_form` derives a coarse form — css / type / async-fn / fn / binding — from the signature, on READ. `kind` is part of the row identity, so widening that column needs a migration and a re-extract (#4204 option 2, still the principled fix); deriving costs nothing and is reversible. Every caller asks `shape_form`, so the day the column carries the answer it returns that. THE BURDEN SCALES WITH THE EVIDENCE, and getting this wrong was the first version. A by-name reference — the payload names the canon's symbol — is strong and needs only the absence of contradiction; demanding positive agreement there silenced it whenever a shape's definition was not in the payload (an Edit rather than a Write), turning strong evidence into none for a reason unrelated to the code. A resemblance score is weak and must positively agree. `canon_form` reads the form a canon's own judged rows agree on, and returns unknown when they disagree. That makes the halves cooperate: a canon already poisoned by loose stamping — Portal's #3283 — falls silent instead of flagging anyone else. `forms_agree` requires BOTH sides known, so an unreadable signature makes the checks quieter rather than more confident. `_RESEMBLE_MIN` is 0.80 rather than the retrieval floors near 0.70: those decide whether to SHOW a record, where being wrong costs a glance; this decides whether to RECORD a claim unattended, where being wrong misinstructs everyone who writes there after. Caught by the tests, not by review: a first pass required `const`/`let`/`var` before a binding, so every Python module constant read as unreadable and a whole form was silently excluded from both checks. Not addressed: rows already carrying a wrong snippet_id are not undone by a guard at the point of classification (lesson #4202). Portal's 17 keep producing dominance until something re-judges them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy |