fix(spec): section 6 forbade a case the corpus ships, and the predicate under it was wrong too

The normative spec read "Such a payload MUST NOT be given a discriminated case
id" while manifest.json defined case_id_derivation.variant_suffix and
conformance/hybrid-xss__script-tag--src-no-close/ sat on disk under it. The
manifest was the correct party: the derivation was extended in corpus 0.3.0 and
the spec was never updated. No data moves here, only the text describing it.

The derivation block now carries the optional '--' suffix and the truncating
reverse, with the '--'-absence measurement stated as the reason the reverse
stays LEXICAL - re-measured at this commit rather than copied from 0.3.0's
numbers, and scoped to id spaces because '--' does occur inside pattern values.

The part that would have passed review while still being wrong: fixing only the
permission. Section 6 also reasoned that equal in-scope finding sets mean the
second case "cannot fail in any way the first does not" - and the shipped
variant falsifies exactly that. It expects the same single finding, same scope,
same match, and still gates what the base cannot, because the base input matches
the pattern under both its published and its superseded stricter form. The
discriminating signal is INSIDE the scope, in the form of the scoped rule. So
the MUST NOT is replaced by a predicate about failure surface rather than
finding sets, checked in both directions: it admits the shipped variant and
still excludes the omitted markdown-image payload, whose only distinguisher
lives in a table this repository does not publish.

manifest.json is untouched and stays at 0.5.0; no case directory moved. The
spec has no version of its own - "Through version 0.1.1" in section 4 is the
CORPUS version, verified against CHANGELOG [0.2.0] before acting, because the
session brief said otherwise. Section 6's stable-id and BREAKING sentences were
read, not edited.

Verified in scratchpad, never in the repo: the amended derivation transcribed
into a checker that reads all 94 cases back from disk, derives each pattern id,
round-trips it forward, and asserts the case expects it. All 94 reproduce.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M4xrxV3EXbSALqvB23kpeY
This commit is contained in:
Kjell Tore Guttormsen 2026-08-13 22:00:24 +02:00
commit cb784fea6f
3 changed files with 157 additions and 14 deletions

View file

@ -9,6 +9,105 @@ 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.7.0] — 2026-08-13
**The normative spec forbade, in as many words, a case the corpus has shipped since
`0.3.0`.** `spec/conformance-corpus.md` §6 read *"Such a payload MUST NOT be given a
discriminated case id; the derivation rule is the contract, and a suffix would break the
reverse transform"* while `conformance/manifest.json` defined `case_id_derivation.
variant_suffix` and `conformance/hybrid-xss__script-tag--src-no-close/` sat on disk under it.
The manifest was the correct party; the spec was simply never updated when the derivation was
extended. **No data moves in this release — only the normative text that describes it.**
### Fixed
- **§6's derivation block now states the rule the corpus actually uses.**
```
before case_id = pattern_id with ":" replaced by "__"
pattern_id = case_id with "__" replaced by ":"
after case_id = pattern_id with ":" replaced by "__",
optionally followed by "--" and a variant slug of [a-z0-9-]
pattern_id = case_id truncated at the first "--" if present,
then "__" replaced by ":"
```
The `--`-absence measurement moves into the spec as the reason the reverse transform stays
**lexical**: a runtime MUST be able to recover a `pattern_id` by splitting the string, and
MUST NOT need a lookup against the published id list to find where the id ends and the
variant begins. Re-measured at this commit rather than copied from the manifest's 0.3.0
numbers: `--` occurs in none of the 83 lexicon ids, none of the three `carrier:` ids, none
of the `active:` construct ids, none of the seven malware rule ids and none of the 19
secret-egress entry names — and in exactly one of the 94 case ids, the variant itself. It
does occur inside *pattern* values (`<!--\s*(?:AGENT|AI|…)`, `-----BEGIN … PRIVATE KEY-----`),
which is why the claim is scoped to id spaces and not to the data files as a whole. The
spec states the property, not the counts, which is what keeps it from going stale the way
"verified collision-free across all 90" in the manifest did.
- **The predicate for minting a variant was wrong, and fixing only the permission would have
legalised the shipped case under a rule that still forbids it.** §6 reasoned that if two
payloads' in-scope finding sets are equal, the second "cannot fail in any way the first does
not." The shipped variant falsifies that: it expects the *same* single finding, in the same
scope, under the same `match`, and still gates something its base cannot — the base input
matches `hybrid-xss:script-tag` under both the published form and the stricter form that
preceded it, so reinstating the stricter form leaves it passing, while the variant input
matches only the published form and fails. The distinguishing signal is *inside* the scope,
in the form of the scoped rule itself, which is exactly what a finding-set comparison cannot
see.
The `MUST NOT` is replaced by a predicate that admits the shipped case and still excludes
the omitted one:
> A variant case MAY be minted when the second input can fail, **within the case's scope**,
> under a change to a scoped data file that the first input would pass. Where no edit to a
> published table separates the two inputs, the second case cannot fail in any way the first
> does not, and it MUST NOT be minted.
Checked against `omitted_payloads`: the guard's seventh active-content payload is
distinguished from the case already built only by `entropy:base64-blob`, and this repository
publishes no entropy table, so no edit to any scoped file separates the two inputs. It stays
omitted, on the one ground the manifest already records as standing. **That verdict is
unchanged by this release** — the manifest's own note that the derivation ground lapsed in
`0.3.0` remains the only part of it that has moved.
- **The manifest's `constraint` is now normative rather than metadata.** A variant case MUST
be scoped and matched exactly like its base case and MUST expect the same `pattern_id`; the
suffix distinguishes inputs, never findings. Two cases at one pattern id expecting different
findings within the same scope are not a variant pair.
### Unchanged, deliberately
- **`conformance/manifest.json` stays at `0.5.0` and no case directory was touched.** The
corpus version tracks the corpus; no case, no id, no expectation and no measurement changed
here. Bumping it would date 94 fixtures to a commit that only edited prose.
- **The spec carries no version of its own, and none was added.** The "Through version 0.1.1"
reference in §4 is the *corpus* version (`conformance/manifest.json` went `0.1.1``0.2.0`
in the `0.2.0` release), not a spec version — verified before acting, because the session
brief said otherwise. Normative specs in this repository are versioned by the repository
tag, exactly as in `0.2.0`, which rewrote §4 and §6 prose under the same mechanism.
- **§6's stable-id paragraph is untouched.** "A case id is a stable identifier. Changing one
is a BREAKING change" is a separate rule that sits in the same section; it was read, not
edited.
- **Minor, not major, and the reason is uncomfortable enough to state:** a consumer whose
reverse transform is `--`-naive has been broken since `v0.3.0`, when the case shipped. This
release documents that break; it does not create it. Nothing here changes a key, a case id
or a disposition.
### Verification
Mechanical, in scratchpad, never in the repository (charter). The amended derivation was
transcribed out of the prose into a checker that reads all 94 cases back from disk and, for
each: asserts `case_id` equals the directory name, applies the reverse transform, round-trips
it forward, and asserts the derived `pattern_id` is one the case expects. All 94 reproduce.
The variant's scope, `match` and findings were asserted identical to its base case — the new
MUST, executed rather than eyeballed — and the checker also asserts that the withdrawn
sentences are gone and that the three stable-id sentences are still present verbatim (the
diff carries them as context lines, not as edits).
## [0.6.0] — 2026-08-13
**A seventh active-content case, and the whole raw-HTML classifier moves forward with it.