fix(accounting-gate): the contract publishes what the gate reads, and a doorless bundle says why
Green: 117 of 117 in the suite that was 3 red. The published `--accounting` sketch gains the four keys the door has been writing and the contract did not name -- `conversions`, with its `from`/`to` pair, plus `normalised_soft_hyphen`, `unaccounted` and `double_booked` -- and a paragraph saying what a door built WITHOUT `conversions` costs the consumer who built it: every converted image claimed-and-not-found, 19 of 50 on R761. The two new tests hold the sketch against both sides, what the gate looks up and what the door serialises, so neither drifting again is silent. N5, and the choice: SAY IT OUT LOUD rather than only in a docstring. A docstring is read by whoever edits this file; the reader who is misled is reading a REPORT, where a count of claimed-and-not-found images appeared with no cause beside it. `_tally` now names the missing ledger when, and only when, something was claimed and not found -- two known-negatives hold that condition. `asset_holds` gets the sentence as well, since it costs nothing. mypy --strict on this file: 11 errors before, 11 after (pre-existing, this file is not in the `src/` gate). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
7e5248aa84
commit
c4d890987a
1 changed files with 48 additions and 6 deletions
|
|
@ -21,17 +21,33 @@ THE DOOR THE CAPABILITY MUST OPEN (the contract this gate reads). `okf build`
|
|||
accepts `--accounting PATH` and writes one JSON object there:
|
||||
|
||||
{"accounting_version": 1,
|
||||
"unaccounted": <elements no fate accounts for, over the whole run>,
|
||||
"double_booked": <elements two fates account for>,
|
||||
"refused": <documents the build read and persisted nothing of>,
|
||||
"normalised_soft_hyphen": <U+00AD removed before the persist gate>,
|
||||
"documents": [
|
||||
{"source_file": "<inbox-relative path>",
|
||||
"status": "persisted" | "rejected", "code": "<rejection code>" | null,
|
||||
"normalised_soft_hyphen": <this document's share of the above>,
|
||||
"conversions": [{"from": "<sha256 of the source image>",
|
||||
"to": "<sha256 of the file the run carried>"}],
|
||||
"inventory": {"<element>": <count>, ...},
|
||||
"fates": {"<element>": {"carried": n, "pointer": n,
|
||||
"rejected": {"<code>": n}}}}],
|
||||
"rejected": {"<code>": n}}},
|
||||
"unaccounted": {"<element>": n}, "double_booked": {"<element>": n}}],
|
||||
"files": [
|
||||
{"source_file": "<inbox-relative path>",
|
||||
"fate": "carried" | "merged" | "rejected", "code": "<code>" | null}]}
|
||||
|
||||
`conversions` IS LOAD-BEARING AND WAS UNPUBLISHED UNTIL 2026-09-19. It is the
|
||||
run's own ledger of images it REWROTE -- today a BMP that reaches the bundle
|
||||
as a PNG -- and `asset_holds` proves such a carry by it, because the same
|
||||
pair stated in the bundle's prose can be written by a document. A door built
|
||||
from a contract without it writes a ledger this gate reads as "nothing was
|
||||
converted", and every converted image comes out claimed-and-not-found: 19 of
|
||||
50 on R761. `normalised_soft_hyphen`, `unaccounted` and `double_booked` had
|
||||
been written for a round longer than they were published.
|
||||
|
||||
`inventory` is taken BEFORE extraction and before the persist gate, in the
|
||||
witness's element vocabulary (per file type, defined in `okf_witness.py`), so
|
||||
a document the gate refuses still has one. `files` covers every inbox file
|
||||
|
|
@ -486,6 +502,15 @@ def asset_holds(build: Build, source: Path) -> bool:
|
|||
a conversion the run did not book still fails, which is the difference
|
||||
between reading the artifacts and believing the report.
|
||||
|
||||
AND NEITHER ROUTE WORKS WITHOUT THE RUN'S LEDGER. The second one reads the
|
||||
pair out of the `--accounting` file, so on a bundle built WITHOUT that flag
|
||||
a converted image cannot be proved at all and this returns False -- the
|
||||
reading R761 gave 19 times the day conversion landed. The gate itself
|
||||
always passes the flag (`door_available()`), so the package build is
|
||||
untouched; any other caller on a doorless bundle gets the weak reading, and
|
||||
since 2026-09-19 the report says so in as many words rather than printing
|
||||
the count alone.
|
||||
|
||||
WHAT NEITHER ROUTE PROVES IS FIDELITY. Both ask whether a file in
|
||||
`assets/` holds the bytes the bundle names, and neither decodes a PIXEL:
|
||||
measured 2026-09-19, a mutated converter that writes a BLANK PNG is
|
||||
|
|
@ -935,15 +960,31 @@ def row2(table: Iterable[str], inventory: Mapping[str, Any], build: Build, door:
|
|||
return _row(2, name, len(good), len(table), reason, details)
|
||||
|
||||
|
||||
def _tally(units: Iterable[Unit]) -> str:
|
||||
"""What the gate FOUND, with the denominator beside it."""
|
||||
def _tally(units: Iterable[Unit], *, ledger: bool = True) -> str:
|
||||
"""What the gate FOUND, with the denominator beside it.
|
||||
|
||||
`ledger` is whether the run that produced these units wrote an
|
||||
`--accounting` file. Without one there is no record of what the run
|
||||
CONVERTED, so `asset_holds` falls back to its first route and a picture
|
||||
that reached the bundle under a new digest is counted claimed-and-not-found
|
||||
-- exactly the reading the conversion route removed (R761: 19 of 50). That
|
||||
fallback is honest and it used to be silent, which is a different thing:
|
||||
the number was printed and its cause was not, so a reader of a doorless
|
||||
bundle's report could only conclude the bundle had lost the pictures.
|
||||
"""
|
||||
units = list(units)
|
||||
verified = sum(u.verified for u in units)
|
||||
unverified = sum(u.unverified for u in units)
|
||||
blind = sum(u.unverifiable for u in units)
|
||||
unproved = (
|
||||
f"; this build carries no `{ACCOUNTING_FLAG}` ledger, so an image the run CONVERTED "
|
||||
"cannot be proved carried and is counted among them"
|
||||
if unverified and not ledger
|
||||
else ""
|
||||
)
|
||||
return (
|
||||
f"{verified} carried element(s) found in the bundle, {unverified} claimed and not found, "
|
||||
f"{blind} carrying no text of their own (the gate cannot check those)"
|
||||
f"{blind} carrying no text of their own (the gate cannot check those){unproved}"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -963,7 +1004,7 @@ def row3(units: list[Unit], door: bool) -> Row:
|
|||
)
|
||||
if not door:
|
||||
reason += f"; no `{ACCOUNTING_FLAG}` door, so no element has a declared fate"
|
||||
details = [_tally(units)] + [
|
||||
details = [_tally(units, ledger=door)] + [
|
||||
f"{u.kind} {u.name}: u={u.unaccounted} d={u.double} "
|
||||
f"unverified={u.unverified} invalid={u.invalid} refused={u.refused}"
|
||||
+ (f" ({'; '.join(u.notes)})" if u.notes else "")
|
||||
|
|
@ -1236,7 +1277,8 @@ def row6(r761: Path | None, n200: Path | None, ci: bool) -> Row:
|
|||
)
|
||||
details.append(
|
||||
f" gate {label}: exit {build.exit_code}, {persisted} of {len(documents)} "
|
||||
f"document(s) persisted, {len(build.assets)} asset file(s); {_tally(units)}"
|
||||
f"document(s) persisted, {len(build.assets)} asset file(s); "
|
||||
f"{_tally(units, ledger=build.accounting is not None)}"
|
||||
)
|
||||
for unit in units:
|
||||
if unit.kind == "document" and not unit.clean:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue