Compare commits

...

6 commits

Author SHA1 Message Date
c362717818 fix(security): use security@ as the reporting contact, not hello@
hello@ works, but two different addresses across sibling org repos
force a reporter finding a vulnerability to guess which one is the
security channel. security@fromaitochitta.com is the designated one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fm8ErxAacrm5s8ZWWubgMP
2026-08-21 11:22:58 +02:00
e56812eb39 docs(readme): add table of contents
The README crossed 200 lines with eight H2 sections and no navigation
aid, forcing readers to scroll to find whether it solves their
problem before they've decided anything. AAA+ B-axis order 32, round 2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XZzcSbaiw9nR686HDrF8KN
2026-08-16 16:17:45 +02:00
f0a511369d fix(spec): section 7 stated its own premise and then applied itself beyond it
Section 7 justified the fixture-is-ground-truth ordering with "Two
implementations that return different verdicts" and then stated the rule
with no scope. For signatures/active-content.json there is no second
implementation, and the seed runtime has stated the classification behind
it is calibration it does not freeze. As written, section 7 turned a change
they reserved into a bug on their side.

New section 7.1 keys the scope on a structural property, never on a table
name: a scope only one runtime implements, whose payload that runtime
authored. It creates no fourth verdict - the declaration schema closes
result with additionalProperties:false over four counts, so a fifth would
break every consumer's parser. The case still fails and is still named in
failed_cases; what changes is what the failure licenses concluding.

Two limits are stated rather than left to inference: it does not reach a
third-party implementer of the same table, and it is not a licence for a
runtime to self-declare its own divergence as calibration.

manifest.json 0.6.1 -> 0.6.2 retires the open-question sentence, quoted
rather than dropped. The retirement is partial: "section 7 is NOT amended
by this block" stays true, because the spec was amended by its own release.

Neighbours measured over the whole repository, widened past "ground truth"
to the second paragraph's own wording. CONVENTIONS.md and CLAUDE.md carried
the premise and are changed; SECURITY.md gets a cross-reference only, since
its claim is about a fixture expecting too little and 7.1 does not narrow
that direction; README.md and docs/extraction-plan.md are named as
deliberately untouched.

Breaking in category, minor in number - 0.x, per the reading [0.3.0]
recorded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012pZ2FLQ6xkWvj2VcwgwnQv
2026-08-13 23:31:28 +02:00
c75c546614 docs(manifest): the precision field stated its exposure as a hand-derived count
0.8.0 added a field whose whole purpose is precision, and bounded the exposure
with "three of the four dimensions ... cannot move one of these cases at all".
The total was derived by hand over a taxonomy the field had itself
recategorized. Lexicon entries ARE one of the seed runtime's four calibration
dimensions, and they are not absent from these fixtures: two of the seven carry
a lexicon id in observed_out_of_scope. And "the fourth" substituted the
classification for lexicon entries as the fourth item of their sentence.

manifest 0.6.0 -> 0.6.1: the field enumerates three named things and totals
none of them. A lexicon change ages the residue as evidence without moving a
verdict (spec section 5); the classification is the one that can move one.
Retired sentence quoted in an AMENDED IN clause, not dropped.

No measurement changed, no verdict moved, no case or data file touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yZobrgUiRtpLSWx8i7u2Z
2026-08-13 23:15:10 +02:00
c23aea9062 docs(manifest): the active-content fixtures pin a version, and nothing said so
The seed runtime's v1.0.0 freezes its exported Python surface and explicitly
not its detection behaviour. The manifest pinned commit and version per
measurement block but never recorded that the thing pinned is a version rather
than a frozen classification.

manifest 0.5.2 -> 0.6.0, one new field next to active_content_provenance.
asymmetry, bounded by what the fixtures actually assert: all seven carry
pattern_id only, so three of the four calibration dimensions cannot move them.
Both pins named, not one. Their statement is attributed, not restated as ours.

