fix(u6): fold the cold contract panel — the import selection gap, and a document arguing with itself
/heid-contract-review panel 01M35BWCJ806MT75NA630Y4WFH. The headline arrived from all four arms independently and it is a missing feature, not a wording problem. `bench import --apply` registered every candidate, while the same contract says roughly 14 of 35 are reference bookmarks that must stay on the board. There was no selection mechanism between the dry-run report and the write -- so the write path did the exact thing this unit's rationale calls impossible, tell a bench from a bookmark by its URL, silently, to rows that belong where they are. The report existed precisely because the decision is not mechanizable. `--apply` now takes the ids the operator names; a bare `--apply` is refused and an unknown id is refused, both writing nothing. Two solo findings, both real: - A successful registration could push the registry past the size its own reader refuses, so the LAST bench added would make every other bench invisible while reporting success. The writer now respects the reader's cap. - The credential ban covered bench URLs and not `booth link`, the door this unit did not touch -- and the board renders on an unauthenticated LAN surface. A password can no longer reach it through either door. A small deliberate widening, named rather than smuggled. Cap semantics were readable three ways (refuse / clip-for-display / truncate-and-store) with a different build behind each, 4-of-4. Now stated per field: name and owner truncate, url and state are refused at the write and are DAMAGE at the read. url is not a display budget -- INV-7 promises the click goes to the posted address byte for byte, and a clipped URL keeps that promise in the type system while breaking it in the browser. The code had been clipping it; fixed. Two passages disagreed about one character: INV-7's specimen named "a trailing slash on a non-empty path" as something normalization changes, while the rule list keeps it and INV-6 makes the two spellings two benches. The rule list is right; the specimen was wrong. Found by 3-of-4. Also: INV-6's component list was illustrative where it had to be exhaustive and was short scheme and port; "writes nothing" appeared twice with different lists; the dead marker's predicate was readable two ways with 221 rows riding on it; and INV-2's falsifier read as though three callers agreeing pinned something, when three callers of one wrong predicate agree perfectly -- the table's expected values are the real check and now say so. 597 -> 604 tests.
This commit is contained in:
+26
-2
@@ -174,9 +174,21 @@ def _bench_from(bench_id: str, row: object) -> Bench:
|
||||
state = row.get("state", "live")
|
||||
if state not in BENCH_STATES:
|
||||
raise ValueError(f"{bench_id}: unknown state {state!r}")
|
||||
url = row.get("url", bench_id)
|
||||
if not isinstance(url, str):
|
||||
raise ValueError(f"{bench_id}: url must be text, not {type(url).__name__}")
|
||||
if len(url) > URL_MAX:
|
||||
# REFUSED, NOT TRUNCATED — unlike `name` and `owner`. Those are display
|
||||
# budgets and clipping one costs a few characters in a panel row. A
|
||||
# clipped URL is a DEAD ANCHOR, and INV-7 promises the click goes to the
|
||||
# posted address byte for byte; silently shortening it keeps the promise
|
||||
# in the type system and breaks it in the browser. Nothing this code
|
||||
# writes can get here (normalize refuses over-long input); a hand-edited
|
||||
# registry can, and it is damage, which is what the reader reports.
|
||||
raise ValueError(f"{bench_id}: url is longer than {URL_MAX} characters")
|
||||
return Bench(
|
||||
id=bench_id,
|
||||
url=_cap(row.get("url", bench_id), URL_MAX, "url"),
|
||||
url=url,
|
||||
name=_cap(row.get("name", ""), NAME_MAX, "name"),
|
||||
owner=_cap(row.get("owner", ""), OWNER_MAX, "owner"),
|
||||
state=state,
|
||||
@@ -275,9 +287,21 @@ def _write_all(root: Path, benches: dict[str, Bench]) -> None:
|
||||
# Per-pid scratch name so two writers cannot share it: the atomic-replace
|
||||
# promise is that a READER never sees a partial file, not that two writers
|
||||
# never collide on the way there.
|
||||
body = json.dumps(payload, indent=2, sort_keys=True) + "\n"
|
||||
# THE WRITER RESPECTS THE READER'S CAP. Without this, a successful
|
||||
# registration can push the file past BENCHES_MAX_BYTES and every
|
||||
# subsequent read fails — so the LAST bench somebody added is the one that
|
||||
# makes all the others invisible, and the write that did it reported
|
||||
# success. The reader is lenient about damage; it is not lenient about
|
||||
# size, and a writer that ignores a limit its own reader enforces is
|
||||
# manufacturing exactly the state the leniency exists to survive.
|
||||
if len(body.encode("utf-8")) > BENCHES_MAX_BYTES:
|
||||
raise ValueError(
|
||||
f"that registration would push the registry past {BENCHES_MAX_BYTES} "
|
||||
f"bytes, which its own reader refuses; nothing was written")
|
||||
tmp = path.with_suffix(path.suffix + f".tmp.{os.getpid()}")
|
||||
try:
|
||||
tmp.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
|
||||
tmp.write_text(body)
|
||||
os.replace(tmp, path)
|
||||
except BaseException:
|
||||
# A write that dies between create and replace would otherwise strand
|
||||
|
||||
Reference in New Issue
Block a user