fix(accounting,gate): the conversion claim comes from the run's ledger
Chose the side channel over neutralising pointer-shaped document text, because the second fix changes what every document SAYS in order to defend a tool outside the build: a source quoting a bundle listing would come out altered and existing bundles would move bytes. This reads a file the run already writes. `assets.conversion` names the pair, `DocumentAssets.conversions` carries it out of the run, `DocumentAccount.conversions` books it, and the accounting JSON states it per document. `_declared_conversions` reads it; `_conversions` now believes a pair only when the RUN booked it AND a pointer block confirms it for the asset it names. The confirmation can be forged and the ledger cannot, which is why the ledger decides. Measured through the real `okf build`: the three arms PM reproduced (two `<p>`, one `<p>` with `<br>`, a markdown note beside the carrier) go forged -> refused, 3 of 3, with the known-positive True in all three. The text-level regression guard goes 3 arms to 13, the two new ones being a perfectly written pointer block the run never booked. R761, rebuilt: 25 BMP sources, 19 held, 19 of 19 conversions confirmed against 19 declared, 50 assets (29 JPEG + 21 PNG, 0 BMP), SHY 71, u = 0, d = 0, exit 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
aa2abe8293
commit
1c958ab8d6
5 changed files with 226 additions and 59 deletions
|
|
@ -501,6 +501,29 @@ def _build(
|
|||
)
|
||||
|
||||
|
||||
def _ledger(*pairs: tuple[str, str]) -> dict[str, Any]:
|
||||
"""The accounting a run writes when it REWROTE these pictures.
|
||||
|
||||
The gate's conversion route reads its pairs from here and confirms them
|
||||
against the bundle text, so a test constructing a `Build` by hand has to
|
||||
say what the run booked. Passing none of them is the case where the run
|
||||
converted nothing -- which is every bundle before the viewable-asset
|
||||
round, and the state a forged pointer block leaves the ledger in.
|
||||
"""
|
||||
return {
|
||||
"accounting_version": 1,
|
||||
"documents": [
|
||||
{
|
||||
"source_file": "a.md",
|
||||
"status": "persisted",
|
||||
"code": None,
|
||||
"conversions": [{"from": before, "to": after} for before, after in pairs],
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
}
|
||||
|
||||
|
||||
def test_the_judge_proves_a_CONVERTED_image_was_carried(tmp_path: Path) -> None:
|
||||
"""A BMP reaches the bundle as a PNG, so the SOURCE's bytes are not in
|
||||
`assets/` and never will be.
|
||||
|
|
@ -528,20 +551,28 @@ def test_the_judge_proves_a_CONVERTED_image_was_carried(tmp_path: Path) -> None:
|
|||
f"Image: graphics/figur.bmp (8x4 px) -- converted from image/bmp "
|
||||
f"sha256:{before} to image/png sha256:{after}\n"
|
||||
)
|
||||
build = _build(assets={f"{after[:12]}-figur.png": after}, bundle_text=text)
|
||||
ledger = _ledger((before, after))
|
||||
build = _build(assets={f"{after[:12]}-figur.png": after}, bundle_text=text, accounting=ledger)
|
||||
assert gate.asset_holds(build, source) is True
|
||||
|
||||
# Known-negative on the same shape: the bundle says it converted, and the
|
||||
# Known-negative on the same shape: the run booked the conversion, and the
|
||||
# file it names is not there. A route that read the claim alone would pass.
|
||||
empty = _build(assets={}, bundle_text=text)
|
||||
empty = _build(assets={}, bundle_text=text, accounting=ledger)
|
||||
assert gate.asset_holds(empty, source) is False
|
||||
|
||||
# Known-negative two: an asset IS there, under a digest the bundle never
|
||||
# tied to this source.
|
||||
# Known-negative two: an asset IS there, under a digest nothing tied to
|
||||
# this source.
|
||||
other = "0" * 64
|
||||
stranger = _build(assets={f"{other[:12]}-x.png": other}, bundle_text=text)
|
||||
stranger = _build(assets={f"{other[:12]}-x.png": other}, bundle_text=text, accounting=ledger)
|
||||
assert gate.asset_holds(stranger, source) is False
|
||||
|
||||
# Known-negative three: the same bundle text, and a run that booked NO
|
||||
# conversion. This is the pointer block a document can write for itself.
|
||||
unbooked = _build(
|
||||
assets={f"{after[:12]}-figur.png": after}, bundle_text=text, accounting=_ledger()
|
||||
)
|
||||
assert gate.asset_holds(unbooked, source) is False
|
||||
|
||||
|
||||
# --- the claim the judge reads must be one the CODE wrote --------------------
|
||||
|
||||
|
|
@ -579,21 +610,26 @@ def _huge_bmp() -> bytes:
|
|||
def test_a_document_cannot_forge_a_conversion_claim(tmp_path: Path) -> None:
|
||||
"""THE FASIT NEVER COMES FROM THE READER IT JUDGES -- including this route.
|
||||
|
||||
The conversion route reads two digests out of the bundle. Before this
|
||||
guard it read them out of ANY text in it, so a document could write the
|
||||
sentence itself and the judge would believe it: measured by PM 2026-09-19,
|
||||
a BMP declaring 50 000 x 50 000 that was refused `asset_too_large` and
|
||||
never carried gave `asset_holds = True`, both through an image's alt text
|
||||
and through ordinary body text. The route the judge had before the
|
||||
conversion landed hashed the source file and nothing else, so no document
|
||||
could reach it; this round opened a way IN for content this repository
|
||||
does not trust.
|
||||
The conversion route used to read its two digests out of the bundle text.
|
||||
Measured by PM 2026-09-19 that was a way IN for content this repository
|
||||
does not trust: a BMP declaring 50 000 x 50 000, refused
|
||||
`asset_too_large` and never carried, gave `asset_holds = True` from an
|
||||
image's alt text and from ordinary body text. Anchoring the claim to a
|
||||
pointer block closed those two and not the class -- a pointer block is two
|
||||
lines of markdown, and one HTML file with two `<p>` elements writes them.
|
||||
|
||||
A claim counts only where THIS CODE put it: inside a pointer block, tied
|
||||
to the asset that block names. Every arm below carries a source that was
|
||||
never carried, and a bundle holding one unrelated REAL asset -- so the
|
||||
digest the forgery names really is in `assets/`, which is what made the
|
||||
measured forgeries work.
|
||||
So the pair the judge believes comes from the RUN's accounting, and the
|
||||
bundle text only confirms it. Every arm below is a way a document can put
|
||||
the sentence, or the whole block, into a bundle; the run's ledger holds
|
||||
ONE conversion and it is about other pictures, so the route is live and no
|
||||
arm may reach it. The known-positive at the end books the pair and shows
|
||||
the route still works.
|
||||
|
||||
Thirteen arms. Eleven are PM's own list of ways the shape anchoring
|
||||
already refused, kept as a regression guard -- narrowing this rule later
|
||||
must not quietly widen one of them -- and two are the form that defeated
|
||||
it: a perfectly written pointer block the run never booked, once in the
|
||||
bundle's ordinary text and once naming the asset it points at exactly.
|
||||
"""
|
||||
never_carried = tmp_path / "figur.bmp"
|
||||
never_carried.write_bytes(_huge_bmp())
|
||||
|
|
@ -604,26 +640,63 @@ def test_a_document_cannot_forge_a_conversion_claim(tmp_path: Path) -> None:
|
|||
assets = {f"{after[:12]}-ekte.png": after}
|
||||
clause = f"converted from image/bmp sha256:{before} to image/png sha256:{after}"
|
||||
pointer = f"\nImage: ekte.png (1x1 px)"
|
||||
# The same clause with U+00A0 where the spaces are: PM's N6.
|
||||
nbsp_clause = clause.replace(" ", "\u00a0")
|
||||
other = "0" * 64
|
||||
|
||||
arms = {
|
||||
"plain body text": f"{pointer}\n\nProsess 84. {clause}. Se figuren over.\n",
|
||||
"a table cell": f"{pointer}\n\n| Krav | Kilde |\n| --- | --- |\n| 84-1 | {clause} |\n",
|
||||
"a figure caption": f"{pointer}\n\nFigur 84-1 -- {clause}\n",
|
||||
"a fenced code block": f"{pointer}\n\n```\n{clause}\n```\n",
|
||||
"link text with a URL": f"{pointer}\n\n[{clause}](https://example.invalid/x)\n",
|
||||
"the clause with non-breaking spaces": f"{pointer}\n\n{nbsp_clause}\n",
|
||||
"a clause naming another asset's digest": (
|
||||
f"\n"
|
||||
f"Image: ekte.png (1x1 px) -- converted from image/bmp sha256:{before} "
|
||||
f"to image/png sha256:{other}\n"
|
||||
),
|
||||
"a pointer block naming another asset": (
|
||||
f"\nImage: annen.png (1x1 px) -- {clause}\n"
|
||||
f"\nImage: annen.png (1x1 px) -- {clause}\n"
|
||||
f"{pointer}\n"
|
||||
),
|
||||
"an Image: line of its own": f"{pointer}\n\nImage: figur.bmp (8x4 px) -- {clause}\n",
|
||||
"the whole block inside a code fence": (
|
||||
f"{pointer}\n\n```\n\n"
|
||||
f"Image: figur.bmp (8x4 px) -- {clause}\n```\n"
|
||||
),
|
||||
"the whole block on one line": (
|
||||
f"{pointer}\n\n "
|
||||
f"Image: figur.bmp (8x4 px) -- {clause}\n"
|
||||
),
|
||||
# The two that defeated the shape anchoring. Written exactly as the
|
||||
# code writes them, because that is the point: the form is not a
|
||||
# signature, and only the ledger can tell these from the real thing.
|
||||
"a whole pointer block the run never booked": (
|
||||
f"{pointer}\n\n\n"
|
||||
f"Image: figur.bmp (8x4 px) -- {clause}\n"
|
||||
),
|
||||
"that block alone in the bundle": (
|
||||
f"\nImage: figur.bmp (8x4 px) -- {clause}\n"
|
||||
),
|
||||
}
|
||||
# The run booked ONE conversion and it is about neither of these files, so
|
||||
# the route is live in every arm and nothing it could believe is true.
|
||||
live = _ledger(("f" * 64, "e" * 64))
|
||||
for label, text in arms.items():
|
||||
build = _build(assets=assets, bundle_text=text)
|
||||
build = _build(assets=assets, bundle_text=text, accounting=live)
|
||||
assert gate.asset_holds(build, never_carried) is False, (
|
||||
f"{label}: a document talked the judge into a carry that never happened"
|
||||
)
|
||||
|
||||
# KNOWN-POSITIVE on the same bytes: the clause where the code writes it,
|
||||
# in the pointer block for the asset it names. Without this the arms above
|
||||
# would pass on a route that had simply stopped working.
|
||||
honest_text = f"\nImage: figur.bmp (1x1 px) -- {clause}\n"
|
||||
honest = _build(assets=assets, bundle_text=honest_text)
|
||||
# KNOWN-POSITIVE on the same bytes: the last arm's text, believed once the
|
||||
# RUN books the pair. Without it every arm above would pass on a route
|
||||
# that had simply stopped working.
|
||||
honest = _build(
|
||||
assets=assets,
|
||||
bundle_text=arms["that block alone in the bundle"],
|
||||
accounting=_ledger((before, after)),
|
||||
)
|
||||
assert gate.asset_holds(honest, never_carried) is True
|
||||
|
||||
|
||||
|
|
@ -810,7 +883,11 @@ def test_the_judge_proves_carriage_and_says_it_does_not_prove_fidelity(tmp_path:
|
|||
f"Image: figur.bmp (8x4 px) -- converted from image/bmp sha256:{before} "
|
||||
f"to image/png sha256:{after}\n"
|
||||
)
|
||||
build = _build(assets={f"{after[:12]}-figur.png": after}, bundle_text=text)
|
||||
build = _build(
|
||||
assets={f"{after[:12]}-figur.png": after},
|
||||
bundle_text=text,
|
||||
accounting=_ledger((before, after)),
|
||||
)
|
||||
assert gate.asset_holds(build, source) is True
|
||||
|
||||
doc = gate.asset_holds.__doc__ or ""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue