explain: make each refusal name itself - #74
Draft
ChelseaKR wants to merge 1 commit into
Draft
Conversation
A refusal is this project's deliverable and it was legible only in the source. `coverage` says a line went unread and gives a coarse reason -- "3 of 13 lines in a recognized section matched no rule" -- and nothing says which recognizer got closest or what stopped it. `explain <doc> p.3 L11` answers both. Every line lands in one of four states: consumed, refused at a named fence, examined (a recognizer claimed the section, read it and took nothing from this line, with no fence firing), or unclaimed (no recognizer claimed the section at all, so no fence could have fired). The fourth state is why this does not offer a nearest fence. A closest rule picked by proximity is a value invented from an absence, and the difference between "unstated on the page" and "unread by this parser" is the one ADR 0018 draws by hand. trace.py is the channel. Recording is off unless a caller opens it, nothing reads a trace back while parsing, and recognizers call refuse() with no conditional so there is one code path rather than two that can drift. That is what makes `parse` byte-identical either way, asserted over every committed fixture rather than argued. Five fences are registered, each with the ADR that decided it and a reason written about the page. A sixth was written and removed: rate_table's row-prices-nothing sits on a branch its only caller cannot reach, so it would have been a row `explain --fences` prints and can never report. The census test names any registered fence no fixture trips, which is what found it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #48.
What was wrong
coveragereports that a line went unread and gives a coarse reason — "3 of 13 lines in a recognized section matched no rule". That is true, and it does not say which recognizer got closest to the line or what stopped it. Reading the source was the only way to find out, which made a refusal legible to whoever wrote the parser and to nobody else. Refusals are this project's deliverable, so that was the wrong reader.The fourth state is the design, and it is why there is no "nearest fence"
consumedrefusedexaminedunclaimedrefusedmeans the page states something this parser will not read as written.unclaimedmeans there is no rule here whose shape matches the section, so no fence could have fired. That is the distinction ADR 0018 draws by hand — "unstated on the page" is not "unread by this parser" — and it is the reasonexplainnever offers a closest rule. A nearest fence picked by proximity is a value invented from an absence, which is the defect this project exists to refuse. A control that makes anunclaimedline reportrefusedturns two tests red.Recording cannot change what
parseemitsca_tariff_parse.traceis the channel. Recording is off unless a caller opens it, and nothing in the package reads a trace back while parsing, so no decision the parser makes can depend on whether it was watched. Recognizers callrefuse()unconditionally rather than behindif tracing:— one code path cannot drift from itself, and the call is a single context lookup when nothing is listening.Asserted rather than argued: every committed fixture is parsed with and without a trace open and the emitted JSON is compared as bytes. A control that lets a recognizer see the trace and act on it reddens exactly those tests.
unparsed[].reasonis untouched, and no flag changes it. The issue allows carrying a nearest fence there behind a flag; not done, because it would be a second refusal vocabulary alongsideexplain's, and the goldens and every baseline underdata/parsed/embed those strings.Coverage, both numbers
The report prints how many of the parser's registered fences a document reached out of how many exist. A count of refusals says nothing about how much of the vocabulary an input exercised.
SYNTHETIC-example-schedule-complete.txtSYNTHETIC-example-schedule-unknown-section.txtSYNTHETIC-example-refused-rows.txt(new)SYNTHETIC-example-keyword-schedule.txtSYNTHETIC-example-unclosed-bracket.txt(new)162 lines explained, 5 of 5 registered fences reached across the corpus, all four states produced. Before the two new fixtures the registry stood at 0 of 5 reached — five names nothing exercised, which is the shape this campaign keeps finding.
A fence was written and removed
rate_table.row-prices-nothingsat on theif not accountedbranch at the end of_read_value_row. That function is called only when some word in the value area matched an amount or an explicitn/a, and every path through the loop for such a word either returns early or setsaccounted— so the branch cannot be reached from its only caller. A fence there would have been a rowexplain --fencesprints and can never report.It is deleted, with a comment at the branch saying why rather than leaving the omission silent.
test_every_registered_fence_fires_on_a_committed_fixtureis what found it, and a control that registers a deliberately unreachable fence turns that one test red — so the census can see the thing it exists for.Against the issue's "Done when"
explainthenparseleavestests/golden/byte for byte unchanged" — done, and stronger: the byte comparison runs on every fixture on every test run rather than being checked once.explainnames exactly one consuming recognizer or at least one examining recognizer with a fence; no line reports 'unknown'" — the second half of that is not achievable honestly, and this is the measured reason. A line in a section every recognizer declined has no fence to name, because no rule reached it; producing one would be the nearest-fence guess the same issue forbids two bullets earlier. What is achievable, and what is asserted over every fixture, is that every line lands in a named state and none reports "unknown" — withunclaimedcarrying the recognizers that declined the section, so the answer is still specific. The clause needs amending on the issue and I have not edited it; flagging rather than quietly satisfying a weaker version.sources/is populated bymake fetchand this repository does not redistribute them (ADR 0003). A green CI run is not evidence for that criterion and the fixtures above are what CI can see.Fence vocabulary
Five fences, each carrying the ADR that decided it and a reason written about the page in the present tense — printed to somebody who has the PDF open and not the source.
CONTRIBUTING.mdgains the four rules for adding one, including that a fence must be reachable and that an unreachable branch gets a comment rather than a name.Negative controls
Committed first; every sabotage asserted to have changed the blob and every restore verified by comparing
git hash-objectwithgit rev-parse HEAD:<path>.test_every_registered_fence_fires_on_a_committed_fixture, nothing elsetest_a_traced_parse_emits_the_same_bytes_as_an_untraced_onefor the two fixtures whose output moves, nothing elseunclaimedline asrefusedtest_a_line_no_recognizer_claimed_says_so_rather_than_naming_a_nearest_fenceandtest_every_state_is_produced_by_the_committed_corpus— the second because the vocabulary loses a statetest_a_line_no_recognizer_claimed_says_so_rather_than_naming_a_nearest_fence, on its "examined by nobody either" assertionmake verify:649 passed, 65 skipped, 93.72% coverage, exit 0. The 65 skips are the real-document spot checks, which needmake fetch.Prepared with AI assistance; reviewed before submission.