Skip to content

feat: publish and gate the Latin-shape exposure set (#815) - #930

Closed
raeq wants to merge 4 commits into
mainfrom
fix/815-census
Closed

feat: publish and gate the Latin-shape exposure set (#815)#930
raeq wants to merge 4 commits into
mainfrom
fix/815-census

Conversation

@raeq

@raeq raeq commented Sep 3, 2026

Copy link
Copy Markdown
Owner

#815 items 4, 5 and 7. Stacked behind #929 (item 3), which is independent of this.

What lands

tests/fixtures/latin_shape_exposure.tsv299 non-ASCII code points that read as a Latin letter and reach ASCII on no surface, with a per-block gate.

Per block and not only in total, deliberately: a refresh that gained rows in one block and lost them in another would pass a total-only check while changing what a deployment is exposed to.

block rows
Latin Extended-D 146
Latin Extended-E 40
Latin Extended-G 37
Phonetic Extensions 26
Phonetic Extensions Supplement 24
Latin Extended-C 19
Latin Extended Additional 6
Latin-1 Supplement / Ext-A / Ext-B 1

An exposure set, not a bug list: Latin Extended-D is 146 of the 299 and they are medievalist letters with no sensible ASCII fold. The useful output is a reviewed list, which is what the issue asked for.

The selector, which is item 7 and the interesting part

The issue's 401 was wrong in both directions, and my first correction was wrong three more ways.

#815 matched names starting with LATIN , MODIFIER LETTER or TURNED , or containing SMALL CAPITAL — so NEGATIVE CIRCLED LATIN CAPITAL LETTER A and 51 siblings were never inside the number. Widening to "the name contains LATIN" then pulled in:

wrongly counted why it is not exposure
53 COMBINING LATIN SMALL LETTER … diacritics over a base; strip_accents' business
52 TAG LATIN … LETTER … stripped, not folded (#413) — correct handling reads as a gap
LATIN CROSS, LATINATE MYSLITE a symbol and a Glagolitic letter

I found all three by reading the generator's output, not by reasoning about the pattern. The selector is now a word-bounded LATIN [CAPITAL|SMALL] LETTER plus a category of letter or symbol, which excludes marks and format characters by construction.

The caveat the census cannot express

It counts unfolded code points, so one that folds to the wrong letter is invisible to it. That is #916, and it is why 299 is a floor rather than a measure of correctness. Recorded on limitations.md beside the number.

Tests

The fixture matches the library; each block holds its count; the three exclusion classes stay excluded; what #915 and #920 fixed is absent from the set; and the selector is pointed at something.

Gate

cargo fmt/clippy · cargo test · ruff check/format . · mypy · mkdocs --strict · 41/41 doc pages · 6725 passed, 38 skipped.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings September 3, 2026 07:09
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

📄 Docs preview: https://b7e9d5bf.disarm-docs.pages.dev

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new tests and generator currently repeat a full Unicode census multiple times, which will significantly slow CI and local runs unless cached/reused.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Publishes the “Latin-shape exposure set” requested in #815 as a reviewed TSV fixture and adds gates to prevent the set from drifting (including per-block counts), documenting the limitation that the census only measures unfolded code points.

Changes:

  • Add scripts/gen_latin_shape_exposure.py to generate the exposure-set fixture and support --check gating.
  • Add tests/test_latin_shape_exposure.py to validate the fixture against live library behavior and gate per-block counts plus key exclusions.
  • Update documentation and release notes to reflect the new published/gated exposure set and its limitations.
File summaries
File Description
tests/test_latin_shape_exposure.py Adds a test gate to keep the exposure fixture aligned with the generator/library and asserts key selector invariants.
tests/fixtures/latin_shape_exposure.tsv Introduces the generated exposure-set fixture (299 rows) used as the reviewable, non-drifting published list.
scripts/gen_latin_shape_exposure.py Implements the census + selector used to generate/check the exposure fixture.
docs/limitations.md Updates the limitations narrative to point at the published fixture and record the “wrong-fold” caveat (#916).
CHANGELOG.md Adds an Unreleased entry documenting the new exposure-set publication and gate.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/gen_latin_shape_exposure.py
Comment thread tests/test_latin_shape_exposure.py Outdated
raeq added 2 commits September 3, 2026 09:14
Items 4, 5 and 7. 299 non-ASCII code points that read as a Latin letter reach ASCII on no
surface, published as `tests/fixtures/latin_shape_exposure.tsv` with a per-block gate.

Per block and not only in total, deliberately: a table refresh that gained rows in one
block and lost them in another would pass a total-only check while changing what a
deployment is exposed to.

An exposure set rather than a bug list. Latin Extended-D contributes 146 of the 299 and
they are medievalist letters with no sensible ASCII fold; the useful output is a reviewed
list, which is what #815 asked for.

The selector is the part worth reading, because the issue's number was wrong in both
directions and my first correction was wrong three more ways.

#815 matched names STARTING with `LATIN `, `MODIFIER LETTER ` or `TURNED `, or containing
`SMALL CAPITAL`, so `NEGATIVE CIRCLED LATIN CAPITAL LETTER A` and 51 siblings sat outside
the count. Widening it to "the name contains LATIN" then pulled in:

- 53 COMBINING marks — diacritics over a base, not letters standing in for one, and
  `strip_accents`' business;
- 52 TAG characters — stripped rather than folded (#413), which reads as unhandled to a
  test that only asks whether ASCII came out;
- `LATIN CROSS`, a symbol, and `GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE`, which is
  Glagolitic.

Every one of those three I found by reading the generator's output, not by reasoning about
the pattern. The selector is now a word-bounded `LATIN [CAPITAL|SMALL] LETTER` plus a
category of letter or symbol, which excludes marks and format characters by construction.

One caveat recorded beside the number, because the census cannot express it: it counts
UNFOLDED code points, so one that folds to the WRONG letter is invisible to it. That is
#916, and it is why the 299 is a floor rather than a measure of correctness.

Tests assert the fixture matches the library, each block holds its count, the three
exclusion classes stay excluded, what #915 and #920 fixed is absent, and the selector is
pointed at something.

Refs #815, #916, #413, #915, #920.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Richard Quinn <quinn.richard@gmail.com>
`gen.census()` walks the codespace and calls six disarm surfaces per selected code point.
It ran once for the fixture comparison and again for every parametrized block — nine
sweeps where one would do. A module-scoped fixture now runs it once: 1.20s -> 0.22s.

Fourth time this shape has come up in this cycle — #920's `_enclosed()`, #923's reverse
sweep, #929's `_small_caps()`, and now this — so the fixture's docstring names the pattern
rather than being quietly corrected again: a helper that reads as cheap at the call site
and walks the codespace inside.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Richard Quinn <quinn.richard@gmail.com>
raeq added 2 commits September 3, 2026 09:16
`main()` called `census()` for `render()` and again for the row count in the status line,
so rewriting the fixture walked the codespace twice. Computed once.

Fifth instance of this shape in the cycle and the first in non-test code, which is the
part worth noting: I had been treating it as a testing habit.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Richard Quinn <quinn.richard@gmail.com>
CI failed on `Latin Extended-D: 143 now, 146 in the fixture`. Not a coverage change —
three different UCDs are in play and the fixture was generated against one of them:

    disarm's bundled tables   17.0.0
    this checkout's host      16.0.0
    CI's Python 3.12          15.1.0

The selector reads `unicodedata.name`, so a code point assigned in 16.0 is invisible to an
older interpreter and the totals cannot match. disarm exposes no name or category API, so
the census has to use the host's, which makes a fixed per-code-point fixture
interpreter-dependent by construction.

The gate now compares only the code points the RUNNING interpreter can name. A host with
an older UCD sees a subset and still agrees, while the thing the gate exists for still
fails: a code point the host CAN name changing status. Verified by hiding Latin Extended-G
and watching the comparison drop from 299 to 262 rows without failing.

Also removed `tests/test_adversarial_corpora.py` from this branch. It is #732's work and
was swept in by a `git add -A` while it sat untracked in the tree — which is how it came
to reference a page that does not exist on this branch, and is the second failure in this
CI run.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Richard Quinn <quinn.richard@gmail.com>
@raeq

raeq commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

Superseded by a clean rebuild off main. This branch carried #732's test file — swept in by a git add -A while it sat untracked in the tree — so after #732 merged, rebasing conflicted between a commit that adds that file and a later one that deletes it, against a main that now legitimately owns it.

Rebuilt as a single commit with the same content plus the UCD fix CI found here. See the replacement PR.

@raeq raeq closed this Sep 3, 2026
raeq added a commit that referenced this pull request Sep 3, 2026
Three comments cited "#930 review" as their reason. That PR is closed — it was replaced by
this one — so a reader following the reference lands on a superseded branch rather than an
explanation.

The reasons are inline now: the census is computed once because it walks the codespace and
calls six surfaces per selected code point; the fixture is module-scoped for the same
reason; the gate compares what the interpreter can name because a total comparison fails on
a Python 3.12 runner for reasons unrelated to coverage.

A list of PR numbers illustrating the repeated-sweep pattern went the same way. The pattern
is worth keeping and the numbers age badly.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Richard Quinn <quinn.richard@gmail.com>
raeq added a commit that referenced this pull request Sep 3, 2026
* feat: publish and gate the Latin-shape exposure set (#815)

Items 4, 5 and 7. 299 non-ASCII code points that read as a Latin letter reach ASCII on no
surface, published as `tests/fixtures/latin_shape_exposure.tsv` with a per-block gate —
per block and not only in total, because a refresh that gained rows in one block and lost
them in another would pass a total-only check while changing what a deployment is exposed
to.

An exposure set rather than a bug list. Latin Extended-D contributes 146 of the 299 and
they are medievalist letters with no sensible ASCII fold.

The selector is item 7 and the part worth reading. #815 matched names STARTING with
`LATIN `, `MODIFIER LETTER ` or `TURNED `, or containing `SMALL CAPITAL`, so
`NEGATIVE CIRCLED LATIN CAPITAL LETTER A` and 51 siblings were never inside the number.
Widening it to "the name contains LATIN" then pulled in three classes that are not
exposure, each found by reading the output rather than reasoning about the pattern:

- 53 COMBINING marks, which are diacritics over a base and `strip_accents`' business;
- 52 TAG characters, which are stripped rather than folded (#413), so correct handling
  reads as a gap to a test that only asks whether ASCII came out;
- `LATIN CROSS`, a symbol, and `GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE`.

The gate compares only the code points the RUNNING interpreter can name. Three UCDs are in
play — disarm's tables at 17.0.0, this host at 16.0.0, CI's Python 3.12 at 15.1.0 — and the
selector reads `unicodedata.name`, so a fixed per-code-point fixture is interpreter-
dependent by construction. Comparing on what the host can see is deterministic anywhere
and still fails on what the gate is for. Verified by hiding Latin Extended-G and watching
the comparison drop 299 -> 262 without failing.

One caveat recorded beside the number: the census counts UNFOLDED code points, so one that
folds to the WRONG letter is invisible to it. That is #916, and it is why 299 is a floor.

Rebuilt as a single commit off main. The previous branch carried #732's test file, swept
in by a `git add -A` while it sat untracked, and rebasing after #732 merged conflicted
between adding and deleting a file main now owns.

Refs #815, #916, #413, #915, #920.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Richard Quinn <quinn.richard@gmail.com>

* docs: comments pointed at a superseded PR (#932 review)

Three comments cited "#930 review" as their reason. That PR is closed — it was replaced by
this one — so a reader following the reference lands on a superseded branch rather than an
explanation.

The reasons are inline now: the census is computed once because it walks the codespace and
calls six surfaces per selected code point; the fixture is module-scoped for the same
reason; the gate compares what the interpreter can name because a total comparison fails on
a Python 3.12 runner for reasons unrelated to coverage.

A list of PR numbers illustrating the repeated-sweep pattern went the same way. The pattern
is worth keeping and the numbers age badly.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Richard Quinn <quinn.richard@gmail.com>

---------

Signed-off-by: Richard Quinn <quinn.richard@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants