Both runtimes answered the 2026-08-11 decision request, and they did not ask
for the same name. The guard ruled `sanitize:` names its `Finding.detector`
and offered all three labels for verbatim adoption. llm-security, asked
directly, answered that the name would make its conformance result read as a
claim about neutralisation it does not perform.
Two things decided it. The guard's own unprompted correction: prefix ==
detector holds for those six labels and is no general law in its runtime
(`egress:*` carries detector="output"; decode-and-rescan yields two-part
`decoded:lexicon:*`). A prefix whose meaning is recoverable only by reading
one implementation cannot carry a shared id space. And a measurement taken
here at be9759b: on the surface the guard's own ruling pinned, `sanitize()`
returns changed text on all three carriers, so the counterargument's decisive
case -- that `scan_output` mutates nothing -- does not reach this surface.
Not a mediation. Neither runtime claimed the shared id must equal its label,
and `override:ignore-previous` already carries two different alias strings.
- carriers.json 0.1.0 -> 0.2.0: carrier:zero-width / :bidi-override /
:unicode-tag, aliased to the guard's labels. No aliases.llm_security --
that runtime's carrier findings carry no id yet, and publishing the alias
is the irreversible act that forces the table into its declared set.
- manifest 0.3.4 -> 0.4.0: entry_points_by_scope, carrying findings accessor
and fixture presentation per scope per runtime. This was objection (c), and
it blocked minting harder than the name did.
- Corpus 90 -> 93. Measured through sanitize(text, source=Source.INPUT) at
guard v0.7.0; verified by a separate checker that re-derives everything from
disk -- a generator agreeing with itself proves nothing.
- CLAUDE.md gains the two rules that are not derivable from the data: a shared
id space cannot rest on a one-runtime prefix, and publishing an alias -- not
minting the case -- is the irreversible act.
Not minted on purpose: no artifact-side id (the other runtime would only fail
them), and no ZWJ-exemption case (U+200D between two emoji is exempt on both
guard surfaces since v0.6.1; the fixture avoids it rather than trips it).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U3o4zSQ2kwLsgJNU7apK3Z
Six independent carrier tables: zero-width characters (5), the Unicode Tags
block with its subtraction decode rule, the two Supplementary Private Use
Areas, BIDI controls (9, the Trojan Source class CVE-2021-42574), the
Cyrillic presence set (13), and the fold-to-Latin homoglyph map (28).
Proven, not transcribed: five of the six tables were rebuilt from the commons
JSON alone and diffed against the imported dump module — every constant
identical, and the homoglyph map identical down to insertion order. Folding a
corpus through the rebuilt table and the source table gives identical results.
The tables overlap but are NOT merged, and cross_table_notes states each
divergence as fact: the zero-width carrier set includes U+00AD while the
lexicon's pattern class does not; the lexicon class holds U+0456 which
CYRILLIC_CONFUSABLES lacks, and CYRILLIC_CONFUSABLES holds U+0445 which the
class lacks. Reporting that to llm-security, not fixing it here.
Two honest limits, marked in the file rather than smoothed over: the private
use ranges arrived as a source comment with no constant behind them and carry
verified: false, and the dump's own "~25 entries" estimate for the homoglyph
map is wrong — counted mechanically it is 28.
Character names resolved through Python unicodedata against the Unicode
character database, not written from recollection. Verification log in
docs/extraction-plan.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaYqid3mejFmd9ZHsiHgp3