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.

View file

@ -86,6 +86,13 @@ Ingen. Data + prosa. Filformater: JSON (data + schema), Markdown (spec), rå tek
sann ved commiten `measurement` pinner. Retter du én av 83, står 82 målinger ved én commit
og én ved en annen, under en header som navngir én. Før avviket i manifestet med dato og
commit i stedet. Å re-pinne hele korpuset er en egen beslutning.
- **Like funn-sett betyr ikke lik feilflate.** Spørsmålet som avgjør om en variant-case skal
mintes er ikke om de to inputene gir ulike funn innenfor scope — det er om den andre
inputen kan FEILE, innenfor scope, under en endring i den scopede datafila som den første
ville bestå. Korpusets første variant forventer nøyaktig samme funn som base-casen og
gater likevel noe base-casen ikke ser: base-inputen matcher mønsteret både i publisert og
i tidligere, strengere form. En payload hvis skille ligger i en tabell vi ikke publiserer
består ikke terskelen og føres som navngitt utelatelse. Spec §6 bærer regelen.
### Id-rom: adoptert vs. navngitt

View file

@ -211,8 +211,10 @@ Reading that absence as "this runtime emits nothing else" would be a claim nobod
## 6. Case ids
```
case_id = pattern_id with ":" replaced by "__"
pattern_id = case_id with "__" replaced by ":"
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 ":"
```
`:` is not a legal filename character on Windows, and fork-and-own is a supported use of
@ -220,18 +222,53 @@ this repository, so the id space cannot reach the filesystem unchanged. `__` doe
anywhere in the ratified id space, so the transform is one-to-one — verified collision-free
across all cases rather than assumed.
The transform carries a consequence that is easy to miss: **a case id is derived from a
pattern id alone, so the corpus holds at most one case per `pattern_id` in single-finding
scopes.** There is nowhere in the name to put a second one. That is a real constraint, not a
formality — a source runtime's own test matrix may well drive two payloads at the same
pattern, as one of the seeding runtimes does for `active:markdown-image`. When it does, the
two payloads MUST be compared *within the case's scope* before a second case is minted: if
their in-scope finding sets are equal, the second case cannot fail in any way the first does
not, and its distinguishing signal lies outside the scope where this corpus makes no claim.
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. It SHOULD instead be recorded as
a named omission in `conformance/manifest.json`, so the drop is visible rather than
inferred from a count.
`--` does not occur there either: the ratified ids use single hyphens throughout, measured
across every id space this repository publishes and every case id in the corpus. That
measurement is what keeps the reverse transform **lexical**. A runtime recovers 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 — a reverse transform that has to ask
which of two readings is real is a different rule from the one written above, and it fails
on the first id space that is vendored without its lookup table.
**A `pattern_id` may carry more than one case.** Through corpus version 0.2.0 this section
said the opposite: that a case id derives from a pattern id alone, so a single-finding scope
holds at most one case per pattern id, with nowhere in the name to put a second. Corpus
version 0.3.0 extended the derivation with the optional suffix above, and the corpus has
shipped a case under it since (`hybrid-xss__script-tag--src-no-close`, whose own `$comment`
carries the rationale for that one). The superseded sentence is named here rather than edited
away, because it was the stated ground on which a real payload was dropped —
`omitted_payloads` in [`conformance/manifest.json`](../conformance/manifest.json) records
that ground as withdrawn and a second, independent ground as still standing.
The bar for minting a second case is neither that the two inputs differ, nor that their
in-scope finding sets differ:
> 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.
Equal in-scope finding sets do not settle that question, and reading them as if they did is
the error this paragraph replaces. The corpus's first variant case expects exactly the
finding set its base case expects — one `pattern_id`, one scope, one `match` — and still
gates something the base cannot: the base input matches the scoped pattern both in its
published form and in 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 payload that stays out is the mirror image. A source runtime's own test matrix may drive
two payloads at the same pattern, as one of the seeding runtimes does for
`active:markdown-image`, whose only difference is a signal from a table this repository does
not publish. No edit to any scoped file separates them, so the second case could not fail
where the first passes. Such a payload SHOULD be recorded as a named omission in
`conformance/manifest.json`, so the drop is visible rather than inferred from a count.
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.** It is not a licence
to record a second, different verdict for one rule: two cases at one pattern id expecting
different findings within the same scope are not a variant pair, they are the corpus
contradicting itself.
**A case id is a stable identifier. Changing one is a BREAKING change** and requires a major
bump of the corpus version, exactly like changing a pattern id. Consumers name cases in