Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions AGENTS.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/design/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Skip what the suite already enforces. `tests/v2/test_rules_doc.py`, `tests/v2/te
8. **Guard docstring vs. what the guard enforces**, especially when the promise spans two test modules.
9. **Prose is input to the doc parsers.** In a rule (tests/v2/rules_skeleton.py), every non-blank line inside a fence is an example and must parse; outside a fence, a line, label text or bullet text that parses as an example or opens with a quote or a bracket is refused, as is any line holding an arrow and an assertion (` → family=`) outside a backtick span, so an example cannot sit unrun in prose; so is a `Signal:` or `no-boundary:` line above the `---` footer, a second When, Tolerated or Then paragraph, a bullet marker other than `- ` (a numbered item included), a nested list in any marker or a heading in a bullet's continuation lines, a line opening a block-format rule (`X1. ` at the start of a line, inside a field included), and an unknown or repeated part of the pointer line; a column-0 line ends the rule only when it is a markdown heading (`#` and a space); a fence is a bare ``` line, and one carrying a language tag, or a `~~~` fence, is refused; a line opening `**Word:**`, the colon inside the bold, is a field label and is refused unless Word is one of the seven labels; a line opening with a label word (When, Tolerated, Then, Because, Except, Precedence, Accepted, or Rationale) and a colon in any other spelling is refused as a near miss; other bold text at the start of a line, such as `**Note**:` with the colon outside the bold, is read as the open prose field's prose, and after a bullet list it is a line outside any field and refused. Outside a rule, only a section's `## Name (X)` heading, each letter once, and its one `Background:` paragraph may stand: a subheading in a rule file, a misspelled rule heading or a fence in a section file is refused, glued to the Background or not (the glued check reads structure at the start of a line only), as is an example in a Background and a section heading with no Background. A comment's quoted values join the citation block above them.
10. **Re-run every capability claim.** Axis 1 recomputes numbers; this one recomputes CAN and CANNOT. A statement that the parser never or cannot produce some reading is measured by a parse run written now, never derived from a mechanism — most of all when it justifies building something. (#291 derived "a multi-word credential is unparseable" from "vocabulary is matched one token at a time" — which `given_name_titles` had been an exception to since 2026-07-19, a month before that arc, and `maiden_markers` is a second since #434 — and specified a vocabulary set, a matching unit, a predicate and a `Lexicon` field on it; `parse("John Smith, MD PhD").suffix` had been `'MD PhD'` since 1.4.0.)
11. **State the invariant that relates two DIFFERENT inputs.** Axes 1–10 compare a document to another document, to its own examples, or to the implementation, so every one of them can only find code that DISAGREES with a rule — none of them can find a rule that is simply wrong. When a rule implies that two spellings of the same name must parse alike, write that as an invariant over both inputs and run it: it consults no rule statement, so it is not blind that way. Distinct from an invariant over two VIEWS of one parse, like `test_the_family_partitions_into_particles_and_base` in tests/v2/test_cases.py — that one holds however wrong the rule is. (Two review rounds a month apart found real defects and missed the premise under them: #461 was backed out after the rule turned out wrong, #466 closed unmerged for the same reason, and in both, every reviewer checked the code against the rule and THE RULE was at fault. The worked invariant: the family-comma writing under the default order and the comma-less writing under FAMILY_FIRST must parse alike. Measured, it holds for the great majority of the particle vocabulary and fails exactly on the words that are also suffix vocabulary — recovering P6's contested-membership set from behavior, having read no rule. Composition, measurement and recompute recipe: decisions/two-input-invariants.)
11. **State the invariant that relates two DIFFERENT inputs.** Axes 1–10 compare a document to another document, to its own examples, or to the implementation, so every one of them can only find code that DISAGREES with a rule — none of them can find a rule that is simply wrong. When a rule implies that two spellings of the same name must parse alike, write that as an invariant over both inputs and run it: it consults no rule statement, so it is not blind that way. Distinct from an invariant over two VIEWS of one parse, like `test_the_family_and_the_suffix_partition_into_their_views` in tests/v2/test_cases.py — that one holds however wrong the rule is. (Two review rounds a month apart found real defects and missed the premise under them: #461 was backed out after the rule turned out wrong, #466 closed unmerged for the same reason, and in both, every reviewer checked the code against the rule and THE RULE was at fault. The worked invariant: the family-comma writing under the default order and the comma-less writing under FAMILY_FIRST must parse alike. Measured, it holds for the great majority of the particle vocabulary and fails exactly on the words that are also suffix vocabulary — recovering P6's contested-membership set from behavior, having read no rule. Composition, measurement and recompute recipe: decisions/two-input-invariants.)
12. **Name the signal each new clause reads.** For every clause the diff adds to a rule's statement — for every Except bullet, since each is a place where another reading takes over — say what in the written name it reads, and check it against the rule's `Signal:` line; a diff that moves tests/v2/rules_budget.json is where to look first. A signal the rule did not already read is a new rule wanting its own ID and a line in the hub's map ("One rule per piece of evidence" above), and a clause that restates another rule's condition instead of citing it is the same finding from the other side. This is a structural finding, not a style one: report it under the usual format, the consequence being the next split and its hand retargeting. (S2 reached seven rules' worth of clauses and C1 ten, one sound clause at a time, every review passing, until #682 and #728 split them after the fact.)

Also: before reporting "N names move", report the size of the population that COULD move — a small count over a corpus blind to the shape is evidence about the corpus. For rules/P6 the population is the comma-bearing names that END in a particle — a small fraction of the comma-bearing names, which are themselves under a third of the corpus; decisions/P6 carries the dated measurement and the recompute. Do not restate a corpus count here. Axis 1 over this very sentence once caught it conflating the population with the movers, and the corrected figures that replaced it were superseded within the month as the corpus grew — a standing count in this file is wrong twice over, once for the class and once for the digits.
2 changes: 1 addition & 1 deletion docs/design/decisions/A1-ambiguity-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Open: none in this file.

- 2026-10-21 #793 — AMENDS #626's list of reports worded where the field is settled, for the comma's credential reading. The listed-member report after a family comma said "read as a credential" of a word the listing form binds as a title (`John, Si.`, `John, Ma.`), so that field was not settled by the wording alone. It now names the role the comma stage binds, "a title" or "a credential", worded at the emitter rather than through `field_tail`, since nothing later moves a word of the part the binding empties. The measurement is decisions/suffix-acronym-collisions' 2026-10-21 (#793) entry.

- 2026-10-26 #752 (phase 2m) (its DECLINED `languages` superseded by 2026-10-29 #754, 4a's follow-up, decisions/vocabulary-model) — A REPORT CARRIES THE READING IT DID NOT TAKE. decisions/vocabulary-model's 2026-10-23 #752 entry planned it: an `Ambiguity` carries the reading the parse did not take, and a word the credential's run takes by position alone is told from a word with a declared name reading by that reading's source, so it needs no new ambiguity kind. Derek decided on 2026-10-26 to ship it in 2.7 and to hold the reading taken, `Token.reading`, for 3.0.
- 2026-10-26 #752 (phase 2m) (its DECLINED `languages` superseded by 2026-10-29 #754, 4a's follow-up, decisions/vocabulary-model; its ACCEPTED `post-nominal` reading of a listed suffix word superseded by 2026-10-29 #754, sub-phase 4b, decisions/R8) — A REPORT CARRIES THE READING IT DID NOT TAKE. decisions/vocabulary-model's 2026-10-23 #752 entry planned it: an `Ambiguity` carries the reading the parse did not take, and a word the credential's run takes by position alone is told from a word with a declared name reading by that reading's source, so it needs no new ambiguity kind. Derek decided on 2026-10-26 to ship it in 2.7 and to hold the reading taken, `Token.reading`, for 3.0.
DECIDED: `Ambiguity.alternative: Reading | None`, declared `compare=False`, so two reports and two names compare as before and `ParsedName.comparison_key` is untouched. `Reading` is a frozen, slotted, picklable value of three fields, exported with its two enums: `kind` (`ReadingKind`: credential, generation, agnomen, post-nominal, title, particle, conjunction, initial, name), `role`, and `source` (`ReadingSource`). Its FIELD is the one the word would land in had the call gone the other way with every other word of the name read as the parse read it, which is what a caller moving one word needs. A non-name reading names its field by what it is: the suffix for a credential, a generation, an agnomen or another post-nominal, the title for a title, the family for a particle (rules/P2). A name reading takes the field the positional read gives one more name word in its place: the last name word's under the order that read the part (`JOHN SMITH MA` gives `family`, under FAMILY_FIRST `middle`), the given part's given or middle name after a family comma, the family before one, the maiden name in a marker's clause, and the given name of the listing form where the comma's decision bound the part as a credential run (`John Smith Jr., AD` gives `given`). Where the name piece in front would take the word in, it lands where that piece lands, named at assemble from the piece's final role: a particle chain, which runs to the end of the part's name words (`Juan de la Vega VI` under FAMILY_FIRST gives `given`, the chain's field), a lone particle not opening the part, which stood unchained only for want of a word behind it, and a bound given name opening the part where a family comma or a given-name title licenses its join (`Rahman, Abdul MB ChB` gives `given`, `Sir abdul VI` too); a particle P6 attached is not trailing once the word stands behind it, so it takes nothing in, and a bound join is no chain though `abu` is a particle too (`Smith, Abu Mary VI` gives `middle`). A never-given particle opening the part takes the word into the family by rules/P1's fold, in every order and part (`Dr. mc VI`, `Smith, Dr. de VI` give `family`). O5's lone word names the other primary field from its final role, after any move, and `middle_as_family` folds a middle into the family as it folds the parse's.
DECIDED: four sources, where the RFC's "Readings on the parse" named three. `vocabulary` is a listed word in the reading's category, `declared` a word the vocabulary also declares a name (the derived markers of decisions/vocabulary-model's 2026-10-25 #752 entry, and a given name written short), `shape` a roman numeral, dotted letters or a one-letter initial, and `position` an ordinary word nothing lists, a name only by where it stands: `John Smith PhD Jones` gives `position` for `Jones` where `JOHN SMITH MA` gives `declared` for `MA`, which is the case the plan needed the source for. A shape-admitted member of the ambiguous class carries the class's tag beside the shape tag, so the shape tag decides that it declares no name.
DECIDED: every emitter of every fork kind fills it at its decision site (mechanisms.md#AMBIGUITY-AT-THE-DECISION-SITE). Two stages run before the field exists, classify's connective-or-initial letter and group's particle chain, and defer it (`_state.Deferred.JOINED` and `UNCHAINED`) for assign to name off the positions it reads: the connective's join takes in the name word on each side, so where those stand in two pieces the joined unit takes the place of the first (`JOSE E MARIA SANTOS` gives `given`), and where both are already inside the letter's own piece the join changes nothing the piece did not hold and the letter lands where its piece lands, named at assemble (`MARIA DA SILVA E SOUZA`, a case row, gives `family`); a join that would leave the name one word is none, rules/P3 keeping a single-letter connective in a three-word name a name word, so there too the letter keeps its own field (`JOHN E SMITH` gives `middle`), where a family comma's surname is the word that lets the given part join (`SMITH, JOHN E MARY` gives `given`); the unchained particle stands as one more unit in front of its piece. One assign cannot place stays unnamed and surfaces as no alternative, in two shapes on 2026-10-26: a connective letter opening its part, which has no word before it to join, so no field could hold the reading (`E SMITH JONES`, a case row since; `e j smith` and `e and e` in the committed corpora), and in the case sweep the chain's report in `Freiherr von Berg Dr. and Ed. Prof.`, under each order, whose chained piece ends in a title a connective's join made. The first was found by the design-docs review; rules/A1 names it. The three kinds that report a shape (comma-structure, unbalanced-delimiter, segmentation) carry none; `order` is reserved and emitted nowhere.
Expand Down
Loading
Loading