Caught by the steward asking whether CONTROL-B was PASS 2. It is not — different
document, different question — but checking the answer exposed a defect in the
correlation measurement I had just proposed.
CONTROL-B IS NOT CONTROL-A PLUS FIVE DEFECTS. The transformations overlap the two
real defects trial 04 found:
· clause-5-out-of-scope GONE — D4 deletes that quotation outright
· dropped-qualifier GONE — D1 replaces the sentence with an explicit
version of the same error, which is why the twin
carries openly what the control carried concealed
· asserted precedence SURVIVES, at line 51, UNLOGGED
So the twin holds six defects and the ledger recorded five. The grading rule
would have scored a correct finding on the sixth as a FALSE POSITIVE.
AND THE GATE COULD NOT HAVE CAUGHT IT. twin.py verifies that the ledger records
every DIFFERENCE between the two documents. It does not verify that the ledger
records every DEFECT in the twin. Those are different claims, and the file
asserted the second while proving only the first — a defect already present in
the control is not a difference, so it passes untouched. Fifth instance of a
check certifying a property of the code while claiming a property of the result,
this time inside the artifact built to escape that class.
Fixed: an inherited_defects list records I1 with its provenance and why it
survives; a defects_not_surviving note records the two that do not, so the twin
is never mistaken for a superset of the control; the grading rule now spans both
sets; and the gate's own output states what it does NOT establish, warning when
inherited_defects is absent — because absent is not the same as none, it means
no one has looked.
The correlation measurement can now use the twin honestly. It could not have
before this.
Note on this message: the first attempt lost three terms to shell command
substitution, because backticks in a -m string are evaluated by zsh. Amended.
Recorded rather than silently repaired, since a commit message is part of the
record and this one is about incomplete records.
170 lines
6.8 KiB
Python
Executable File
170 lines
6.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Defect-twin construction — ground truth by ledger rather than by reading.
|
|
|
|
WHY THIS EXISTS
|
|
Every grade in the Fool programme has been assigned by the executor, whose
|
|
own reading is the thing under test. Kernel v1.1 §7 removes that for the
|
|
detection arm: a defect is a RECORDED TRANSFORMATION of a kernel-sound
|
|
control, so what counts as a real finding is a ledger entry, not a judgement.
|
|
|
|
A defect must be invisible to every mechanical check. If §3 caught it, the
|
|
Fool would not be the thing being measured. Injected defects therefore live
|
|
entirely in Kernel §4's judgement residue — a `D` that no longer demonstrates,
|
|
while still parsing, still tagged, and still resolving every quotation.
|
|
|
|
THE BIDIRECTIONAL GATE
|
|
forward(control) == twin AND invert(twin) == control, both byte-exact.
|
|
|
|
Forward alone is not enough. Forward alone would pass a ledger that omits an
|
|
edit, because the omitted edit is simply carried in the twin file — which is
|
|
precisely how laundering would enter. The inverse is what makes the ledger
|
|
COMPLETE rather than merely non-empty: an unlogged edit survives inversion and
|
|
the round trip fails.
|
|
|
|
USAGE
|
|
./twin.py build <control.md> <ledger.json> -o <twin.md>
|
|
./twin.py verify <control.md> <twin.md> <ledger.json>
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def sha256(text: str) -> str:
|
|
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
|
|
|
|
def load_ledger(path: Path) -> dict:
|
|
led = json.loads(path.read_text(encoding="utf-8"))
|
|
for d in led["defects"]:
|
|
for e in d["edits"]:
|
|
if "find" not in e or "replace" not in e:
|
|
sys.exit(f"FATAL: defect {d['id']}: edit needs 'find' and 'replace'")
|
|
return led
|
|
|
|
|
|
def _edits_in_order(ledger: dict) -> list[tuple[str, str, str]]:
|
|
return [
|
|
(d["id"], e["find"], e["replace"])
|
|
for d in ledger["defects"]
|
|
for e in d["edits"]
|
|
]
|
|
|
|
|
|
def apply_forward(control: str, ledger: dict) -> str:
|
|
"""Apply every edit exactly once. A `find` that is absent or ambiguous is fatal."""
|
|
text = control
|
|
for did, find, repl in _edits_in_order(ledger):
|
|
n = text.count(find)
|
|
if n == 0:
|
|
sys.exit(f"FATAL: {did}: 'find' not present in control:\n {find[:90]!r}")
|
|
if n > 1:
|
|
sys.exit(f"FATAL: {did}: 'find' occurs {n} times; must be unique:\n {find[:90]!r}")
|
|
text = text.replace(find, repl, 1)
|
|
return text
|
|
|
|
|
|
def apply_inverse(twin: str, ledger: dict) -> str:
|
|
"""Undo every edit, in reverse, to reconstruct the control."""
|
|
text = twin
|
|
for did, find, repl in reversed(_edits_in_order(ledger)):
|
|
n = text.count(repl)
|
|
if repl == "":
|
|
# A deletion cannot be located by searching for the empty string, so
|
|
# its inverse is an insertion at the point the surrounding text
|
|
# determines. Handled by requiring deletions to carry an `anchor`.
|
|
sys.exit(
|
|
f"FATAL: {did}: deletion has no invertible anchor. Express a deletion "
|
|
"as a replacement of the block INCLUDING a unique neighbouring line."
|
|
)
|
|
if n == 0:
|
|
sys.exit(f"FATAL: {did}: 'replace' text absent from twin; ledger is stale")
|
|
if n > 1:
|
|
sys.exit(f"FATAL: {did}: 'replace' occurs {n} times in twin; must be unique")
|
|
text = text.replace(repl, find, 1)
|
|
return text
|
|
|
|
|
|
def cmd_build(control_path: Path, ledger_path: Path, out: Path) -> None:
|
|
control = control_path.read_text(encoding="utf-8")
|
|
ledger = load_ledger(ledger_path)
|
|
twin = apply_forward(control, ledger)
|
|
back = apply_inverse(twin, ledger)
|
|
if back != control:
|
|
sys.exit("FATAL: ledger is not invertible; refusing to write a twin.")
|
|
out.write_text(twin, encoding="utf-8")
|
|
print(f"control {control_path.name} sha256 {sha256(control)[:16]}…")
|
|
print(f"twin {out.name} sha256 {sha256(twin)[:16]}…")
|
|
print(f"defects {len(ledger['defects'])} "
|
|
f"({sum(len(d['edits']) for d in ledger['defects'])} edits)")
|
|
print("round trip verified: forward and inverse both byte-exact.")
|
|
|
|
|
|
def cmd_verify(control_path: Path, twin_path: Path, ledger_path: Path) -> None:
|
|
control = control_path.read_text(encoding="utf-8")
|
|
twin = twin_path.read_text(encoding="utf-8")
|
|
ledger = load_ledger(ledger_path)
|
|
|
|
problems: list[str] = []
|
|
if apply_forward(control, ledger) != twin:
|
|
problems.append(
|
|
"FORWARD FAILED: control + ledger does not reproduce the twin. The twin "
|
|
"contains a change the ledger does not record, or records one it does not "
|
|
"contain."
|
|
)
|
|
if apply_inverse(twin, ledger) != control:
|
|
problems.append(
|
|
"INVERSE FAILED: twin - ledger does not reproduce the control. The ledger "
|
|
"is incomplete — an edit was made and not written down."
|
|
)
|
|
|
|
print(f"control sha256 {sha256(control)[:16]}…")
|
|
print(f"twin sha256 {sha256(twin)[:16]}…")
|
|
print(f"defects {len(ledger['defects'])}")
|
|
if problems:
|
|
print("\nLEDGER GATE FAILED — ground truth is NOT established:")
|
|
for p in problems:
|
|
print(f" - {p}")
|
|
sys.exit(1)
|
|
print("\nLEDGER GATE PASSED — every DIFFERENCE between the two documents is")
|
|
print("recorded, and nothing recorded is absent.")
|
|
print("\nThis is NOT a claim that the ledger records every DEFECT in the twin.")
|
|
print("A defect already present in the control is not a difference, so it passes")
|
|
print("this gate untouched and must be recorded by reading, under")
|
|
print("`inherited_defects`. Trial 04 found two such defects in a control this")
|
|
print("gate had already passed. Ground truth = defects + inherited_defects.")
|
|
inherited = ledger.get("inherited_defects")
|
|
if inherited is None:
|
|
print("\n WARNING: no `inherited_defects` key. Absent is not the same as none —")
|
|
print(" it means no one has looked. State an empty list to record that they have.")
|
|
else:
|
|
print(f"\n inherited defects recorded by reading: {len(inherited)}")
|
|
|
|
|
|
def main() -> None:
|
|
ap = argparse.ArgumentParser(description="Build and verify a defect twin.")
|
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
b = sub.add_parser("build")
|
|
b.add_argument("control", type=Path)
|
|
b.add_argument("ledger", type=Path)
|
|
b.add_argument("-o", "--out", type=Path, required=True)
|
|
v = sub.add_parser("verify")
|
|
v.add_argument("control", type=Path)
|
|
v.add_argument("twin", type=Path)
|
|
v.add_argument("ledger", type=Path)
|
|
args = ap.parse_args()
|
|
if args.cmd == "build":
|
|
cmd_build(args.control, args.ledger, args.out)
|
|
else:
|
|
cmd_verify(args.control, args.twin, args.ledger)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|