Also closes the omission 0.7.3 named: the same misquote in
docs/secret-egress-divergence.md:75-76. Not the fix the note implied - those
lines are one single-backtick span across a line break, so the outer delimiter
is promoted to double backticks instead.

spec section 7 deliberately untouched and named in CHANGELOG.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yZobrgUiRtpLSWx8i7u2Z
2026-08-13 23:09:33 +02:00
757570dd49 fix(readme): the paragraph still argued the premise our own commit retired
README.md opened the egress gap with "It is not an id question at all -- the two
runtimes carry different tables, cut at different granularities", and sent the
reader to scope_planned.blockers as the authority. Since 4356caa that blocker
opens reason (1) with "NO ID SPACE ON THE COMMONS SIDE. This is the hard
blocker." Before 0.7.2 the README was out of date; after it, two files on a
public remote disagreed, and it was our commit that made them.

The paragraph now carries the three measured, independent reasons from
docs/secret-egress-divergence.md. The counts survived the falsification, so
"different tables" and 19-against-25 are kept; only "cut at different
granularities" and the pending-reconciliation claim are gone. It deliberately
omits the outgoing question's status (true on the day written, untested by
anything, and dated in the manifest) and the standing entry_points_by_scope
requirement (a requirement, not a fourth reason).

conformance/manifest.json 0.5.1 -> 0.5.2 in the same release: the blocker
misquoted the contract it cites, rendering the value without the backticks the
field carries around `order`. The data file is unchanged and was never wrong.
Round-trip proved byte-neutrality before the edit; the read-back matched the
decoded string against the data file that owns it, since json.tool passes on
wrong escaping.

No case minted, no data file touched, no id proposed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SZ5vrpu2kxcRktiW59b7s3
2026-08-13 22:58:29 +02:00
8 changed files with 312 additions and 15 deletions

View file

