fix(accounting): a document refused whole is never clean, and the run says how many (H1)
The gate gains a FIFTH column, `Unit.refused`, and it is the only one that is not a defect in the report: the elements of a document the build read and persisted nothing of. Their fate is declared honestly, so `unaccounted` and `double_booked` both stay 0 -- which is exactly why nothing else could see the loss. `refused_whole` asks its question only for a corpus that persisted NOTHING, so one refused source beside an accepted one, the ordinary case on a heterogeneous corpus, reached row 3 as clean. Row 3's reason now carries `N element(s) lost with R of D document(s) refused whole`, and each unclean unit's detail line carries `refused=` beside u, d, unverified and invalid, with the document's own rejection code in the note. On the build side `Accounting.refused` is written into the JSON and into the `**Accounting**` bullet of `log.md` as `R of D document(s) refused whole`. The exit code is NOT moved: it belongs to the whole run, and a corpus holding one unreadable file among many is ordinary, so the order's other half -- state it in the accounting -- is the one taken. `okf build` still exits 1 when it persisted nothing at all. `test_a_corpus_refused_whole_under_the_default_gate_is_red` kept its point and lost its premise: the numbers still balance, and that is now asserted as u = 0 and d = 0 rather than as a clean unit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
5d1060d467
commit
d27ca503c8
4 changed files with 51 additions and 6 deletions
|
|
@ -21,6 +21,7 @@ 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,
|
||||
"refused": <documents the build read and persisted nothing of>,
|
||||
"documents": [
|
||||
{"source_file": "<inbox-relative path>",
|
||||
"status": "persisted" | "rejected", "code": "<rejection code>" | null,
|
||||
|
|
@ -479,6 +480,11 @@ class Unit:
|
|||
`invalid` are about the bundle and the declaration themselves. A booking
|
||||
the gate could not verify is never clean, and `verified`/`unverifiable`
|
||||
carry the denominator behind that word.
|
||||
|
||||
`refused` is the fifth and the only one that is not a defect in the
|
||||
REPORT: the elements of a document the build refused whole. Their fate is
|
||||
honestly declared and their content is not in the bundle, so the numbers
|
||||
balance and nothing else here can see the loss (H1).
|
||||
"""
|
||||
|
||||
name: str
|
||||
|
|
@ -489,11 +495,14 @@ class Unit:
|
|||
invalid: int = 0
|
||||
verified: int = 0
|
||||
unverifiable: int = 0
|
||||
refused: int = 0
|
||||
notes: list[str] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def clean(self) -> bool:
|
||||
return not (self.unaccounted or self.double or self.unverified or self.invalid)
|
||||
return not (
|
||||
self.unaccounted or self.double or self.unverified or self.invalid or self.refused
|
||||
)
|
||||
|
||||
|
||||
def _conservation_held(build: Build) -> bool:
|
||||
|
|
@ -644,6 +653,18 @@ def _document_unit(
|
|||
if persisted and total > 0 and booked_carried == 0:
|
||||
invalid += 1
|
||||
notes.append("the build persisted this document and the report carries nothing from it")
|
||||
# H1: a document refused whole balances by construction -- every element
|
||||
# is a coded rejection, so u = 0 and d = 0 -- and `refused_whole` below
|
||||
# asks its question only for a corpus that persisted NOTHING. One refused
|
||||
# source beside an accepted one is the ordinary case on a heterogeneous
|
||||
# corpus, and it read CLEAN with the content gone.
|
||||
refused = 0
|
||||
if status == "rejected" and not persisted and total > 0:
|
||||
refused = total
|
||||
notes.append(
|
||||
f"refused whole: {total} element(s) declared rejected `{code}`, "
|
||||
"and the bundle holds nothing from this document"
|
||||
)
|
||||
return Unit(
|
||||
name,
|
||||
"document",
|
||||
|
|
@ -653,6 +674,7 @@ def _document_unit(
|
|||
invalid=invalid,
|
||||
verified=verified,
|
||||
unverifiable=unverifiable,
|
||||
refused=refused,
|
||||
notes=notes,
|
||||
)
|
||||
|
||||
|
|
@ -803,15 +825,19 @@ def row3(units: list[Unit], door: bool) -> Row:
|
|||
d_total = sum(u.double for u in units)
|
||||
unverified = sum(u.unverified for u in units)
|
||||
invalid = sum(u.invalid for u in units)
|
||||
refused = sum(u.refused for u in units)
|
||||
documents = sum(1 for u in units if u.kind == "document")
|
||||
refused_docs = sum(1 for u in units if u.refused)
|
||||
reason = (
|
||||
f"u = {u_total} unaccounted, d = {d_total} double-booked, "
|
||||
f"{unverified} booked carried and not in the bundle, {invalid} declaration(s) the gate refuses"
|
||||
f"{unverified} booked carried and not in the bundle, {invalid} declaration(s) the gate refuses, "
|
||||
f"{refused} element(s) lost with {refused_docs} of {documents} document(s) refused whole"
|
||||
)
|
||||
if not door:
|
||||
reason += f"; no `{ACCOUNTING_FLAG}` door, so no element has a declared fate"
|
||||
details = [_tally(units)] + [
|
||||
f"{u.kind} {u.name}: u={u.unaccounted} d={u.double} "
|
||||
f"unverified={u.unverified} invalid={u.invalid}"
|
||||
f"unverified={u.unverified} invalid={u.invalid} refused={u.refused}"
|
||||
+ (f" ({'; '.join(u.notes)})" if u.notes else "")
|
||||
for u in units
|
||||
if not u.clean
|
||||
|
|
@ -1060,6 +1086,7 @@ def row6(r761: Path | None, n200: Path | None, ci: bool) -> Row:
|
|||
details.append(
|
||||
f" {unit.name}: u={unit.unaccounted} d={unit.double} "
|
||||
f"unverified={unit.unverified} invalid={unit.invalid} "
|
||||
f"refused={unit.refused} "
|
||||
f"({'; '.join(unit.notes)})"
|
||||
)
|
||||
doubled = [u for u in units if u.kind == "file" and u.double]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue