feat(retrieval-gate): row 8 names the bundle it measured, and the three real sets are pinned here
REAL_SET_PINS states what each set IS -- questions, fasit entries, controls and sha256 -- so a one-question file in the right shape is refused (`check_real_pin`, exit 2) instead of reading `1 of 1 | 3 of 3 | GREEN`. Three counts rather than one: the digest says the bytes are the pinned bytes, the counts are what a reader can check against the source. Every set's line now carries `measured against <key> = <path> | bundle_id <id> | ref <sha256-tree:...>`, for the bundles the run actually used and SS 3.3's two halves both printed -- three builds on this machine share one bundle_id at three refs. Stated limit: the table lives in the file a capability session edits, as SYNTHETIC_SETS and SPECS_SHA256 do. It raises the cost of the attack and does not remove it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3dc2491083
commit
34fe70a401
1 changed files with 116 additions and 0 deletions
|
|
@ -1819,6 +1819,111 @@ def read_real_set(name: str, path: Path, expected_sha256: str) -> QuestionSet:
|
|||
REQUIRED_REAL_SETS: tuple[str, ...] = ("wiki-20", "r761-sk2", "vegnormal-32")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RealSetPin:
|
||||
"""What a real set IS, stated here rather than taken from the command line.
|
||||
|
||||
Until 2026-09-20 BOTH the file and its expected sha256 came from the
|
||||
caller, and `set_id` was decided by the adapter rather than by the file:
|
||||
three one-question files written in the three shapes, against a
|
||||
self-written bundle, read `wiki-20: 1 of 1 ... | 3 of 3 | GREEN`. Nothing
|
||||
said how big `wiki-20` is. Row 9 has had the mechanism since 2026-09-19
|
||||
(`K2_QUESTIONS` refuses a set of another size); this is that mechanism for
|
||||
the three sets row 8 requires.
|
||||
|
||||
THREE COUNTS, NOT ONE. The sha256 is the strongest and the least
|
||||
informative: it says the bytes are the pinned bytes and nothing about what
|
||||
they contain. The two counts are what a reader can check against the
|
||||
source, and they are what a re-freeze of a set would move. All three are
|
||||
facts about files this repository never holds -- a digest and two integers
|
||||
name no document.
|
||||
|
||||
THE LIMIT, STATED: this table is in the file a capability session edits,
|
||||
exactly as `SYNTHETIC_SETS` and `SPECS_SHA256` are. It raises the cost of
|
||||
the attack (the set, the bundle AND this table) and does not remove it;
|
||||
the suite is the rest of the gate, and says so.
|
||||
"""
|
||||
|
||||
questions: int
|
||||
fasit_entries: int
|
||||
controls: int
|
||||
sha256: str
|
||||
|
||||
|
||||
#: Measured 2026-09-20 against the three sources, each read through its own
|
||||
#: adapter. `questions` is the number of `Question` objects the adapter
|
||||
#: produces, which is why `vegnormal-32` is 37: five of its 32 questions cite
|
||||
#: two standards, and a payload is built against one bundle.
|
||||
REAL_SET_PINS: Mapping[str, RealSetPin] = {
|
||||
"wiki-20": RealSetPin(
|
||||
questions=20,
|
||||
fasit_entries=29,
|
||||
controls=0,
|
||||
sha256="972d0f5715d1377b3d89b8ddf391612709b96cd0fe8b96dfe517fe1931a9e333",
|
||||
),
|
||||
"r761-sk2": RealSetPin(
|
||||
questions=7,
|
||||
fasit_entries=7,
|
||||
controls=1,
|
||||
sha256="c834a478e4888300845de9e166808a3942085cb73c6e9e5fd2a3e1a6e9c5e6fd",
|
||||
),
|
||||
"vegnormal-32": RealSetPin(
|
||||
questions=37,
|
||||
fasit_entries=43,
|
||||
controls=0,
|
||||
sha256="c3932fc9abd144989bdbc50c4e4627ac5cc59937c4204f92422b7fe10af87faa",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def check_real_pin(question_set: QuestionSet) -> None:
|
||||
"""The set the adapter produced, against what this gate says that set is.
|
||||
|
||||
Raises rather than warning: a set that is not the pinned set measured
|
||||
something else, and row 8's number is then attached to a name it has not
|
||||
earned.
|
||||
"""
|
||||
pin = REAL_SET_PINS.get(question_set.set_id)
|
||||
if pin is None:
|
||||
raise GateUsage(
|
||||
f"{question_set.set_id}: no pin for this set; row 8 measures the "
|
||||
f"pinned sets {', '.join(REQUIRED_REAL_SETS)} and no others"
|
||||
)
|
||||
measured = (
|
||||
len(question_set.questions),
|
||||
question_set.units,
|
||||
len(question_set.controls),
|
||||
question_set.sha256,
|
||||
)
|
||||
expected = (pin.questions, pin.fasit_entries, pin.controls, pin.sha256)
|
||||
if measured != expected:
|
||||
raise GateUsage(
|
||||
f"{question_set.set_id}: pinned as {pin.questions} question(s), "
|
||||
f"{pin.fasit_entries} fasit entr(ies), {pin.controls} control(s), "
|
||||
f"sha256 {pin.sha256[:12]}; measured {measured[0]}, {measured[1]}, "
|
||||
f"{measured[2]}, sha256 {question_set.sha256[:12]} -- a set of another "
|
||||
"size or another content is another set wearing this one's name"
|
||||
)
|
||||
|
||||
|
||||
def bundle_identity(bundle: Path) -> str:
|
||||
"""What a reader needs to run the same measurement again: the path, the
|
||||
`bundle_id` the root index declares and the content ref.
|
||||
|
||||
SS 3.3's own distinction, both halves printed: a `bundle_id` is the
|
||||
producer's assertion and a ref is a fact about bytes. Three builds on this
|
||||
machine carry one `bundle_id` at three refs, so the id alone names a
|
||||
bundle no better than the set's sha256 names a bundle.
|
||||
"""
|
||||
try:
|
||||
return (
|
||||
f"{_display(bundle)} | bundle_id {consume.root_bundle_id_of(bundle)} "
|
||||
f"| ref {consume.bundle_ref(bundle)}"
|
||||
)
|
||||
except Exception as error: # an unreadable bundle is a line, never a crash
|
||||
return f"{_display(bundle)} | identity unreadable: {type(error).__name__}: {error}"
|
||||
|
||||
|
||||
def row_eight(real: Sequence[tuple[QuestionSet, Mapping[str, Path]]]) -> Row:
|
||||
"""The three real sets. RED when they have not run -- always, in this
|
||||
order -- and never green by leaving a set out.
|
||||
|
|
@ -1879,6 +1984,15 @@ def row_eight(real: Sequence[tuple[QuestionSet, Mapping[str, Path]]]) -> Row:
|
|||
f"({'citation' if question_set.quoted else 'concept'} granularity) | "
|
||||
f"{answered} of {asked} questions | sha256 {question_set.sha256[:12]}"
|
||||
)
|
||||
# THE BUNDLE IS NAMED, not only the set. Until 2026-09-20 the row
|
||||
# printed the set's digest and nothing about what it was measured
|
||||
# against, so `44 of 64` could neither be reproduced nor felled by
|
||||
# anyone reading the output.
|
||||
used = sorted({question.bundle or question_set.bundle for question, _ in cases})
|
||||
details.extend(
|
||||
f" measured against {key or '-'} = {bundle_identity(bundles[key])}"
|
||||
for key in used
|
||||
)
|
||||
for unit in units:
|
||||
if not unit.hit:
|
||||
details.append(
|
||||
|
|
@ -2089,6 +2203,8 @@ def _real_sets(
|
|||
real: list[tuple[QuestionSet, Mapping[str, Path]]] = []
|
||||
for name, path, sha, bundle in arguments:
|
||||
question_set = read_real_set(name, Path(path).expanduser(), sha)
|
||||
# The command line said what the file is; this says what the set is.
|
||||
check_real_pin(question_set)
|
||||
bundles = _bundle_map(bundle)
|
||||
if list(bundles) == [""] and question_set.bundle:
|
||||
bundles = {question_set.bundle: bundles[""]}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue