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")
|
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:
|
def row_eight(real: Sequence[tuple[QuestionSet, Mapping[str, Path]]]) -> Row:
|
||||||
"""The three real sets. RED when they have not run -- always, in this
|
"""The three real sets. RED when they have not run -- always, in this
|
||||||
order -- and never green by leaving a set out.
|
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"({'citation' if question_set.quoted else 'concept'} granularity) | "
|
||||||
f"{answered} of {asked} questions | sha256 {question_set.sha256[:12]}"
|
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:
|
for unit in units:
|
||||||
if not unit.hit:
|
if not unit.hit:
|
||||||
details.append(
|
details.append(
|
||||||
|
|
@ -2089,6 +2203,8 @@ def _real_sets(
|
||||||
real: list[tuple[QuestionSet, Mapping[str, Path]]] = []
|
real: list[tuple[QuestionSet, Mapping[str, Path]]] = []
|
||||||
for name, path, sha, bundle in arguments:
|
for name, path, sha, bundle in arguments:
|
||||||
question_set = read_real_set(name, Path(path).expanduser(), sha)
|
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)
|
bundles = _bundle_map(bundle)
|
||||||
if list(bundles) == [""] and question_set.bundle:
|
if list(bundles) == [""] and question_set.bundle:
|
||||||
bundles = {question_set.bundle: bundles[""]}
|
bundles = {question_set.bundle: bundles[""]}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue