Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4844c2e
docs(design)+tooling(differential): the worker pass is sub-second, no…
derek73 Sep 3, 2026
40c82a7
docs(design)+tooling(differential): one dated home for the run figure…
derek73 Sep 3, 2026
9cf544b
tooling(differential): restore the baseline: anchor the twin comments…
derek73 Sep 3, 2026
6db7cd6
tooling(differential): a recorded diff shape the run can contradict
derek73 Sep 3, 2026
a82f73e
tooling(differential): pin the order filter in both directions
derek73 Sep 3, 2026
255ef7a
tooling(differential): say what `compared` holds, and what it does no…
derek73 Sep 3, 2026
9302dac
tooling(differential): the caller's half reads the PRE-skip list
derek73 Sep 3, 2026
962ae43
tooling(differential): the recorded shapes move where the run can see…
derek73 Sep 3, 2026
b793ebc
tooling(differential): four rows pinned a race with one runner
derek73 Sep 3, 2026
95727aa
tests(ledger-guards): point the empty 2.x sections at the open question
derek73 Sep 3, 2026
a5b87c0
tooling(differential): the run refuses a recorded shape it contradicts
derek73 Sep 3, 2026
e1f81c6
tooling(differential): a stale roster row must not eat the report
derek73 Sep 3, 2026
afa7f14
tooling(differential): one home for the measurement, both files in th…
derek73 Sep 3, 2026
22d6b9d
docs(differential): a moved shape names no cause in the README either
derek73 Sep 3, 2026
22e3b24
tooling(differential): keep the findings, drop the digits that rot
derek73 Sep 3, 2026
8593f3a
tooling(differential): the three the first sweep identified and skipped
derek73 Sep 3, 2026
d9c3bd8
docs(design): the recorded-shapes arc
derek73 Sep 3, 2026
f70a0ad
tooling(differential): two claims the sweep did not reach, in its own…
derek73 Sep 3, 2026
a9bb77a
docs(design): the sweep-decline figure drifted before the PR opened
derek73 Sep 3, 2026
9360919
tooling(differential): the window between the two recorded-shape halves
derek73 Sep 3, 2026
02ecdd5
docs+tooling(differential): the prose this PR made stale about itself
derek73 Sep 3, 2026
17a9e66
docs+tooling(differential): five claims this branch made stale or mis…
derek73 Sep 3, 2026
b3f03cf
docs(agents): the unreachable ledger gets an owner (#503)
derek73 Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,32 @@ uv run sphinx-build -b html docs dist/docs
# uv run python tools/differential/compare.py --baseline 1.4.0 # the v1 compat contract
# uv run python tools/differential/compare.py # the previous minor
# uv run python tools/differential/compare.py --baseline 2.0.0 # 2.0.0's ledger has rules too
# NOTE (2026-09-03, #503): that is THREE commands against FOUR ledgers
# with rules -- `--baseline 2.1.0` is the one no line above reaches, so
# the "EVERY baseline" instruction is not what this list does. Since
# #497 a third check is per-ledger as well: the recorded-shape one
# reads _RECORDED_DIFFS[ledger.name], so the ledger nobody runs gets no
# dormancy check, no over-declaration check, and no shape check.
# Two of those three are gaps TODAY; the shape check is latent only
# because expected_since_2.1.0.toml's _RECORDED_DIFFS section is empty
# -- and whether the 2.x sections stay empty is #501, which measured
# six contested diffs across the two 2.x ledgers with no pin, one of
# them at 2.1.0 (`MD, PHD`). The first 2.1.0 row to land would be
# checked by nothing a releaser runs.
# Cost is not the obstacle and no fix may be argued from it: measured
# 2026-09-03, a whole run is under a second at every baseline and all
# four back to back are about two seconds (0.43s at 1.4.0, 0.56s at
# 2.2.0, 1.97s for the four; decisions.md's 2026-09-03 #497 timing
# finding carries the recompute and retires every cost argument in
# this repo's differential prose).
# #503 weighs three fixes and none of them is this note's to make:
# add the fourth command; have the checklist DERIVE the list from the
# expected_since_*.toml glob rather than enumerate it; or say that
# 2.1.0 is deliberately outside the audit and fix the "EVERY baseline"
# line to match. The local precedent for the second is compare.py's
# own `--corpus` default, a glob and deliberately so -- "one that has
# to be asked for by name is one that stops being run" -- which is the
# same failure this note records, one argument up.
# Redirect to a file rather than piping — under zsh a pipe replaces the exit
# code with the pipe's, so a failing run reads as a passing one. The
# classified summary it prints is the source for the release notes' behavior
Expand Down
30 changes: 28 additions & 2 deletions docs/design/decisions.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/design/mechanisms.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ than inventing one.

## RECORDED-ROSTERS — record the answer, don't re-derive it

Problem shape. A guard needs to know what the answer WAS, so it can detect the answer changing. Contract statement. Store the measured answer as literal data (a roster) and compare against it; never re-derive the expectation from the same inputs the check reads, because a derivation from the same data always agrees with itself. Lives in. tests/v2/test_ledger_guards.py (_CORPUS_CLAIMS, _EXCLUSION_EFFECT, _CROSS_RULE_WINNERS, _SPAN_BEARING_RULES, _HONORIFIC_SOURCES, _LATIN_ALTERNATION_SOURCES), tools/differential/compare.py (_CORPUS_FLOORS), tests/v2/test_facade_cases.py (_CORE_ONLY_IDS). Reach for it when. Writing a check whose expected value is computed by the code under test, or a comment that enumerates ids/counts — make it data the suite asserts.
Problem shape. A guard needs to know what the answer WAS, so it can detect the answer changing. Contract statement. Store the measured answer as literal data (a roster) and compare against it; never re-derive the expectation from the same inputs the check reads, because a derivation from the same data always agrees with itself. Known limit, and it is what the contract statement does NOT cover: a roster row can hold a recorded INPUT as well as a recorded expectation, and only the expectation half is asserted. `_CROSS_RULE_WINNERS` obeyed this entry to the letter and still carried wrong rows — it recorded a diff shape per contested name AND the rule that should win, fed the shape to `classify()` as an argument, and asserted only the winner, so a wrong shape that still routed to the same rule agreed with itself for as long as nobody re-measured it by hand. The test to apply to a recorded input is not "is it checked" but "would the consumer answer DIFFERENTLY if it were wrong" — which is a question about the PAIR of shapes and not about the row: a row is inert exactly where the recorded shape and the real one route to the same rule. Counting admitters does not answer it, and is the wrong instrument this entry itself reached for first. Measured 2026-09-03 over `_RECORDED_DIFFS['expected_since_1.4.0.toml']`: 13 of the 31 rows have exactly one rule admitting their recorded shape, and 11 of those 13 route to a DIFFERENT rule under some other shape, so the assertion would have caught a wrong one — `'Andrews, M.D.'` wins `fix(comma-family)` at its recorded `('given','suffix')` and `fix(comma-precomma-family)` at `('family',)`. Nor does "the same rule admits both shapes" make a row inert, since file order still decides: over that ledger 152 of the 246 shape pairs sharing an admitter route to different rules. RECOMPUTE: load the ledger, sort with `_sorted_rules`, and call `classify(name, shape, rules, exclusions, None)` at each row's recorded shape and at every other subset of the roles the roster uses. The four rows #497 deleted were inert for the PARTICULAR wrong shape they happened to carry, and not for any wrong shape — `'Jane née and Jones Smith'` routes to `fix(#445)` at both its recorded and its real shape in either 2.x ledger, and to `fix(#412)` at `('family',)`. The repair is to move the input to where something measures it — the shapes now live in tools/differential/compare.py as `_RECORDED_DIFFS` and a differential run verifies them against the diffs it has already computed, the roster reading them from there — so a recorded roster's recorded half is checked by a run rather than by the guard that consumes it (#497). Lives in. tests/v2/test_ledger_guards.py (_CORPUS_CLAIMS, _EXCLUSION_EFFECT, _CROSS_RULE_WINNERS, _ORDER_EXEMPTION_EFFECT, _SPAN_BEARING_RULES, _HONORIFIC_SOURCES, _LATIN_ALTERNATION_SOURCES), tools/differential/compare.py (_CORPUS_FLOORS, _RECORDED_DIFFS), tests/v2/test_facade_cases.py (_CORE_ONLY_IDS). Reach for it when. Writing a check whose expected value is computed by the code under test, or a comment that enumerates ids/counts — make it data the suite asserts. And when a roster row carries anything the check CONSUMES rather than compares, ask what measures that half.

## LEDGER-RULE-SEPARATION — file order decides, fields narrow by subset

Expand Down
Loading