feat(carriers): three cases minted, and the id is named rather than adopted
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
This commit is contained in:
parent
7ce0ba706c
commit
8f08e9ac73
11 changed files with 350 additions and 30 deletions
79
CHANGELOG.md
79
CHANGELOG.md
|
|
@ -9,6 +9,85 @@ Versioning note: the repository tag versions **the contract** (file set, key nam
|
|||
case ids, disposition semantics). Each JSON file additionally carries its own
|
||||
`"version"` field, bumped when that file changes.
|
||||
|
||||
## [0.5.0] — 2026-08-13
|
||||
|
||||
**Three cases minted, and the id they carry is the first in this repository that was *named*
|
||||
rather than adopted. Both consuming runtimes answered the decision request of 2026-08-11, and
|
||||
they did not ask for the same name.**
|
||||
|
||||
### Added
|
||||
|
||||
- `conformance/carrier__zero-width`, `carrier__bidi-override`, `carrier__unicode-tag` — the
|
||||
first cases scoped to `codepoints/carriers.json`. Corpus goes 90 → **93**. Measured through
|
||||
the guard's input surface `sanitize(text, source=Source.INPUT)` at `be9759b` (v0.7.0),
|
||||
findings read at `.report.findings`. Each input produces exactly one in-scope finding and no
|
||||
residue.
|
||||
|
||||
- `codepoints/carriers.json` `0.1.0` → `0.2.0` — a `pattern_id_space` block carrying
|
||||
`carrier:zero-width`, `carrier:bidi-override` and `carrier:unicode-tag`, each aliased to the
|
||||
guard's label. **These ids name what was OBSERVED, not what was done about it**, and that is a
|
||||
departure from how every other id space here was built: the lexicon's 83 and active content's
|
||||
6 were adopted verbatim from a runtime's own labels.
|
||||
|
||||
The guard ruled — measured, and correctly — that its `sanitize:` prefix names the producing
|
||||
module in `Finding.detector` and never an action, offering all three for verbatim adoption.
|
||||
The other runtime, asked the question directly, answered that the name would make its
|
||||
conformance result read as a claim about neutralisation it does not perform. Two things
|
||||
decided it against verbatim adoption. First, 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:*`), and a
|
||||
prefix whose meaning is recoverable only by reading one implementation cannot carry a *shared*
|
||||
id space. Second, a measurement taken here on 2026-08-13: on the very surface the guard's
|
||||
ruling pinned, `sanitize()` returns changed text on all three carriers — so the counter-
|
||||
argument's decisive case, that `scan_output` mutates nothing, does not reach this surface.
|
||||
|
||||
This is not a mediation between two runtimes. Neither claimed the shared id must equal its own
|
||||
label, and the shape is the one the lexicon already had: `override:ignore-previous` carries two
|
||||
*different* alias strings today, and neither runtime's label is the id.
|
||||
|
||||
- `conformance/manifest.json` `0.3.4` → `0.4.0` — `entry_points_by_scope`, stating entry point,
|
||||
**findings accessor** and **fixture presentation** per scope per runtime. The corpus previously
|
||||
pinned one entry point per runtime for all cases, which held for exactly as long as every case
|
||||
was reachable from one surface. Neither pinned entry point reaches carriers. Both runtimes
|
||||
said independently that the manifest was the right place for this: the guard rather than grow
|
||||
an API wrapper for this corpus's benefit, llm-security to record that its carrier entry point
|
||||
is path-based and **cannot measure a bare `input.txt`** — the fixture must reach it as a
|
||||
discovered file. Also adds `carrier_measurement`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `conformance/manifest.json` — the retired carrier blocker asserted a general rule, "THE PREFIX
|
||||
NAMES THE DETECTOR", about a runtime this repository does not own. The guard corrected it at
|
||||
its commit `0903785`: true of those six labels, false as a law. The claim is now **scoped to
|
||||
the six** wherever it is stated, and the general form appears nowhere — checked across
|
||||
`CONVENTIONS.md`, `README.md`, `spec/` and `docs/`. The retired text is kept verbatim under
|
||||
`scope_planned.blockers_resolved`, because a record corrected in place cannot be audited.
|
||||
|
||||
### Notes
|
||||
|
||||
- **`not-applicable` here records a missing name, not a missing capability.** Both runtimes
|
||||
implement the carrier tables — llm-security builds its zero-width, tag-range and BIDI sets from
|
||||
this very file — but its carrier findings carry no id yet (`scanner: 'UNI'`, a severity and a
|
||||
prose title). So `carriers.json` carries **no `aliases.llm_security`**, the file stays outside
|
||||
that runtime's declared set, and the three cases are `not-applicable` there today. This is
|
||||
deliberate and stated in the file: publishing that alias is the irreversible act, since it
|
||||
forces the table into that runtime's declared set by force of its own test suite. The slot is
|
||||
filled when the label exists and is named to us.
|
||||
|
||||
- **Not minted, on purpose.** No artifact-side carrier id (`output:zero-width-present`,
|
||||
`output:bidi-present`, `lexicon:unicode-tags-present`) — the other runtime has no artifact
|
||||
surface for carriers, so those cases could only fail there, and a permanently red gate is one a
|
||||
reader learns to ignore. And no case for the ZWJ exemption: U+200D between two emoji is exempt
|
||||
on both guard surfaces since v0.6.1, verified here, and the zero-width fixture carries a bare
|
||||
U+200B between two letters so as not to be failed by design. Whether that exemption is a shared
|
||||
rule or one runtime's policy has not been put to the other runtime.
|
||||
|
||||
- **The guard's v0.7.0 does not move a published fixture.** Re-measured, not inferred: all six
|
||||
active-content cases produce their committed verdicts at `be9759b`, and the new
|
||||
`active:raw-html-link` label is confirmed to reach `<a>` and `<area>` only, with
|
||||
`<a onclick=…>` still grading `active:raw-html` at HIGH. Adopting that seventh construct into
|
||||
`signatures/active-content.json` is a separate decision and is **not** taken here.
|
||||
|
||||
## [0.4.3] — 2026-08-11
|
||||
|
||||
**No fixture, id or `expected.json` moved — a runtime that passes `0.4.2` passes `0.4.3`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue