diff --git a/docs/research/recursive-invoice-preflight.md b/docs/research/recursive-invoice-preflight.md index d55f6c136c..0df27930ca 100644 --- a/docs/research/recursive-invoice-preflight.md +++ b/docs/research/recursive-invoice-preflight.md @@ -50,6 +50,46 @@ claim. Keep the source word, extraction command and exact head with the invoice. The [worked package](../../research/owned-relation-exhaustion/README.md) independently reconstructs its complete source invoice before comparing it. +## Export the selected P104 bill + +For the selected P104 helper-subspace source, use the opt-in exporter: + +```sh +python3 -B scripts/export_selected_invoice.py \ + --evidence /tmp/selected-invoice-evidence.json > /tmp/selected-invoice.json +python3 -B scripts/compare_recursive_invoices.py \ + /tmp/selected-invoice.json CANDIDATE.json +``` + +The exporter first runs the selected source-binding check. It then counts each +local MOVE from its frame dimensions and each productive public helper from its +endpoint frames. It counts LOCAL calls, query calls, whole idle jobs and main +diagonal targets from the physical controller. All component bills and the full +bill must agree with the authenticated local, native and complete caller receipts. +The evidence file records separate components and exact input hashes. The invoice +on standard output uses the comparator schema without extra fields. + +This closes the manual transcription gap for the supported selected source. +The candidate still needs its own complete, justified bill. The exporter does +not replay scalar endpoint matrices, native phases or compiler inequalities. +Use the selected record's reproduction command for those checks. Unknown source +formats and controllers fail; a future selection can require another extractor. +The source word, controller, receipts and selected result remain unchanged. + +The tests compare the bill with an independent count from the two path lengths. +They remove each physical charge class and each paid or target rank entry, test +source and ownership errors, and corrupt actual source files before running the +CLI. Run them with: + +```sh +python3 -B -m unittest discover -s tests -p 'test_research_selected_invoice.py' -v +``` + +The source P104 word is by Pierre-Loic Doulcet (hexapode). Ali Romman supplied +the complete caller and helper accounting; Cinna Davis supplied the controller. +Jacob Alan Sussman supplied productive recursion. The comparator is by zxv. +The exporter is by Ali Romman with substantial OpenAI Codex assistance. + | Result | What it establishes | Next action | |---|---|---| | `uniformly_worse` | Larger paid-minus-target cost for every power 0= public, "Owned operation on a public field") + if op == "INIT": + require(role not in owned, "Repeated owned initialization") + owned.add(role) + current[role] = frame + else: + require(role in owned and current[role] == frame, "Invalid owned erasure") + owned.remove(role) + current[role] = None + else: + require(op in ("SCALE", "ADD", "COPY"), "Unsupported local opcode: " + str(op)) + require(len(event) == (4 if op == "SCALE" else 5), "Scalar event length") + q, frame = Fraction(event[-2]), event[-1] + index(frame, len(frames)) + require(q != 0 and current[role] == frame, "Scalar destination frame") + if op != "SCALE": + source = index(event[2], public + 8) + require(source != role and current[source] == frame, "Scalar source frame") + if op == "COPY": + require(role in owned, "COPY destination must be owned") + require(not owned and current[:public] == word["final"], "Incomplete public or owned exits") + live = word["live"] + require(len(live) == len(set(live)) == len(word["ports"]) == 104, "P104 main inventory") + for role in live: + index(role, 112) + main_roles = set(live) | {r + 112 for r in live} + helpers = Counter() + for role in range(public): + if role not in main_roles: + a, b = word["initial"][role], word["final"][role] + require(frames[a] < frames[b], "Productive helper inclusion") + helpers[len(word["frames"][b]) - len(word["frames"][a])] += 1 + + require((controller["kind"], controller["pairs"], controller["prefix_pairs"], + controller["ambient_rank"]) == ("complex", 16, 7, 145), + "Unsupported P104 controller") + events, query, idle = Counter(), Counter(), Counter() + for event in controller["physical_events"]: + op = event[0] + events[op] += 1 + if op in ("LOCAL", "QUERY_SHEAR"): + _, stage, pair, back = event + index(stage, 33); index(pair, 16) + require(back is False, "Only forward P104 calls are supported") + if op == "QUERY_SHEAR": + query[1] += 104 + elif op == "IDLE": + _, pair, stages = event + index(pair, 16) + require(stages and len(stages) == len(set(stages)), "Invalid whole IDLE stages") + for stage in stages: + index(stage, 33) + idle[8 * len(stages)] += 208 + elif op == "Q_POWER": + _, role, power = event + index(role, 32) + require(type(power) is int and power in (-2, 2), "Unsupported Q_POWER") + elif op in ("ADD", "SWAP", "SIGNED_SWAP"): + require(len(event) == (4 if op == "ADD" else 3), "Physical scalar event length") + a, b = index(event[1], 32), index(event[2], 32) + require(a != b, "Physical scalar roles must differ") + if op == "ADD": + require(type(event[3]) is int and event[3] in (-1, 1), "Unsupported physical ADD") + else: + raise ValueError("Unsupported physical opcode: " + str(op)) + main = Counter() + require(len(controller["diagonal"]) == 32, "Main diagonal size") + for term in controller["diagonal"]: + require(len(term) == 34 and type(term[0]) is int and term[0] in (-1, 1) + and all(type(x) is int and x in (0, 1) for x in term[1:]), "Main diagonal term") + main[1 + 8 * sum(term[1:])] += 104 + local_paid = Counter({r: n * events["LOCAL"] for r, n in paid.items()}) + local_targets = Counter({r: n * events["LOCAL"] for r, n in helpers.items()}) + invoice = {"schema": "integer-mult-recursive-invoice/v1", "contract": { + "model": "mixed-target-unnormalized-power-v1", + "rank_semantics": "H_r costs r^(1-C); debit surrendered productive fields", + "ambient_rank": 145, + "boundary": "complete unit before the same mixed-target compiler"}, + "paid": histogram(local_paid + query + idle), "targets": histogram(local_targets + main)} + components = {"local_paid": histogram(paid), "local_helpers": histogram(helpers), + "query_paid": histogram(query), "idle_paid": histogram(idle), + "main_targets": histogram(main), "local_events": dict(sorted(operations.items())), + "physical_events": dict(sorted(events.items()))} + return invoice, components + + +def compare_receipt(invoice, components, receipt): + """Reject disagreement with the authenticated local, native and caller checks.""" + for name, expected in ( + ("local_paid", receipt["local"]["paid"]), + ("local_helpers", receipt["local"]["helpers"]), + ("local_helpers", receipt["native"]["helpers"]), + ("idle_paid", receipt["physical_caller"]["whole_idle_jobs"]), + ("main_targets", receipt["physical_caller"]["main_targets"]), + ): + require(components[name] == expected, "Receipt mismatch: " + name) + for name in ("paid", "targets"): + require(invoice[name] == receipt["complete_cost"][name], "Receipt mismatch: " + name) + + +def export(root=ROOT): + root = Path(root).resolve() + record_bytes = (root / SELECTED).read_bytes() + record = json.loads(record_bytes) + binding = runpy.run_path(str(ROOT / "scripts/verify_selected_result.py")) + binding["check"](record, root) + manifest = json.loads((root / record["source_manifest"]).read_bytes()) + require(manifest.get("format") == "repository-files-sha256-v1", "Explicit source inventory required") + certificate = json.loads((root / record["certificate"]).read_bytes()) + require(certificate.get("source_result") == RESULT + and record.get("source_certificate") == RESULT, "Unsupported selected source") + inputs = {} + for name in (WORD, CONTROLLER, RESULT): + require(name in manifest["files"], "Unbound invoice input: " + name) + raw = binding["explicit_file"](root, name).read_bytes() + require(sha256(raw).hexdigest() == manifest["files"][name], "Invoice input bytes changed") + inputs[name] = json.loads(gzip.decompress(raw) if name.endswith(".gz") else raw) + invoice, components = extract(inputs[WORD], inputs[CONTROLLER]) + compare_receipt(invoice, components, inputs[RESULT]) + validator = runpy.run_path(str(ROOT / "scripts/compare_recursive_invoices.py")) + validator["validate_invoice"](invoice) + evidence = {"schema": "selected-p104-invoice-evidence/v1", "selected_kappa": record["kappa"], + "invoice_sha256": sha256((json.dumps(invoice, indent=2) + "\n").encode("utf-8")).hexdigest(), + "selected_record_sha256": sha256(record_bytes).hexdigest(), + "source_head": record["audited_source_head"], "components": components, + "inputs_sha256": {name: manifest["files"][name] for name in inputs}, + "scope": "Authenticated accounting reconstruction; no native, scalar endpoint or compiler replay"} + return invoice, evidence + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=ROOT, help="Repository or exported selected source tree") + parser.add_argument("--evidence", type=Path, help="Write source hashes and separate accounting components") + args = parser.parse_args() + try: + invoice, evidence = export(args.root) + if args.evidence: + args.evidence.write_text(json.dumps(evidence, indent=2, sort_keys=True) + "\n", encoding="utf-8") + print(json.dumps(invoice, indent=2)) + except (OSError, ValueError, KeyError, TypeError) as error: + print("Invoice export failed: " + str(error), file=sys.stderr) + return 2 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_research_selected_invoice.py b/tests/test_research_selected_invoice.py new file mode 100644 index 0000000000..50b8c3598a --- /dev/null +++ b/tests/test_research_selected_invoice.py @@ -0,0 +1,165 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Check invoice extraction, missing charge classes and real source corruption. + +Ali Romman with substantial OpenAI Codex assistance. +""" + +from collections import Counter +from copy import deepcopy +import gzip +from hashlib import sha256 +import importlib.util +import json +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts/export_selected_invoice.py" +SPEC = importlib.util.spec_from_file_location("selected_invoice", SCRIPT) +M = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(M) + + +class SelectedInvoiceTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.word = json.loads(gzip.decompress((ROOT / M.WORD).read_bytes())) + cls.controller = json.loads((ROOT / M.CONTROLLER).read_bytes()) + cls.receipt = json.loads((ROOT / M.RESULT).read_bytes()) + cls.invoice, cls.components = M.extract(cls.word, cls.controller) + + def test_independent_path_length_bill(self): + # This count uses the two controller path lengths, not physical events + # or the emitted main diagonal. The local receipt is a separate oracle. + paid = Counter({int(r): 18 * n for r, n in self.receipt["local"]["paid"].items()}) + targets = Counter({int(r): 18 * n for r, n in self.receipt["local"]["helpers"].items()}) + paid[1] += 15 * 104 + targets[145] += 208 + for length in (7, 8): + for j in range(1, length + 1): + paid[8 * (j + 1)] += 208 + targets[1 + 8 * (j + 1)] += 208 + self.assertEqual(self.invoice["paid"], M.histogram(paid)) + self.assertEqual(self.invoice["targets"], M.histogram(targets)) + self.assertEqual((sum(paid.values()), sum(targets.values())), (66726, 13048)) + self.assertEqual(sum(r * n for r, n in targets.items()) + - sum(r * n for r, n in paid.items()), 760) + M.compare_receipt(self.invoice, self.components, self.receipt) + + def test_each_physical_charge_class_is_required(self): + for op in ("LOCAL", "QUERY_SHEAR", "IDLE"): + controller = deepcopy(self.controller) + events = controller["physical_events"] + del events[next(i for i, e in enumerate(events) if e[0] == op)] + invoice, components = M.extract(self.word, controller) + with self.subTest(op=op), self.assertRaisesRegex(ValueError, "Receipt mismatch"): + M.compare_receipt(invoice, components, self.receipt) + + def test_every_paid_rank_and_target_debit_is_checked(self): + for field in ("paid", "targets"): + for rank in self.invoice[field]: + bad = deepcopy(self.invoice) + bad[field][rank] -= 1 + with self.subTest(field=field, rank=rank), self.assertRaisesRegex(ValueError, field): + M.compare_receipt(bad, self.components, self.receipt) + + def test_component_and_native_receipts_cannot_hide_in_total(self): + for field in ("local_paid", "local_helpers", "idle_paid", "main_targets"): + bad = deepcopy(self.components) + bad[field][next(iter(bad[field]))] -= 1 + with self.subTest(field=field), self.assertRaisesRegex(ValueError, field): + M.compare_receipt(self.invoice, bad, self.receipt) + bad = deepcopy(self.receipt) + bad["native"]["helpers"]["9"] -= 1 + with self.assertRaisesRegex(ValueError, "local_helpers"): + M.compare_receipt(self.invoice, self.components, bad) + + def test_local_chronology_and_unknown_opcodes_fail(self): + for change in ("move", "private", "unknown", "exit", "basis", "owned"): + word = deepcopy(self.word) + event = next(e for e in word["program"] if e[0] == "MOVE") + if change == "move": + event[2], event[3] = event[3], event[2] + elif change == "private": + event[4] = not event[4] + elif change == "unknown": + word["program"][0][0] = "UNPRICED" + elif change == "exit": + word["final"][0] = (word["final"][0] + 1) % len(word["frames"]) + elif change == "basis": + word["frames"][event[2]].append(0) + else: + word["program"].append(["INIT", word["public"], 0]) + with self.subTest(change=change), self.assertRaises(ValueError): + M.extract(word, self.controller) + + def test_physical_semantic_changes_fail(self): + for change in ("unknown", "reverse", "idle", "diagonal", "controller"): + controller = deepcopy(self.controller) + if change == "unknown": + controller["physical_events"].append(["UNPRICED", 0]) + elif change == "reverse": + next(e for e in controller["physical_events"] if e[0] == "LOCAL")[3] = True + elif change == "idle": + next(e for e in controller["physical_events"] if e[0] == "IDLE")[2] = [0, 0] + elif change == "diagonal": + controller["diagonal"][0][1] = 2 + else: + controller["ambient_rank"] = 146 + with self.subTest(change=change), self.assertRaises(ValueError): + M.extract(self.word, controller) + + def test_authenticated_cli_and_comparator(self): + record = json.loads((ROOT / M.SELECTED).read_bytes()) + if record.get("source_certificate") != M.RESULT: + self.skipTest("The selected source has moved beyond the opt-in P104 exporter") + manifest = json.loads((ROOT / record["source_manifest"]).read_bytes()) + names = set(manifest["files"]) | set(record["proof_supplements"]) + names.update([M.SELECTED, record["source_manifest"], record["certificate"], + record["review"], record["validation"]]) + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + for name in names: + dest = root / name + dest.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(ROOT / name, dest) + evidence = root / "evidence.json" + command = [sys.executable, "-I", "-O", "-B", str(SCRIPT), "--root", str(root), + "--evidence", str(evidence)] + run = subprocess.run(command, text=True, capture_output=True) + self.assertEqual(run.returncode, 0, run.stderr) + self.assertEqual(json.loads(run.stdout), self.invoice) + self.assertEqual(json.loads(evidence.read_bytes())["components"], self.components) + self.assertEqual(json.loads(evidence.read_bytes())["invoice_sha256"], + sha256(run.stdout.encode("utf-8")).hexdigest()) + invoice_path = root / "invoice.json" + invoice_path.write_text(run.stdout, encoding="utf-8") + compare = subprocess.run([sys.executable, "-I", "-B", + str(ROOT / "scripts/compare_recursive_invoices.py"), str(invoice_path), str(invoice_path)], + capture_output=True, text=True) + self.assertEqual(compare.returncode, 0, compare.stderr) + self.assertEqual(json.loads(compare.stdout)["classification"], "equal") + for name in (M.WORD, M.CONTROLLER, M.RESULT): + path = root / name + original = path.read_bytes() + path.write_bytes(original + b"changed") + bad = subprocess.run(command, text=True, capture_output=True) + self.assertEqual(bad.returncode, 2, name) + self.assertEqual(bad.stdout, "") + self.assertIn("bytes changed", bad.stderr) + path.write_bytes(original) + # A manifest which omits an input must not leave a trusted receipt + # pointing to an unauthenticated word. + del manifest["files"][M.WORD] + (root / record["source_manifest"]).write_text(json.dumps(manifest), encoding="utf-8") + bad = subprocess.run(command, text=True, capture_output=True) + self.assertEqual(bad.returncode, 2) + self.assertIn("Unbound invoice input", bad.stderr) + + +if __name__ == "__main__": + unittest.main()