diff --git a/docs/research/pr-ledger.md b/docs/research/pr-ledger.md index eccf4d6c1d..bb856b2a16 100644 --- a/docs/research/pr-ledger.md +++ b/docs/research/pr-ledger.md @@ -28,7 +28,7 @@ This frozen round covers #543/#545/#555 and related construction, research and t 1. Follow the new Linux CI for the published #442/#484/#509/#516/#520 batch; the [scoped review](community-formal-20261012-review.md) records completed local checks. 2. Follow the explicit construction-family dispositions and remaining obligations; do not repeat completed foundation replays without a source/input change. 3. Review #525’s checkpoint/ceiling and #529/#532’s distinct construction mechanisms; retain parallel credit. -4. Reconcile #210/#279 updates with their earlier incorporated heads; audit #236’s source-package integration separately from its already published review receipts. +4. Reconcile #210's remaining ancestral paths and audit #236's source-package integration separately from its published review receipts. For #279, the [commit-authenticated parent inventory](../../research/pr279-parent-closure/README.md) closes the 116-path source-coverage question left by batch970 using #828's retained sources and replay; acceptance and parent disposition remain separate maintainer decisions. 5. Continue finite-instance connections for formal interfaces; abstract theorem builds do not instantiate all current physical constructions. ## Starting PR inventory diff --git a/research/pr279-parent-closure/OVERLAP.json b/research/pr279-parent-closure/OVERLAP.json new file mode 100644 index 0000000000..d69cc5fe58 --- /dev/null +++ b/research/pr279-parent-closure/OVERLAP.json @@ -0,0 +1,63 @@ +{ + "date": "2026-10-13", + "base": "1b59b624d21924f8e3537cdc1ac52d3be6eda2fe", + "queries": [ + "gh pr list --state open --limit 200", + "gh pr list --state all --search 279 --limit 40", + "gh pr list --state all --search PR279 --limit 40", + "discussion 1166 latest 30 comments; PR279 live head" + ], + "explicit_pr279_results": [ + { + "headRefOid": "f063aab8d6e514188e53db57218515df9cdd186e", + "number": 280, + "state": "OPEN", + "title": "Conditional \u03ba = 0.000724189082265949: gen4 retiming, 440 early restorations and 60 dirty entrances", + "url": "https://github.com/CrocSwap/integer-mult-bounds/pull/280" + }, + { + "headRefOid": "e2fae6a92775b8f609d917a9cc93b31e2559dd5f", + "number": 372, + "state": "OPEN", + "title": "Refine reviewed gen4 envelopes: conditional kappa = 0.000723571593879006127174", + "url": "https://github.com/CrocSwap/integer-mult-bounds/pull/372" + }, + { + "headRefOid": "2e43537c0e8ed1c9f9ef21cab0ef9cddb5d79976", + "number": 828, + "state": "MERGED", + "title": "Research consolidation: reconcile PR279 target-prefix sources and replay", + "url": "https://github.com/CrocSwap/integer-mult-bounds/pull/828" + }, + { + "headRefOid": "6fe78cbe3a5499acabdeefd7f91ea710a20f6f8a", + "number": 393, + "state": "CLOSED", + "title": "Deep retiming", + "url": "https://github.com/CrocSwap/integer-mult-bounds/pull/393" + }, + { + "headRefOid": "f6092eac77d965d6dc197cf796b0a3580382c0ef", + "number": 526, + "state": "OPEN", + "title": "[Lean] Bound automatic-width counting by 12n+4", + "url": "https://github.com/CrocSwap/integer-mult-bounds/pull/526" + }, + { + "headRefOid": "222fb22c0ae2d13c25674cb8c18d168d8518cef5", + "number": 930, + "state": "MERGED", + "title": "Research consolidation: reconcile and replay PR210's parity/fusion update (no new kappa)", + "url": "https://github.com/CrocSwap/integer-mult-bounds/pull/930" + } + ], + "parent_refresh": { + "headRefOid": "913ccc48a0bc1173709ea92280b96252256b7754", + "state": "OPEN", + "updatedAt": "2026-10-10T05:21:07Z" + }, + "claim_urls": [ + "https://github.com/CrocSwap/integer-mult-bounds/discussions/1166#discussioncomment-18908144" + ], + "distinction": "PR828 already reconstructs/replays the update; this contribution authenticates both original Git commits/27 trees, every matched parent path/blob, and the repository-wide increment. No new mathematical replay or disposition claim. PR116 and selected PR1042 provenance audits concern different heads." +} diff --git a/research/pr279-parent-closure/README.md b/research/pr279-parent-closure/README.md new file mode 100644 index 0000000000..93405268eb --- /dev/null +++ b/research/pr279-parent-closure/README.md @@ -0,0 +1,132 @@ +# PR279: commit-authenticated parent inventory closure + +**Research consolidation; no new bound or mathematical replay.** The batch970 +[companion disposition](../community-batch970-audit/COMPANION-REVIEWS.json) +explicitly leaves reconciliation of PR279's original **116-file parent +inventory** open after incorporating #828. This contribution supplies an offline +Git-object proof that resolves that source-identity question, without changing +PR279's disposition or the selected result. + +## Finding and distinction from #828 + +[mihir-s-05's #828](https://github.com/CrocSwap/integer-mult-bounds/pull/828) +already reconstructs all 115 later package files and the unchanged workflow, +checks their declared Git blobs/SHA-256 digests, and retains a nine-stage replay. +It must receive credit for that reconstruction and execution: neither is new +here. Its checker verifies the twelve unresolved path/blob pairs and the count +of 104 previously matching paths, but does not authenticate the latter path/blob +pairs to a repository commit. Its declared commit IDs and file inventory do not +contain Git tree/commit payloads from which that relationship can be checked. + +The new checker authenticates both raw commits and their relevant Git trees, +then binds the **actual retained bytes** to their complete package subtrees and +workflow entries. It also compares the entire two commit roots, recursively +opening every differing tree and skipping only equal authenticated object IDs. +Consequently the delta census is repository-wide, not just a search for changes +inside a presumed package root. Exact file modes are retained in that census. + +The result distinguishes three boundaries: + +| Boundary | Exact coverage | +|---|---| +| Earlier reviewed package | 113 files authenticated to `4d8c31eec7ae618645cb058101cb4e72a97268d3` | +| Later parent inventory | 115 package files plus one unchanged workflow authenticated to `913ccc48a0bc1173709ea92280b96252256b7754` | +| Repository-wide increment between those commits | Ten modified files and two added files, exactly #828's twelve-file archive | + +The 116 paths are exactly the union of the original comparison's 104 matched +paths and twelve unresolved paths, with **every path and Git blob** checked, +not only the counts. The candidate commit's direct parent is the reviewed head. +No additional repository change exists between those two authenticated trees. +This is not a claim that every unrelated inherited repository file was replayed. + +The remaining batch970 instruction to reconcile the original parent inventory +is therefore satisfied at this pinned head. Whether the source and already +recorded scoped review warrant accepting or closing PR279 is a separate +maintainer decision. The historical review and central disposition remain +unchanged; the original source packages, archives and selected frontier are not +modified. + +## Reproduce + +Python 3.11+ and the repository's retained source/evidence files suffice. No +network, Git installation, `.git` directory, SymPy, or mathematical replay is +needed by the verification commands: + +```sh +python3 -B research/pr279-parent-closure/verify.py +python3 -I -O -B research/pr279-parent-closure/test_verify.py +``` + +The first emits `PASS_PR279_PARENT_COMMIT_CLOSURE`, `parent_paths: 116`, +`retained_paths: 104`, `companion_paths: 12`, and repository changes +`added: 2, modified: 10`. It reports nine **retained** replay stages and +`fresh_mathematical_execution: false`. The second exercises the positive case +and 17 negative controls: altered historical/companion/workflow bytes; +omitted/extra files; a later file substituted into the reviewed base; duplicated, +omitted or false-blob inventory entries at unchanged cardinality; swapped +coverage partitions; missing/misclassified updates; forged/substituted commits; +forged/missing trees; and an unrelated authentic tree object. + +`TREE-PROOF.json` stores raw Git object payloads in base64. Git's exact +`type + space + decimal-length + NUL + payload` SHA-1 identity is recomputed +before use. Relevant tree leaves are bound to the retained bytes; every provided +tree object must be used. The fixed source head is the trust anchor, not a +mutable API response or an unauthenticated replacement file list. As with Git +source pinning elsewhere, this relies on the integrity of the pinned Git SHA-1 +identities; it does not certify authorship or establish a signature trust chain. + +To independently recapture the proof from fetched original Git objects, write +to a **new** output path (the collector refuses overwrite): + +```sh +git fetch origin pull/279/head +python3 -B research/pr279-parent-closure/capture.py --output /var/tmp/pr279-tree-proof-new.json +``` + +The collector reads the fixed heads, never `FETCH_HEAD`, and changes no source +checkout. The result is deterministic. The proof verifier does not invoke or +trust this collector. No compressed source artifact is regenerated. + +## Pins, bounded process and evidence reuse + +- Research base: `1b59b624d21924f8e3537cdc1ac52d3be6eda2fe`. +- Fresh OS-random assignment seed: **97**, research consolidation. +- Reviewed PR279 head: `4d8c31eec7ae618645cb058101cb4e72a97268d3`. +- Later PR279 head: `913ccc48a0bc1173709ea92280b96252256b7754`. +- #828 head: `2e43537c0e8ed1c9f9ef21cab0ef9cddb5d79976`. +- The inherited #828 checker, source inventory and evidence inventory SHA-256 + pins are explicit in `verify.py`; its unmodified source and evidence checks + are reused. No replacement verifier or rerun of its nine mathematical stages + is claimed. +- Local claim: [discussion 1166 comment](https://github.com/CrocSwap/integer-mult-bounds/discussions/1166#discussioncomment-18908144). + +Three candidates were compared: (1) this complete PR279 parent inventory; +(2) PR210's broader 128 unresolved ancestral paths; (3) PR236's source-package +integration, distinct from its already published review receipts. The bounded, +explicitly recorded PR279 gap was selected instead of a newest-frontier audit. +The stopping rule was closure or a concrete unresolved path for the two fixed +heads and original 116-path inventory, not a search for a stronger exponent. +There is no failed search or impossibility theorem here. + +Nearest related work: #828 supplies the source/replay foundation; #1007 concerns +a different PR116 evidence-only head; #1142 concerns the selected PR1042 replay +capsule, not this historical parent commit; #1132 distinguishes R480/R492 +receipts; #1110 reconciles PR225 pricing. The current #1094 conditional selected +value at the research base is `836330098538459/500000000000000000`; this work +neither changes it nor claims historical PR279 values as current. PR279's +all-size/compiler, chart, restoration, routing, prime-supply, precision/recovery, +complex symbolic and analytic-transfer assumptions remain inherited. This is +not a formal multiplication theorem, new acceptance decision or hosted CI run. + +## Attribution and license + +Prepared by **Adam Gall (@adamgall)** with substantial **OpenAI Codex assistance** +in research, implementation, source comparison, controls and writing. +**mihir-s-05** authored #828's retained reconstruction and replay. **Rohan Arun** +authored PR279's descent/integration, with the original Anthropic Claude +assistance disclosed in its commit; **eumemic** supplied the PR268 target-prefix +transform; **DreamingOfClouds** supplied the gen4 word/package. **Douglas Colkitt** +supplied the review and reconciliation context. Henry Grant, Jacob Sussman, +Evan McKinney, Chafik Boukhalfa, Avi Eisenberg and the full source lineage retain +their original credit and notices. No independent human peer review is claimed. +New work is Apache-2.0; all original licenses and source bytes remain intact. diff --git a/research/pr279-parent-closure/RESULTS.json b/research/pr279-parent-closure/RESULTS.json new file mode 100644 index 0000000000..f89ab193df --- /dev/null +++ b/research/pr279-parent-closure/RESULTS.json @@ -0,0 +1,40 @@ +{ + "base_commit": "1b59b624d21924f8e3537cdc1ac52d3be6eda2fe", + "assignment_seed": 97, + "track": "research consolidation", + "python": "Python 3.14.4", + "command": "python3 -B research/pr279-parent-closure/verify.py && python3 -I -O -B research/pr279-parent-closure/test_verify.py", + "exit_code": 0, + "verification_stdout": { + "authenticated_commits": 2, + "authenticated_trees": 27, + "candidate_head": "913ccc48a0bc1173709ea92280b96252256b7754", + "candidate_package_tree": "4c7079b88c9f6ab5b597c50073782d039a24ecc8", + "candidate_tree": "f419e848c935cfa7b666be39797edb245b2a9e91", + "companion_paths": 12, + "fresh_mathematical_execution": false, + "parent_paths": 116, + "repository_changes": {"added": 2, "modified": 10}, + "retained_paths": 104, + "retained_replay": { + "fresh_execution": false, + "retained_stages": ["banks", "bit", "complex", "finite", "math", "primes", "raw", "scalar", "virtual"], + "scope": "Checks stored evidence; use --replay for a fresh run.", + "status": "PASS_RETAINED_REPLAY_BINDING" + }, + "reviewed_head": "4d8c31eec7ae618645cb058101cb4e72a97268d3", + "reviewed_package_tree": "70d8599cc7f840251a4298105ada23dd2730a3c7", + "reviewed_tree": "9620d20482a4ef68dddeb0d081ec64025b8ec59e", + "scope": "Source identity and retained evidence only; parent disposition remains a maintainer decision.", + "status": "PASS_PR279_PARENT_COMMIT_CLOSURE" + }, + "tests": { + "positive_cases": 1, + "negative_controls": 17, + "summary": "Ran 18 tests in 17.764s\n\nOK", + "optimized_isolated_interpreter": true + }, + "not_executed": ["new mathematical replay", "selected frontier cold replay", "Lean", "hosted CI"], + "retained_mathematical_evidence": "research/pr279-target-prefix-reconciliation/EVIDENCE.json", + "claim_url": "https://github.com/CrocSwap/integer-mult-bounds/discussions/1166#discussioncomment-18908144" +} diff --git a/research/pr279-parent-closure/TREE-PROOF.json b/research/pr279-parent-closure/TREE-PROOF.json new file mode 100644 index 0000000000..de78911b4b --- /dev/null +++ b/research/pr279-parent-closure/TREE-PROOF.json @@ -0,0 +1,36 @@ +{ + "commits": { + "4d8c31eec7ae618645cb058101cb4e72a97268d3": "dHJlZSA5NjIwZDIwNDgyYTRlZjY4ZGRkZWIwZDA4MWVjNjQwMjViOGVjNTllCnBhcmVudCAyZTJhMzhiYTNmNDhlZGJkYTIxNTJlZDg2NjkxYzc1ZmYzMjZiMjU0CmF1dGhvciBSb2hhbiBBcnVuIDxyb2hhbmFydW5AZ21haWwuY29tPiAxNzkxNjA3NzkxIC0wNDAwCmNvbW1pdHRlciBSb2hhbiBBcnVuIDxyb2hhbmFydW5AZ21haWwuY29tPiAxNzkxNjA3NzkxIC0wNDAwCgpwcmltZV9jaGVjazogcmV0YWluIGRldGVybWluYW50IG9ibGlnYXRpb25zIGZvciBwcm9kdWNlciBmcmFtZXMgdGhlIHJldGltaW5nIHJlbW92ZXMgKFBSMjQ0J3MgcnVsZSkKCkNvLUF1dGhvcmVkLUJ5OiBDbGF1ZGUgRmFibGUgNSA8bm9yZXBseUBhbnRocm9waWMuY29tPgo=", + "913ccc48a0bc1173709ea92280b96252256b7754": "dHJlZSBmNDE5ZTg0OGM5MzVjZmE3YjY2NmJlMzk3OTdlZGIyNDViMmE5ZTkxCnBhcmVudCA0ZDhjMzFlZWM3YWU2MTg2NDVjYjA1ODEwMWNiNGU3MmE5NzI2OGQzCmF1dGhvciBSb2hhbiBBcnVuIDxyb2hhbmFydW5AZ21haWwuY29tPiAxNzkxNjA4MzMwIC0wNDAwCmNvbW1pdHRlciBSb2hhbiBBcnVuIDxyb2hhbmFydW5AZ21haWwuY29tPiAxNzkxNjA4MzMwIC0wNDAwCgpUYXJnZXQtcHJlZml4IGNvbXByZXNzaW9uICgyMjAgc3F1YXJlIGdyb3Vwcykgb24gdGhlIHJldGltZWQgZ2VuNCB3b3JkOiBrYXBwYSA9IDcuMjQwOTg3NDE5MzI3MDNlLTQKCmV1bWVtaWMncyBQUjI2OCBleGFjdCBGMiB0YXJnZXQtcHJlZml4IHRyYW5zZm9ybSBhcHBsaWVkIGFmdGVyIHRoZSBkZXNjZW50IHJldGltaW5nOiAyMjAgZm91ci10YXJnZXQgc3F1YXJlcyB3aG9zZSBkZXBlbmRlbnQncyBmcmFtZS0yMCBwcmVmaXggcmVzcG9uc2UgaXMgdGhlIEYyIHN1bSBvZiB0aHJlZSByZXRhaW5lZCBzcXVhcmUtbWF0ZXM7IDExMDAgZnJhbWUtMjAgcmVhZHMgb21pdHRlZCwgMTMyMCBzZXR1cC9yZXN0b3JlIGFkZGl0aW9ucywgZGVsdGEgezE6LTIyMCwyMDotMjIwLDIxOisyMjB9LiBTY2FsYXIvZmluaXRlL2JhbmsgY291bnRzIGFuZCBrYXBwYSB1cGRhdGVkOyBmdWxsIGVpZ2h0LXN0YWdlIHJlcGxheSBwYXNzZXMuCgpDby1BdXRob3JlZC1CeTogQ2xhdWRlIEZhYmxlIDUgPG5vcmVwbHlAYW50aHJvcGljLmNvbT4K" + }, + "schema": "pr279-parent-git-proof/1", + "trees": { + "305bd726a68ff7e53b236b11edd580f90729ade9": "MTAwNjQ0IGFyY3NfcDEyLmpzb24AmDlzqnCmQj3YFf6+cRtw16UtPRsxMDA2NDQgbG9jYWxfZGVzaWduX3AxMi5qc29uAGbOZoK7F+bTwpuNRhsNKpIHertfMTAwNjQ0IHBhaXJfbW9kdWxlX3AxMi5qc29uAFeAnfRLr+Eycc47Bzr4Sa+XIxnnMTAwNjQ0IHFtb2RfcDEyLmpzb24AIrsJJ88cbkY1zJI5Gn4xbVaI4Mw=", + "3c76e0cb5a9143b046ae8ea3d4258c48d4ba726e": "NDAwMDAgY29tcGxleABHNTBDUeqWYDRD9C99e/GtcC7akg==", + "40754c89f96824a4077f2b2aecf09fb68fcde3e0": "MTAwNjQ0IGNvbXBsZXhfYmFzaXMucHkAjEAHmKXyXTZnR7NctV6YUiIDkIMxMDA2NDQgY29tcGxleF9sYWJlbHMucHkAHyfW3jSjaV8ComjkiEzTt1ReMb4xMDA2NDQgY29tcGxleF9zY2FsYXJzLnB5AHcQV7VnYTY6j8IIco6FV8ZEgpqiMTAwNjQ0IGNvbXBsZXhfc3BsaWNlLnB5ABE3PR1XIL1wXMGHIoqkOIx1kSsJMTAwNjQ0IGdlb21ldHJ5NTI3LnB5AB6fnA/3eFRh+44T1H7Mey5jwubUMTAwNjQ0IGdsb2JhbF9sb3dlcmluZy5weQA3Eq31Iv7RNP72vRJJyuIwc9yteTEwMDY0NCBwaHlzaWNhbDUyNy5weQCCoXB/Sf8BzoWveRxRID4y4FEIHg==", + "41144158cdb8018a60438383d73c89d74079d6d8": "MTAwNjQ0IFNPVVJDRS5qc29uAMV+3K4lLtwvrl4cbG+RbtdeOw3BNDAwMDAgcmVzZWFyY2gATYLmoxoNA272li9i2RZO6RfM2iA=", + "4735304351ea96603443f42f7d7bf1ad702eda92": "MTAwNjQ0IExJQ0VOU0UAJh7rnp+LK0sNEZNm3amcb9fTXGQxMDA2NDQgTk9USUNFAGOqKVZGc09ixCcOZTgRUw+aVw06MTAwNjQ0IGdjZXJ0MS1wMTEtcHIxOTMuanNvbi5negCpgCanL1dPzhft2UvK/xDhWjFHNjQwMDAwIHJldGFpbmVkAKOae/3NQGapcAql+BDBqm7s05aFMTAwNjQ0IHNvdXJjZS1waW5zLmpzb24AXLBLDHJ1AQ8wjXUWZlBLGdf+a5E0MDAwMCBzb3VyY2VzAKp1w6myphAvwP4FJZr/BAKi4RAs", + "47aacbe235cb57fd797cc045d11862562f704286": "MTAwNjQ0IEZJTklURV9CSVRfQUNDT1VOVElORy5tZABCpYXXQmYPntROvTyiqdHggyuwvjEwMDY0NCBJTlRFR1JBVEVEX1BST09GLm1kAMYk2mxcH45EyOzaJK9HfRHf7o4SMTAwNjQ0IFBBUklUWS1DT05UUkFDVC5tZAD61hULH5mHKA+OLccuNBjrhfPY5jEwMDY0NCBSRUFETUUubWQAGXUWcb6NSiBsYIdfG6S3/0I+24kxMDA2NDQgY29waWVkLWNlbnRlcnMtbGVtbWEudGV4AIkzqvUIqp1MdboSkJE+AvaGjTUdNDAwMDAgcHJpbWFyeQDUcOMiB6kkp6sZg593KneNQatZBTEwMDY0NCB0aHJlZS1zdGFnZS1jb3Zlci1iaXQudGV4APBSJdIfks+M7yG24SYB1bTWGZHwMTAwNjQ0IHRocmVlLXN0YWdlLWNvdmVyLXJvd3MudGV4AFkNpLiSoIudUYL7PYqYPGI53Sfb", + "4c7079b88c9f6ab5b597c50073782d039a24ecc8": "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", + "4d82e6a31a0d036ef6962f62d9164ee917ccda20": "NDAwMDAgcGFpcmVkLWN1YmUtYml0AFj6WMr7KfJ02NRbFSGyttbZx0qU", + "55fa85f3cc5700e8af2df77795dc0cae05de2940": "NDAwMDAgYml0ALqOhCJddLzCuZVH+zBnh7F6gQOw", + "58fa58cafb29f274d8d45b1521b2b6d6d9c74a94": "MTAwNjQ0IGNoZWNrX3BhaXJlZF9jdWJlX2JpdC5weQBp1TWmwuInqB0zlqv2/eLL4qb2KQ==", + "70d8599cc7f840251a4298105ada23dd2730a3c7": "MTAwNjQ0IEJBTkstUFJPT0YubWQAYlYKT7nGNMfTaEuN9lfDC4x1Dv0xMDA2NDQgTElDRU5TRQAmHuuen4srSw0Rk2bdqZxv19NcZDEwMDY0NCBNQU5JRkVTVC5qc29uAM8V5pgr3fECN3sZulFlTvehRTuIMTAwNjQ0IE5PVElDRS5tZACBa84lokkXGiEekFCwziXgZXJ6kzEwMDY0NCBQUi1TVEFURU1FTlQubWQA9UksSyh4Nn4nEmdVxW23w/MznbwxMDA2NDQgUFJPT0YubWQAFLreOhkRJ8t//OYdnXIsPl69ifkxMDA2NDQgUkVBRE1FLm1kAPVJLEsoeDZ+JxJnVcVtt8PzM528MTAwNjQ0IFVQU1RSRUFNLVBSMjM0LU5PVElDRS5tZABfj7slwSeBQgKEvCIKBBWSYZ4nwDEwMDY0NCBVUFNUUkVBTS1QUjIzNC1QUk9PRi5tZADb2IBpZ3K2jGOvFTNqT+dJVQ8RRjEwMDY0NCBiYW5rX2NoZWNrLnB5AAESLIKECrMWyhExxli2Wv5NmZe2MTAwNjQ0IGJhbmtfdGVtcGxhdGUucHkA0r08AWVPI6UbCAl8b4h3XO9O7b8xMDA2NDQgYmFzZV90d29fbW9tZW50LnB5ABSpD6DniCF4VvBwOCyeYb18YDxhNDAwMDAgY29kZQBAdUyJ+WgkpAd/Kyrs8J+2j83j4DEwMDY0NCBkZXNjZW50LXNlbGVjdGlvbi5qc29uAHhspGEwPg9M36Z22SeFOw/k2++pMTAwNjQ0IGRlc2NlbnRfdHJhbnNmb3JtLnB5AJ1qinl8RoHDOZvsMjLoJ22gD4dgMTAwNjQ0IGZpbml0ZV9jaGVjay5weQBKoW/FnzHIv1JVteQJxjClhMfQ+jQwMDAwIGdlbjRiaXQAws/OFpYFEllbZxmprAuiwMVCJxM0MDAwMCBpbnB1dHMAPHbgy1qRQ7BGro6j1CWMSNS6cm4xMDA2NDQgbWF0aF9jaGVjay5weQDjRTcDvLcdg2x5qXLBBp+pQoe0wDEwMDY0NCBtb21lbnQucHkAHkOq6sobnR/0C4gFynrj5qIZw5I0MDAwMCBub3RpY2VzAMtXEwF/u3qDjTLMiK6QCFoUXDwQMTAwNjQ0IG91dGVyLnB5AEMAdIitLEMYfHfMZ5i+/4sc12rSMTAwNjQ0IHBhcml0eV90cmFuc2Zvcm0ucHkAMFKFUL496uvQ4YfjL6bs0ecrumIxMDA2NDQgcG9ydGFibGVfYml0LnB5ALjfqLSddEW8AauDWFM37L8iZ+hvMTAwNjQ0IHBvcnRhYmxlX2NvbXBsZXgucHkAwGi+Phy5ruVP7tIA1B2ZfICB/tExMDA2NDQgcHJlcGFyZS5weQCVK9cntGuazUn+P66PILiVGyLx4zEwMDY0NCBwcmltZV9jaGVjay5weQAFLS/my8THFWnE+bfgdm8/+HozVTEwMDY0NCBwcmltZV93aXRuZXNzZXMucHkAfxbZMDAq6HnFYt1Y4IXZdH/KwHc0MDAwMCBwcm9vZgBHqsviNctX/Xl8wEXRGGJWL3BChjEwMDY0NCByYXdfbGVkZ2VyLnB5APw3E/Jclpitmka3r8+k4yxMGlStMTAwNjQ0IHJlcXVpcmVtZW50cy50eHQAHKX9BhYFQ/i9c28bUdtvQjcsZvQ0MDAwMCBzY2FsYXIAn7oejchnl6zDdYoSA6N87kkWcrMxMDA2NDQgc2NhbGFyX2NoZWNrLnB5AM0z7v6q9ArxnkxcUOyllFdGrQ3cMTAwNjQ0IHZlcmlmeS5weQAGSTVy1B/wdt/dwbBouAabEiBeOzEwMDY0NCB2aXJ0dWFsX2NoZWNrLnB5AIU4vRKX1hMIzabm4nXw3R//kWmL", + "9620d20482a4ef68dddeb0d081ec64025b8ec59e": "MTAwNjQ0IC5naXRhdHRyaWJ1dGVzAERAQMzbAk+M3DJA1tyR9yVCTNJgNDAwMDAgLmdpdGh1YgDzSW+1h19ZCQvIorUDk62ou84GxTEwMDY0NCAuZ2l0aWdub3JlAAxe+/HUWqdKo4X1hrBTxw4XCoMkMTAwNjQ0IENJVEFUSU9OLmNmZgDtnGhdfQu5YOEkhycehpqvk2lcFzEwMDY0NCBDT05UUklCVVRJTkcubWQA2+Fp9TSmzQwql6BBvgbEcMMCdlkxMDA2NDQgQ09OVFJJQlVUT1JTLm1kAASbRKz6/ffrcSc9DXTugbKYFeemMTAwNjQ0IExJQ0VOU0UAJh7rnp+LK0sNEZNm3amcb9fTXGQxMDA2NDQgTWFrZWZpbGUArgMcylqPP3nuL+Jn52+ZflES4GcxMDA2NDQgTk9USUNFAFnY4M8xAuFb0R20MIODZVMeLX8YMTAwNjQ0IFBBVENISU5HLm1kAKAuIwJWuVkkFriMDzMt9vHExk1RMTAwNjQ0IFJFQURNRS5tZABm3pFLyfYLdYqdV2fPPNXaAlWGMjEwMDY0NCBTT1VSQ0VTLmpzb24A05H8EGZp4ihdhmAAin1disz6tv80MDAwMCBhcnRpZmFjdHMAfXF6FhMg3zzeLRYitg2qINpxU380MDAwMCBjZXJ0aWZpY2F0ZXMAV04KItUIrtGN/Qbhm8s4XABHkpQ0MDAwMCBkb2NzAEY7W/As11exvLbopRBYY3edj5IENDAwMDAgZm9ybWFsAHEYL44HbJQjNV/d6r9FGwyt6MWuNDAwMDAgbm90ZXMADiLYcaveXnBHwg8wUGtpouWF3og0MDAwMCBwYXRjaGVzAJS/+lmD/emP5dXj9YxIw+xhsqRPNDAwMDAgcmVmZXJlbmNlcwDk3uEdufw5RM5317Mt0cOtItEawDQwMDAwIHJlc2VhcmNoAKF8NRb7ZU9yaZ1idmMpi8a3naOFNDAwMDAgc2NyaXB0cwAK67VjHDyMUztBdvdGFfWXiwZrcDQwMDAwIHRlc3RzAF975M2o6ifC2XAGDZGX7Y3sEJlsNDAwMDAgdXBzdHJlYW0A5E05YhtY4rNR+RB7n6Fw1qjU/hw=", + "99bdf48e87c48d6ee8d6922b9d93c8c705a5145c": "NDAwMDAgcHIxNjgtdjQAQRRBWM24AYpgQ4OD1zyJ10B51tg=", + "9fba1e8dc86797acc3758a1203a37cee491672b3": "MTAwNjQ0IHdvcmQucHkAlbmv/US6nFCkkG8HRFdwH4YvRM4=", + "a17c3516fb654f72699d627663298bc6b79da385": "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": "MTAwNjQ0IEZJTklURV9CUklER0UudHh0AMGohDYROhNR+AxyvQj2QG7V+qUdMTAwNjQ0IGNvbXBhY3QtY29udHJvbC1sYXlvdXQudGV4ABb00jNHGn0YkWOufnkCvsrey7pkMTAwNjQ0IGNvbXBhY3QtY29udHJvbC1tb3ZlbWVudC50ZXgABrap7yQkWse3JMU6IEY1QVtAI1cxMDA2NDQgY29waWVkLWNlbnRlcnMtYXNzZW1ibHkudGV4ALDGkWbDBI0nUYNVmV4+W2InIsaWMTAwNjQ0IGNvcGllZC1jZW50ZXJzLWxlbW1hLnRleACJM6r1CKqdTHW6EpCRPgL2ho01HTEwMDY0NCBlbmRwb2ludC1nYXVnZS1jb21wbGV4LnRleACQegztqN8DF93H8rcd9a8qpNR9/DEwMDY0NCBnZW5lcmFsLWNsaWZmb3JkLWZyYW1lcy50ZXgAA2fusAUphgHyimc4Ydb3G/HQ67sxMDA2NDQgc3RvcHBlZC1wcm9kdWN0LWNvbXBsZXgudGV4AEqQNxN80xWNvsptnjsYgaWZ6/y1", + "aa75c3a9b2a6102fc0fe05259aff0402a2e1102c": "MTAwNjQ0IFdvcmtfX0JyaWRnZV9fQ2VydC5sZWFuAPe9w9kDNBqVRgVs3hJNZC4mdzGLMTAwNjQ0IFdvcmtfX0JyaWRnZV9fTmV0LmxlYW4AIE9tnPkCyFkA8je9maI3dOmj+BsxMDA2NDQgV29ya19fQ2Fycmllcl9fTGl2ZS5sZWFuALgwZSfWSuBiccaqVcXgs97YXGUcMTAwNjQ0IFdvcmtfX0NhcnJpZXJfX1BoYXNlcy5sZWFuADBdoVtdEplZoGluIDEonuqR7BgTMTAwNjQ0IFdvcmtfX0ZvdXJpZXJfX01haW4ubGVhbgCaoGtb8oojgO+dVKB4WsI2n2AS5DEwMDY0NCBXb3JrX19HQ2VydF9fQ2hhaW5fX0dlb20ubGVhbgDrRJCviw9bl5hNnb3zRdhv5l0CxzEwMDY0NCBXb3JrX19HQ2VydF9fQ2hhaW5fX0ludlRobS5sZWFuANafy0mSd4vNLRJZbjZe6Eg6pTiiMTAwNjQ0IFdvcmtfX0dDZXJ0X19DaGFpbl9fTmV0Q2VydC5sZWFuAGM2ugdmB/QinFjlGwHzW4RX7/uIMTAwNjQ0IFdvcmtfX0dDZXJ0X19DaGFpbl9fTmV0RGVmLmxlYW4AwTBC60Myj5dxpfheg+21mBDzoAYxMDA2NDQgV29ya19fR0NlcnRfX0NoYWluX19OZXRTdGFnZS5sZWFuAGcPTGqEs0U90LG8CHn/8+zZQQkZMTAwNjQ0IFdvcmtfX0dDZXJ0X19DaGFpbl9fUGhhc2VkLmxlYW4A5oxCN39P0NA1K6aXNX0F9gMzyNwxMDA2NDQgV29ya19fR0NlcnRfX0NoYWluX19XaW5CLmxlYW4AlR7d5c9f37HdX3kCSZ88yrlhv0oxMDA2NDQgV29ya19fR0NlcnRfX0RhdGFfX0IyR3AxOTNNYWluLmxlYW4A1+z+vOqASvggmg2z2JyqntNk6noxMDA2NDQgV29ya19fR0NlcnRfX0RhdGFfX0dsdWUubGVhbgDGy6w446L7q6x0YQUZ4m/MBl9/9TEwMDY0NCBXb3JrX19HQ2VydF9fRGF0YV9fSW5zdEIyR3AxOTMubGVhbgCbmSXOJoqfdX/USRMUClfflTFXaTEwMDY0NCBXb3JrX19HQ2VydF9fRGF0YV9fUDE5M0NlcnQubGVhbgA8MlAnnma1bJ/uihLCZa4ngM5HYTEwMDY0NCBXb3JrX19HQ2VydF9fRGF0YV9fUDE5M1ByaWNlLmxlYW4A7u0BHWRIKtvfDW/0inw5WnJS1XoxMDA2NDQgV29ya19fR0NlcnRfX0RhdGFfX1AxOTNTY2FsT0subGVhbgA1ORkqwdOlFkH3ugj6BqhlmxK8CzEwMDY0NCBXb3JrX19HQ2VydF9fRGF0YV9fUmF0ZUIyR3AxOTN4LmxlYW4A31Y83nOo+ULpyQGJJh3W6EgtQPExMDA2NDQgV29ya19fR0NlcnRfX0RhdGFfX1Jhdy5sZWFuAEmfpn6I6/4K13cIHLYd7z903oG6MTAwNjQ0IFdvcmtfX0dDZXJ0X19EYXRhX19Tb2x1dGlvbkIyR3AxOTN4LmxlYW4AA5Lh8Fu4nMpHLJdjkaZJQY3eGG4xMDA2NDQgV29ya19fR0ZyYW1lX19FbmdpbmVfX0FkYXB0LmxlYW4AJOJhrQhiunePDn8A1IdWhlt/TrgxMDA2NDQgV29ya19fR0ZyYW1lX19FbmdpbmVfX0xldHRlcnMubGVhbgAuym5xO/1HRJuHKgRihXgGhhvp1TEwMDY0NCBXb3JrX19HRnJhbWVfX0VuZ2luZV9fTGV2ZWwubGVhbgBr18+gw/NLvQT8RF7OrgzE1MVn/DEwMDY0NCB0b29sc19fZ3hfX09SSUdJTi5tZADiizVdCicdpsjH2ottaEUYFzeL8TEwMDY0NCB0b29sc19fZ3hfX2d4YmluZC5weQD8tiVhnLB6m+lZRCI1d4bKbjxK3TEwMDY0NCB0b29sc19fZ3hfX2d4Y29udi5weQBJ67ZZc6JAO7IqoyaSdizQUioChzEwMDY0NCB0b29sc19fZ3hfX2d4Y29yZS5weQA+PwowdvPDJueDPHTs7ss0Nt0xhQ==", + "ba8e84225d74bcc2b99547fb306787b17a8103b0": "MTAwNjQ0IGZyYW1lc19wMTIuanNvbi5negB9TZhYqX/Bqm/nBvnHdjRwYF2hwjEwMDY0NCBncmFwaF9wMTIuanNvbgDDrjD+3bMU10C5x67DVUmF9IwxuzEwMDY0NCBrY2hyb25fcDEyLmpzb24A85sC4/eFQwS5WcmnhmTxeIIOgeIxMDA2NDQgcHJvZmlsZV9wMTIuanNvbgDaQiAUtyFer/RSiT8YtxJddRqPmjEwMDY0NCB3b3JkX3AxMi5qc29uLmd6AG2yir6VjBrOLyXw+Klq+f4kBHFF", + "c2cfce16960512595b6719a9ac0ba2c0c5422713": "MTAwNjQ0IE5PVElDRS1QUjIwMADC15Ndf0Wmh43FR9MBwPQ6u7L6kDEwMDY0NCBSRUFETUUubWQAkR2lCxxe2e53rKPiwe5sVxKHJpA0MDAwMCBiaXQA+L9mMVwLJjwJHCUfhPgE0xOHE2A0MDAwMCBwcm9kdWNlcgDz7taD0IngBzNIb8gyn+WapCZFsTQwMDAwIHJlZmVyZW5jZXMAmb30jofEjW7o1pIrnZPIxwWlFFw0MDAwMCBzZWxlY3RlZABV+oXzzFcA6K8t93eV3AyuBd4pQA==", + "c4d6c4a7e7bc692577e1fd41bc1964b4b744c121": "MTAwNjQ0IGJpdC1lbGltLTE0NC55bWwA8TZhRjfGb3ePVffmFbXScNBym3sxMDA2NDQgYml0LXJldXNlLTE0Ny55bWwAao06J+ymZf2V0ABGTB9eueFPkakxMDA2NDQgZml2ZS1zdGFnZS1nZW40LWJhbmtzLnltbABGhRichBvDkCmW9NFGU2cDLEOxSzEwMDY0NCBwYXJhbGxlbC1hbm5vdW5jZW1lbnRzLnltbACnfcc/5DEutLyfUyGEWRkIcXHlRTEwMDY0NCBzd2FwbmlsLXBhcmFsbGVsLnltbAC3VALuo+hoHtTxnR4QhaPkSQkI6jEwMDY0NCB2ZXJpZnkueW1sALwXQbCP+0Dgd+L7Qf/8LJmxOhDx", + "cb5713017fbb7a838d32cc88ae90085a145c3c10": "MTAwNjQ0IFBSMjEwLWNvb3JkaW5hdGVkLWNyb3Nzb3Zlci1OT1RJQ0UA0Iy2aseRRKpk1Pwr/frdmKw69iQxMDA2NDQgUFIyMzAtTk9USUNFALwc/FKhO9qoh/rnEBerffEOsUqQMTAwNjQ0IFBSMjMwLVBST1ZFTkFOQ0UuanNvbgD/OVeTh/hizAdzCdQYbuGI8K5WujEwMDY0NCBSRUFETUUubWQAevfKpzOrrg/SLnJqNzt6kfH1m/oxMDA2NDQgYmFzZS1iaXQtUFIyMDAtUFIyMDItTk9USUNFALpeqA3X41nUTPH/rmOneQ9uUHc5MTAwNjQ0IHNvdXJjZTUyNy1sb2FkZXItTk9USUNFLm1kAEcJDW6T7PSOcQGb1SsEW+0xtfiJ", + "d470e32207a924a7ab19839f772a778d41ab5905": "MTAwNjQ0IDAzLW1vdGlmcy50ZXgAzbC6Unqo311XwVEYP9OJPBp+IasxMDA2NDQgTElDRU5TRQAmHuuen4srSw0Rk2bdqZxv19NcZDEwMDY0NCBQUk9WRU5BTkNFLm1kAL69tykQ5qGvhTWyBuHR6aoWN6ViMTAwNjQ0IFVQU1RSRUFNLVJFQURNRS5tZADIyzw4V5B780ZQF7n37xrdTfXCmw==", + "f3496fb5875f59090bc8a2b50393ada8bbce06c5": "NDAwMDAgSVNTVUVfVEVNUExBVEUAFtE8M0kBwrwg+WaIjQL7YjZxksoxMDA2NDQgcHVsbF9yZXF1ZXN0X3RlbXBsYXRlLm1kAEiA3qf+WCg/h8ClaKxz1DjeSzSENDAwMDAgd29ya2Zsb3dzAMTWxKfnvGkld+H9QbwZZLS3RMEh", + "f3eed683d089e00733486fc8329fe59aa42645b1": "NDAwMDAgZGF0YQAwW9cmpo/35TsjaxHt1YD5Bymt6TEwMDY0NCBkZXNjZW50LnB5AGva/rHbDkJ1kh5hX3da0UMUACDoMTAwNjQ0IG1ha2VfcGh5c2ljYWwucHkApveQOO3LXUX9Tq5CFzc9iwK2yg0xMDA2NDQgcGFpcmVkX2N1YmVfYml0X3dvcmQucHkAew1XX37PR+O5Y9LxL/WA8NDqWpExMDA2NDQgcmVnZW5lcmF0ZS5weQCqyq4mb8kIRbubT9B+IeydBpH+Hg==", + "f419e848c935cfa7b666be39797edb245b2a9e91": "MTAwNjQ0IC5naXRhdHRyaWJ1dGVzAERAQMzbAk+M3DJA1tyR9yVCTNJgNDAwMDAgLmdpdGh1YgDzSW+1h19ZCQvIorUDk62ou84GxTEwMDY0NCAuZ2l0aWdub3JlAAxe+/HUWqdKo4X1hrBTxw4XCoMkMTAwNjQ0IENJVEFUSU9OLmNmZgDtnGhdfQu5YOEkhycehpqvk2lcFzEwMDY0NCBDT05UUklCVVRJTkcubWQA2+Fp9TSmzQwql6BBvgbEcMMCdlkxMDA2NDQgQ09OVFJJQlVUT1JTLm1kAASbRKz6/ffrcSc9DXTugbKYFeemMTAwNjQ0IExJQ0VOU0UAJh7rnp+LK0sNEZNm3amcb9fTXGQxMDA2NDQgTWFrZWZpbGUArgMcylqPP3nuL+Jn52+ZflES4GcxMDA2NDQgTk9USUNFAFnY4M8xAuFb0R20MIODZVMeLX8YMTAwNjQ0IFBBVENISU5HLm1kAKAuIwJWuVkkFriMDzMt9vHExk1RMTAwNjQ0IFJFQURNRS5tZABm3pFLyfYLdYqdV2fPPNXaAlWGMjEwMDY0NCBTT1VSQ0VTLmpzb24A05H8EGZp4ihdhmAAin1disz6tv80MDAwMCBhcnRpZmFjdHMAfXF6FhMg3zzeLRYitg2qINpxU380MDAwMCBjZXJ0aWZpY2F0ZXMAV04KItUIrtGN/Qbhm8s4XABHkpQ0MDAwMCBkb2NzAEY7W/As11exvLbopRBYY3edj5IENDAwMDAgZm9ybWFsAHEYL44HbJQjNV/d6r9FGwyt6MWuNDAwMDAgbm90ZXMADiLYcaveXnBHwg8wUGtpouWF3og0MDAwMCBwYXRjaGVzAJS/+lmD/emP5dXj9YxIw+xhsqRPNDAwMDAgcmVmZXJlbmNlcwDk3uEdufw5RM5317Mt0cOtItEawDQwMDAwIHJlc2VhcmNoAPeR+q1SOkVem66jnHYWKKG9ZIDkNDAwMDAgc2NyaXB0cwAK67VjHDyMUztBdvdGFfWXiwZrcDQwMDAwIHRlc3RzAF975M2o6ifC2XAGDZGX7Y3sEJlsNDAwMDAgdXBzdHJlYW0A5E05YhtY4rNR+RB7n6Fw1qjU/hw=", + "f791faad523a455e9baea39c761628a1bd6480e4": "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", + "f8bf66315c0b263c091c251f84f804d313871360": "MTAwNjQ0IGJhc2Vfd29yZC5weQAETyePvmpkMDM53kGoZD1ETktenTEwMDY0NCB3b3JkLnB5AGuNlgQ6VB/+WPTJX6BH4SkYI9Rl" + } +} diff --git a/research/pr279-parent-closure/capture.py b/research/pr279-parent-closure/capture.py new file mode 100755 index 0000000000..477c22e7d9 --- /dev/null +++ b/research/pr279-parent-closure/capture.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Capture Git objects for the two pinned PR279 heads; no checkout is changed. + +Adam Gall (@adamgall), with substantial OpenAI Codex assistance. Apache-2.0. +""" +import argparse +import base64 +import json +from pathlib import Path +import subprocess + +REVIEWED = '4d8c31eec7ae618645cb058101cb4e72a97268d3' +CANDIDATE = '913ccc48a0bc1173709ea92280b96252256b7754' +PACKAGE = 'research/five-stage-gen4-banks' +WORKFLOW = '.github/workflows/five-stage-gen4-banks.yml' + + +def git(*args): + return subprocess.check_output(['git', *args]) + + +def entries(raw): + result = {} + pos = 0 + while pos < len(raw): + end = raw.index(b'\0', pos) + mode, name = raw[pos:end].split(b' ', 1) + result[name.decode('utf-8')] = (mode.decode('ascii'), raw[end + 1:end + 21].hex()) + pos = end + 21 + return result + + +def capture(): + trees = {} + + def tree(oid): + if oid not in trees: + trees[oid] = git('cat-file', 'tree', oid) + return entries(trees[oid]) + + commits = {oid: git('cat-file', 'commit', oid) for oid in (REVIEWED, CANDIDATE)} + roots = [commits[oid].splitlines()[0].split()[1].decode() for oid in (REVIEWED, CANDIDATE)] + + def subtree(oid): + for mode, child in tree(oid).values(): + if mode == '40000': + subtree(child) + + def path(root, name, recursive=False): + oid = root + for part in name.split('/'): + mode, oid = tree(oid)[part] + if recursive: + subtree(oid) + + def changed(left, right): + if left == right: + return + old, new = tree(left), tree(right) + for name in old.keys() | new.keys(): + a, b = old.get(name), new.get(name) + if a == b: + continue + if a and b and a[0] == b[0] == '40000': + changed(a[1], b[1]) + else: + for item in (a, b): + if item and item[0] == '40000': + subtree(item[1]) + + changed(*roots) + for root in roots: + path(root, PACKAGE, recursive=True) + path(root, WORKFLOW) + return { + 'schema': 'pr279-parent-git-proof/1', + 'commits': {oid: base64.b64encode(raw).decode() for oid, raw in commits.items()}, + 'trees': {oid: base64.b64encode(raw).decode() for oid, raw in sorted(trees.items())}, + } + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', required=True, type=Path) + args = parser.parse_args() + with args.output.open('x', encoding='utf-8') as stream: + json.dump(capture(), stream, indent=2, sort_keys=True) + stream.write('\n') + + +if __name__ == '__main__': + main() diff --git a/research/pr279-parent-closure/test_verify.py b/research/pr279-parent-closure/test_verify.py new file mode 100755 index 0000000000..7f87ae05d0 --- /dev/null +++ b/research/pr279-parent-closure/test_verify.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""Offline parent-inventory controls. Adam Gall; OpenAI Codex; Apache-2.0.""" +import base64 +import copy +import importlib.util +import json +from pathlib import Path +import unittest + +SPEC = importlib.util.spec_from_file_location('parent_closure', Path(__file__).with_name('verify.py')) +V = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(V) + + +class ParentClosureTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.inputs, cls.retained = V.load_inputs() + + def reject(self, index, mutate, message): + values = list(self.inputs) + value = copy.deepcopy(values[index]) + replacement = mutate(value) + values[index] = value if replacement is None else replacement + with self.assertRaisesRegex(ValueError, message): + V.audit(*values) + + def test_positive_complete_parent(self): + report = V.audit(*self.inputs) + self.assertEqual(report['parent_paths'], 116) + self.assertEqual(report['repository_changes'], {'added': 2, 'modified': 10}) + self.assertFalse(self.retained['fresh_execution']) + self.assertEqual(len(self.retained['retained_stages']), 9) + + def test_changed_retained_blob(self): + self.reject(1, lambda files: files.__setitem__('bank_template.py', b'changed'), 'wrong blob') + + def test_changed_companion_blob(self): + self.reject(1, lambda files: files.__setitem__('target_transform.py', b'changed'), 'wrong blob') + + def test_omitted_retained_file(self): + def omit(files): + del files['bank_template.py'] + self.reject(1, omit, 'incomplete or additional file coverage') + + def test_extra_candidate_file(self): + self.reject(1, lambda files: files.__setitem__('unlisted.py', b''), 'incomplete or additional') + + def test_current_bytes_cannot_replace_reviewed_base(self): + self.reject(2, lambda files: files.__setitem__('verify.py', self.inputs[1]['verify.py']), + 'Reviewed package: wrong blob') + + def test_changed_workflow(self): + self.reject(3, lambda data: data + b'\n', 'Workflow: wrong blob') + + def test_wrong_matched_blob_with_unchanged_count(self): + def change(comparison): + comparison['matched'][0]['blob'] = comparison['matched'][1]['blob'] + self.reject(4, change, 'Parent inventory blob mismatch') + + def test_duplicate_parent_path_with_unchanged_count(self): + def duplicate(comparison): + comparison['matched'][0] = comparison['matched'][1].copy() + self.reject(4, duplicate, 'Duplicate parent inventory path') + + def test_omitted_parent_path_with_unchanged_count(self): + def replace(comparison): + comparison['matched'][0]['path'] = 'not-in-pinned-parent' + self.reject(4, replace, 'Parent inventory path omission') + + def test_swapped_coverage_partition(self): + def swap(comparison): + comparison['matched'][0], comparison['unresolved'][0] = ( + comparison['unresolved'][0], comparison['matched'][0]) + self.reject(4, swap, 'Wrong unresolved partition') + + def test_wrong_update_kind(self): + def change(source): + source['changes'][0]['status'] = 'added' + self.reject(5, change, 'Incorrect update attribution') + + def test_missing_update_with_unchanged_count(self): + def change(source): + source['changes'][0]['path'] = 'not-in-commit.py' + self.reject(5, change, 'Source update misses a repository change') + + def test_forged_commit_payload(self): + def change(document): + raw = base64.b64decode(document['commits'][V.CANDIDATE]) + b'\n' + document['commits'][V.CANDIDATE] = base64.b64encode(raw).decode() + self.reject(0, change, 'Git object hash mismatch') + + def test_substituted_commit_identity(self): + def change(document): + raw = document['commits'].pop(V.CANDIDATE) + document['commits']['0' * 40] = raw + self.reject(0, change, 'Wrong commit inventory') + + def test_forged_tree_payload(self): + def change(document): + oid = next(iter(document['trees'])) + raw = base64.b64decode(document['trees'][oid]) + b'\0' + document['trees'][oid] = base64.b64encode(raw).decode() + self.reject(0, change, 'Git object hash mismatch') + + def test_missing_root_tree(self): + def change(document): + proof = V.Proof(document) + del document['trees'][proof.root(V.CANDIDATE)] + self.reject(0, change, 'Missing tree proof') + + def test_unrelated_authentic_tree(self): + def change(document): + document['trees'][V.object_id('tree', b'')] = '' + self.reject(0, change, 'Unused tree proof objects') + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/research/pr279-parent-closure/verify.py b/research/pr279-parent-closure/verify.py new file mode 100755 index 0000000000..d84b836b55 --- /dev/null +++ b/research/pr279-parent-closure/verify.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +"""Authenticate PR279's complete retained parent inventory to its Git commit. + +No network, Git executable, checkout metadata, or mathematical replay is used. +Adam Gall (@adamgall), with substantial OpenAI Codex assistance. Apache-2.0. +""" +import base64 +from collections import Counter +from hashlib import sha1, sha256 +import importlib.util +import json +from pathlib import Path + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parents[1] +COMPANION = ROOT / 'research/pr279-target-prefix-reconciliation' +PACKAGE = 'research/five-stage-gen4-banks' +WORKFLOW = '.github/workflows/five-stage-gen4-banks.yml' +REVIEWED = '4d8c31eec7ae618645cb058101cb4e72a97268d3' +CANDIDATE = '913ccc48a0bc1173709ea92280b96252256b7754' +PINS = { + 'verify.py': '8af5b16b17f586b9971b9ce4d5b5beb2ab113c2f3c6b96e180fb9d1e0b9e53a4', + 'SOURCE.json': '6208c7f90a03fb6093241f964674bf0d18445ec193385c8558db082c48ea1e40', + 'EVIDENCE.json': '5144c468cf42ee64d1a28a6e8f6c59f295485dce2a56af61233732f63a93f957', +} + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def object_id(kind, data): + return sha1(kind.encode('ascii') + b' ' + str(len(data)).encode('ascii') + b'\0' + data).hexdigest() + + +def parse_tree(raw): + result = {} + position = 0 + while position < len(raw): + end = raw.find(b'\0', position) + require(end >= position and end + 21 <= len(raw), 'Truncated Git tree entry') + header = raw[position:end].split(b' ', 1) + require(len(header) == 2, 'Malformed Git tree header') + mode, name = header[0].decode('ascii'), header[1].decode('utf-8') + require(mode in ('40000', '100644', '100755', '120000', '160000'), 'Invalid Git mode') + require(name not in ('', '.', '..') and '/' not in name and name not in result, + 'Ambiguous Git tree name') + result[name] = (mode, raw[end + 1:end + 21].hex()) + position = end + 21 + return result + + +class Proof: + def __init__(self, document): + require(document['schema'] == 'pr279-parent-git-proof/1', 'Wrong proof schema') + require(set(document['commits']) == {REVIEWED, CANDIDATE}, 'Wrong commit inventory') + self.commits = {} + self.trees = {} + self.used = set() + for kind, encoded, destination in ( + ('commit', document['commits'], self.commits), + ('tree', document['trees'], self.trees)): + for oid, text in encoded.items(): + raw = base64.b64decode(text, validate=True) + require(object_id(kind, raw) == oid, 'Git object hash mismatch: ' + oid) + destination[oid] = raw + candidate_headers = self.commits[CANDIDATE].split(b'\n\n', 1)[0].splitlines() + require([row for row in candidate_headers if row.startswith(b'parent ')] == + [b'parent ' + REVIEWED.encode('ascii')], 'Wrong reviewed parent') + + def root(self, commit): + first = self.commits[commit].splitlines()[0] + require(first.startswith(b'tree '), 'Missing commit tree') + return first[5:].decode('ascii') + + def tree(self, oid): + require(oid in self.trees, 'Missing tree proof: ' + oid) + self.used.add(oid) + return parse_tree(self.trees[oid]) + + def at(self, root, path): + mode, oid = '40000', root + for component in path.split('/'): + require(mode == '40000', 'Non-directory path prefix') + entries = self.tree(oid) + require(component in entries, 'Missing source path: ' + path) + mode, oid = entries[component] + return mode, oid + + def leaves(self, mode, oid, prefix=''): + if mode != '40000': + return {prefix: (mode, oid)} + files = {} + for name, child in self.tree(oid).items(): + path = prefix + '/' + name if prefix else name + files.update(self.leaves(*child, path)) + return files + + def changes(self, old, new, prefix=''): + if old == new: + return {} + if old and new and old[0] == new[0] == '40000': + a, b = self.tree(old[1]), self.tree(new[1]) + result = {} + for name in sorted(a.keys() | b.keys()): + path = prefix + '/' + name if prefix else name + result.update(self.changes(a.get(name), b.get(name), path)) + return result + a = self.leaves(*old, prefix) if old else {} + b = self.leaves(*new, prefix) if new else {} + return {name: (a.get(name), b.get(name)) for name in a.keys() | b.keys() + if a.get(name) != b.get(name)} + + +def compare_payloads(entries, payloads, label): + require(set(entries) == set(payloads), label + ': incomplete or additional file coverage') + for path, data in payloads.items(): + mode, oid = entries[path] + require(mode in ('100644', '100755'), label + ': non-regular source file') + require(object_id('blob', data) == oid, label + ': wrong blob at ' + path) + + +def audit(document, candidate, historical, workflow, comparison, source): + proof = Proof(document) + old_root, new_root = proof.root(REVIEWED), proof.root(CANDIDATE) + old_package = proof.at(old_root, PACKAGE) + new_package = proof.at(new_root, PACKAGE) + old_files = proof.leaves(*old_package) + new_files = proof.leaves(*new_package) + require((len(old_files), len(new_files)) == (113, 115), 'Unexpected package cardinality') + compare_payloads(old_files, historical, 'Reviewed package') + compare_payloads(new_files, candidate, 'Candidate package') + old_workflow, new_workflow = proof.at(old_root, WORKFLOW), proof.at(new_root, WORKFLOW) + require(old_workflow == new_workflow, 'Workflow changed') + compare_payloads({WORKFLOW: new_workflow}, {WORKFLOW: workflow}, 'Workflow') + + changes = proof.changes(('40000', old_root), ('40000', new_root)) + require(len(changes) == 12, 'Unexpected repository-wide update count') + recorded_changes = {PACKAGE + '/' + row['path']: row for row in source['changes']} + require(set(changes) == set(recorded_changes), 'Source update misses a repository change') + statuses = Counter() + for path, (old, new) in changes.items(): + require(new is not None and new[0] in ('100644', '100755'), 'Deleted or special update') + if old: + require(old[0] == new[0], 'Unexpected mode change') + status = 'modified' if old else 'added' + statuses[status] += 1 + row = recorded_changes[path] + require((row['status'], row['old_git_blob'], row['new_git_blob']) == + (status, old[1] if old else None, new[1]), 'Incorrect update attribution') + require(statuses == {'modified': 10, 'added': 2}, 'Wrong change classification') + + universe = {PACKAGE + '/' + name: entry for name, entry in new_files.items()} + universe[WORKFLOW] = new_workflow + matched, unresolved = comparison['matched'], comparison['unresolved'] + rows = matched + unresolved + require(comparison['total'] == len(rows) == len(universe) == 116, 'Wrong parent inventory size') + require(len({row['path'] for row in rows}) == len(rows), 'Duplicate parent inventory path') + require({row['path'] for row in rows} == set(universe), 'Parent inventory path omission') + for row in rows: + require(row['blob'] == universe[row['path']][1], 'Parent inventory blob mismatch') + require(len(matched) == 104 and len(unresolved) == 12, 'Wrong coverage partition') + require({row['path'] for row in unresolved} == set(changes), 'Wrong unresolved partition') + require(proof.used == set(proof.trees), 'Unused tree proof objects') + return { + 'status': 'PASS_PR279_PARENT_COMMIT_CLOSURE', + 'reviewed_head': REVIEWED, 'candidate_head': CANDIDATE, + 'reviewed_tree': old_root, 'candidate_tree': new_root, + 'reviewed_package_tree': old_package[1], 'candidate_package_tree': new_package[1], + 'authenticated_commits': 2, 'authenticated_trees': len(proof.trees), + 'parent_paths': len(universe), 'retained_paths': len(matched), + 'companion_paths': len(unresolved), 'repository_changes': dict(sorted(statuses.items())), + 'fresh_mathematical_execution': False, + 'scope': 'Source identity and retained evidence only; parent disposition remains a maintainer decision.', + } + + +def load_inputs(): + for name, digest in PINS.items(): + require(sha256((COMPANION / name).read_bytes()).hexdigest() == digest, + 'Changed inherited checker or evidence metadata: ' + name) + spec = importlib.util.spec_from_file_location('pr279_companion', COMPANION / 'verify.py') + companion = importlib.util.module_from_spec(spec) + spec.loader.exec_module(companion) + source, candidate = companion.collect_candidate(ROOT, COMPANION) + retained = companion.check_retained_evidence(COMPANION, source) + historical_root = ROOT / PACKAGE + old_names = set(json.loads((historical_root / 'MANIFEST.json').read_text(encoding='utf-8'))['files']) + historical = {name: (historical_root / name).read_bytes() for name in old_names | {'MANIFEST.json'}} + comparison = json.loads((ROOT / 'research/pr-reconciliation-20261012/content-comparison.json') + .read_text(encoding='utf-8'))['279'] + document = json.loads((HERE / 'TREE-PROOF.json').read_text(encoding='utf-8')) + return (document, candidate, historical, (ROOT / WORKFLOW).read_bytes(), comparison, source), retained + + +def main(): + inputs, retained = load_inputs() + report = audit(*inputs) + report['retained_replay'] = retained + print(json.dumps(report, indent=2, sort_keys=True)) + + +if __name__ == '__main__': + main()