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:
parent
e6ca5ae5ee
commit
cf05909c82
3 changed files with 157 additions and 14 deletions
99
CHANGELOG.md
99
CHANGELOG.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue