On this page
01 / The finding
A passing test, and text the classifier was never shown
The tool cut every document into sections of 340 words, on the stated premise that 340 words sits comfortably inside the classifier’s 512-token window. On dense prose it does not. Words are not tokens, and the sections that ran over the window did not fail loudly. They were quietly cut off at 512 and scored on what was left.
1,348 of 23,318 sections ran over the 512-token window and had their ends silently thrown away, across 684 of 5,558 documents and 276,466 of 9,287,413 tokens.
1,348 of 23,318 sections under the 340-word rule ran past the window. Roughly one section in twenty was being read short.
684 of 5,558 documents lost the end of at least one section. Nothing in the rule bounded how much text that could be.
Token mismatches between the browser and server implementations. Both truncated identically, so the routes agreed about the wrong text.
Recovering the lost text then changed no verdict at all on this corpus. That result is published here because it is true, not because it flatters the fix, and the section it sits in explains why maximum aggregation absorbed the defect.
segments-v3 | Quantity | 340-word rule | Shipped segments-v3 |
|---|---|---|
| Sections over the 512-token window | 1,348 of 23,318 (5.78%) | 0 of 21,093 (0%) |
| Documents with an end silently dropped | 684 of 5,558 (12.31%) | 0 of 5,558 (0%) |
| Tokens never seen by the classifier | 276,466 of 9,287,413 (2.98%) | 0 (0%) |
| Worst single section | 3,406 tokens needed, 2,894 lost | maximum measured exactly 512 |
| Forward passes over the corpus | 23,318 | 21,093 (9.4% fewer) |
docs/measurements/SEGMENT-TOKEN-FIX.md §2 and §4. Tokenised with the checkpoint's own WordPiece tokeniser run without truncation, so the count is what a section would have needed rather than what it was allowed. Scoring runtime for the section counts: fp32 reference pipeline, Python onnxruntime 1.29.0, 23,318 forward passes under the old rule against 21,093 under the new.
02 / How it was measured
What produced these numbers
Every section of the corpus was tokenised with the checkpoint’s own WordPiece tokeniser, run without truncation, so what is counted is the length a section would have needed rather than the length it was allowed. That distinction is the whole measurement: with truncation on, every section reads as 512 tokens and the defect is invisible.
Measurement conditions
- Corpus
- 5,558 long-form documents: 922 AI across 13 models, 4,636 human from Europe PMC, GOV.UK, CRS, Global Voices, Mongabay, SEC EDGAR and PERSUADE 2.0.
- Runtime
- fp32 reference pipeline, Python onnxruntime 1.29.0, the container's pinned version. 23,318 forward passes under the old rule, 21,093 under the new.
- Measured
- 29 August 2026
- Also
- Browser check: int8 onnxruntime-web on WASM, over a deterministic register-stratified 596-document subset (296 AI, 300 human).
| Corpus | 5,558 documents: 922 AI across 13 models, 4,636 human from Europe PMC, GOV.UK, CRS, Global Voices, Mongabay, SEC EDGAR and PERSUADE 2.0 |
|---|---|
| Tokeniser | the checkpoint’s own WordPiece tokeniser, run without truncation |
| Scoring runtime | fp32 reference pipeline, Python onnxruntime 1.29.0, the container’s pinned version |
| Browser check | int8 onnxruntime-web, WASM, on a deterministic register-stratified 596-document subset (296 AI, 300 human) |
| Measured | 29 August 2026 |
| Source | docs/measurements/SEGMENT-TOKEN-FIX.md |
The shipped segmentation contract is segments-v3. It produces the identical 21,093 sections as the segments-v2 rule described in the source record, so every coverage quantity on this page is true of the shipped product. Only the contract string moved on.
03 / The assumption
What the rule assumed, and where the assumption broke
WordPiece splits unfamiliar, technical and hyphenated words into several pieces, so a 340-word passage of biomedical or policy prose can need far more than 340 tokens. The proxy fails on exactly the dense writing the tool is weakest on anyway.
Measured across the corpus, tokens per word run at a median of 1.27, a 95th percentile of 1.62 and a maximum of 3.89. The section token distribution tells the same story from the other end.
| Percentile | Tokens | Against the 512-token window |
|---|---|---|
| min | 135 | inside |
| p25 | 306 | inside |
| median | 419 | inside |
| p75 | 448 | inside |
| p90 | 483 | inside |
| p95 | 523 | over |
| p99 | 923 | over |
| max | 3,406 | over |
SEGMENT-TOKEN-FIX.md §2. The box spans the interquartile range, p25 306 to p75 448.
A 95th percentile over the limit is not a tail risk. It is the ordinary behaviour of the rule on a twentieth of its work, and it had been the ordinary behaviour for months.
04 / Concentration
Where it concentrated
It fell where you would expect: on dense academic prose and on long human-written reports. On the AI side, academic literature reviews lost 80 of 597 sections and white papers 83 of 634. On the human side, fiction lost 122 of 1,031 and white papers 327 of 3,363. Student essays lost nothing at all, 0 of 1,011, which is a measured zero rather than an absent row.
Show the numbers this was drawn from
| Register | AI, sections over the window | Human, sections over the window |
|---|---|---|
academic-lit-review | 80 of 597 (13.40%) | 23 of 1,193 (1.93%) |
white-paper | 83 of 634 (13.09%) | 327 of 3,363 (9.72%) |
story | 17 of 662 (2.57%) | 122 of 1,031 (11.83%) |
longform-journalism | 5 of 812 (0.62%) | 300 of 3,771 (7.96%) |
company-update | 7 of 402 (1.74%) | 212 of 3,021 (7.02%) |
academic-essay | 48 of 780 (6.15%) | no row in the source table |
research-summary | 22 of 451 (4.88%) | 17 of 717 (2.37%) |
academic-discussion | 30 of 621 (4.83%) | 13 of 1,867 (0.70%) |
academic-introduction | no row in the source table | 32 of 1,449 (2.21%) |
academic-conclusion | no row in the source table | 10 of 936 (1.07%) |
student-essay | no row in the source table | 0 of 1,011 (0.00%) |
SEGMENT-TOKEN-FIX.md §2. Register labels are machine-assigned, so every split here inherits that. Tokenised without truncation on the fp32 reference pipeline.
The AI literature-review figure covers 28 of 107 documents, and the human fiction figure 65 of 260. A register absent from one side’s source table is printed as absent rather than as a zero, because those are different claims.
05 / Why it hid
A test that could not see a fault both routes shared
The tool has two implementations of segmentation, one in TypeScript for the browser and one in Python for the server, and a route-parity test exists precisely to catch them drifting apart. That test saw 0 of 284 token mismatches.
Both routes truncated identically. A test that asks “do these two agree” cannot see a fault they share.
There was a second signal, and it was misread. The server’s truncated flag carried a comment saying that a true value means the two routes have drifted apart. The flag was firing constantly, and never for that reason. A flag that fires all the time stops being read, which is how a loud warning becomes a silent one.
06 / The replacement
A token-exact split, with the exactness verified rather than assumed
Two facts make a word-granularity split exact. Whole-document tokenisation decomposes precisely across /\S+/ boundaries, checked token for token on all 5,558 corpus documents and on the eight golden texts, 5,558 of 5,558. And a WordPiece token never consumes fewer than one code unit, which bounds the one case word granularity cannot handle.
Each word is one atom, tokenised and measured. A document whose measured tokens plus the two special tokens fit in 512 is one section, verbatim. Otherwise it is cut into the fewest sections that all fit, as near equal in tokens as word boundaries allow, using integer arithmetic only so no rounding rule can drift between the two languages. Every resulting section is then re-tokenised and measured, and the split widens by one part if any section overshoots.
One word in 6,916,005 needed the oversized-word path: a 987-character run of punctuation that BERT basic tokenisation splits into 987 tokens before WordPiece runs.
| Leg | What it establishes |
|---|---|
| By construction | The loop cannot return a partition it has not tokenised and checked. |
| By termination | Widening cannot run away, because at one atom per section every atom is inside the budget. |
| By measurement | All 21,093 sections of the corpus were measured: 0 over 512, maximum exactly 512, and the widening loop never needed a second iteration. |
07 / What the rewrite found
Three cross-language divergences that were sitting in shipped code
Running the two implementations over the whole corpus and diffing every section’s start offset, end offset, word count and measured token count found 5,558 of 5,558 documents identical across 21,093 sections. Getting there exposed three genuine divergences that the eight golden test texts could not reach.
| Divergence | Effect | Fix |
|---|---|---|
| Python’s \S treats U+001C–U+001F and U+0085 as whitespace; JavaScript’s does not, and JavaScript treats U+FEFF as whitespace where Python does not | Word counts disagreed on 6 documents (0.11%), enough for the front end’s drift guard to refuse the server’s answer on them | segments.py now spells the class out as JavaScript’s, because the browser is what ships |
| JavaScript’s whole-string toLowerCase() applies the Greek final-sigma rule; the Rust tokeniser the checkpoint was trained with does not | A different token sequence, changing the segmentation of 1 document | tokenizer.ts now lower-cases character by character |
| tokenizer.ts tested whitespace as [\t\n\r \p{Zs}], missing U+2028, U+2029 and U+0085 | A spurious [UNK] in one GOV.UK document | tokenizer.ts now uses \p{White_Space} |
All eight golden token-id sequences still match after those two tokeniser changes: 8 of 8.
08 / The result
The result nobody predicted
The obvious hypothesis is that recovering the dropped text raises scores on the documents that were being truncated. That is not what happened.
The before-and-after comparison below is at 0.980, the single threshold that shipped on the server at the time, with a second reading at 0.984. Both are superseded by the shipped minimum-evidence pair. These numbers answer the same question at a rule this tool no longer uses, and they must not be read as a description of how it behaves now. The coverage quantities elsewhere on this page carry no operating point at all and are unaffected.
Measured at a rule that no longer ships
- Corpus
- The 684 documents the old rule truncated, drawn from the 5,558-document long-form corpus: 126 AI, 558 human.
- Retired flag point
- 0.980, with a second reading at 0.984
- Detector
tier3-cycle2-e5small-fp32.onnx, SHA-256e313ab00de1fffd2…4d2788d- Runtime
- Python onnxruntime 1.29.0, CPU, fp32
- Measured
- 29 August 2026
- Also
- Before-and-after comparison of two segmentation rules over the same documents, with the same weights reading the same text both times.
The rule that ships today is 0.9855 / 0.9763. The figures under this stamp answer the same question at a flag point this tool no longer uses, and they are not a description of the tool as it runs now.
| On the 684 truncated documents, at the retired 0.980 point | Old rule | Shipped segments-v3 rule |
|---|---|---|
| AI flagged (n = 126) | 123 (97.62%) | 123 (97.62%) |
| Human flagged (n = 558) | 2 (0.36%) | 2 (0.36%) |
Retired flag point 0.980. Not one verdict on that subset changed, at either threshold measured. Of the 15 AI documents newly flagged at the retired 0.984 point, none had a truncated section under the old rule.
Maximum aggregation is why. A truncated section still contributed its first 512 tokens, and on a document where any section reads as machine-written the maximum was already being set by some other section. The lost text would have had to be the only machine-looking part of the document to change anything, and on these 5,558 documents it never was.
The detection that did improve came from somewhere else. The new rule produces fewer, longer, equal-sized sections where the old one produced a full 340-word run followed by short rebalanced remainders, and detection is length-sensitive. Every newly flagged document has a lower section count under the new rule. There is a symmetric cost, recorded rather than buried: 72 documents that the old rule split needlessly are kept whole by the new one, all 72 human, and at the retired 0.980 point two of them are now flagged that were not before.
The fix removed a real, measured, unbounded coverage defect. On this corpus that defect happened not to be costing verdicts. That is luck rather than design.
It also costs 9.4% fewer forward passes on the same documents, 21,093 against 23,318, so the inference bill went down.
09 / Limits
What this does not prove
The coverage figures are threshold-free; the detection figures are not. Every before-and-after detection number in the source record is at 0.980 or 0.984, both superseded by the shipped minimum-evidence pair 0.9855 / 0.9763. The detection deltas are quoted above only with their own threshold named, and they must not be reprinted under a shipped-pair heading. The coverage quantities carry no operating point and publish as they stand.
Of the 922 AI documents, 268 (29.1%) appear in the cycle-2 dataset and 168 of those in the training split; the human side is effectively clean at 11 of 4,636. For a before-and-after comparison of two segmentation rules over the same documents this does not matter, because the same weights read the same text both times. It would matter for an absolute accuracy claim, and none is made here.
The browser check is a subset, and it is register-balanced rather than corpus-proportional. Its 2.33% human false-positive rate is not comparable to any corpus-wide figure: fiction, the worst register, is 10% of it against 5.6% of the corpus. Read the deltas, not the levels. At denominators of 296 and 300, one document is 0.34 percentage points.
Earlier figures for the same phenomenon are superseded. A 60-document route-parity sample reported 12.7% of sections and 21.7% of documents. The corpus here is 93 times larger and differently composed. Those figures should not appear beside these.
The Python halves of the measurement code are not reproducible from the repository. They live in a session scratchpad. The TypeScript verification scripts are committed.
Register labels are machine-assigned, so every per-register split in Figure 3 inherits that.
10 / Provenance
Where every figure on this page came from
| Figure | File | Section |
|---|---|---|
| 1,348 of 23,318 sections; 684 of 5,558 documents; 276,466 of 9,287,413 tokens; worst section 3,406 needing, 2,894 lost | docs/measurements/SEGMENT-TOKEN-FIX.md | §2 |
| Section token percentiles, min 135 to max 3,406; tokens per word 1.27 / 1.62 / 3.89 | SEGMENT-TOKEN-FIX.md | §2 |
| Sections over the window by register and side, both denominators | SEGMENT-TOKEN-FIX.md | §2 |
Route-parity test, 0 of 284 token mismatches; the misread truncated flag | SEGMENT-TOKEN-FIX.md | §3 |
| 21,093 sections, 0 over 512, maximum exactly 512; 5,558 of 5,558 documents identical; 8 of 8 golden sequences | SEGMENT-TOKEN-FIX.md | §4 |
| Three cross-language divergences, 6 documents and 1 document affected | SEGMENT-TOKEN-FIX.md | §4 |
| Detection before and after on the 684 truncated documents — retired flag points 0.980 and 0.984 | SEGMENT-TOKEN-FIX.md | §5 |
| 72 documents kept whole, all human; 9.4% fewer forward passes | SEGMENT-TOKEN-FIX.md | §5 |
| 268 of 922 AI documents in the cycle-2 dataset, 168 in train; human 11 of 4,636 | corpus-reconciliation-2026-08-29/analysis.txt | §2 |
Every file named above ships with the open measurement repository, so any figure on this page can be re-run rather than taken on trust. The Python halves of this particular measurement are the exception noted in the limits above.