fix(page): the strip says where it cut, so a lone name for text and a dressed mark stop being the same shape (#444)
Closes #439. Closes #440.
Two blank pages were reported as holes, both made by the marks strip that
runs before the affirmation read, and one mechanism answers them.
#439 — a name for text that was only DRESSING a mark survived the noun it
belonged to and stood where a subject goes, with the mark's own verb behind
it: `Page is blank. Print artifacts are visible.` was `blank_contradicted` →
`page_no_output`, and the quote gave it away by being ungrammatical
(`affirmed: "print are visible"`). A name for text now leaves WITH the mark
when the mark is one only the capture leaves (`artifact`, `debris`, `dust`).
The HEAD NOUN decides that, not the dressing word — the opposite of what the
issue asks for, and settled by #431's pin rather than by an argument. Read
inside the branch that hands the name back, taking that one word and nothing
else, so a stacked doubt word still vetoes (#226) — and NOT added to
`MARK_MODIFIER`, where `CONT_CORE` would read it as licence to continue a
denial (#190).
#440 — a statement that is a name for text and nothing else refused the
declaration off one word with no determiner, count or predicate. The guard
the issue asks for (`tokens.length === 1`) was written and reverted in
c43dff9, because after the strip `Handwriting smudges.` is one token too.
One token is not one word, so every strip now leaves `PHRASE_GONE` (`" \f "`)
where it cut: `\f` is in `\s`, so every `\s+`, `[\s/-]+`, `[^.!?;\n]` and `\b`
here behaves as across a space, it is not in the statement split, and
`words()` cannot start a token on it. A log cannot forge one — `\f` and `\v`
are removed from the scope's input first.
One token is not the whole statement either: `words()` reads letters, so
`2 images.` and a `- text` line are one token each. And a boundary is not
always a sentence end, and a marker is not what makes a list — the SEQUENCE
is. A run of lone names is a list, one lone name is a lone name, so the
neighbour decides; `1.`, `-`, `i.` and `A.` all fall out of that clause. The
guard's cost is a ONE-ITEM list (`Page is blank.\ntext`), documented as that
shape.
Measured over every page reply on disk — 3,747 with a parseable log, 204 of
them blank declarations: 0 verdicts moved, by the marker alone and by the
whole change. An arm carrying the marker with both reads deleted fails 2 of
1,649 tests, and they are exactly the two whose pins are those reads. 76
replies write a name for text in front of a mark (81 occurrences, 9
spellings; 74 `artifact(s)`); transplanted into a declaration, 18 of 51 cells
flip, every one an `artifact(s)` head, all 18 refused by a contradiction and
0 by a doubt word — with `blurry`/`faint`/`grainy` stacked in front, 0 of 51
move. 1,129 replies carry a one-token statement (3,402 statements, 1,073
bare); all four naming text are decorated, so this guard's reach on the
corpus is 0. Of the 1,073 bare ones 147 are enumerations (2 letterless, 145
sequence) and none names text.
Three published figures were wrong and are corrected in the code and docs
rather than repeated: #440's "0 of 3,747" counted the wrong population, #439's
`image noise` refuses as a contradiction and not as a doubt, and this PR's own
"four one-token statements name text" is the population and not the reach.
Six review rounds; every round's finding was made by the previous round's fix.
`npm run typecheck` clean; `npm test` 1,649 pass / 0 fail.
Co-Authored-By: bbertucc <46652+bbertucc@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>