@ -9,6 +9,224 @@ 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.9.0] — 2026-08-13
**A normative rule stated its own premise and then applied itself beyond it.**
`spec/conformance-corpus.md` §7 justified the fixture-is-ground-truth ordering with *"**Two
implementations** that return different verdicts…"* and then stated the rule with no scope at
all. For `signatures/active-content.json` there is no second implementation — the seed runtime
authored both the payloads and the table — and that runtime has stated that the classification
behind it is calibration it does not freeze. §7 as written made a reserved change on their side
into a bug on their side.
**Breaking in category, minor in number.** This changes disposition semantics, which the
versioning note at the top of this file counts as contract. The repository is in 0.x, where a
breaking change is a minor bump by the rules — the same reading `[0.3.0]` recorded: *read the
entry, not the version number*.
### Changed
- **`spec/conformance-corpus.md` — new §7.1, *Where the second paragraph does not hold*.** The
scope is keyed on a **structural property**, never on a table name: a case whose scope is a
table only one runtime implements, whose payload that runtime authored. A rule naming
`active-content` would rot the day a second runtime implements it. §7's own second paragraph
already carried the premise; §7.1 makes it explicit and states the disposition for the case
the premise excludes — the fixture is not rewritten on the divergence alone, the divergence is
recorded against the version pinned, and re-pinning is a separate release. That is the
disposition §5 already applies to a stale `observed_out_of_scope` entry, extended to the one
place where it can reach a verdict.
**It creates no fourth verdict, and that constraint shaped the wording.**
`schema/conformance-declaration.schema.json` closes `result` with `additionalProperties: false`
over four counts plus two arithmetic invariants; a fifth verdict would have broken every
consumer's parser, which is a worse break than the one intended. A case whose expected findings
are not produced still **fails** and is still named in `failed_cases`. What §7.1 changes is what
the failure licenses concluding, not what is reported.
Two limits stated in the section rather than left to be inferred: it does **not** reach a
third-party implementer of the same table — against them the fixture is the contract, exactly
as §7 says, and that is the only thing these cases can prove while one runtime is all there is
— and it is **not** a licence for a runtime to self-declare, since the exemption is carried by
the corpus's provenance record for the scope and not asserted per case by whoever failed.
Superseded text is named rather than edited away, following §6's own pattern: *"Through corpus
version 0.8.1 this section stated the rule above with no scope at all."*
**The competing reading was tested and disposed of**, because it is the one that would have
avoided this release: that §7's existing hatch (*"unless the fixture itself is proven wrong"*)
already covered it. It does not. The hatch's consequence is that **the fixture changes**, and
the manifest field asserts the opposite — pinned, not rewritten, re-pinning a separate
decision. And a runtime recalibrating does not prove the earlier classification wrong: the
fixture measured `de09711` / `0.4.0` correctly, and a later release does not reach back and
falsify an earlier measurement. The case fits neither of §7's two dispositions, which is the
defect.
- **`conformance/manifest.json` `0.6.1``0.6.2`
`active_content_provenance.pins_a_version_not_a_frozen_classification` no longer records an
open question.** The retirement is **partial and it is quoted, not dropped**, per the house
style this field established one release ago (*"a correction that does not say what it corrects
cannot be audited"*). What falls is only the open-question status; the clause *"section 7 …
is NOT amended by this block"* **stays true and is kept**, because §7 was amended by its own
release and not by a data file. Value change only — read back from disk against `HEAD` with a
flattened key diff: `added: 0, removed: 0, changed: 2` (the field and `version`), and the new
string printed and read rather than inferred from the count, since a value edit reports
`changed: 1` whatever it wrote.
Six prose dashes in the new text were written `--` and promoted to `—` before commit: `--` is
the variant-suffix separator token of §6's case-id grammar, and every other occurrence of it in
this file is that token, a real case id, or a CLI flag.
### Neighbours — measured, and the ones left alone are named
A sweep for the retired premise was run over the whole repository, widened past *"ground truth"*
to the second paragraph's own wording (*"one of them has a bug"*, *"two implementations"*), since
a restatement in that phrasing would have survived the first search.
- **`CONVENTIONS.md` — changed.** Carried the rule unscoped and called the proven-wrong hatch
*"the one way that reverses"*. There are now two, and both are listed.
- **`CLAUDE.md` — changed.** The Norwegian restatement that governs sessions in this repository
carried the same unscoped rule; left alone, the next session here would have acted on it.
- **`SECURITY.md` §2 — minimal cross-reference only.** Its claim is about a fixture that expects
**too little**, and §7.1 narrows *who the rule reaches*, not that direction. The conclusion
survives intact, so it was not rewritten.
- **`SECURITY.md` "Why a confirmed defect is usually not fixed here first" — untouched.** Its
*"two implementations answering differently"* is about extracted **data** diverging from its
source, not about fixtures.
- **`README.md` — untouched.** Its conformance row says *"Ground truth"* as a descriptor and does
not restate the disagreement rule, and it already names the asymmetry it would otherwise hide:
the seven active-content cases are *"measured against the one runtime that implements that
table"*. Nothing there became false.
- **`docs/extraction-plan.md` — untouched, and it is supporting evidence rather than a stale
neighbour.** It already records that the calibration file *"inverts this repository's central
rule"* — so this is the second place the unscoped rule was known not to hold, and the first was
documented before this release.
### Not in this release
Whether the seven active-content cases still pass at the seed runtime's `v1.1.0` is **unmeasured**,
and §7.1 is silent on it. No case was minted, no data file touched, no id string proposed.
## [0.8.1] — 2026-08-13
**The field 0.8.0 added to make the exposure precise stated it with a hand-derived count, and the
count was wrong.** Caught in the same session, before any consumer read it, and corrected inside
the field rather than by rewriting it. No measurement changed and no verdict moved.
### Fixed
- **`conformance/manifest.json` 0.6.0 → 0.6.1 —
`active_content_provenance.pins_a_version_not_a_frozen_classification` now enumerates instead of
totalling.** As published it read *"three of the four dimensions they name as calibration cannot
move one of these cases at all. The fourth can: which `active:` ids a payload yields IS the
classification"*. Two defects in one sentence. First, the total was derived by hand over a
taxonomy the field had itself recategorized: the seed runtime's four calibration dimensions are
severities, thresholds, **lexicon entries** and dispositions, and lexicon entries are *not* absent
from these fixtures — `active__data-uri` carries `data-uri:executable` and `active__raw-html`
carries `hybrid-xss:script-tag` in `observed_out_of_scope`, both verified as members of
`lexicon/injection-lexicon.json` and non-members of `signatures/active-content.json`. Second,
*"the fourth"* silently substituted the classification for lexicon entries as the fourth item of
their sentence, which it is not — the classification is what they addressed separately.
- The replacement names three things and totals none of them: severities/thresholds/dispositions
are absent and move no verdict; lexicon entries move no verdict either — spec section 5 forbids
failing a runtime over `observed_out_of_scope` — but a lexicon calibration change **ages** those
two entries as evidence, which is the exposure
`active_content_measurement_0_7_0.movement_sweep.residue_is_the_field_no_test_protects` already
names as a class, and this corpus pins a stale residue entry rather than rewriting it; and the
active-content classification is the one thing that can move a verdict. The retired sentence is
**quoted** in the field's `AMENDED IN 0.6.1` clause, not merely dropped, for the same reason
`scope_planned.$comment` quotes what it retired: a correction that does not say what it corrects
cannot be audited.
## [0.8.0] — 2026-08-13
**The seven active-content fixtures pin a VERSION of the seed runtime, and nothing said so.**
That runtime tagged `v1.0.0` on 2026-08-13 and stated that the freeze covers its exported Python
surface only, excluding detection behaviour: severities, thresholds, lexicon entries and
dispositions are calibration there and move in minor and patch releases. The manifest already
pinned commit and version per measurement block, but nowhere recorded that the thing pinned is a
version rather than a frozen classification. No case is minted, no data file is touched, no id is
proposed.
### Added
- **`conformance/manifest.json` 0.5.2 → 0.6.0 —
`active_content_provenance.pins_a_version_not_a_frozen_classification`.** One field, scoping the
neighbouring `asymmetry` rather than replacing it, and deliberately narrower than the runtime's
own statement. The exposure is bounded by what the fixtures assert, which was read from all seven
rather than assumed: every finding carries `pattern_id` and nothing else — no severity, no
threshold, no disposition — so three of the four dimensions that runtime names as calibration
cannot move one of these cases at all. The fourth can, because which `active:` ids a payload
yields *is* the classification. The field names both pins (six at 0.4.0 / `de09711`, the seventh
at 0.7.0 / `be9759b`) rather than one, since a single version would flatten two measurements into
one header — the defect `superseded_for_one_case` exists to prevent. Their v1.0.0 statement is
**attributed** to their coord message of 2026-08-13T20:40:31Z, not restated as a fact measured
from this side.
- The field also names the disposition of a future divergence, so it is not left to be inferred: a
later 1.x that classifies one of these payloads differently is not a breach by them and does not
make the fixture wrong. The fixture stays ground truth at its pinned version, the divergence is
measured and recorded, and re-pinning is a separate decision — the same disposition this corpus
already applies to a stale `observed_out_of_scope` entry.
### Fixed
- **`docs/secret-egress-divergence.md:75-76` carried the same misquote `conformance/manifest.json`
had corrected in 0.7.3**, named there as a deliberate omission and closed here. The field's value
ends ``ascending `order` `` — the backticks are the field's own. The fix is *not* the one the
omission note implied: those two lines are a single code span delimited by **single** backticks
across a line break, so inserting the field's backticks inside it would have terminated the span
at the first one and rendered the quote broken. The outer delimiter is promoted to double
backticks instead, which is what lets the inner singles survive. The manifest's correction ported
as a literal string because JSON has no backtick semantics; markdown does. Verified by extracting
the span from the file on disk, unfolding the line break, and comparing to the decoded value in
`signatures/secret-egress.json` — equal — and by confirming no backtick run of length ≥ 2 sits
inside the span.
### Not done, and named rather than left silent
- **`spec/conformance-corpus.md` section 7 is untouched.** It states the disagreement rule without
scope: *"The fixture is ground truth. A runtime that disagrees is wrong."* Read against the
active-content scope, whose only implementing runtime has now said in writing that its
classification may legitimately move, that rule would call a calibration change there a bug. The
manifest field records the interaction and explicitly does not amend the spec. Whether the
normative rule needs a scope is a decision for its own release.
## [0.7.3] — 2026-08-13
**The README still argued the premise 0.7.2 retired, and the two files sat on a public remote
disagreeing.** `README.md` opened the egress gap with "It is not an id question at all"; the
blocker it sends the reader to for authority now opens reason (1) with "NO ID SPACE ON THE
COMMONS SIDE. This is the hard blocker." Before 0.7.2 the README was merely out of date. After
it, our own commit had made it contradictory — the same defect class 0.7.1 existed to close. No
data moves, no case is minted, no id is proposed.
### Fixed
- **The README now carries the three measured reasons instead of the retired one.** (1) No id
space on the commons side — the hard blocker, and the only one an answer can resolve; the
answer belongs to the runtimes that own the seeds. (2) Match semantics disagree, and an id
space would not close it. (3) Membership diverges in both directions and the divergence is
inherited: the two sides hold 19 entries and 25, and they are ports of two *different* source
tables in one source repository. The counts survived the falsification; only the causal claim
fell, so `different tables` is kept and "cut at different granularities" is gone. The
paragraph deliberately does **not** restate the outgoing question's status: that is true on
the day it is written, nothing tests README prose, and `conformance/manifest.json` already
carries the date. The standing `entry_points_by_scope` requirement is likewise left out rather
than printed as a fourth reason.
- **`conformance/manifest.json` 0.5.1 → 0.5.2: the blocker misquoted the contract it cites.** It
rendered the field as `match_semantics: "… evaluated in ascending order"`; the value in
`signatures/secret-egress.json` ends ``ascending `order` `` — the backticks are the field's
own. A blocker that misquotes the semantics it is blocking on invites a consumer to implement
the wrong one. The data file is
unchanged and was never wrong — only the quotation of it was, which `scope_planned.$comment`
now records. Verified by reading the edited file back from disk and matching the decoded
string against the data file that owns it; `json.tool` passes on wrong escaping.
Known and deliberately left: `docs/secret-egress-divergence.md` renders the same value without
its backticks. That document is `Status: informative` and was outside this release's scope.
## [0.7.2] — 2026-08-13
**`scope_planned.blockers` named the premise that `docs/secret-egress-divergence.md`

View file

@ -65,6 +65,13 @@ Ingen. Data + prosa. Filformater: JSON (data + schema), Markdown (spec), rå tek
- `expected.json` er ground truth. Er en runtime uenig med `expected.json`, er runtimen
feil — med mindre fixturen selv bevises feil, og da endres fixturen i eget commit med
begrunnelse.
- **Regelen over er skopet, og skopet er bærende.** Er casens scope en tabell bare ÉN runtime
implementerer, og den runtimen skrev payloaden, finnes ikke den andre implementasjonen
regelen dømmer mellom. Da er en divergens fra *den* runtimen verken en bevist feil fixture
eller nødvendigvis deres bug: fixturen skrives ikke om på divergensen alene, den føres mot
versjonen som er pinnet, og re-pinning er en egen release. Mot en TREDJEPARTS-implementasjon
av samme tabell gjelder §7 uendret. Til og med `v0.8.1` sto regelen uskopet. Se
`spec/conformance-corpus.md` §7.1.
- **En case er ikke mintbar uten inngangspunkt for sitt scope.** Korpuset pinner ikke
lenger ett inngangspunkt per runtime for alt — `manifest.json`
`entry_points_by_scope` bærer inngangspunkt, **findings-accessor** og

View file

@ -107,10 +107,16 @@ this document:
- `<case-id>` is stable and descriptive. **Changing a case id is a breaking change** — a
published conformance result names it.
- `expected.json` is **ground truth**. If a runtime disagrees with it, the runtime is wrong.
- The one way that reverses: the fixture is proven wrong. Then the fixture changes **in its own
- One way that reverses: the fixture is proven wrong. Then the fixture changes **in its own
commit, with the reason written down** — never folded into a change that does something else,
because a fixture edit is the one edit that can make every conforming runtime wrong
identically.
- The other, added in `v0.9.0`: where a case's scope is a table only one runtime implements and
that runtime authored the payload, a divergence by **that** runtime is neither a proven-wrong
fixture nor necessarily its bug. The fixture is not rewritten on the divergence alone — it is
recorded against the version pinned, and re-pinning is a separate release. Through `v0.8.1`
this list carried only the first way. See
[`spec/conformance-corpus.md` §7.1](spec/conformance-corpus.md).
- A case declares the data files it is `scope`d to. A runtime that does not implement a scoped
table reports the case `not-applicable` — a third verdict beside pass and fail, and one that
must be reported rather than dropped from the denominator. See

View file

@ -16,6 +16,18 @@ unicode-carrier smuggling or active content in untrusted text, on any runtime.
**It holds no runnable code.** Data, specifications and fixtures only.
## Table of Contents
- [Install](#install)
- [Requirements](#requirements)
- [What it does](#what-it-does)
- [Non-goals](#non-goals)
- [Known limitations](#known-limitations)
- [Contributing](#contributing)
- [Reporting a wrong entry](#reporting-a-wrong-entry)
- [Changelog](#changelog)
- [License](#license)
## Install
Nothing to install — this repository is **vendored into consumers**, not installed.
@ -109,11 +121,22 @@ number would misreport all three:
missing **name** rather than a missing capability. It lapses the moment that runtime names
its label and the alias is added.
One case remains unshipped, for the secret-egress table, and it is not blocked on effort.
It is not an id question at all — the two runtimes carry *different tables*, 19 entries
against 25, cut at different granularities, and a shared id space presupposes a reconciliation
nobody has performed. `conformance/manifest.json` records that blocker under
`scope_planned.blockers`, measured, so the gap is visible rather than inferred.
One case remains unshipped, for the secret-egress table, and it is not blocked on effort. The
reasons are three, they were measured, and they are independent — none of them dissolves under
anything this repository can run alone. **(1) There is no id space on the commons side.** The
seed this table was ported from carries a name and a pattern per entry and nothing else, so its
entries are keyed by human-readable name while the other runtime emits `egress:<id>` labels —
and a fixture names labels. This is the hard blocker, and the only one of the three that an
answer can resolve; the answer belongs to the runtimes that own the seeds, not to a name coined
here. **(2) Match semantics disagree**, and an id space would not close it: this table declares
first-match-wins with `ordering.normative: true`, the other runtime reports every match, and one
witness — an `Authorization` header holding a three-part JWT — produces one label here and two
there. That difference is exactly what an `expected.json` encodes. **(3) Membership diverges in
both directions, and the divergence is inherited rather than introduced.** The two sides hold 19
entries and 25, but they are ports of two *different* source tables in one source repository, so
re-measuring either port cannot close it. `conformance/manifest.json` records all three under
`scope_planned.blockers`, and the method behind every number is in
[the divergence measurement](docs/secret-egress-divergence.md).
The carrier blocker closed in v0.5.0 and is kept, with its retired text, under
`scope_planned.blockers_resolved` — including the correction one runtime volunteered against

View file

@ -18,7 +18,7 @@ detector, and that is a working bypass against every consumer until it is closed
Report privately by email:
- **hello@fromaitochitta.com**, with `SECURITY` at the start of the subject.
- **security@fromaitochitta.com**, with `SECURITY` at the start of the subject.
Pull requests are not the channel either — they are switched off on the canonical
repository, and not as an oversight. This repository is vendored into independent runtimes
@ -45,8 +45,9 @@ In scope — all of these are real reports:
escaping is wrong for the declared dialect, missing or wrong flags, a pattern that fails
to compile in a documented engine and gets skipped rather than reported.
2. **A conformance fixture that sanctions a miss.** `expected.json` is ground truth: a
runtime that disagrees with it is deemed wrong. A fixture that expects too little makes
every conforming runtime wrong identically, and the corpus will not catch it.
runtime that disagrees with it is deemed wrong (as scoped by `spec/conformance-corpus.md`
§7.1, which narrows who that reaches and not this direction). A fixture that expects too
little makes every conforming runtime wrong identically, and the corpus will not catch it.
3. **A normative clause that mandates unsafe behaviour.** The `spec/` files bind the
implementations that consume them, so a weak rule propagates to all of them.
4. **A real secret or personal data in the repository or its history.** The history is

File diff suppressed because one or more lines are too long

View file

@ -72,8 +72,8 @@ rather than by measurement.
This is the finding a membership table would hide, and it is the one a consumer implementing
from commons will get wrong first.
`signatures/secret-egress.json` declares `match_semantics: "first match wins; patterns are
evaluated in ascending order"`, marks `ordering.normative: true`, and names
`signatures/secret-egress.json` declares ``match_semantics: "first match wins; patterns are
evaluated in ascending `order`"``, marks `ordering.normative: true`, and names
`last_entry_is_load_bearing: "JWT (three-part token)"` — the JWT entry is placed last
precisely so a token inside an `Authorization` header is reported as the header, not as a
bare JWT.

View file

@ -284,6 +284,47 @@ This ordering is the whole point of the repository. Two implementations that ret
different verdicts on the same input are not holding different opinions; one of them has a
bug.
### 7.1 Where the second paragraph does not hold
**Through corpus version 0.8.1 this section stated the rule above with no scope at all**, and
the scope was load-bearing: the justification names *two* implementations. Where a case's
scope is a table only one runtime implements, and that runtime authored the payload the case
was extracted from, there is no second implementation whose disagreement the paragraph could
adjudicate. Which cases those are is recorded in the corpus, not asserted per run — see
`active_content_provenance.asymmetry` in
[`conformance/manifest.json`](../conformance/manifest.json).
For such a case, a disagreement by the **seed runtime itself** is a third thing, and it is
neither of the two the paragraph offers:
- The fixture is not proven wrong. It recorded that runtime's behaviour correctly at the
commit and version its own measurement block pins, and a later classification does not
reach back and falsify an earlier measurement.
- The runtime does not necessarily have a bug. Where the seed runtime has stated that the
classification behind such a table is calibration it does not freeze, a release that
classifies the payload differently is a change it reserved, not a defect.
So: the fixture MUST NOT be rewritten on the strength of the divergence alone; the divergence
SHOULD be recorded against the version pinned; and re-pinning the case to a later version of
the seed runtime is a separate decision, taken deliberately and released on its own. This is
the disposition §5 already applies to a stale `observed_out_of_scope` entry, extended to the
one place where it can reach a verdict — and a divergence recorded here is the signal that
the re-pinning decision is due, not a reason to leave it open.
Three things this does **not** do.
- **It creates no fourth verdict.** The counts of §1.1 and
[`schema/conformance-declaration.schema.json`](../schema/conformance-declaration.schema.json)
are unchanged: a case whose expected findings are not produced still **fails**, and is still
named in `failed_cases`. What changes is what the failure licenses concluding, not what is
reported.
- **It does not reach a third-party implementer** of the same table. Against them the fixture
is the contract, exactly as §7 states — which is what these cases were minted to provide,
and the only thing they can prove while one runtime is all there is.
- **It is not a licence to self-declare.** The exemption is carried by the corpus's own
provenance record for the scope. A runtime MUST NOT claim it for a case by asserting that
its own divergence is calibration.
## 8. What conformance does and does not prove
Passing this corpus proves that a runtime agrees with the other runtimes that pass it, on