From 682687b4b41d3252ca6ea1a50e62aee83cf56ca2 Mon Sep 17 00:00:00 2001 From: Mihir Srivastava Date: Sat, 24 Oct 2026 23:33:31 -0700 Subject: [PATCH] Audit shared Windows gate lifetime after owner termination --- .../windows-owner-death-audit/.gitattributes | 6 + .../windows-owner-death-audit/LICENSE | 201 ++++ .../research/windows-owner-death-audit/NOTICE | 415 +++++++ .../windows-owner-death-audit/README.md | 197 ++++ .../windows-owner-death-audit/SOURCES.json | 19 + .../windows-owner-death-audit/VALIDATION.json | 83 ++ .../evidence/standalone-integrity.json | 37 + .../evidence/windows-normal.json | 969 ++++++++++++++++ .../windows-optimized-standalone.json | 969 ++++++++++++++++ .../windows-owner-death-audit/probe.py | 425 +++++++ .../sources/NOTICE-stream | 17 + .../sources/owned_pipe.py | 153 +++ .../sources/research_package.py | 1026 +++++++++++++++++ 13 files changed, 4517 insertions(+) create mode 100644 docs/research/windows-owner-death-audit/.gitattributes create mode 100644 docs/research/windows-owner-death-audit/LICENSE create mode 100644 docs/research/windows-owner-death-audit/NOTICE create mode 100644 docs/research/windows-owner-death-audit/README.md create mode 100644 docs/research/windows-owner-death-audit/SOURCES.json create mode 100644 docs/research/windows-owner-death-audit/VALIDATION.json create mode 100644 docs/research/windows-owner-death-audit/evidence/standalone-integrity.json create mode 100644 docs/research/windows-owner-death-audit/evidence/windows-normal.json create mode 100644 docs/research/windows-owner-death-audit/evidence/windows-optimized-standalone.json create mode 100644 docs/research/windows-owner-death-audit/probe.py create mode 100644 docs/research/windows-owner-death-audit/sources/NOTICE-stream create mode 100644 docs/research/windows-owner-death-audit/sources/owned_pipe.py create mode 100644 docs/research/windows-owner-death-audit/sources/research_package.py diff --git a/docs/research/windows-owner-death-audit/.gitattributes b/docs/research/windows-owner-death-audit/.gitattributes new file mode 100644 index 0000000000..ef1663e606 --- /dev/null +++ b/docs/research/windows-owner-death-audit/.gitattributes @@ -0,0 +1,6 @@ +# Retain exact frozen inputs and native execution receipts across checkouts. +sources/** -text +evidence/** -text whitespace=cr-at-eol +LICENSE -text +NOTICE -text +SOURCES.json -text whitespace=cr-at-eol diff --git a/docs/research/windows-owner-death-audit/LICENSE b/docs/research/windows-owner-death-audit/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/docs/research/windows-owner-death-audit/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/docs/research/windows-owner-death-audit/NOTICE b/docs/research/windows-owner-death-audit/NOTICE new file mode 100644 index 0000000000..59d8e0cf31 --- /dev/null +++ b/docs/research/windows-owner-death-audit/NOTICE @@ -0,0 +1,415 @@ +A sharper exponent for integer multiplication +Copyright 2026 Douglas Colkitt + +This project includes unmodified source files from OpenAI's manuscript +"Integer multiplication below n log n" and its accompanying repository: +https://github.com/openai/math +Commit: adc7f1241b42e322a6451854ab7e4b4c146bf78a + +The original manuscript identifies OpenAI as its author. Its Apache-2.0 +license is retained at upstream/LICENSE. The exact source URLs and hashes +are recorded in upstream/manifest.json. Proposed modifications are supplied +as separate files under patches/; upstream source files remain unchanged. + +The original local research note, scripts, certificates, patches, and +documentation were prepared by Douglas Colkitt with assistance from OpenAI +Codex. The community release incorporates work by the contributors identified +in the notices below; it is not solely Douglas Colkitt's work. +This project is not an official OpenAI release or an OpenAI endorsement. + +Related contributions (not imported in this checkpoint): +Zhihao Chen (jacklightChen), PR #7, supplied an earlier ternary five-subset +motif, finite-alphabet transfer and a different compressed producer: +https://github.com/CrocSwap/integer-mult-bounds/pull/7 +The present checkpoint claims no priority for that motif. Related compressed +complex-network work was submitted by eumemic in PR #3: +https://github.com/CrocSwap/integer-mult-bounds/pull/3 +The pending contributions and their stronger bounds require their own review; +this notice does not represent adoption or verification of their claims. + +Additional community work, including parallel and superseded submissions, is +acknowledged in CONTRIBUTORS.md, with pinned submission metadata in +docs/research/contribution-snapshot.json. These acknowledgements do not imply +joint authorship of the checkpoint, incorporation of pending code, or verification +of submitted mathematical claims. Original source-specific notices remain in force. + +Community release (accepted by conditional maintainer audit): +The following original contributor notices apply to the imported PR #39 chain. +The preceding non-import statements describe the preserved 2^-30 checkpoint. + +Ternary five-subset construction and common-four-point resynthesis +Copyright 2026 Zhihao Chen (jacklightChen) + +This is Zhihao Chen's first submission of this specific construction to this +repository. Research, proof development, code and verification were prepared +with substantial assistance from GPT-6 Astra (OpenAI Codex), as identified by +the contributor. This is not independent review or an OpenAI endorsement. +Future research building on this contribution should explicitly acknowledge +Zhihao Chen (jacklightChen), cite the accompanying note and pull request, and +retain the preceding contributors' attribution. This notice records research +provenance; the repository's Apache-2.0 license remains unchanged. + +Dependencies retained from eumemic's Claude-assisted PRs #3 and #5: +https://github.com/CrocSwap/integer-mult-bounds/pull/3 +https://github.com/CrocSwap/integer-mult-bounds/pull/5 +PR #5 commit: d3d370c34937503bd3fa63c83d505a05e8e50893 +These include complex_circuit.py, complex_network.py, fast_gaussian.py, +their generators, construction notes and certificates. The Gaussian input +scaling proof is corrected in this derivative; see the modification notice below. +Aligned pairing also appears in Bortlesboat's PR #2. Retained totals and bank +sharing appear in dleen's PR #4 and cheaper retained centers in eumemic's PR #6. +Their results and ideas are credited separately from this new construction. + +Bulk recursion and controlled tensor basis, 8 October 2026 +Copyright 2026 icekylinx + +The new projector batching, controlled simultaneous basis, mixed-width tape +recursions, whole-residual complex recursion, and dependency-path precision +guard in the batched-23 proof package are contributed by icekylinx, developed +with substantial OpenAI GPT-6 Astra assistance. Review corrections and +repository integration were prepared with OpenAI Codex assistance. They build on the retained +PR #7 finite producers and the preceding contributions credited above. +The new package retains the repository's Apache-2.0 license and the original +authors' attribution. It is not an official OpenAI release or endorsement. + +Modification notice, 8 October 2026 (icekylinx): the retained fast Gaussian +proof now bounds shifted approximation error by F=2^ceil(1.14 alpha^2). +The P=34p precision budget and asymptotic exponent are unchanged. New batching +checks are integrated into make verify. Existing attribution and licenses +are retained. + +Partial-swap extension, 8 October 2026 +Copyright 2026 icekylinx + +The partial-swap framing, binary retained-total triple construction, +dependency-preserving positive labels and carrier reuse, unequal (25,23,57) +common basis, improved complex batching, and exact assembly are contributed +by icekylinx with substantial OpenAI GPT-6 Astra and Codex assistance. +This extension directly inherits PR #10, commit +62691e395a0458ce089a1c7b5d89e74291e95e29. The portable producer adapters +modify the retained paired-exclusion and shared-point implementations credited +above. The original licenses, attribution, and Gaussian correction are retained. + +Endpoint-gauge extension, 8 October 2026 +Copyright 2026 icekylinx + +The complementary auxiliary endpoint gauges, (32,30,40) controlled basis, +general nondegenerate binary phase residuals, retained-center complex producer, +updated path guard and exact assembly are contributed by icekylinx with +substantial OpenAI GPT-6 Astra and Codex assistance. This extension inherits +f2ab41aebad47861caf6316282c1793e5513845e and its PR #10 ancestry. New portable +adapters retain the paired-exclusion and shared-point contributions credited +above. Original licenses, notices and the Gaussian correction are retained. + +Structured-basis and semantic-bulk extension, 8 October 2026 +Copyright 2026 icekylinx + +The selected (32,30,36) bit construction, A1/A5 block refinements, fixed +middle-axis I+J profiles with bounded-minor CRT certificates, mixed dyadic +complex centers, complete data-front batching and circuit-specific assembly +are contributed by icekylinx with substantial OpenAI GPT-6 Astra and Codex +assistance. The immediate parent is ed90fd940279c336ebc45968631bdebfb087b505. + +The semantic/bulk compatibility argument, literal scalar charge, product row +stock and parameterization adapt Zhihao Chen (jacklightChen)'s PR #23, +commit 661ebabc1076c9f525d7ce4967c876b203fa9827. Translated complex endpoints +are adopted from the same contributor's PR #21, +commit 5ba6cf0bfb68f2be8d15610e7972207c50254d6a, following eumemic's source-frame +work and the earlier nested-basis contributions. + +Completed-child semantic precision, arbitrary-coordinate routing, phase-cell +Gaussian inversion and bulk tape transfer are credited to the RaD project +(hipotures), commit 45d9b60355872041f9275f77c057514def0d45bc, exposed through +PR #20 at 4f8d6c8272b5ff307a0da51df545ec3cd96a8b6e. Their selected proof +sources retain their separate CC0 license and original NOTICE under +references/semantic-bulk/rad20. The PR #23 source retains its recorded OpenAI +Codex assistance; RaD retains its recorded Codex and GPT-6.1-Sol Ultra +assistance. These are source attributions, not claims about this session's +runtime model. Related contributions by Rohan Arun and Aurel Prosz remain +acknowledged in the imported notices. No exclusive priority or additional +license condition is asserted. + +Copied retained-center extension, 8 October 2026 +Copyright 2026 icekylinx + +The copied retained-center schedule in both orientations, its complex-phase +transfer, the Wm-N+L rank budget, and the selected (25,23)/(28,28) composition +are contributed by icekylinx with substantial OpenAI GPT-6 Astra and Codex +assistance. This extension inherits PR #32 at +0ef3aeb61f55cc0b321ce6a0ef00acee25cefe52 and the preceding PR #10/#18/#24 work. + +The two-stage topology and paid copied-stream endpoint correction are due +to Aurel Prosz (Paureel), c82d09eb4781b68435e3fa3eb6beea72fa900fab, +through Zhihao Chen (jacklightChen)'s PR #29 unequal-axis composition, +9d963275075fa98f1da821e27757b238dafd6b3c. That source also credits Swapnil +Jain's two-stage development at ae405eb474d1486b2d8aef90139869f927f4836a. + +The contiguous corner method, incidence factorization and exact partition +certificates are due to Rohan Arun (PR #31), method commit +02f68f95369c39955c5ffcfddbd75cd83e26b305. The selected (25,23) certificate +specializes Dominik Scholz's parameterization (PR #33), +a499a345c040aa15b68e984a0904eadc485c7a7e. Their source notices and proof +snapshots are preserved in references/copied-centers. Historical OpenAI +Codex, GPT-6 Astra and Anthropic Claude Opus 5.5 assistance is credited to +the respective imported sources. Existing PR #21/#23 and RaD attribution, +licenses and the Gaussian correction remain in force. + +Copied centers with reversed (23,25) data corners, 2026-10-08 +Rohan Arun with substantial OpenAI Codex assistance supplies the composition, +exact dimension23 controls and cost/assembly integration. James Chang's PR34 +supplies the reversed boundary family; icekylinx's PR36 supplies copied retained +centers and the selected complex graph. The balanced assembly remains attributed +to RaD/hipotures and its PR34 specialization. Retain Dominik Scholz PR33, Rohan +Arun PR31, Zhihao Chen PR29/21/23, Paureel/Aurel Prosz, Swapnil Jain, icekylinx, +eumemic, Douglas Colkitt, OpenAI and all earlier notices and hypotheses. +This is not a claim of formal verification, new foundational rank identities, +global optimality or measured runtime speedup. + +Fixed middle basis with copied reversed corners (2026-10-08): +Rohan Arun, with substantial OpenAI Codex assistance. Composition and +simultaneous-basis proof retain icekylinx PR32/36, James Chang PR34, +Dominik Scholz PR35/38, Zhihao Chen, Paureel, Swapnil Jain, RaD/hipotures, +eumemic and all preceding attribution and licenses. See research/copied-fixed-reversed. + +Changed-graph composition with copied centers and fixed bases, 8 October 2026 +Prepared with OpenAI Codex assistance. +The new composition uses icekylinx PR36 copied centers and PR32 fixed-I+J +projectors/profiler, Dominik Scholz PR35 two fixed factors and CRT argument, +Zhihao Chen (jacklightChen) PR29/23 interfaces, Aurel Prosz (Paureel) and +Swapnil Jain's two-stage development, Rohan Arun PR25/31 corner methods, +Dominik Scholz PR33/38 dimension/composition work; James Chang (jamesyc) +PR34 reversed geometry and balanced transfer; Rohan Arun PR37/39/40 +reversed integration, modular data tests and both-fixed baseline; +RaD (hipotures) PR41 changed scalar DAGs, alternating point order and +independent role compiler; RaD balanced routing/bulk interfaces, and all +retained predecessors. +Complete attribution and the conditional proof boundary are recorded in +research/copied-fixed/PROOF.md; imported originals and source pins are retained. +Original Apache-2.0 and CC0 notices remain applicable. + +Community follow-up integration (PRs #49, #45 and #26), 8 October 2026: +Rohan Arun contributes weighted carrier matching and climbed summand/leave-one-out +orders (PRs #44/#47/#49), with Anthropic Claude assistance. The final construction +composes Chafik Boukhalfa's changed-graph checks, exact rational data recovery and +reordered exclusion sums (PRs #43/#46/#48, OpenAI Codex assistance) with RaD / +hipotures's alternating pair order and independent physical role compiler (#41). +All predecessor attribution and conditional proof interfaces remain in force. + +Alejandro Zarzuelo Urdiales contributes Gaussian parity and canonical arithmetic +proofs, finite tensor execution, profile precision accounting and mixed-center +reference checks (#45), with substantial OpenAI Codex assistance. See +research/gaussian-parity-synthesis/PROVENANCE.md for his full attribution. +Ryan S / princezuda contributes historical Lean certificate arithmetic, algebraic +frame/movement/guard lemmas and independent paired-circuit checks (#26). Original +source notices and historical snapshots are preserved. Neither formal package +claims a full formal proof of the multiplication algorithm or a new kappa. + +Joint frame compilation of skip-prefix strips + +The local compiler and exact finite witnesses were prepared by eumemic with +substantial OpenAI Codex assistance. Avi Eisenberg / ikeboy with Anthropic +Claude assistance supplied the unmodified PR #53 skip-prefix producer. +The pinned PR #48 pipeline is Chafik Boukhalfa's contribution, incorporating +Rohan Arun, RaD / hipotures, icekylinx, Dominik Scholz, James Chang, +Zhihao Chen, Aurel Prosz / Paureel, Swapnil Jain, eumemic, Douglas Colkitt, +OpenAI, David Harvey, Joris van der Hoeven and the earlier contributors +credited in the retained sources. See references/frame-compiler/pr48/NOTICE +and references/frame-compiler/pr53/NOTICE for their original attribution. +No authorship or review by those predecessors is claimed for this compiler. + +Joint dual-skip/frame-compiler composition (2026-10-08) +--------------------------------------------------- +Rohan Gupta / gupt1156 with Anthropic Claude assistance: PR55 dual-skip +scalar graph; original LICENSE and NOTICE are retained under +references/frame-compiler/pr55. eumemic with substantial OpenAI Codex +assistance: PR57 joint reversible frame compiler, serialized physical-word +replay and profile reconstruction. Chafik Boukhalfa with OpenAI Codex +assistance: this composition, independent integration and exact certificate. +Avi Eisenberg / ikeboy, Rohan Arun, RaD / hipotures, icekylinx, and all +predecessor authors retain the notices above and in pinned source files. +No original copyright or Apache-2.0 notice is removed. + +Ranked reclamation after the PR58 joint-dual composition (2026-10-08) +---------------------------------------------------------------- +Chafik Boukhalfa with OpenAI Codex assistance: prioritize retired physical +slots by descending current frame rank, retaining slot ID as the tie-breaker; +complete replay, exact profiles, comparison and adversarial controls. +The dedicated joint_dual_reclaim_compiler.py is derived from eumemic's PR57 +compiler. Its original shared compiler remains byte-identical. The PR55 +producer and all preceding attribution remain unchanged. The complete PR58 +comparison certificate and its original source/proof provenance are retained +under references/frame-compiler/pr58. + +Skip-prefix strip producer, 8 October 2026 +By Avi Eisenberg (ikeboy), with Anthropic Claude (Claude Opus 5.5) +assistance. The skip-prefix leave-one-out strip layout in +research/skip-strips/skip_graph.py is new. Everything else reuses Chafik +Boukhalfa's PR #43/#46/#48 research/copied-fixed pipeline, its verification +code, exact data recovery and support-order sorting, together with: +- Rohan Arun's PR #44 weighted carrier matching optimizer; +- RaD (hipotures) PR #41 alternating point order; +- every predecessor credited above. +Complete attribution and the conditional proof boundary are recorded in +research/skip-strips/PROOF.md. Original Apache-2.0 and CC0 notices remain +applicable. + +Local split-pair skip-prefix research continuation (2026-10-08) +Prepared with substantial OpenAI Codex assistance at the user's request. +New contribution: singleton/pair recursive partition refinement, finite summand +order and moment search, and verified composition with inherited paid clones. +Avi Eisenberg/ikeboy with Anthropic Claude assistance: PR53 skip-prefix strips. +Chafik Boukhalfa with OpenAI Codex assistance: PR54 original-envelope clone +specialization and PR43/46/48 checking interfaces. RaD/hipotures: PR51 +whole-chain/source-partition cloning and earlier producer/interfaces. +Rohan Arun: PR44 weighted matching and earlier fixed/reversed work, with all +stated historical AI assistance. All predecessor notices remain applicable. + +Original-envelope whole-chain clones on skip-prefix strips (research/skip-clones): +Chafik Boukhalfa, prepared with OpenAI Codex assistance. The complete-chain +source-partition clone operation is adapted from RaD / hipotures, PR #51, +with original envelopes retained here. The scalar base is Avi Eisenberg's +PR #53 skip-prefix layout, prepared with Anthropic Claude assistance. +All credited PR #43/#46/#48, PR #44 and earlier code/proof notices remain +in force. This extension is provided under the repository's Apache-2.0 terms. + +Community review checkpoint #50–#61, 8 October 2026 +Maintainer integration and independent arithmetic: Douglas Colkitt, with OpenAI Codex. +Avi Eisenberg / ikeboy: skip-prefix strip framework (#53), with Anthropic Claude. +Rohan Gupta / gupt1156: parallel order search (#50, stated Antigravity assistance) +and dual-suffix strips (#55, Anthropic Claude assistance). +Eumemic: equal-frame joint compilation and paid signal reclamation (#57), with Codex. +Chafik Boukhalfa: original-envelope paid clones (#54), composition (#58) and ranked +reclamation (#60), with Codex. Rohan Arun: order search (#52) and positive-frame +composition (#56), with Anthropic Claude and stated Codex validation assistance. +RaD / hipotures: enlarged frames, source partitions and paid whole-chain clones +(#51), with Codex. Rohan Garg / rohangar1: singleton/pair recursion and paid-clone +composition (#59), with Codex. Alejandro Zarzuelo Urdiales: exact parameter +refinement, scoped Lean arithmetic, matrix/parity and search tools (#61), with +his disclosed multi-tool and Codex assistance. +Parallel #50/#52 records are reviewed and credited without replacing the earlier +climbed-48 implementation; their exact commit history is retained. The separate +#51 witness is retained as research; its source-graph/negative-basis data proof +has not received the same full replay as #56's consumed compiler machinery. +See docs/research/community-round2-review.md for precise validation boundaries. +All earlier licenses, contributions and notices remain applicable. + +Interval strips and core-aware pair assembly + +The interval leave-one-out strips and the core-aware group-pair assembly in +research/pair-assembly were prepared by Avi Eisenberg / ikeboy with Anthropic +Claude assistance. They build on PR #53's skip-prefix pipeline and its +inherited PR #48 sources. The stacked witness in research/pair-assembly/frame +uses eumemic's PR #57 joint frame compiler unchanged; its driver and verifier +are adaptations of PR #57's skip_frame_compiler.py, skip_frame_compose.py and +verify_skip_frame.py. Original notices are retained. + +Stopped product-ring extension, 9 October 2026 +Copyright 2026 icekylinx + +The opposite-bank generic projector factorization, componentwise atom-ring +adapters, stopped recurrence and outer ordinary-interchange conversion, +aligned pair-star producer with rational centers, exact denominator-21 +grid and selected (23,23)/(24,24) composition are contributed by icekylinx +with substantial OpenAI GPT-6 Astra and Codex assistance. This extension +builds on merged PR #36, 11817ccacb564bb7f98789c20dc11d3fece207e3, and is +integrated on upstream main 56b66d58297deca1d7dd130247d720e960f77a37. + +The retained ordinary leaf uses the preceding projector, corner and +copied-center proofs. The selected two-stage topology retains Aurel Prosz +(Paureel), Swapnil Jain and Zhihao Chen's credits; the semantic assembly +retains PR #21/#23 and RaD's attribution. Paired producers, carrier matching, +original source notices and each imported license remain unchanged. + +Partial-gauge extension, 9 October 2026 +Copyright 2026 icekylinx + +The pair-first cube assembly, compatible backward frame enlargement, +partial source-gauge schedule and stopped whole-projector composition are +contributed by icekylinx with substantial OpenAI GPT-6 Astra and Codex +assistance, extending PR #104 at 948ce1510df750f4c18b96bdaef436a86f8bf834. + +The physical deferred bit word, reflected event accounting, fan-order +clarification and signed two-stage endpoint are adopted from Zhihao Chen +(jacklightChen), PR #97, e15350b66e4bf796153a68c240a6cc1b788abe3e, with +Codex assistance. Its deferred readouts, V leaves, lifted frames and frozen +witness originate in Swapnil Jain's 741e7aa078392553815df7926ee17ac5e25a8c38, +with the original Claude disclosure retained. Cyclic interval and core-aware +pair primitives build on Avi Eisenberg (ikeboy), PR #62, +ad0f25ff7b23cff7f08ad237c2254e6ecf74257e, with Anthropic Claude assistance. +Prior two-stage, copied-center, ordinary-leaf, semantic/bulk, routing and +precision credits remain in force. Imported source notices and licenses +are preserved under references/partial-gauge/; see SOURCES.json. + +Three-stage cover extension, 9 October 2026 +Copyright 2026 icekylinx + +The arbitrary-subspace Clifford frame theorem, three-stage Cayley geometry, +weighted q-adic GL cover, unit-lower weighted compiler, charged local-ring +fallback, uniform batching, internal row borrowing and exact composition +are contributed by icekylinx with substantial OpenAI GPT-6 Astra assistance. This +repository integration uses Codex assistance and extends +PR #115, cf60442983c153a3a0f5272ea4713f99eba13c86. + +The selected local complex DAG and support/label replay are adopted from +Copyright 2026 eumemic, PR #117, cbb05ce504d571546d9b7794c186a613c659c3bf, +with its original Apache-2.0 terms and Claude assistance disclosure. +The unchanged bit physical word retains Zhihao Chen (jacklightChen), +PR #97, and Swapnil Jain's underlying witness and assistance notices. +The prior opposite-bank, stopped ordinary-leaf, copied-center, topology, +semantic/bulk, precision and analytic credits remain applicable. +Original source notices and license bytes are preserved in +references/three-stage-cover/pr117/ and references/partial-gauge/. + +Paired-cube and shared-core extension, 9 October 2026 +Copyright 2026 icekylinx + +The paired-coordinate triple-cube identity, signed overlap channels, +original-source involution, coordinate-star centers, concrete frame/carrier +compiler, chronological source gauges, PR97 gauge-subset selection, +orthogonal cross-stage routing and exact supplier/assembly integration +are contributed by icekylinx with substantial OpenAI GPT-6 Astra assistance. +Repository integration uses Codex assistance and extends PR #130 at +6a9970a530119174507904e23592fd59ede19a5d. + +The substantial completed-core workspace-sharing principle is credited to +an664, PR #128, 530588a019b4a74f09180680c9e3961bf649ec89, with its reported +OpenAI Codex assistance. No source code, partition or serialized witness +from that PR is imported. The new routing and paired-motif integration +are described separately and claim no exclusive priority for broad sharing, +paired-coordinate, Fourier or carrier-matching ideas. + +Selected coarse queries restrict the retained eumemic PR #117 positive DAG; +its original Apache-2.0 and Claude assistance notices remain unchanged. +The bit word and original frames retain Zhihao Chen's PR #97 and Swapnil +Jain's frozen witness, notices and historical assistance disclosures. +Earlier generalized Clifford, three-stage, uniform weighted recursion, +ordinary-leaf, semantic/bulk, precision and analytic credits remain in force. +Source-specific license bytes are preserved under references/. + +October 9 recycled-bit integration: +DaysSky (#150), William Porter/hpst3r (#147), Thomas Marchand/Th0rgal (#146), +Rohan Gupta/gupt1156 (#148), James Chang/jamesyc (#124), SovereignSteak (#122). +The original notices and assistance disclosures are retained under research/, +including research/recycled-bit-integration/NOTICE. Andrew Barnes/Bortlesboat +(#101) repairs shared verifiers using rfu08's #64 counterexample. rfu08's +scoped finite and formal-transfer packages are included separately; they do +not establish the full multiplication theorem. Earlier and parallel credits +remain applicable. Douglas Colkitt integrated these with OpenAI Codex assistance. + +SovereignSteak PR151 independently supplies the matching gauge/recycling +composition and the selected 473/10^6 atom refinement; its original package +and assistance disclosure are retained under research/gauge-recycling/. + +October 9 coordinated-frame and completed-entrance-bank integration: +Dugongue PR186, 166a34d75e853e933855f0f7e940fe77c4bb6b8d, builds on +Chafik Boukhalfa/chafreaky's PR181, 7fb2194801e3a10f54772c7f0d5505035a4fc0ad, +and eumemic's PR168 v4, 4a3c769e5c5430e7114c4d3e099ff34664677f17. +The frozen package and prerequisite archive preserve all author, source-license +and assistance notices, including PR175's compiler optimization. DaysSky's +carrier closure, James Chang's terminal substitution and balanced assembly, +geckods' stopping refinements and all preceding framework credits remain. +Rohan Arun's PR185 bootstrap and chafreaky's PR182 bit refinement are reviewed +parallel contributions; their gains are not added to the selected witness. +Douglas Colkitt integrated the package and supplied the bank scheduling +supplement with OpenAI Codex assistance, under Apache-2.0. The all-size +interfaces remain conditional; finite verification is not full formalization. diff --git a/docs/research/windows-owner-death-audit/README.md b/docs/research/windows-owner-death-audit/README.md new file mode 100644 index 0000000000..7e64379d82 --- /dev/null +++ b/docs/research/windows-owner-death-audit/README.md @@ -0,0 +1,197 @@ +# Windows gate lifetime after abrupt owner death + +An **unassigned gate that has already opened its event survives abrupt owner +termination until the original 30-second native wait expires**. It does not +start the requested workload. If the owner dies before the gate opens that +event, the subsequent real `OpenEventW` fails and the gate exits. Once native +assignment succeeds, owner termination stops the gate, including a ready +workload and its child after gate release. + +This is source-pinned, author-checked native evidence for the specific startup +handoff in [Discussion #1472](https://github.com/CrocSwap/integer-mult-bounds/discussions/1472#discussioncomment-19174060). +It exercises current main's `run_windows_command` and PR5463's opt-in +`OwnedPopen` directly. Their source bytes are unchanged. The isolated-test CLI, +complete Git reader, production adoption and mathematical construction are +outside this replay. There is no new kappa or selected-frontier change. + +The final overlap refresh exposed a [concurrent PR5463 setup-death +audit](https://github.com/CrocSwap/integer-mult-bounds/discussions/4681#discussioncomment-19174028). +Its three shared stream-adapter cuts retain that investigation's credit; this +probe's corresponding observations are independent corroboration. This +contribution centers on #4905's **shared-runner** handoff, the separate +**before/after event-open schedules**, and the **kill-on-close-disabled +counterfactual**. The concurrent audit remains separately owned. These are +separate Codex-assisted sessions under the same contributor account. + +## Observed boundaries + +Both targets have seven cases. Ten boundary/positive cases and four deliberate +mutation cases pass in each complete run on Windows 11 build 26300, CPython +3.12.0 x64. The receipts bind the final probe and both executable source hashes: +[normal](evidence/windows-normal.json) and +[optimized standalone](evidence/windows-optimized-standalone.json). + +| Boundary at owner termination | Observed result for both targets | +|---|---| +| Before gate `OpenEventW`, before assignment | Owner's event handle disappears. After the observer releases the gate's **instrumentation** barrier, the real open fails, the gate exits and no workload starts. | +| Gate has opened the event and entered its native wait, before assignment | The retained gate handle is still live after the owner stops. The gate exits with code 1 at approximately 30 seconds from wait entry, with no workload-start marker. The timeout argument remains exactly 30,000 ms. | +| Successful native assignment, before `SetEvent` | The gate stops after owner termination; no workload starts. | +| Released gate with ready workload and child | Owner, gate, workload and child handles all stop before the outer fixture job is closed. | +| Resume the paused owner instead of killing it | Work begins after successful assignment, the workload joins its child and both launchers return zero with all retained handles stopped. | + +Two counterfactuals run for **each** target. Replacing the gate's wait condition +with `if False` starts the workload while the owner remains paused before +assignment; the early-work predicate rejects it. Clearing only the +`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` flag before the real native limits call +leaves the ready gate/workload/child alive after owner termination; the owned +lifetime predicate rejects it. The shared and stream implementations execute +their real native APIs in both controls. These are explicit fixture mutations, +not changes to the retained source files or claims about the production settings. + +The separate outer containment job remains open during every observation. It +cleans the deliberate survivors **after** their failure is recorded; its cleanup +is not credited to the target. Every recorded fixture process is stopped before +its retained handle is closed. The `resume` case's `observation_boundary` is +`joined_owner_return`; its legacy-shaped `alive_after_owner_death` field records +the state after that normal return, with no forced termination in this case. + +## What the probe instruments + +The observer executes authenticated source bytes using `compile`, without an +import of a cached source module. In the owner, thin wrappers forward each +native function's ctypes signature and real return value. They pause immediately +before assignment, after successful assignment, or after gate release. The +`Popen` seam substitutes a small tracing launcher for the original `-c` entry; +that launcher executes the **same gate string** with its original arguments. +Its hooks record successful event opening and entry to the real native wait. +The gate retains its event handle; the observer never opens or signals it. +The hooks preserve native thread-local last-error values across marker writes. + +This distinction matters: the source files and original gate algorithm are +unchanged, but the execution is instrumented and its timing is controlled. +These are reproducible schedules, not an incidence measurement of naturally +occurring crashes. The pre-open case deliberately pauses just before the real +open so that it can test the other side of the event-handle race. + +At each readiness barrier, the observer opens native process handles while +those fixture processes are live. It subsequently uses those same handles for +termination/state/exit-code queries, rather than considering disappearance of +a PID sufficient evidence. Only the known fixture owner is forcibly terminated +for the result. Owner Python `finally` blocks cannot provide its cleanup. An +independent outer Job Object contains every scenario, including the mutants, +and is released only after observation. All cases therefore exercise nested +jobs under this host's policy. + +Microsoft's [Job Objects contract](https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects) +describes default child membership and termination when the final kill-on-close +job handle closes. These finite observations cover the recorded native +environment and source schedules; they do not prove every OS policy or launch +mechanism satisfies that contract. + +## Reproduce and inspect + +Use native Windows and Python 3.12; no Git installation or third-party Python +package is needed to execute the snapshot: + +```text +python -I -W error -B docs/research/windows-owner-death-audit/probe.py --output normal.json +python -I -O -W error -B docs/research/windows-owner-death-audit/probe.py --output optimized.json +``` + +A complete invocation prints 14 case lines, followed by +`CONFIRMED_WINDOWS_OWNER_DEATH_BOUNDARIES cases=14`. Allow about two minutes per +run; two cases intentionally execute the full original 30-second wait. The +checks use explicit exceptions under `-O`, which also propagates to the fixture +interpreters. `-W error` applies to the outer probe. For a single named case, +use, for example, `--target shared --phase before_assign`. A partial run reports +its actual case count and is not equivalent to the complete run. On another +platform the probe rejects with `native Windows required` rather than claiming +Windows coverage. + +The optimized receipt comes from a fresh standalone copy with no `.git` and an +empty `PATH`. [The standalone receipt](evidence/standalone-integrity.json) +records its input inventory before/after execution and the additional rejection +of a changed executable source byte. The snapshot only contains this directory; +the interpreter remains the installed, absolute-path CPython executable. +The inventory records the execution-time snapshot; the final notice links, +byte-preservation attributes and validation receipt were added afterward and +do not change its executable inputs. + +[SOURCES.json](SOURCES.json) records exact commits, paths, Git blobs and SHA256s. +The two files under `sources/` are byte-identical executable inputs, required +to preserve this experiment if either launcher later changes: + +| Input | Exact source | +|---|---| +| Shared job helper and gate | `scripts/research_package.py`, main `32890bc5de034ab8fc93ac88ead7279a6a416d04`, blob `48fe9feb4699c09e95505d95c327ea998ac8b3a7` | +| Opt-in stream owner and outer fixture containment | `research/windows-git-stream-ownership/owned_pipe.py`, PR5463 head `991da9010925914ec64a5293286edb10a7910218`, blob `8150b9a79df8fa59997caffe96f28889a3452c6e` | + +This native-only audit is intentionally not registered as a generic Linux +research replay. Its local receipts do not imply hosted native CI execution. + +## Task, overlap and attribution + +Started from canonical main `32890bc5de034ab8fc93ac88ead7279a6a416d04`, priority +revision 7. Handoff-first selection took [#4905's explicit pre-assignment +request](https://github.com/CrocSwap/integer-mult-bounds/discussions/1166#discussioncomment-19134563) +and PR5463's corresponding untested setup-death boundary. The `portable-replay` +card was then retrieved, nonce `be461e0156490165c286b00a0ca6046a`, scope key +`831cc1a3c8f9ff01b197`. **Scope status: changed** from payload/environment repair +to a native lifetime audit: the complete replay needs an accurate cancellation +contract, and the handoff supplies a bounded unresolved question. Success is +the source-bound boundary table and meaningful controls, not a new algorithm. + +The initial idea of launch-error preservation was abandoned before implementing +or claiming it, after the refresh found [the concurrent diagnostic +claim](https://github.com/CrocSwap/integer-mult-bounds/discussions/4681#discussioncomment-19174004). +[The pointer in #4681](https://github.com/CrocSwap/integer-mult-bounds/discussions/4681#discussioncomment-19174075) +keeps these scopes separate. The entry overlap snapshot covered 377 open PR +bodies and 4,786 Discussion records with reported complete pagination. After +sparse materialization, the helper searched all 7,402 tracked in-scope +README/TASK/ledger notes. This excludes arbitrary diffs, nested package sources, +unpublished work and a complete remote closed-PR scan. The final checkpoint is +recorded in [VALIDATION.json](VALIDATION.json). +That checkpoint searched 379 open PR bodies, 4,797 Discussion records and all +7,402 tracked in-scope notes, then read the concurrent claim live. The scope +distinction above was posted to its thread before submission. Canonical main +and the relevant guidance remained unchanged. + +The current selected metadata-baseline command was attempted and stopped at +the sparse-omitted `certificates/frontier5413/CLAIM.json`; no selected baseline +pass or mathematical cold replay is claimed. The selected record remains #5413 +and its five assumptions. This audit does not edit the selection, catalogue, +shared code, predecessor package or existing test suite. + +Credit **ywz65 / #2469** for the gated Job Object protocol and **#2452** for +retained native-handle observations; **Mihir Srivastava / #4683, #4905 and #5463** +for the isolated supervisor, native replay handoff and opt-in streaming owner; +**Nick Petalas / #2906 and #2752**, **Brian / JoyboyBrian / #4738 and #5185**, +**Paul Romeo / #1484**, **Douglas Colkitt**, **Aaron Wang/navinor**, **woklerx**, +**Tekalign**, **Arham Tahir** and the retained lifecycle/reader/source lineage. +The source headers, [LICENSE](LICENSE), [repository NOTICE](NOTICE) and +[stream adapter's original NOTICE](sources/NOTICE-stream) are retained. + +Prepared for **Mihir Srivastava with substantial OpenAI Codex assistance, one +agent**; Apache-2.0. This is a newly authored probe from another session under +the same contributor account, not independent human review or a formal proof. + +## Open next questions and limits + +1. **Is the transient unassigned waiter acceptable?** The measured gap leaves + no workload running, but immediate cleanup throughout startup is not the + implemented contract. If that stronger requirement is needed, design + creation-time ownership or a parent-liveness wait, then use these schedules + and mutation controls to test it. The design must retain the prohibition on + workload execution before ownership. This audit supplies no such repair. +2. **Can the stream owner be adopted by its actual consumers?** The diagnostic + successor and production test seams remain separately coordinated. Start + from the exact source pins and combine lifetime, launch-error, binary-stream + and fallback checks in the proposed production composition. This probe + doesn't rerun the Git blob protocol or the entire isolated-test CLI. + +Other Windows/Python versions, restrictive enclosing-job policies, owner death +inside native API calls, external/WMI launch mechanisms, malicious breakaway, +and uninstrumented crash frequencies were not tested. The native five-second +exit observations and original 30-second gate timeout are finite test bounds, +not a universal operating-system scheduling guarantee. No claim about the full +integer multiplication theorem follows from these tooling results. diff --git a/docs/research/windows-owner-death-audit/SOURCES.json b/docs/research/windows-owner-death-audit/SOURCES.json new file mode 100644 index 0000000000..3aeaef4c45 --- /dev/null +++ b/docs/research/windows-owner-death-audit/SOURCES.json @@ -0,0 +1,19 @@ +{ + "schema": 1, + "files": [ + { + "commit": "32890bc5de034ab8fc93ac88ead7279a6a416d04", + "path": "scripts/research_package.py", + "local": "sources/research_package.py", + "git_blob": "48fe9feb4699c09e95505d95c327ea998ac8b3a7", + "sha256": "84a45190a6a96c20993a0d4a1045fbf24de660d2e6fef8f8a447bb3d1aa78db6" + }, + { + "commit": "991da9010925914ec64a5293286edb10a7910218", + "path": "research/windows-git-stream-ownership/owned_pipe.py", + "local": "sources/owned_pipe.py", + "git_blob": "8150b9a79df8fa59997caffe96f28889a3452c6e", + "sha256": "01f597c8ecb955b2ef7faa8f0650e67c0eca5c0d5f6a817a181c536bef9c37aa" + } + ] +} diff --git a/docs/research/windows-owner-death-audit/VALIDATION.json b/docs/research/windows-owner-death-audit/VALIDATION.json new file mode 100644 index 0000000000..68df69eed1 --- /dev/null +++ b/docs/research/windows-owner-death-audit/VALIDATION.json @@ -0,0 +1,83 @@ +{ + "status": "PASS_SCOPED_SOURCE_RECEIPT_AND_STAGED_BYTE_CHECKS", + "checked_at_utc": "2026-10-25T06:32:35.852747+00:00", + "canonical_main": "32890bc5de034ab8fc93ac88ead7279a6a416d04", + "priority_revision": 7, + "scope": "Native process-lifetime audit, not a production change or mathematical replay", + "commands": [ + "python -I -W error -B docs/research/windows-owner-death-audit/probe.py --output docs/research/windows-owner-death-audit/evidence/windows-normal.json", + "python -I -O -W error -B /probe.py --output /windows-optimized-standalone.json (empty PATH, no .git)", + "git diff --cached --check" + ], + "executed_source_pins_match_git_objects": true, + "probe_sha256": "afcc16f221ba09d9cfa87d4995f9a807c0f1de786cca2daf0d07a70dc39499a7", + "normal_and_optimized_full_cases": [ + 14, + 14 + ], + "original_gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db", + "gate_bypass_control_sha256": "7ac91dc15d521c87b26d2c69d0e338681744455e2cfe9bcff30dae8e3aa27a44", + "receipts": { + "windows-normal.json": "15d32888312d80fca678eb819c6a473f27c69fc036c88d353a32e25020d26502", + "windows-optimized-standalone.json": "84f3ca90c4f0ccc47a165c7876ffb337e3bda22b6f30c5d12084eba8818e5aa3" + }, + "standalone_integrity_receipt_sha256": "cef81c0ef174c4abcdf6a1ca569f6c317b55a026632ecbed9ca50173f84a28d3", + "checked_staged_files": { + "docs/research/windows-owner-death-audit/.gitattributes": "22ed04d435a0bb7555a180951fe12000f9a933d153a7d50e22dd95fbf52800e1", + "docs/research/windows-owner-death-audit/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", + "docs/research/windows-owner-death-audit/NOTICE": "b49022cb4104d90b182e4c335f670faf45529025b15db77bf0291835083c28bd", + "docs/research/windows-owner-death-audit/README.md": "43350b04a0e248e12dfe209ea0caee6467d43423c84b1e52be4d07e20344c984", + "docs/research/windows-owner-death-audit/SOURCES.json": "ffa6553eb0e2ce4b0766ae6f0736ace2ab75f70347f57d18bd7c55df381baad0", + "docs/research/windows-owner-death-audit/evidence/standalone-integrity.json": "cef81c0ef174c4abcdf6a1ca569f6c317b55a026632ecbed9ca50173f84a28d3", + "docs/research/windows-owner-death-audit/evidence/windows-normal.json": "15d32888312d80fca678eb819c6a473f27c69fc036c88d353a32e25020d26502", + "docs/research/windows-owner-death-audit/evidence/windows-optimized-standalone.json": "84f3ca90c4f0ccc47a165c7876ffb337e3bda22b6f30c5d12084eba8818e5aa3", + "docs/research/windows-owner-death-audit/probe.py": "afcc16f221ba09d9cfa87d4995f9a807c0f1de786cca2daf0d07a70dc39499a7", + "docs/research/windows-owner-death-audit/sources/NOTICE-stream": "cf3d9fe806ac9ae186e72a0f03f2af252494fcfaf7aa9f05520aff471fd6db8d", + "docs/research/windows-owner-death-audit/sources/owned_pipe.py": "01f597c8ecb955b2ef7faa8f0650e67c0eca5c0d5f6a817a181c536bef9c37aa", + "docs/research/windows-owner-death-audit/sources/research_package.py": "84a45190a6a96c20993a0d4a1045fbf24de660d2e6fef8f8a447bb3d1aa78db6" + }, + "source_notice": { + "path": "sources/NOTICE-stream", + "source_commit": "991da9010925914ec64a5293286edb10a7910218", + "sha256": "cf3d9fe806ac9ae186e72a0f03f2af252494fcfaf7aa9f05520aff471fd6db8d" + }, + "overlap": { + "coverage": { + "local": { + "head": "32890bc5de034ab8fc93ac88ead7279a6a416d04", + "records": 7402, + "head_records": 7402, + "complete_head_inventory": true + }, + "open_prs": { + "fetched_at": "2026-10-25T06:26:12.213951+00:00", + "complete_pagination": true, + "records": 379 + }, + "discussions": { + "fetched_at": "2026-10-25T06:27:49.111401+00:00", + "complete_pagination": true, + "records": 4797 + } + }, + "warnings": [], + "concurrent_audit": "https://github.com/CrocSwap/integer-mult-bounds/discussions/4681#discussioncomment-19174028", + "scope_coordination": "https://github.com/CrocSwap/integer-mult-bounds/discussions/4681#discussioncomment-19174200", + "distinction": "Shared runner, event-open race and kill-on-close control; shared stream cuts are corroboration" + }, + "selected_baseline": { + "command": "python -B scripts/research_selected_baseline.py", + "exit_code": 1, + "error": "Invalid selected file: certificates/frontier5413/CLAIM.json", + "reason": "Input omitted by sparse checkout; no baseline pass claimed" + }, + "not_run": [ + "selected cold replay", + "full historical suite", + "production consumer adoption", + "other native OS/Python versions", + "hosted native Windows CI", + "full source-history preflight" + ], + "assistance": "Mihir Srivastava with substantial OpenAI Codex assistance, one agent; no independent human review" +} diff --git a/docs/research/windows-owner-death-audit/evidence/standalone-integrity.json b/docs/research/windows-owner-death-audit/evidence/standalone-integrity.json new file mode 100644 index 0000000000..8986598daa --- /dev/null +++ b/docs/research/windows-owner-death-audit/evidence/standalone-integrity.json @@ -0,0 +1,37 @@ +{ + "status": "PASS_STANDALONE_INTEGRITY_AND_SOURCE_REJECTION", + "empty_PATH": true, + "git_directory_present": false, + "command": [ + "C:\\Python312\\python.exe", + "-I", + "-O", + "-W", + "error", + "-B", + "C:\\Users\\mihir\\AppData\\Local\\Temp\\owner-death-standalone-mggaikep\\audit\\probe.py", + "--output", + "C:\\Users\\mihir\\Documents\\Codex\\2026-10-24\\contribute-one-useful-independently-reviewable-result-5\\scoped-repo\\docs\\research\\windows-owner-death-audit\\evidence\\windows-optimized-standalone.json" + ], + "snapshot_files": { + "evidence/windows-normal.json": "15d32888312d80fca678eb819c6a473f27c69fc036c88d353a32e25020d26502", + "LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", + "NOTICE": "b49022cb4104d90b182e4c335f670faf45529025b15db77bf0291835083c28bd", + "probe.py": "afcc16f221ba09d9cfa87d4995f9a807c0f1de786cca2daf0d07a70dc39499a7", + "README.md": "bb07c12efabf96cfd81500796d17fea50c9202e1d1cad2e6822bef87364b2965", + "sources/owned_pipe.py": "01f597c8ecb955b2ef7faa8f0650e67c0eca5c0d5f6a817a181c536bef9c37aa", + "sources/research_package.py": "84a45190a6a96c20993a0d4a1045fbf24de660d2e6fef8f8a447bb3d1aa78db6", + "SOURCES.json": "ffa6553eb0e2ce4b0766ae6f0736ace2ab75f70347f57d18bd7c55df381baad0" + }, + "inventory_unchanged_after_replay": true, + "returncode": 0, + "stdout": "PASS shared before_event_open none\nPASS shared before_assign none\nPASS shared assigned none\nPASS shared released none\nPASS shared resume none\nPASS shared before_assign bypass-gate\nPASS shared released no-kill-on-close\nPASS stream before_event_open none\nPASS stream before_assign none\nPASS stream assigned none\nPASS stream released none\nPASS stream resume none\nPASS stream before_assign bypass-gate\nPASS stream released no-kill-on-close\nCONFIRMED_WINDOWS_OWNER_DEATH_BOUNDARIES cases=14\n", + "stderr": "", + "source_mutation": { + "source": "sources/owned_pipe.py", + "returncode": 1, + "stdout": "", + "stderr": "Traceback (most recent call last):\n File \"C:\\Users\\mihir\\AppData\\Local\\Temp\\owner-death-standalone-mggaikep\\audit\\probe.py\", line 425, in \n main()\n File \"C:\\Users\\mihir\\AppData\\Local\\Temp\\owner-death-standalone-mggaikep\\audit\\probe.py\", line 401, in main\n load_sources() # Authenticate every executable source before starting fixtures.\n ^^^^^^^^^^^^^^\n File \"C:\\Users\\mihir\\AppData\\Local\\Temp\\owner-death-standalone-mggaikep\\audit\\probe.py\", line 41, in load_sources\n require(hashlib.sha256(raw).hexdigest() == expected, \"source drift: \" + name)\n File \"C:\\Users\\mihir\\AppData\\Local\\Temp\\owner-death-standalone-mggaikep\\audit\\probe.py\", line 33, in require\n raise RuntimeError(message)\nRuntimeError: source drift: owned_pipe.py\n", + "receipt_created": false + } +} diff --git a/docs/research/windows-owner-death-audit/evidence/windows-normal.json b/docs/research/windows-owner-death-audit/evidence/windows-normal.json new file mode 100644 index 0000000000..13e818e97f --- /dev/null +++ b/docs/research/windows-owner-death-audit/evidence/windows-normal.json @@ -0,0 +1,969 @@ +{ + "schema": 1, + "status": "CONFIRMED_WINDOWS_OWNER_DEATH_BOUNDARIES", + "started_utc": "2026-10-25T06:22:38.039987+00:00", + "platform": "Windows-11-10.0.26300-SP0", + "python": "3.12.0 (tags/v3.12.0:0fb18b0, Oct 2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)]", + "optimized": 0, + "probe_sha256": "afcc16f221ba09d9cfa87d4995f9a807c0f1de786cca2daf0d07a70dc39499a7", + "source_sha256": { + "research_package.py": "84a45190a6a96c20993a0d4a1045fbf24de660d2e6fef8f8a447bb3d1aa78db6", + "owned_pipe.py": "01f597c8ecb955b2ef7faa8f0650e67c0eca5c0d5f6a817a181c536bef9c37aa" + }, + "windows_observations_executed": true, + "cases": [ + { + "target": "shared", + "phase": "before_event_open", + "mutation": "none", + "workload_before_release": false, + "observed_pids": { + "owner": 59352, + "gate": 45352 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 137967.281, + "event_open_after_owner_death": false, + "alive_after_owner_death": { + "owner": false, + "gate": false + }, + "observation_seconds_after_owner_death": 0.0470000000204891, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "Traceback (most recent call last):\n File \"C:\\Users\\mihir\\Documents\\Codex\\2026-10-24\\contribute-one-useful-independently-reviewable-result-5\\scoped-repo\\docs\\research\\windows-owner-death-audit\\probe.py\", line 425, in \n main()\n File \"C:\\Users\\mihir\\Documents\\Codex\\2026-10-24\\contribute-one-useful-independently-reviewable-result-5\\scoped-repo\\docs\\research\\windows-owner-death-audit\\probe.py\", line 391, in main\n gate_worker(args)\n File \"C:\\Users\\mihir\\Documents\\Codex\\2026-10-24\\contribute-one-useful-independently-reviewable-result-5\\scoped-repo\\docs\\research\\windows-owner-death-audit\\probe.py\", line 142, in gate_worker\n exec(compile(code, \"\", \"exec\"), {\"__name__\": \"__main__\"})\n File \"\", line 16, in \nFileNotFoundError: [WinError 2] The system cannot find the file specified.\n", + "events": { + "checkpoint": { + "pid": 59352, + "time": 137967.062, + "phase": "before_event_open" + }, + "gate-before-open": { + "pid": 45352, + "time": 137967.25 + }, + "gate-open": { + "pid": 45352, + "time": 137967.296, + "succeeded": false + }, + "gate-process": { + "pid": 59352, + "time": 137967.062, + "process_pid": 45352, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "owner-ready": { + "pid": 59352, + "time": 137967.046, + "target": "shared", + "phase": "before_event_open", + "mutation": "none" + } + } + }, + { + "target": "shared", + "phase": "before_assign", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 137968.062, + "workload_before_release": false, + "observed_pids": { + "owner": 22248, + "gate": 35576 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 137968.093, + "alive_after_owner_death": { + "owner": false, + "gate": true + }, + "observation_seconds_after_owner_death": 0.5, + "gate_exit_seconds_from_native_wait": 30.030999999988126, + "gate_exit_code": 1, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "Traceback (most recent call last):\n File \"C:\\Users\\mihir\\Documents\\Codex\\2026-10-24\\contribute-one-useful-independently-reviewable-result-5\\scoped-repo\\docs\\research\\windows-owner-death-audit\\probe.py\", line 425, in \n main()\n File \"C:\\Users\\mihir\\Documents\\Codex\\2026-10-24\\contribute-one-useful-independently-reviewable-result-5\\scoped-repo\\docs\\research\\windows-owner-death-audit\\probe.py\", line 391, in main\n gate_worker(args)\n File \"C:\\Users\\mihir\\Documents\\Codex\\2026-10-24\\contribute-one-useful-independently-reviewable-result-5\\scoped-repo\\docs\\research\\windows-owner-death-audit\\probe.py\", line 142, in gate_worker\n exec(compile(code, \"\", \"exec\"), {\"__name__\": \"__main__\"})\n File \"\", line 19, in \nRuntimeError: replay ownership gate was not released\n", + "events": { + "checkpoint": { + "pid": 22248, + "time": 137967.875, + "phase": "before_assign" + }, + "gate-open": { + "pid": 35576, + "time": 137968.062, + "succeeded": true + }, + "gate-process": { + "pid": 22248, + "time": 137967.875, + "process_pid": 35576, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-wait": { + "pid": 35576, + "time": 137968.062, + "timeout_ms": 30000 + }, + "owner-ready": { + "pid": 22248, + "time": 137967.843, + "target": "shared", + "phase": "before_assign", + "mutation": "none" + } + } + }, + { + "target": "shared", + "phase": "assigned", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 137998.953, + "workload_before_release": false, + "observed_pids": { + "owner": 51780, + "gate": 48760 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 137998.968, + "alive_after_owner_death": { + "owner": false, + "gate": false + }, + "observation_seconds_after_owner_death": 0.01600000000325963, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 51780, + "time": 137998.765, + "succeeded": true + }, + "checkpoint": { + "pid": 51780, + "time": 137998.765, + "phase": "assigned" + }, + "gate-open": { + "pid": 48760, + "time": 137998.953, + "succeeded": true + }, + "gate-process": { + "pid": 51780, + "time": 137998.765, + "process_pid": 48760, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-wait": { + "pid": 48760, + "time": 137998.953, + "timeout_ms": 30000 + }, + "owner-ready": { + "pid": 51780, + "time": 137998.734, + "target": "shared", + "phase": "assigned", + "mutation": "none" + } + } + }, + { + "target": "shared", + "phase": "released", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 137999.5, + "workload_before_release": false, + "observed_pids": { + "owner": 25292, + "gate": 37324, + "workload": 15352, + "leaf": 38152 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 137999.843, + "alive_after_owner_death": { + "owner": false, + "gate": false, + "workload": false, + "leaf": false + }, + "observation_seconds_after_owner_death": 0.0, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 25292, + "time": 137999.343, + "succeeded": true + }, + "checkpoint": { + "pid": 25292, + "time": 137999.359, + "phase": "released" + }, + "gate-open": { + "pid": 37324, + "time": 137999.5, + "succeeded": true + }, + "gate-process": { + "pid": 25292, + "time": 137999.343, + "process_pid": 37324, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 25292, + "time": 137999.359, + "succeeded": true + }, + "gate-wait": { + "pid": 37324, + "time": 137999.5, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 38152, + "time": 137999.796 + }, + "owner-ready": { + "pid": 25292, + "time": 137999.328, + "target": "shared", + "phase": "released", + "mutation": "none" + }, + "workload-ready": { + "pid": 15352, + "time": 137999.812, + "leaf_pid": 38152 + }, + "workload-start": { + "pid": 15352, + "time": 137999.64 + } + } + }, + { + "target": "shared", + "phase": "resume", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138001.343, + "workload_before_release": false, + "observed_pids": { + "owner": 61184, + "gate": 54204, + "workload": 56532, + "leaf": 31336 + }, + "joined_return": 0, + "observation_boundary": "joined_owner_return", + "alive_after_owner_death": { + "owner": false, + "gate": false, + "workload": false, + "leaf": false + }, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 61184, + "time": 138001.375, + "succeeded": true + }, + "checkpoint": { + "pid": 61184, + "time": 138000.562, + "phase": "resume" + }, + "gate-open": { + "pid": 54204, + "time": 138001.343, + "succeeded": true + }, + "gate-process": { + "pid": 61184, + "time": 138000.562, + "process_pid": 54204, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 61184, + "time": 138001.39, + "succeeded": true + }, + "gate-wait": { + "pid": 54204, + "time": 138001.343, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 31336, + "time": 138001.828 + }, + "owner-finished": { + "pid": 61184, + "time": 138001.937, + "returncode": 0 + }, + "owner-ready": { + "pid": 61184, + "time": 138000.546, + "target": "shared", + "phase": "resume", + "mutation": "none" + }, + "workload-ready": { + "pid": 56532, + "time": 138001.843, + "leaf_pid": 31336 + }, + "workload-start": { + "pid": 56532, + "time": 138001.562 + } + } + }, + { + "target": "shared", + "phase": "before_assign", + "mutation": "bypass-gate", + "workload_before_release": true, + "observed_pids": { + "owner": 38632, + "gate": 5752, + "workload": 25144, + "leaf": 20676 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138003.265, + "alive_after_owner_death": { + "owner": false, + "gate": true, + "workload": true, + "leaf": true + }, + "observation_seconds_after_owner_death": 0.5, + "rejected_rule": "workload_crossed_unreleased_gate", + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "checkpoint": { + "pid": 38632, + "time": 138002.812, + "phase": "before_assign" + }, + "gate-open": { + "pid": 5752, + "time": 138002.953, + "succeeded": true + }, + "gate-process": { + "pid": 38632, + "time": 138002.812, + "process_pid": 5752, + "gate_sha256": "7ac91dc15d521c87b26d2c69d0e338681744455e2cfe9bcff30dae8e3aa27a44" + }, + "leaf-ready": { + "pid": 20676, + "time": 138003.218 + }, + "owner-ready": { + "pid": 38632, + "time": 138002.781, + "target": "shared", + "phase": "before_assign", + "mutation": "bypass-gate" + }, + "workload-ready": { + "pid": 25144, + "time": 138003.234, + "leaf_pid": 20676 + }, + "workload-start": { + "pid": 25144, + "time": 138003.093 + } + } + }, + { + "target": "shared", + "phase": "released", + "mutation": "no-kill-on-close", + "native_wait_ms": 30000, + "gate_wait_started": 138004.343, + "workload_before_release": false, + "observed_pids": { + "owner": 24772, + "gate": 38212, + "workload": 21340, + "leaf": 34380 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138004.75, + "alive_after_owner_death": { + "owner": false, + "gate": true, + "workload": true, + "leaf": true + }, + "observation_seconds_after_owner_death": 0.5150000000139698, + "rejected_rule": "assigned_process_survived_owner_death", + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 24772, + "time": 138004.218, + "succeeded": true + }, + "checkpoint": { + "pid": 24772, + "time": 138004.218, + "phase": "released" + }, + "gate-open": { + "pid": 38212, + "time": 138004.343, + "succeeded": true + }, + "gate-process": { + "pid": 24772, + "time": 138004.203, + "process_pid": 38212, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 24772, + "time": 138004.218, + "succeeded": true + }, + "gate-wait": { + "pid": 38212, + "time": 138004.343, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 34380, + "time": 138004.703 + }, + "mutated-limit": { + "pid": 24772, + "time": 138004.203, + "flags": 0 + }, + "owner-ready": { + "pid": 24772, + "time": 138004.187, + "target": "shared", + "phase": "released", + "mutation": "no-kill-on-close" + }, + "workload-ready": { + "pid": 21340, + "time": 138004.718, + "leaf_pid": 34380 + }, + "workload-start": { + "pid": 21340, + "time": 138004.5 + } + } + }, + { + "target": "stream", + "phase": "before_event_open", + "mutation": "none", + "workload_before_release": false, + "observed_pids": { + "owner": 59968, + "gate": 55544 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138005.953, + "event_open_after_owner_death": false, + "alive_after_owner_death": { + "owner": false, + "gate": false + }, + "observation_seconds_after_owner_death": 0.062000000005355105, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "checkpoint": { + "pid": 59968, + "time": 138005.734, + "phase": "before_event_open" + }, + "gate-before-open": { + "pid": 55544, + "time": 138005.921 + }, + "gate-open": { + "pid": 55544, + "time": 138005.968, + "succeeded": false + }, + "gate-process": { + "pid": 59968, + "time": 138005.734, + "process_pid": 55544, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "owner-ready": { + "pid": 59968, + "time": 138005.718, + "target": "stream", + "phase": "before_event_open", + "mutation": "none" + } + } + }, + { + "target": "stream", + "phase": "before_assign", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138006.718, + "workload_before_release": false, + "observed_pids": { + "owner": 52076, + "gate": 40228 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138006.75, + "alive_after_owner_death": { + "owner": false, + "gate": true + }, + "observation_seconds_after_owner_death": 0.5, + "gate_exit_seconds_from_native_wait": 30.03200000000652, + "gate_exit_code": 1, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "checkpoint": { + "pid": 52076, + "time": 138006.593, + "phase": "before_assign" + }, + "gate-open": { + "pid": 40228, + "time": 138006.718, + "succeeded": true + }, + "gate-process": { + "pid": 52076, + "time": 138006.578, + "process_pid": 40228, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-wait": { + "pid": 40228, + "time": 138006.718, + "timeout_ms": 30000 + }, + "owner-ready": { + "pid": 52076, + "time": 138006.546, + "target": "stream", + "phase": "before_assign", + "mutation": "none" + } + } + }, + { + "target": "stream", + "phase": "assigned", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138037.5, + "workload_before_release": false, + "observed_pids": { + "owner": 61104, + "gate": 49672 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138037.515, + "alive_after_owner_death": { + "owner": false, + "gate": false + }, + "observation_seconds_after_owner_death": 0.0, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 61104, + "time": 138037.296, + "succeeded": true + }, + "checkpoint": { + "pid": 61104, + "time": 138037.296, + "phase": "assigned" + }, + "gate-open": { + "pid": 49672, + "time": 138037.484, + "succeeded": true + }, + "gate-process": { + "pid": 61104, + "time": 138037.296, + "process_pid": 49672, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-wait": { + "pid": 49672, + "time": 138037.5, + "timeout_ms": 30000 + }, + "owner-ready": { + "pid": 61104, + "time": 138037.265, + "target": "stream", + "phase": "assigned", + "mutation": "none" + } + } + }, + { + "target": "stream", + "phase": "released", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138038.234, + "workload_before_release": false, + "observed_pids": { + "owner": 34028, + "gate": 59408, + "workload": 34052, + "leaf": 52220 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138038.64, + "alive_after_owner_death": { + "owner": false, + "gate": false, + "workload": false, + "leaf": false + }, + "observation_seconds_after_owner_death": 0.0, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 34028, + "time": 138038.062, + "succeeded": true + }, + "checkpoint": { + "pid": 34028, + "time": 138038.062, + "phase": "released" + }, + "gate-open": { + "pid": 59408, + "time": 138038.234, + "succeeded": true + }, + "gate-process": { + "pid": 34028, + "time": 138038.062, + "process_pid": 59408, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 34028, + "time": 138038.062, + "succeeded": true + }, + "gate-wait": { + "pid": 59408, + "time": 138038.234, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 52220, + "time": 138038.593 + }, + "owner-ready": { + "pid": 34028, + "time": 138038.031, + "target": "stream", + "phase": "released", + "mutation": "none" + }, + "workload-ready": { + "pid": 34052, + "time": 138038.593, + "leaf_pid": 52220 + }, + "workload-start": { + "pid": 34052, + "time": 138038.406 + } + } + }, + { + "target": "stream", + "phase": "resume", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138039.375, + "workload_before_release": false, + "observed_pids": { + "owner": 37228, + "gate": 20684, + "workload": 54164, + "leaf": 39420 + }, + "joined_return": 0, + "observation_boundary": "joined_owner_return", + "alive_after_owner_death": { + "owner": false, + "gate": false, + "workload": false, + "leaf": false + }, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 37228, + "time": 138039.406, + "succeeded": true + }, + "checkpoint": { + "pid": 37228, + "time": 138039.203, + "phase": "resume" + }, + "gate-open": { + "pid": 20684, + "time": 138039.375, + "succeeded": true + }, + "gate-process": { + "pid": 37228, + "time": 138039.203, + "process_pid": 20684, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 37228, + "time": 138039.406, + "succeeded": true + }, + "gate-wait": { + "pid": 20684, + "time": 138039.375, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 39420, + "time": 138039.765 + }, + "owner-finished": { + "pid": 37228, + "time": 138039.843, + "returncode": 0 + }, + "owner-ready": { + "pid": 37228, + "time": 138039.187, + "target": "stream", + "phase": "resume", + "mutation": "none" + }, + "workload-ready": { + "pid": 54164, + "time": 138039.796, + "leaf_pid": 39420 + }, + "workload-start": { + "pid": 54164, + "time": 138039.625 + } + } + }, + { + "target": "stream", + "phase": "before_assign", + "mutation": "bypass-gate", + "workload_before_release": true, + "observed_pids": { + "owner": 60444, + "gate": 32424, + "workload": 58564, + "leaf": 22836 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138041.062, + "alive_after_owner_death": { + "owner": false, + "gate": true, + "workload": true, + "leaf": true + }, + "observation_seconds_after_owner_death": 0.5, + "rejected_rule": "workload_crossed_unreleased_gate", + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "checkpoint": { + "pid": 60444, + "time": 138040.468, + "phase": "before_assign" + }, + "gate-open": { + "pid": 32424, + "time": 138040.609, + "succeeded": true + }, + "gate-process": { + "pid": 60444, + "time": 138040.468, + "process_pid": 32424, + "gate_sha256": "7ac91dc15d521c87b26d2c69d0e338681744455e2cfe9bcff30dae8e3aa27a44" + }, + "leaf-ready": { + "pid": 22836, + "time": 138041.0 + }, + "owner-ready": { + "pid": 60444, + "time": 138040.453, + "target": "stream", + "phase": "before_assign", + "mutation": "bypass-gate" + }, + "workload-ready": { + "pid": 58564, + "time": 138041.031, + "leaf_pid": 22836 + }, + "workload-start": { + "pid": 58564, + "time": 138040.812 + } + } + }, + { + "target": "stream", + "phase": "released", + "mutation": "no-kill-on-close", + "native_wait_ms": 30000, + "gate_wait_started": 138042.531, + "workload_before_release": false, + "observed_pids": { + "owner": 57732, + "gate": 41160, + "workload": 57156, + "leaf": 35772 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138042.937, + "alive_after_owner_death": { + "owner": false, + "gate": true, + "workload": true, + "leaf": true + }, + "observation_seconds_after_owner_death": 0.5, + "rejected_rule": "assigned_process_survived_owner_death", + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 57732, + "time": 138042.375, + "succeeded": true + }, + "checkpoint": { + "pid": 57732, + "time": 138042.375, + "phase": "released" + }, + "gate-open": { + "pid": 41160, + "time": 138042.531, + "succeeded": true + }, + "gate-process": { + "pid": 57732, + "time": 138042.375, + "process_pid": 41160, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 57732, + "time": 138042.375, + "succeeded": true + }, + "gate-wait": { + "pid": 41160, + "time": 138042.531, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 35772, + "time": 138042.89 + }, + "mutated-limit": { + "pid": 57732, + "time": 138042.359, + "flags": 0 + }, + "owner-ready": { + "pid": 57732, + "time": 138042.359, + "target": "stream", + "phase": "released", + "mutation": "no-kill-on-close" + }, + "workload-ready": { + "pid": 57156, + "time": 138042.906, + "leaf_pid": 35772 + }, + "workload-start": { + "pid": 57156, + "time": 138042.718 + } + } + } + ], + "completed_utc": "2026-10-25T06:23:55.560159+00:00" +} diff --git a/docs/research/windows-owner-death-audit/evidence/windows-optimized-standalone.json b/docs/research/windows-owner-death-audit/evidence/windows-optimized-standalone.json new file mode 100644 index 0000000000..c3514810f5 --- /dev/null +++ b/docs/research/windows-owner-death-audit/evidence/windows-optimized-standalone.json @@ -0,0 +1,969 @@ +{ + "schema": 1, + "status": "CONFIRMED_WINDOWS_OWNER_DEATH_BOUNDARIES", + "started_utc": "2026-10-25T06:25:58.692866+00:00", + "platform": "Windows-11-10.0.26300-SP0", + "python": "3.12.0 (tags/v3.12.0:0fb18b0, Oct 2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)]", + "optimized": 1, + "probe_sha256": "afcc16f221ba09d9cfa87d4995f9a807c0f1de786cca2daf0d07a70dc39499a7", + "source_sha256": { + "research_package.py": "84a45190a6a96c20993a0d4a1045fbf24de660d2e6fef8f8a447bb3d1aa78db6", + "owned_pipe.py": "01f597c8ecb955b2ef7faa8f0650e67c0eca5c0d5f6a817a181c536bef9c37aa" + }, + "windows_observations_executed": true, + "cases": [ + { + "target": "shared", + "phase": "before_event_open", + "mutation": "none", + "workload_before_release": false, + "observed_pids": { + "owner": 53308, + "gate": 41072 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138168.281, + "event_open_after_owner_death": false, + "alive_after_owner_death": { + "owner": false, + "gate": false + }, + "observation_seconds_after_owner_death": 0.031000000017229468, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "Traceback (most recent call last):\n File \"C:\\Users\\mihir\\AppData\\Local\\Temp\\owner-death-standalone-mggaikep\\audit\\probe.py\", line 425, in \n main()\n File \"C:\\Users\\mihir\\AppData\\Local\\Temp\\owner-death-standalone-mggaikep\\audit\\probe.py\", line 391, in main\n gate_worker(args)\n File \"C:\\Users\\mihir\\AppData\\Local\\Temp\\owner-death-standalone-mggaikep\\audit\\probe.py\", line 142, in gate_worker\n exec(compile(code, \"\", \"exec\"), {\"__name__\": \"__main__\"})\n File \"\", line 16, in \nFileNotFoundError: [WinError 2] The system cannot find the file specified.\n", + "events": { + "checkpoint": { + "pid": 53308, + "time": 138168.046, + "phase": "before_event_open" + }, + "gate-before-open": { + "pid": 41072, + "time": 138168.25 + }, + "gate-open": { + "pid": 41072, + "time": 138168.281, + "succeeded": false + }, + "gate-process": { + "pid": 53308, + "time": 138168.046, + "process_pid": 41072, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "owner-ready": { + "pid": 53308, + "time": 138168.015, + "target": "shared", + "phase": "before_event_open", + "mutation": "none" + } + } + }, + { + "target": "shared", + "phase": "before_assign", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138169.109, + "workload_before_release": false, + "observed_pids": { + "owner": 34788, + "gate": 38100 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138169.156, + "alive_after_owner_death": { + "owner": false, + "gate": true + }, + "observation_seconds_after_owner_death": 0.5, + "gate_exit_seconds_from_native_wait": 30.046999999991385, + "gate_exit_code": 1, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "Traceback (most recent call last):\n File \"C:\\Users\\mihir\\AppData\\Local\\Temp\\owner-death-standalone-mggaikep\\audit\\probe.py\", line 425, in \n main()\n File \"C:\\Users\\mihir\\AppData\\Local\\Temp\\owner-death-standalone-mggaikep\\audit\\probe.py\", line 391, in main\n gate_worker(args)\n File \"C:\\Users\\mihir\\AppData\\Local\\Temp\\owner-death-standalone-mggaikep\\audit\\probe.py\", line 142, in gate_worker\n exec(compile(code, \"\", \"exec\"), {\"__name__\": \"__main__\"})\n File \"\", line 19, in \nRuntimeError: replay ownership gate was not released\n", + "events": { + "checkpoint": { + "pid": 34788, + "time": 138168.921, + "phase": "before_assign" + }, + "gate-open": { + "pid": 38100, + "time": 138169.109, + "succeeded": true + }, + "gate-process": { + "pid": 34788, + "time": 138168.921, + "process_pid": 38100, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-wait": { + "pid": 38100, + "time": 138169.109, + "timeout_ms": 30000 + }, + "owner-ready": { + "pid": 34788, + "time": 138168.906, + "target": "shared", + "phase": "before_assign", + "mutation": "none" + } + } + }, + { + "target": "shared", + "phase": "assigned", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138200.015, + "workload_before_release": false, + "observed_pids": { + "owner": 61184, + "gate": 46396 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138200.046, + "alive_after_owner_death": { + "owner": false, + "gate": false + }, + "observation_seconds_after_owner_death": 0.0, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 61184, + "time": 138199.828, + "succeeded": true + }, + "checkpoint": { + "pid": 61184, + "time": 138199.828, + "phase": "assigned" + }, + "gate-open": { + "pid": 46396, + "time": 138200.015, + "succeeded": true + }, + "gate-process": { + "pid": 61184, + "time": 138199.828, + "process_pid": 46396, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-wait": { + "pid": 46396, + "time": 138200.015, + "timeout_ms": 30000 + }, + "owner-ready": { + "pid": 61184, + "time": 138199.812, + "target": "shared", + "phase": "assigned", + "mutation": "none" + } + } + }, + { + "target": "shared", + "phase": "released", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138200.812, + "workload_before_release": false, + "observed_pids": { + "owner": 38896, + "gate": 52796, + "workload": 32000, + "leaf": 41872 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138201.312, + "alive_after_owner_death": { + "owner": false, + "gate": false, + "workload": false, + "leaf": false + }, + "observation_seconds_after_owner_death": 0.0, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 38896, + "time": 138200.609, + "succeeded": true + }, + "checkpoint": { + "pid": 38896, + "time": 138200.609, + "phase": "released" + }, + "gate-open": { + "pid": 52796, + "time": 138200.812, + "succeeded": true + }, + "gate-process": { + "pid": 38896, + "time": 138200.609, + "process_pid": 52796, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 38896, + "time": 138200.609, + "succeeded": true + }, + "gate-wait": { + "pid": 52796, + "time": 138200.812, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 41872, + "time": 138201.281 + }, + "owner-ready": { + "pid": 38896, + "time": 138200.593, + "target": "shared", + "phase": "released", + "mutation": "none" + }, + "workload-ready": { + "pid": 32000, + "time": 138201.296, + "leaf_pid": 41872 + }, + "workload-start": { + "pid": 32000, + "time": 138201.031 + } + } + }, + { + "target": "shared", + "phase": "resume", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138202.125, + "workload_before_release": false, + "observed_pids": { + "owner": 7752, + "gate": 4164, + "workload": 34360, + "leaf": 46400 + }, + "joined_return": 0, + "observation_boundary": "joined_owner_return", + "alive_after_owner_death": { + "owner": false, + "gate": false, + "workload": false, + "leaf": false + }, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 7752, + "time": 138202.14, + "succeeded": true + }, + "checkpoint": { + "pid": 7752, + "time": 138201.953, + "phase": "resume" + }, + "gate-open": { + "pid": 4164, + "time": 138202.125, + "succeeded": true + }, + "gate-process": { + "pid": 7752, + "time": 138201.937, + "process_pid": 4164, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 7752, + "time": 138202.156, + "succeeded": true + }, + "gate-wait": { + "pid": 4164, + "time": 138202.125, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 46400, + "time": 138202.531 + }, + "owner-finished": { + "pid": 7752, + "time": 138202.609, + "returncode": 0 + }, + "owner-ready": { + "pid": 7752, + "time": 138201.921, + "target": "shared", + "phase": "resume", + "mutation": "none" + }, + "workload-ready": { + "pid": 34360, + "time": 138202.562, + "leaf_pid": 46400 + }, + "workload-start": { + "pid": 34360, + "time": 138202.343 + } + } + }, + { + "target": "shared", + "phase": "before_assign", + "mutation": "bypass-gate", + "workload_before_release": true, + "observed_pids": { + "owner": 61404, + "gate": 35488, + "workload": 51004, + "leaf": 35636 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138204.14, + "alive_after_owner_death": { + "owner": false, + "gate": true, + "workload": true, + "leaf": true + }, + "observation_seconds_after_owner_death": 0.5, + "rejected_rule": "workload_crossed_unreleased_gate", + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "checkpoint": { + "pid": 61404, + "time": 138203.328, + "phase": "before_assign" + }, + "gate-open": { + "pid": 35488, + "time": 138203.64, + "succeeded": true + }, + "gate-process": { + "pid": 61404, + "time": 138203.328, + "process_pid": 35488, + "gate_sha256": "7ac91dc15d521c87b26d2c69d0e338681744455e2cfe9bcff30dae8e3aa27a44" + }, + "leaf-ready": { + "pid": 35636, + "time": 138204.093 + }, + "owner-ready": { + "pid": 61404, + "time": 138203.312, + "target": "shared", + "phase": "before_assign", + "mutation": "bypass-gate" + }, + "workload-ready": { + "pid": 51004, + "time": 138204.109, + "leaf_pid": 35636 + }, + "workload-start": { + "pid": 51004, + "time": 138203.875 + } + } + }, + { + "target": "shared", + "phase": "released", + "mutation": "no-kill-on-close", + "native_wait_ms": 30000, + "gate_wait_started": 138205.453, + "workload_before_release": false, + "observed_pids": { + "owner": 58604, + "gate": 57828, + "workload": 43880, + "leaf": 38792 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138205.906, + "alive_after_owner_death": { + "owner": false, + "gate": true, + "workload": true, + "leaf": true + }, + "observation_seconds_after_owner_death": 0.5, + "rejected_rule": "assigned_process_survived_owner_death", + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 58604, + "time": 138205.25, + "succeeded": true + }, + "checkpoint": { + "pid": 58604, + "time": 138205.25, + "phase": "released" + }, + "gate-open": { + "pid": 57828, + "time": 138205.453, + "succeeded": true + }, + "gate-process": { + "pid": 58604, + "time": 138205.25, + "process_pid": 57828, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 58604, + "time": 138205.25, + "succeeded": true + }, + "gate-wait": { + "pid": 57828, + "time": 138205.453, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 38792, + "time": 138205.859 + }, + "mutated-limit": { + "pid": 58604, + "time": 138205.234, + "flags": 0 + }, + "owner-ready": { + "pid": 58604, + "time": 138205.218, + "target": "shared", + "phase": "released", + "mutation": "no-kill-on-close" + }, + "workload-ready": { + "pid": 43880, + "time": 138205.875, + "leaf_pid": 38792 + }, + "workload-start": { + "pid": 43880, + "time": 138205.656 + } + } + }, + { + "target": "stream", + "phase": "before_event_open", + "mutation": "none", + "workload_before_release": false, + "observed_pids": { + "owner": 59092, + "gate": 49616 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138207.375, + "event_open_after_owner_death": false, + "alive_after_owner_death": { + "owner": false, + "gate": false + }, + "observation_seconds_after_owner_death": 0.030999999988125637, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "checkpoint": { + "pid": 59092, + "time": 138207.156, + "phase": "before_event_open" + }, + "gate-before-open": { + "pid": 49616, + "time": 138207.359 + }, + "gate-open": { + "pid": 49616, + "time": 138207.375, + "succeeded": false + }, + "gate-process": { + "pid": 59092, + "time": 138207.156, + "process_pid": 49616, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "owner-ready": { + "pid": 59092, + "time": 138207.14, + "target": "stream", + "phase": "before_event_open", + "mutation": "none" + } + } + }, + { + "target": "stream", + "phase": "before_assign", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138208.312, + "workload_before_release": false, + "observed_pids": { + "owner": 59672, + "gate": 32692 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138208.343, + "alive_after_owner_death": { + "owner": false, + "gate": true + }, + "observation_seconds_after_owner_death": 0.5, + "gate_exit_seconds_from_native_wait": 30.046999999991385, + "gate_exit_code": 1, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "checkpoint": { + "pid": 59672, + "time": 138208.109, + "phase": "before_assign" + }, + "gate-open": { + "pid": 32692, + "time": 138208.312, + "succeeded": true + }, + "gate-process": { + "pid": 59672, + "time": 138208.109, + "process_pid": 32692, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-wait": { + "pid": 32692, + "time": 138208.312, + "timeout_ms": 30000 + }, + "owner-ready": { + "pid": 59672, + "time": 138208.093, + "target": "stream", + "phase": "before_assign", + "mutation": "none" + } + } + }, + { + "target": "stream", + "phase": "assigned", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138239.437, + "workload_before_release": false, + "observed_pids": { + "owner": 43956, + "gate": 60448 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138239.453, + "alive_after_owner_death": { + "owner": false, + "gate": false + }, + "observation_seconds_after_owner_death": 0.0, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 43956, + "time": 138239.187, + "succeeded": true + }, + "checkpoint": { + "pid": 43956, + "time": 138239.187, + "phase": "assigned" + }, + "gate-open": { + "pid": 60448, + "time": 138239.437, + "succeeded": true + }, + "gate-process": { + "pid": 43956, + "time": 138239.171, + "process_pid": 60448, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-wait": { + "pid": 60448, + "time": 138239.437, + "timeout_ms": 30000 + }, + "owner-ready": { + "pid": 43956, + "time": 138239.156, + "target": "stream", + "phase": "assigned", + "mutation": "none" + } + } + }, + { + "target": "stream", + "phase": "released", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138240.343, + "workload_before_release": false, + "observed_pids": { + "owner": 46724, + "gate": 19272, + "workload": 39184, + "leaf": 61336 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138240.937, + "alive_after_owner_death": { + "owner": false, + "gate": false, + "workload": false, + "leaf": false + }, + "observation_seconds_after_owner_death": 0.0, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 46724, + "time": 138240.14, + "succeeded": true + }, + "checkpoint": { + "pid": 46724, + "time": 138240.14, + "phase": "released" + }, + "gate-open": { + "pid": 19272, + "time": 138240.328, + "succeeded": true + }, + "gate-process": { + "pid": 46724, + "time": 138240.14, + "process_pid": 19272, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 46724, + "time": 138240.14, + "succeeded": true + }, + "gate-wait": { + "pid": 19272, + "time": 138240.343, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 61336, + "time": 138240.89 + }, + "owner-ready": { + "pid": 46724, + "time": 138240.109, + "target": "stream", + "phase": "released", + "mutation": "none" + }, + "workload-ready": { + "pid": 39184, + "time": 138240.921, + "leaf_pid": 61336 + }, + "workload-start": { + "pid": 39184, + "time": 138240.609 + } + } + }, + { + "target": "stream", + "phase": "resume", + "mutation": "none", + "native_wait_ms": 30000, + "gate_wait_started": 138241.89, + "workload_before_release": false, + "observed_pids": { + "owner": 33472, + "gate": 54428, + "workload": 60080, + "leaf": 30976 + }, + "joined_return": 0, + "observation_boundary": "joined_owner_return", + "alive_after_owner_death": { + "owner": false, + "gate": false, + "workload": false, + "leaf": false + }, + "rejected_rule": null, + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 33472, + "time": 138241.906, + "succeeded": true + }, + "checkpoint": { + "pid": 33472, + "time": 138241.687, + "phase": "resume" + }, + "gate-open": { + "pid": 54428, + "time": 138241.875, + "succeeded": true + }, + "gate-process": { + "pid": 33472, + "time": 138241.671, + "process_pid": 54428, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 33472, + "time": 138241.906, + "succeeded": true + }, + "gate-wait": { + "pid": 54428, + "time": 138241.89, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 30976, + "time": 138242.328 + }, + "owner-finished": { + "pid": 33472, + "time": 138242.421, + "returncode": 0 + }, + "owner-ready": { + "pid": 33472, + "time": 138241.656, + "target": "stream", + "phase": "resume", + "mutation": "none" + }, + "workload-ready": { + "pid": 60080, + "time": 138242.359, + "leaf_pid": 30976 + }, + "workload-start": { + "pid": 60080, + "time": 138242.109 + } + } + }, + { + "target": "stream", + "phase": "before_assign", + "mutation": "bypass-gate", + "workload_before_release": true, + "observed_pids": { + "owner": 31412, + "gate": 57772, + "workload": 44620, + "leaf": 57868 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138244.125, + "alive_after_owner_death": { + "owner": false, + "gate": true, + "workload": true, + "leaf": true + }, + "observation_seconds_after_owner_death": 0.5, + "rejected_rule": "workload_crossed_unreleased_gate", + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "checkpoint": { + "pid": 31412, + "time": 138243.406, + "phase": "before_assign" + }, + "gate-open": { + "pid": 57772, + "time": 138243.64, + "succeeded": true + }, + "gate-process": { + "pid": 31412, + "time": 138243.39, + "process_pid": 57772, + "gate_sha256": "7ac91dc15d521c87b26d2c69d0e338681744455e2cfe9bcff30dae8e3aa27a44" + }, + "leaf-ready": { + "pid": 57868, + "time": 138244.062 + }, + "owner-ready": { + "pid": 31412, + "time": 138243.375, + "target": "stream", + "phase": "before_assign", + "mutation": "bypass-gate" + }, + "workload-ready": { + "pid": 44620, + "time": 138244.078, + "leaf_pid": 57868 + }, + "workload-start": { + "pid": 44620, + "time": 138243.875 + } + } + }, + { + "target": "stream", + "phase": "released", + "mutation": "no-kill-on-close", + "native_wait_ms": 30000, + "gate_wait_started": 138245.515, + "workload_before_release": false, + "observed_pids": { + "owner": 61408, + "gate": 38660, + "workload": 23292, + "leaf": 22200 + }, + "observation_boundary": "forced_owner_termination", + "owner_terminated_at": 138246.093, + "alive_after_owner_death": { + "owner": false, + "gate": true, + "workload": true, + "leaf": true + }, + "observation_seconds_after_owner_death": 0.5, + "rejected_rule": "assigned_process_survived_owner_death", + "observed_before_outer_cleanup": true, + "all_stopped_after_outer_cleanup": true, + "owner_output": "", + "events": { + "assignment": { + "pid": 61408, + "time": 138245.312, + "succeeded": true + }, + "checkpoint": { + "pid": 61408, + "time": 138245.312, + "phase": "released" + }, + "gate-open": { + "pid": 38660, + "time": 138245.515, + "succeeded": true + }, + "gate-process": { + "pid": 61408, + "time": 138245.312, + "process_pid": 38660, + "gate_sha256": "8d158579544d53a3adcfa30210ce1693831653df5b8fbad0767ad0c5bf9085db" + }, + "gate-release": { + "pid": 61408, + "time": 138245.312, + "succeeded": true + }, + "gate-wait": { + "pid": 38660, + "time": 138245.515, + "timeout_ms": 30000 + }, + "leaf-ready": { + "pid": 22200, + "time": 138246.046 + }, + "mutated-limit": { + "pid": 61408, + "time": 138245.296, + "flags": 0 + }, + "owner-ready": { + "pid": 61408, + "time": 138245.296, + "target": "stream", + "phase": "released", + "mutation": "no-kill-on-close" + }, + "workload-ready": { + "pid": 23292, + "time": 138246.078, + "leaf_pid": 22200 + }, + "workload-start": { + "pid": 23292, + "time": 138245.812 + } + } + } + ], + "completed_utc": "2026-10-25T06:27:18.584357+00:00" +} diff --git a/docs/research/windows-owner-death-audit/probe.py b/docs/research/windows-owner-death-audit/probe.py new file mode 100644 index 0000000000..14cfec4d4e --- /dev/null +++ b/docs/research/windows-owner-death-audit/probe.py @@ -0,0 +1,425 @@ +#!/usr/bin/env python3 +"""Observe abrupt owner death at pinned Windows gated-launch boundaries. + +Mihir Srivastava, with substantial OpenAI Codex assistance. Apache-2.0. +Credits and the distinction between source execution and instrumentation are +in README.md. This audit does not repair or adopt either launcher. +""" +import argparse +import base64 +import ctypes +from ctypes import wintypes +from datetime import datetime, timezone +import hashlib +import json +import os +from pathlib import Path +import platform +import subprocess +import sys +import tempfile +import time +import types + +HERE = Path(__file__).resolve().parent +PINS = { + "research_package.py": "84a45190a6a96c20993a0d4a1045fbf24de660d2e6fef8f8a447bb3d1aa78db6", + "owned_pipe.py": "01f597c8ecb955b2ef7faa8f0650e67c0eca5c0d5f6a817a181c536bef9c37aa", +} + + +def require(ok, message): + if not ok: + raise RuntimeError(message) + + +def load_sources(): + modules = [] + for name, expected in PINS.items(): + path = HERE / "sources" / name + raw = path.read_bytes() + require(hashlib.sha256(raw).hexdigest() == expected, "source drift: " + name) + module = types.ModuleType(name[:-3]) + module.__file__ = str(path) + exec(compile(raw, str(path), "exec"), module.__dict__) + modules.append(module) + return modules + + +def publish(directory, name, **values): + path = directory / (name + ".json") + temporary = path.with_suffix(".tmp") + temporary.write_text(json.dumps({"pid": os.getpid(), "time": time.monotonic(), + **values}), encoding="utf-8") + os.replace(temporary, path) + + +def read_event(directory, name, timeout=15): + path = directory / (name + ".json") + deadline = time.monotonic() + timeout + while not path.exists(): + require(time.monotonic() < deadline, "readiness timeout: " + name) + time.sleep(0.01) + return json.loads(path.read_text(encoding="utf-8")) + + +def wait_release(directory, name): + deadline = time.monotonic() + 65 + while not (directory / name).exists(): + require(time.monotonic() < deadline, "fixture release timeout: " + name) + time.sleep(0.01) + + +def python_argv(): + return [sys.executable, "-I", "-S", "-B", *(["-O"] if sys.flags.optimize else []), + str(Path(__file__).resolve())] + + +class FunctionProxy: + """Forward ctypes prototypes as well as calls; never truncate native handles.""" + def __init__(self, function, before=None, after=None): + object.__setattr__(self, "function", function) + object.__setattr__(self, "before", before) + object.__setattr__(self, "after", after) + + def __getattr__(self, name): + return getattr(self.function, name) + + def __setattr__(self, name, value): + setattr(self.function, name, value) + + def __call__(self, *args): + if self.before: + self.before(args) + result = self.function(*args) + # A marker write can itself call Windows APIs. Preserve the native + # thread-local error for the frozen caller, including failed OpenEvent. + error = ctypes.get_last_error() + try: + if self.after: + self.after(result, args) + finally: + ctypes.set_last_error(error) + return result + + +def instrument_kernel(hooks): + original = ctypes.WinDLL + + class Proxy: + def __init__(self, api): + self.api, self.cache = api, {} + + def __getattr__(self, name): + if name not in self.cache: + function = getattr(self.api, name) + self.cache[name] = FunctionProxy(function, *hooks.get(name, (None, None))) + return self.cache[name] + + ctypes.WinDLL = lambda *args, **kwargs: Proxy(original(*args, **kwargs)) + + +def gate_worker(args): + directory = Path(args.directory) + + def before_open(_): + if args.phase == "before_event_open": + publish(directory, "gate-before-open") + wait_release(directory, "gate-continue") + + def after_open(result, _): + publish(directory, "gate-open", succeeded=bool(result)) + + def before_wait(call): + publish(directory, "gate-wait", timeout_ms=call[1]) + + instrument_kernel({"OpenEventW": (before_open, after_open), + "WaitForSingleObject": (before_wait, None)}) + code = base64.b64decode(args.gate_code, validate=True) + sys.argv = ["-c", args.event, args.payload] + # The original gate bytes execute after installing observation hooks. + # The bypass control alone executes the explicitly recorded one-line mutant. + exec(compile(code, "", "exec"), {"__name__": "__main__"}) + + +def owner_worker(args): + directory = Path(args.directory) + shared, stream = load_sources() + gate = shared._WINDOWS_GATE + if args.mutation == "bypass-gate": + needle = "if api.WaitForSingleObject(event, 30000) != 0:" + require(gate.count(needle) == 1, "gate mutation no longer applies") + gate = gate.replace(needle, "if False: # deliberate gate-bypass control") + shared._WINDOWS_GATE = gate + real_popen = subprocess.Popen + + def interpose(argv, **kwargs): + index = argv.index("-c") + require(argv[index + 1] == gate, "unexpected gated launch") + wrapped = [*python_argv(), "--internal", "gate", "--directory", str(directory), + "--phase", args.phase, "--gate-code", + base64.b64encode(gate.encode("utf-8")).decode("ascii"), + "--event", argv[index + 2], "--payload", argv[index + 3]] + process = real_popen(wrapped, **kwargs) + publish(directory, "gate-process", process_pid=process.pid, + gate_sha256=hashlib.sha256(gate.encode("utf-8")).hexdigest()) + return process + + def before_assign(_): + if args.phase in ("before_event_open", "before_assign", "resume"): + publish(directory, "checkpoint", phase=args.phase) + wait_release(directory, "owner-continue") + + def after_assign(result, _): + require(result, "native assignment failed in fixture") + publish(directory, "assignment", succeeded=True) + if args.phase == "assigned": + publish(directory, "checkpoint", phase=args.phase) + wait_release(directory, "owner-continue") + + def after_release(result, _): + require(result, "native gate release failed in fixture") + publish(directory, "gate-release", succeeded=True) + if args.phase == "released": + publish(directory, "checkpoint", phase=args.phase) + wait_release(directory, "owner-continue") + + def before_limits(call): + if args.mutation == "no-kill-on-close": + limits = call[2]._obj + require(limits.Basic.Flags == 0x2000, "limit mutation no longer applies") + limits.Basic.Flags = 0 + publish(directory, "mutated-limit", flags=0) + + instrument_kernel({"AssignProcessToJobObject": (before_assign, after_assign), + "SetEvent": (None, after_release), + "SetInformationJobObject": (before_limits, None)}) + shared.subprocess = types.SimpleNamespace(Popen=interpose, + TimeoutExpired=subprocess.TimeoutExpired) + stream.REAL_POPEN = interpose + publish(directory, "owner-ready", target=args.target, phase=args.phase, + mutation=args.mutation) + command = [*python_argv(), "--internal", "workload", "--directory", str(directory)] + if args.target == "shared": + code = shared.run_windows_command(command, directory, 60, os.environ.copy()) + else: + with stream.OwnedPopen(command, gate, cwd=directory, + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) as process: + code = process.wait(timeout=60) + require(code == 0, "joined workload failed") + publish(directory, "owner-finished", returncode=code) + + +def workload_worker(args): + directory = Path(args.directory) + publish(directory, "workload-start") + with subprocess.Popen([*python_argv(), "--internal", "leaf", "--directory", str(directory)], + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) as leaf: + read_event(directory, "leaf-ready") + publish(directory, "workload-ready", leaf_pid=leaf.pid) + wait_release(directory, "finish") + require(leaf.wait(timeout=10) == 0, "joined leaf failed") + + +class ProcessHandle: + def __init__(self, pid): + self.api = ctypes.WinDLL("kernel32", use_last_error=True) + for name, result, arguments in ( + ("OpenProcess", wintypes.HANDLE, [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]), + ("WaitForSingleObject", wintypes.DWORD, [wintypes.HANDLE, wintypes.DWORD]), + ("TerminateProcess", wintypes.BOOL, [wintypes.HANDLE, wintypes.UINT]), + ("GetExitCodeProcess", wintypes.BOOL, [wintypes.HANDLE, ctypes.POINTER(wintypes.DWORD)]), + ("CloseHandle", wintypes.BOOL, [wintypes.HANDLE]), + ): + function = getattr(self.api, name) + function.restype, function.argtypes = result, arguments + self.handle = self.api.OpenProcess(0x00100001 | 0x1000, False, pid) + require(self.handle, "could not retain live fixture process " + str(pid)) + self.pid = pid + require(self.alive(), "fixture exited before its identity was retained") + + def alive(self): + status = self.api.WaitForSingleObject(self.handle, 0) + require(status in (0, 258), "native process wait failed") + return status == 258 + + def stopped(self, timeout=5): + status = self.api.WaitForSingleObject(self.handle, round(timeout * 1000)) + require(status in (0, 258), "native bounded process wait failed") + return status == 0 + + def terminate(self): + require(self.api.TerminateProcess(self.handle, 91), "fixture owner termination failed") + require(self.stopped(), "fixture owner did not terminate") + + def exit_code(self): + code = wintypes.DWORD() + require(self.api.GetExitCodeProcess(self.handle, ctypes.byref(code)), "exit-code query failed") + return code.value + + def close(self): + require(self.api.CloseHandle(self.handle), "process handle close failed") + + +def judge(record): + """Actual source properties; the same predicates must reject both mutants.""" + if record["phase"] != "released" and record["workload_before_release"]: + return "workload_crossed_unreleased_gate" + if record["phase"] in ("assigned", "released") and any(record["alive_after_owner_death"].values()): + return "assigned_process_survived_owner_death" + return None + + +def run_case(target, phase, mutation="none"): + shared, stream = load_sources() + record = {"target": target, "phase": phase, "mutation": mutation} + handles = {} + with tempfile.TemporaryDirectory(prefix="owner-death-") as temporary: + directory = Path(temporary) + command = [*python_argv(), "--internal", "owner", "--directory", temporary, + "--target", target, "--phase", phase, "--mutation", mutation] + with (directory / "owner-output.txt").open("wb") as output: + try: + # This separate outer job is only fixture containment. It stays + # open throughout every observation, including expected leaks. + with stream.OwnedPopen(command, shared._WINDOWS_GATE, cwd=directory, + stdin=subprocess.DEVNULL, stdout=output, stderr=output): + owner = read_event(directory, "owner-ready") + handles["owner"] = ProcessHandle(owner["pid"]) + read_event(directory, "checkpoint") + gate = read_event(directory, "gate-process") + handles["gate"] = ProcessHandle(gate["process_pid"]) + if phase == "before_event_open": + read_event(directory, "gate-before-open") + require(not (directory / "gate-open.json").exists(), "gate opened before checkpoint") + elif mutation != "bypass-gate": + event = read_event(directory, "gate-wait") + require(event["timeout_ms"] == 30000, "original gate timeout changed") + record["native_wait_ms"] = event["timeout_ms"] + record["gate_wait_started"] = event["time"] + record["workload_before_release"] = (directory / "workload-start.json").exists() + if phase == "resume": + require(not record["workload_before_release"], "positive gate control started early") + (directory / "owner-continue").touch() + if phase in ("released", "resume") or mutation == "bypass-gate": + workload = read_event(directory, "workload-ready") + leaf = read_event(directory, "leaf-ready") + handles["workload"] = ProcessHandle(workload["pid"]) + handles["leaf"] = ProcessHandle(leaf["pid"]) + require(workload["leaf_pid"] == leaf["pid"], "leaf identity mismatch") + record["workload_before_release"] = phase != "released" and phase != "resume" + require(all(h.alive() for h in handles.values()), "barrier process exited early") + record["observed_pids"] = {name: h.pid for name, h in handles.items()} + if phase == "resume": + (directory / "finish").touch() + require(handles["owner"].stopped(15), "joined owner did not return") + require(handles["owner"].exit_code() == 0, "joined owner failed") + record["joined_return"] = 0 + record["observation_boundary"] = "joined_owner_return" + record["alive_after_owner_death"] = {name: h.alive() for name, h in handles.items()} + require(not any(record["alive_after_owner_death"].values()), "joined processes survive") + else: + handles["owner"].terminate() + record["observation_boundary"] = "forced_owner_termination" + record["owner_terminated_at"] = time.monotonic() + if phase == "before_event_open": + (directory / "gate-continue").touch() + require(handles["gate"].stopped(), "gate survived failed event open") + event = read_event(directory, "gate-open") + require(not event["succeeded"], "event unexpectedly survived all owner handles") + record["event_open_after_owner_death"] = False + elif mutation == "none" and phase in ("assigned", "released"): + require(all(h.stopped() for name, h in handles.items() if name != "owner"), + "owned process did not stop") + else: + time.sleep(0.5) + record["alive_after_owner_death"] = {name: h.alive() for name, h in handles.items()} + record["observation_seconds_after_owner_death"] = time.monotonic() - record["owner_terminated_at"] + if phase == "before_assign" and mutation == "none": + require(record["alive_after_owner_death"]["gate"], "unassigned waiter did not survive") + require(handles["gate"].stopped(35), "unassigned gate did not time out") + elapsed = time.monotonic() - record["gate_wait_started"] + require(29 <= elapsed <= 45, "unexpected original gate-wait duration") + record["gate_exit_seconds_from_native_wait"] = elapsed + record["gate_exit_code"] = handles["gate"].exit_code() + require(record["gate_exit_code"] == 1, "expected gate timeout exception") + if mutation == "none" and phase != "released": + require(not (directory / "workload-start.json").exists(), "unreleased workload started") + rejected = judge(record) + if mutation == "none": + require(rejected is None, "source observation rejected: " + str(rejected)) + else: + expected = ("workload_crossed_unreleased_gate" if mutation == "bypass-gate" + else "assigned_process_survived_owner_death") + require(rejected == expected, "mutation escaped its actual observation predicate") + record["rejected_rule"] = rejected + record["observed_before_outer_cleanup"] = True + record["all_stopped_after_outer_cleanup"] = all(h.stopped() for h in handles.values()) + require(record["all_stopped_after_outer_cleanup"], "fixture teardown incomplete") + finally: + for handle in handles.values(): + handle.close() + record["owner_output"] = (directory / "owner-output.txt").read_text(encoding="utf-8", errors="replace") + record["events"] = {p.stem: json.loads(p.read_text(encoding="utf-8")) + for p in sorted(directory.glob("*.json"))} + if phase in ("released", "resume") and mutation == "none": + require(record["events"]["assignment"]["time"] + < record["events"]["workload-start"]["time"], + "workload started before successful assignment") + if phase == "before_assign" and mutation == "none" and target == "shared": + require("replay ownership gate was not released" in record["owner_output"], + "missing actual gate-timeout diagnostic") + return record + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--output", type=Path) + parser.add_argument("--target", choices=("shared", "stream", "all"), default="all") + parser.add_argument("--phase", default="all") + parser.add_argument("--mutation", default="none") + parser.add_argument("--internal", choices=("owner", "gate", "workload", "leaf")) + parser.add_argument("--directory") + parser.add_argument("--gate-code") + parser.add_argument("--event") + parser.add_argument("--payload") + args = parser.parse_args() + if args.internal: + if args.internal == "gate": + gate_worker(args) + elif args.internal == "owner": + owner_worker(args) + elif args.internal == "workload": + workload_worker(args) + else: + publish(Path(args.directory), "leaf-ready") + wait_release(Path(args.directory), "finish") + return + require(os.name == "nt", "native Windows required; no Windows coverage was executed") + load_sources() # Authenticate every executable source before starting fixtures. + targets = ("shared", "stream") if args.target == "all" else (args.target,) + cases = [(phase, "none") for phase in ("before_event_open", "before_assign", "assigned", "released", "resume")] + cases += [("before_assign", "bypass-gate"), ("released", "no-kill-on-close")] + if args.phase != "all": + cases = [(args.phase, args.mutation)] + report = {"schema": 1, "status": "RUNNING", "started_utc": datetime.now(timezone.utc).isoformat(), + "platform": platform.platform(), "python": sys.version, "optimized": sys.flags.optimize, + "probe_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), + "source_sha256": PINS, "windows_observations_executed": True, "cases": []} + for target in targets: + for phase, mutation in cases: + result = run_case(target, phase, mutation) + report["cases"].append(result) + print("PASS", target, phase, mutation, flush=True) + report["status"] = "CONFIRMED_WINDOWS_OWNER_DEATH_BOUNDARIES" + report["completed_utc"] = datetime.now(timezone.utc).isoformat() + if args.output: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") + print(report["status"], "cases=" + str(len(report["cases"]))) + + +if __name__ == "__main__": + main() diff --git a/docs/research/windows-owner-death-audit/sources/NOTICE-stream b/docs/research/windows-owner-death-audit/sources/NOTICE-stream new file mode 100644 index 0000000000..5bcd615910 --- /dev/null +++ b/docs/research/windows-owner-death-audit/sources/NOTICE-stream @@ -0,0 +1,17 @@ +Windows streaming Git ownership experiment + +The unmodified baseline/research_package.py is from CrocSwap/integer-mult-bounds +commit 32890bc5de034ab8fc93ac88ead7279a6a416d04. +Copyright 2026 Douglas Colkitt. Prepared with OpenAI Codex assistance. + +The Job Object lifecycle, native structures and isolated event gate in the +adapter derive from ywz65's PR2469 and its retained successors. Native handle +observation follows ywz65's PR2452; the response deadline and POSIX lifetime +controls follow Brian / JoyboyBrian's PR4738 and PR5185. Nick Petalas's PR2752 +and the credited source reader/fallback lineage remain credited. The README +names the Windows, runner, process-tree and source/export predecessors. + +New stream adapter and bounded composition controls prepared for Mihir +Srivastava with substantial OpenAI Codex assistance. Apache-2.0, as in LICENSE. +This is not an official OpenAI release or endorsement. Tooling checks do not +establish a mathematical result or independent human scientific review. diff --git a/docs/research/windows-owner-death-audit/sources/owned_pipe.py b/docs/research/windows-owner-death-audit/sources/owned_pipe.py new file mode 100644 index 0000000000..8150b9a79d --- /dev/null +++ b/docs/research/windows-owner-death-audit/sources/owned_pipe.py @@ -0,0 +1,153 @@ +"""Opt-in Windows stream lifetime for the source-pinned Git-reader experiment. + +The native structures, gate and ownership protocol are adapted from ywz65's +run_windows_command in the frozen research_package.py. Its notice and exact +source are retained beside this module. Streaming interface by Mihir Srivastava +with substantial OpenAI Codex assistance; Apache-2.0. +""" +from contextlib import contextmanager +import ctypes +from ctypes import wintypes +import json +import subprocess +import sys +import threading +import time +import uuid + +REAL_POPEN = subprocess.Popen + + +@contextmanager +def windows_pipe(argv, gate, **kwargs): + """Yield a gated process and an idempotent, thread-safe whole-job stop. + + Only the job handle owner may release the gate. The requested command's + stdin/stdout are inherited through the gate without Python-level copying. + """ + class BasicLimits(ctypes.Structure): + _fields_ = [("ProcessTime", ctypes.c_int64), ("JobTime", ctypes.c_int64), + ("Flags", wintypes.DWORD), ("MinWorkingSet", ctypes.c_size_t), + ("MaxWorkingSet", ctypes.c_size_t), ("ActiveProcessLimit", wintypes.DWORD), + ("Affinity", ctypes.c_size_t), ("Priority", wintypes.DWORD), + ("Scheduling", wintypes.DWORD)] + + class IOCounts(ctypes.Structure): + _fields_ = [(name, ctypes.c_uint64) for name in + ("ReadOps", "WriteOps", "OtherOps", "ReadBytes", "WriteBytes", "OtherBytes")] + + class ExtendedLimits(ctypes.Structure): + _fields_ = [("Basic", BasicLimits), ("IO", IOCounts), + ("ProcessMemory", ctypes.c_size_t), ("JobMemory", ctypes.c_size_t), + ("PeakProcessMemory", ctypes.c_size_t), ("PeakJobMemory", ctypes.c_size_t)] + + class Accounting(ctypes.Structure): + _fields_ = [(name, ctypes.c_int64) for name in + ("UserTime", "KernelTime", "PeriodUserTime", "PeriodKernelTime")] + [ + (name, wintypes.DWORD) for name in + ("PageFaults", "TotalProcesses", "ActiveProcesses", "TerminatedProcesses")] + + api = ctypes.WinDLL("kernel32", use_last_error=True) + + def native(name, result, *arguments): + fn = getattr(api, name) + fn.restype, fn.argtypes = result, arguments + return fn + + handle, dword, boolean = wintypes.HANDLE, wintypes.DWORD, wintypes.BOOL + create_job = native("CreateJobObjectW", handle, ctypes.c_void_p, wintypes.LPCWSTR) + set_info = native("SetInformationJobObject", boolean, handle, ctypes.c_int, ctypes.c_void_p, dword) + assign = native("AssignProcessToJobObject", boolean, handle, handle) + terminate = native("TerminateJobObject", boolean, handle, wintypes.UINT) + query = native("QueryInformationJobObject", boolean, handle, ctypes.c_int, + ctypes.c_void_p, dword, ctypes.c_void_p) + create_event = native("CreateEventW", handle, ctypes.c_void_p, boolean, boolean, wintypes.LPCWSTR) + set_event = native("SetEvent", boolean, handle) + close = native("CloseHandle", boolean, handle) + + def checked(ok): + if not ok: + raise ctypes.WinError(ctypes.get_last_error()) + return ok + + job = checked(create_job(None, None)) + event, process, assigned = None, None, False + stopped = False + lock = threading.Lock() + + def stop(): + nonlocal stopped + with lock: + if stopped: + return + checked(terminate(job, 1)) + deadline = time.monotonic() + 10 + while True: + state = Accounting() + checked(query(job, 1, ctypes.byref(state), ctypes.sizeof(state), None)) + if not state.ActiveProcesses: + break + if time.monotonic() >= deadline: + raise RuntimeError("owned Git process cleanup timed out") + time.sleep(0.01) + stopped = True + + try: + limits = ExtendedLimits() + limits.Basic.Flags = 0x2000 # JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + checked(set_info(job, 9, ctypes.byref(limits), ctypes.sizeof(limits))) + name = "Local\\research-git-stream-" + uuid.uuid4().hex + event = checked(create_event(None, True, False, name)) + if ctypes.get_last_error() == 183: + raise ValueError("Git ownership gate name already exists") + process = REAL_POPEN([sys.executable, "-I", "-S", "-c", gate, name, + json.dumps(argv, ensure_ascii=False)], **kwargs) + checked(assign(job, int(process._handle))) + assigned = True + checked(set_event(event)) + yield process, stop + finally: + try: + if process is not None: + if assigned: + stop() + elif process.poll() is None: + process.kill() + process.wait(timeout=10) + finally: + try: + if process is not None: + # In the actual reader these are already closed. Setup + # failure can reach here before the reader owns any streams. + for stream in (process.stdin, process.stdout, process.stderr): + if stream is not None: + stream.close() + finally: + try: + if event: + checked(close(event)) + finally: + checked(close(job)) + + +class OwnedPopen: + """The Popen subset used by the frozen reader, with job-aware kill/exit. + + This is an experimental reader adapter, not a general subprocess replacement. + The caller must use the context manager, as the actual reader does. + """ + def __init__(self, argv, gate, **kwargs): + self.context = windows_pipe(argv, gate, **kwargs) + self.process, self.stop = self.context.__enter__() + + def __getattr__(self, name): + return getattr(self.process, name) + + def kill(self): + self.stop() + + def __enter__(self): + return self + + def __exit__(self, *exception): + return self.context.__exit__(*exception) diff --git a/docs/research/windows-owner-death-audit/sources/research_package.py b/docs/research/windows-owner-death-audit/sources/research_package.py new file mode 100644 index 0000000000..48fe9feb46 --- /dev/null +++ b/docs/research/windows-owner-death-audit/sources/research_package.py @@ -0,0 +1,1026 @@ +#!/usr/bin/env python3 +"""Validate and replay explicitly registered research packages, never every verify.py. + +Douglas Colkitt, with OpenAI Codex assistance. Apache-2.0. +""" +import argparse +import errno +from contextlib import closing, contextmanager +from itertools import chain +import hashlib +import importlib.metadata +import json +import os +from pathlib import Path, PurePosixPath +import re +import signal +import stat +import subprocess +import sys +import tempfile +import threading + +ROOT = Path(__file__).resolve().parents[1] +GIT_BLOB_RESPONSE_TIMEOUT = 90 + + +def require(ok, message): + if not ok: + raise ValueError(message) + + +def canonical_relative_path(name): + # Git's changed paths use one repository-relative POSIX spelling. Accepting + # aliases here would let a declared input evade the planner's exact match. + require(isinstance(name, str), "canonical relative path must be a string") + path = PurePosixPath(name) + require(bool(path.parts) and not path.is_absolute() and ".." not in path.parts + and "\\" not in name and path.as_posix() == name, + "use a canonical repository-relative POSIX path: " + name) + return path + + +def symlink_free_relative_path(root, name): + """Reject symlink/junction components without requiring a deleted input to exist. + + Git reports changes under the target's path, not a symlink alias declared + by its consumer. Inspect existing components below the repository root; + the checkout must remain stable during planning, validation and replay. + """ + parts = canonical_relative_path(name).parts + current = root + for part in parts: + current = current / part + try: + info = current.lstat() + except (FileNotFoundError, NotADirectoryError): + break + require(not stat.S_ISLNK(info.st_mode), "symlink in repository-relative path: " + name) + # Windows junctions have directory mode, not S_IFLNK. Inspect the same + # lstat result so even a dangling junction is rejected before traversal. + require(getattr(info, "st_reparse_tag", 0) != getattr(stat, "IO_REPARSE_TAG_MOUNT_POINT", None), + "junction in repository-relative path: " + name) + return root / name + + +def relative_file(root, name): + result = symlink_free_relative_path(root, name) + require(result.resolve().is_relative_to(root.resolve()) and result.is_file(), + "missing or external file: " + name) + return result + + +# Adapted from the package-local policy in +# research/t74-2530-source-partner-audit/audit_common.py (Apache-2.0). +# Keep repository configuration and linked-worktree .git discovery intact; +# inherited shell overrides must not choose a different repository or index. +_GIT_REDIRECT_VARS = frozenset(( + "GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_OBJECT_DIRECTORY", + "GIT_ALTERNATE_OBJECT_DIRECTORIES", "GIT_NAMESPACE", "GIT_CEILING_DIRECTORIES", + "GIT_COMMON_DIR", "GIT_CONFIG_PARAMETERS", "GIT_CONFIG_COUNT", +)) +_GIT_CONFIG_PREFIXES = ("GIT_CONFIG_KEY_", "GIT_CONFIG_VALUE_") + + +def git_source_env(): + """Use cwd's repository and stored objects, without changing the parent. + + Git can ignore cwd when inherited selection variables point elsewhere. + Config-injection variables can also redirect worktree/index operations. + This policy is only for Git metadata/source subprocesses, not verifiers. + """ + env = {name: value for name, value in os.environ.items() + if name not in _GIT_REDIRECT_VARS and not name.startswith(_GIT_CONFIG_PREFIXES)} + # Replacement refs can report an original ID while returning other bytes. + env["GIT_NO_REPLACE_OBJECTS"] = "1" + return env + + +def git(root, *args): + return subprocess.run(["git", *args], cwd=root, env=git_source_env(), + capture_output=True, check=True).stdout + + +def tracked_input_paths(root): + """Use HEAD spelling, never case folding, filesystem resolution or the index. + + Staged/untracked files are not replay inputs until committed. NUL framing + preserves Git filenames containing whitespace, including CR and LF. + """ + raw = subprocess.run( + ["git", "ls-tree", "-r", "--name-only", "-z", "HEAD"], cwd=root, env=git_source_env(), + capture_output=True, check=True, timeout=30).stdout + return frozenset(name.decode("utf-8") for name in raw.split(b"\0") if name) + + +def require_tracked_input(name, paths): + canonical_relative_path(name) + require(name in paths, "use an exact Git-tracked input path from HEAD: " + name) + + +def git_source_rows(root, base, names): + """Read one pinned object at a time through one Git process; no persistent cache. + + Each request is consumed before the next is sent. This avoids pipe deadlocks + and retains the old one-blob memory bound, even for large source archives. + Newline paths and non-blob objects retain the legacy Git-show semantics. + The caller must close this generator if source validation fails mid-stream. + """ + with subprocess.Popen(["git", "cat-file", "--batch"], cwd=root, env=git_source_env(), + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as process: + try: + for name in names: + expression = base + ":" + name + if "\n" in expression or "\r" in expression: + yield git(root, "rev-parse", expression).decode().strip(), git(root, "show", expression) + continue + process.stdin.write(expression.encode("utf-8") + b"\n") + process.stdin.flush() + header = process.stdout.readline().rstrip(b"\n").split(b" ") + require(len(header) == 3 and re.fullmatch(rb"[0-9a-f]{40,64}", header[0]) + and header[2].isdigit(), "missing or invalid Git source: " + name) + size = int(header[2]) + payload = process.stdout.read(size) + require(len(payload) == size and process.stdout.read(1) == b"\n", + "truncated Git source: " + name) + if header[1] != b"blob": + payload = git(root, "show", expression) + yield header[0].decode("ascii"), payload + process.stdin.close() + code = process.wait(timeout=90) + require(code == 0, "Git source reader failed: " + process.stderr.read().decode("utf-8", errors="replace")) + finally: + if not process.stdin.closed: + process.stdin.close() + if process.poll() is None: + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + + +class _UnsupportedNulBatch(Exception): + """Git rejected the initial NUL-mode request before yielding any source.""" + + +def _closed_stdin_error(error): + # Windows can report EINVAL instead of EPIPE when a child closes stdin. + # This classifies the pipe error only; the retry still requires exit 129. + return (isinstance(error, BrokenPipeError) + or (os.name == "nt" and error.errno == errno.EINVAL)) + + +def _stop_git_blob_process(process): + """Stop this reader's owned POSIX group even after its leader exits.""" + try: + if os.name == "posix": + # Reap an exited leader first: signaling a group containing only + # its unreaped zombie can report EPERM on macOS. A surviving + # descendant keeps the group alive after the leader is reaped. + process.poll() + os.killpg(process.pid, signal.SIGKILL) + else: + process.kill() + except ProcessLookupError: + pass + + +@contextmanager +def _git_blob_response_deadline(stop): + """Bound one response, excluding time spent by its consumer. + + On POSIX the reader owns a session: ordinary descendants retaining a pipe + are stopped with the leader. Escaped sessions and separate git() calls are + outside this contract; non-POSIX retains the direct-child deadline. + """ + expired = threading.Event() + + def terminate(): + expired.set() + stop() + + timer = threading.Timer(GIT_BLOB_RESPONSE_TIMEOUT, terminate) + timer.start() + try: + yield + finally: + timer.cancel() + timer.join() + if expired.is_set(): + raise subprocess.TimeoutExpired("git cat-file blob response", GIT_BLOB_RESPONSE_TIMEOUT) + + +def _git_blob_batch(root, specs, nul): + argv = ["git", "cat-file", "--batch"] + (["-z"] if nul else []) + # Usage diagnostics from the unsupported-option probe are expected. Avoid + # a second pipe that could block while we consume the framed stdout reply. + with subprocess.Popen(argv, cwd=root, env=git_source_env(), stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, + start_new_session=(os.name == "posix")) as process: + stopped = False + + def stop(): + nonlocal stopped + if not stopped: + _stop_git_blob_process(process) + stopped = True + + try: + for index, spec in enumerate(specs): + require("\0" not in spec, "invalid Git object request") + request = spec + if not nul and ("\n" in spec or "\r" in spec): + # Resolve one literal argument, never split a path into requests. + request = git(root, "rev-parse", "--verify", "--end-of-options", spec).decode("ascii").strip() + require(re.fullmatch(r"[0-9a-f]{40,64}", request), "invalid Git object ID") + with _git_blob_response_deadline(stop): + try: + process.stdin.write(request.encode("utf-8") + (b"\0" if nul else b"\n")) + process.stdin.flush() + except OSError as exc: + if not _closed_stdin_error(exc): + raise + header = b"" + else: + header = process.stdout.readline() + if nul and index == 0 and not header and process.wait(timeout=90) == 129: + raise _UnsupportedNulBatch(spec) + fields = header.rstrip(b"\n").split(b" ") + require(len(fields) == 3 and re.fullmatch(rb"[0-9a-f]{40,64}", fields[0]) + and fields[1] == b"blob" and fields[2].isdigit(), + "missing or non-blob Git source: " + spec) + size = int(fields[2]) + pinned = process.stdout.read(size) + require(len(pinned) == size and process.stdout.read(1) == b"\n", + "incomplete Git blob response: " + spec) + yield fields[0].decode("ascii"), pinned + process.stdin.close() + require(process.wait(timeout=90) == 0, "Git source reader failed") + finally: + # Terminate ordinary descendants before closing buffered pipes. This + # also covers successful leader exit and an early generator close. + if os.name == "posix": + stop() + # Option rejection can close stdin before our first write/flush. + try: + process.stdin.close() + except OSError as exc: + if not _closed_stdin_error(exc): + raise + process.stdout.close() + if process.poll() is None: + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + + +def git_blobs(root, specs): + """Stream exact blobs, retaining the one-process NUL protocol on newer Git. + + Git without -z rejects it with usage status 129. Retry only before any + response, replaying the consumed first request through the line protocol. + The fallback resolves CR/LF paths to object IDs before requesting content. + The caller must close this iterator if validation stops before exhaustion. + """ + specs = iter(specs) + sentinel = object() + first = next(specs, sentinel) + if first is sentinel: + return + with closing(_git_blob_batch(root, chain((first,), specs), nul=True)) as rows: + try: + yield from rows + except _UnsupportedNulBatch as exc: + with closing(_git_blob_batch(root, chain((exc.args[0],), specs), nul=False)) as legacy: + yield from legacy + + +def meaningful(value): + return isinstance(value, str) and bool(value.strip()) and not re.search(r"\bTODO\b", value, re.I) + + +def brief(value): + text = json.dumps(value, ensure_ascii=True, sort_keys=True) + return text if len(text) <= 120 else text[:117] + "..." + + +def require_key(mapping, key, where, shape=None): + """Name the missing key and its expected shape instead of a bare KeyError.""" + require(isinstance(mapping, dict), + where + " must be a JSON object; found " + type(mapping).__name__ + ": " + brief(mapping)) + require(key in mapping, where + "." + key + " is missing" + + ("; expected " + shape if shape else "")) + return mapping[key] + + +def require_text(mapping, key, where, shape="a non-empty string without TODO"): + """Name the key and distinguish missing, wrong-shaped and weak values.""" + require(isinstance(mapping, dict), + where + " must be a JSON object; found " + type(mapping).__name__ + ": " + brief(mapping)) + require(key in mapping, where + "." + key + " is missing; expected " + shape) + value = mapping[key] + require(isinstance(value, str) and meaningful(value), + where + "." + key + " must be " + shape + "; found " + type(value).__name__ + ": " + brief(value)) + + +def card_differences(own, pinned): + """Name every key by which a TASK.json original_card differs from its pin.""" + if not isinstance(own, dict): + return "TASK.json original_card is " + type(own).__name__ + ", expected a JSON object" + details = [] + missing = [key for key in pinned if key not in own] + extra = [key for key in own if key not in pinned] + if missing: + details.append("missing keys: " + ", ".join(missing)) + if extra: + details.append("unexpected keys: " + ", ".join(extra)) + for key in (key for key in pinned if key in own and own[key] != pinned[key]): + details.append(key + " differs (TASK.json: " + brief(own[key]) + "; pinned: " + brief(pinned[key]) + ")") + return "; ".join(details) or "cards compare unequal without a key difference" + + +def source_pin(row, base): + """Resolve one SOURCES.json row to its exact Git object expression. + + Rows without an explicit pin keep the historical base_commit: form. + A file vendored from a pull-request head is pinned exactly with + head:<40-hex sha>:, so its bytes resolve at that commit, not at the + package base. An explicit but unparseable pin fails closed. + """ + name = row["path"] + pin = row.get("pin") + if pin is None: + return base + ":" + name + if pin == "base_commit:" + name: + return base + ":" + name + # Git filenames can contain LF; it is part of the exact path, not a delimiter. + match = re.fullmatch(r"head:([0-9a-f]{40}):(.+)", pin, re.DOTALL) if isinstance(pin, str) else None + if match is not None and match.group(2) == name: + return match.group(1) + ":" + name + raise ValueError("SOURCES.json pin for " + name + " is not a supported pin form: " + brief(pin) + + "; accepted pin forms are base_commit: and head:<40-hex sha>:") + + +def validate(root, package, check_pins=True): + parts = PurePosixPath(package).parts + require(len(parts) == 2 and parts[0] == "research" and re.fullmatch(r"[a-z0-9][a-z0-9-]*", parts[1]), + "package must be research/") + data = {} + for name in ("TASK.json", "SOURCES.json", "VERIFY.json"): + data[name] = json.loads(relative_file(root, package + "/" + name).read_text(encoding="utf-8")) + version = require_key(data[name], "schema_version", name) + require(version == 1, name + ".schema_version must be 1; found " + brief(version)) + task, sources, manifest = (data[n] for n in ("TASK.json", "SOURCES.json", "VERIFY.json")) + scope_status = require_key(task, "scope_status", "TASK.json", "unchanged, refined or changed") + require(scope_status in ("unchanged", "refined", "changed"), + "TASK.json.scope_status must be unchanged, refined or changed; found " + brief(scope_status)) + for field in ("actual_scope", "bottleneck_link", "stop_rule", "evidence", "author", "ai_assistance"): + require_text(task, field, "TASK.json") + if scope_status == "unchanged": + question = require_key(require_key(task, "original_card", "TASK.json", + "the pinned catalogue card object"), + "question", "TASK.json.original_card") + require(task["actual_scope"] == question, + "different wording/scope requires refined or changed plus a reason" + + " (TASK.json.actual_scope " + brief(task["actual_scope"]) + + " differs from TASK.json.original_card.question " + brief(question) + ")") + else: + require_text(task, "scope_change_reason", "TASK.json", + "a non-empty string without TODO explaining the scope refinement/change") + outcome = require_key(task, "outcome", "TASK.json", + "partial, completed, blocked or negative-result") + require(outcome in ("partial", "completed", "blocked", "negative-result"), + "TASK.json.outcome must be partial, completed, blocked or negative-result; found " + brief(outcome)) + questions = task.get("open_questions") + require(isinstance(questions, list), + "TASK.json.open_questions must be a list; found " + type(questions).__name__ + ": " + brief(questions)) + if not questions: + require_text(task, "no_open_questions_reason", "TASK.json", + "a non-empty string without TODO explaining an empty next-question list") + for position, question in enumerate(questions): + for field in ("question", "why_it_matters", "first_step", "blocked_by"): + require_text(question, field, "TASK.json.open_questions[" + str(position) + "]") + readme = relative_file(root, package + "/README.md").read_text(encoding="utf-8") + require("## Open next questions" in readme, "README.md needs an \"## Open next questions\" section") + status = require_key(manifest, "status", "VERIFY.json") + require(status == "ready", + "VERIFY.json.status must be \"ready\"; complete the draft before requesting replay; found " + brief(status)) + python = require_key(manifest, "python", "VERIFY.json") + require(python == "3.12", + "VERIFY.json.python must be \"3.12\" (generic replay currently uses Python 3.12); found " + brief(python)) + requirements = manifest.get("requirements", []) + require(isinstance(requirements, list), + "VERIFY.json.requirements must be a list of exact \"name==version\" pins; found " + + type(requirements).__name__ + ": " + brief(requirements)) + require(len(requirements) == len(set(requirements)), "VERIFY.json.requirements must not repeat a pin") + for item in requirements: + require(isinstance(item, str) + and re.fullmatch(r"(?:numpy|sympy|scipy)==[0-9]+(?:\.[0-9]+){1,2}", item), + "generic CI supports exact numpy/sympy/scipy pins; use a dedicated workflow for other environments" + + "; offending entry: " + brief(item)) + names = [item.split("==")[0] for item in requirements] + require(len(names) == len(set(names)), "declare only one version per dependency") + # Validation already treats an omitted list as empty; execution must receive + # that same default without rewriting the contributor's manifest on disk. + manifest["requirements"] = requirements + commands = manifest.get("commands") + require(isinstance(commands, list) and 1 <= len(commands) <= 8, + "VERIFY.json.commands must be a list of 1..8 explicit verification commands; found " + + type(commands).__name__ + ": " + brief(commands)) + total = 0 + for position, command in enumerate(commands): + path = "VERIFY.json.commands[" + str(position) + "]" + require(isinstance(command, dict), + path + " must be a JSON object; found " + type(command).__name__ + ": " + brief(command)) + argv = command.get("argv") + require(isinstance(argv, list) and len(argv) >= 2 and all(isinstance(a, str) for a in argv), + path + ".argv must be a list of at least 2 strings; found " + brief(argv)) + require(argv[0] == "{python}", path + ".argv must explicitly use {python}; no implicit shell") + require_text(command, "checks", path, "a non-empty string without TODO describing command coverage") + seconds = command.get("timeout_seconds") + require(type(seconds) is int, + path + ".timeout_seconds must be an integer number of seconds; found " + + type(seconds).__name__ + ": " + brief(seconds)) + require(1 <= seconds <= 600, + path + ".timeout_seconds " + str(seconds) + + (" exceeds the 600 s per-command cap" if seconds > 600 + else " is below the 1 s per-command minimum")) + total += seconds + require(total <= 1200, "declared replay budget " + str(total) + + " s (sum of timeout_seconds) exceeds the 1200 s total cap" + + "; use a dedicated job for larger checks") + inputs = manifest.get("inputs") + require(isinstance(inputs, list), + "VERIFY.json.inputs must be a list of canonical repository-relative paths; found " + + type(inputs).__name__ + ": " + brief(inputs)) + tracked = tracked_input_paths(root) + for name in inputs: + symlink_free_relative_path(root, name) + require_tracked_input(name, tracked) + relative_file(root, name) + files = require_key(sources, "files", "SOURCES.json") + require(files, "SOURCES.json.files is empty; declare the source inventory") + base = require_key(sources, "base_commit", "SOURCES.json", "a 40-64 hex string pinning an exact base commit") + require(isinstance(base, str) and re.fullmatch(r"[0-9a-f]{40,64}", base), + "SOURCES.json.base_commit must be a 40-64 hex string pinning an exact base commit; found " + brief(base)) + task_base = require_key(task, "base_commit", "TASK.json", + "a 40-64 hex string equal to SOURCES.json.base_commit") + require(task_base == base, "TASK.json.base_commit " + brief(task_base) + + " must equal SOURCES.json.base_commit " + brief(base)) + for row in files: + require(isinstance(row, dict) and isinstance(row.get("path"), str), + "SOURCES.json.files rows must be JSON objects with a string path; offending row: " + brief(row)) + require(row.get("usage") in ("input", "provenance"), + "SOURCES.json.files row " + row["path"] + " must declare usage \"input\" or \"provenance\"; found " + + brief(row.get("usage"))) + undeclared = sorted({r["path"] for r in sources["files"] if r["usage"] == "input"} - set(inputs)) + require(not undeclared, + "every SOURCES.json files row with usage \"input\" must also be declared in VERIFY.json inputs" + + " (they drive CI invalidation); undeclared: " + ", ".join(undeclared)) + if check_pins: + catalog_name = "docs/research/agent-task-catalog.json" + require(catalog_name in {r["path"] for r in sources["files"]}, + "SOURCES.json.files must pin the originating task catalogue " + catalog_name + + " (a provenance row pinned at base_commit)") + names = [r["path"] for r in sources["files"]] + for name in names: + p = PurePosixPath(name) + require(not p.is_absolute() and ".." not in p.parts, "invalid source path") + specs = [source_pin(row, sources["base_commit"]) for row in sources["files"]] + require(all(spec == sources["base_commit"] + ":" + catalog_name + for name, spec in zip(names, specs) if name == catalog_name), + "originating task catalogue must be pinned at base_commit") + with closing(git_blobs(root, specs)) as objects: + for index, (obj, pinned) in enumerate(objects): + row = sources["files"][index] + name = row["path"] + require(obj == row["git_blob"], "source blob provenance mismatch: " + name) + require(hashlib.sha256(pinned).hexdigest() == row["sha256"], "source SHA256 provenance mismatch: " + name) + if row["usage"] == "input": + require(pinned == relative_file(root, name).read_bytes(), "working source drift: " + name) + if name == catalog_name: + catalog = json.loads(pinned) + cards = [t for tr in catalog["tracks"] for t in tr["tasks"] if t["id"] == task["task_id"]] + require(len(cards) == 1, + "TASK.json task_id " + brief(task["task_id"]) + " matches " + str(len(cards)) + + " cards in pinned " + catalog_name + " at base_commit " + sources["base_commit"] + + "; need exactly one") + original_card = require_key(task, "original_card", "TASK.json", + "the pinned catalogue card object (copy --print-card output verbatim)") + if original_card != cards[0]: + raise ValueError("original card in TASK.json must equal the pinned catalogue card object exactly; " + "copy it verbatim from " + catalog_name + " at base_commit " + sources["base_commit"] + + ": " + card_differences(original_card, cards[0])) + return task, manifest + + +def catalogue_card(root, package, task_id): + """The exact original_card object a hand-authored TASK.json must copy. + + Reads the catalogue blob pinned at the package base commit, never the + working tree, because validate() compares original_card against that pin. + """ + sources = json.loads(relative_file(root, package + "/SOURCES.json").read_text(encoding="utf-8")) + name = "docs/research/agent-task-catalog.json" + rows = require_key(sources, "files", "SOURCES.json") + row = next((r for r in rows if isinstance(r, dict) and r.get("path") == name), None) + require(row is not None, "SOURCES.json.files must pin the originating task catalogue " + name) + base = require_key(sources, "base_commit", "SOURCES.json", "a 40-64 hex string pinning an exact base commit") + spec = source_pin(row, base) + require(spec == base + ":" + name, "originating task catalogue must be pinned at base_commit") + catalog = json.loads(git(root, "show", spec)) + cards = [t for tr in catalog["tracks"] for t in tr["tasks"] if t["id"] == task_id] + require(len(cards) == 1, + "task_id " + brief(task_id) + " matches " + str(len(cards)) + + " cards in pinned " + name + " at base_commit " + base + "; need exactly one") + return cards[0] + + +_WINDOWS_GATE = r""" +import ctypes +from ctypes import wintypes +import json +import subprocess +import sys +api = ctypes.WinDLL('kernel32', use_last_error=True) +api.OpenEventW.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.LPCWSTR] +api.OpenEventW.restype = wintypes.HANDLE +api.WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD] +api.WaitForSingleObject.restype = wintypes.DWORD +api.CloseHandle.argtypes = [wintypes.HANDLE] +api.CloseHandle.restype = wintypes.BOOL +event = api.OpenEventW(0x00100000, False, sys.argv[1]) +if not event: + raise ctypes.WinError(ctypes.get_last_error()) +try: + if api.WaitForSingleObject(event, 30000) != 0: + raise RuntimeError('replay ownership gate was not released') +finally: + api.CloseHandle(event) +# No verification command or descendant starts until the parent owns this job. +sys.exit(subprocess.call(json.loads(sys.argv[2]))) +""" + +def run_windows_command(argv, cwd, timeout, env): + """Own ordinary verification descendants even after their leader exits. + + The gated launcher joins a kill-on-close Job Object before it may spawn + the requested command. A late taskkill cannot recover an exited leader's + tree. Job handles are not inherited and breakaway is not enabled. + """ + import ctypes + from ctypes import wintypes + import time + import uuid + + class BasicLimits(ctypes.Structure): + _fields_ = [("ProcessTime", ctypes.c_int64), ("JobTime", ctypes.c_int64), + ("Flags", wintypes.DWORD), ("MinWorkingSet", ctypes.c_size_t), + ("MaxWorkingSet", ctypes.c_size_t), ("ActiveProcessLimit", wintypes.DWORD), + ("Affinity", ctypes.c_size_t), ("Priority", wintypes.DWORD), + ("Scheduling", wintypes.DWORD)] + + class IOCounts(ctypes.Structure): + _fields_ = [(name, ctypes.c_uint64) for name in + ("ReadOps", "WriteOps", "OtherOps", "ReadBytes", "WriteBytes", "OtherBytes")] + + class ExtendedLimits(ctypes.Structure): + _fields_ = [("Basic", BasicLimits), ("IO", IOCounts), + ("ProcessMemory", ctypes.c_size_t), ("JobMemory", ctypes.c_size_t), + ("PeakProcessMemory", ctypes.c_size_t), ("PeakJobMemory", ctypes.c_size_t)] + + class Accounting(ctypes.Structure): + _fields_ = [(name, ctypes.c_int64) for name in + ("UserTime", "KernelTime", "PeriodUserTime", "PeriodKernelTime")] + [ + (name, wintypes.DWORD) for name in + ("PageFaults", "TotalProcesses", "ActiveProcesses", "TerminatedProcesses")] + + api = ctypes.WinDLL("kernel32", use_last_error=True) + + def native(name, result, *arguments): + fn = getattr(api, name) + fn.restype, fn.argtypes = result, arguments + return fn + + handle, dword, boolean = wintypes.HANDLE, wintypes.DWORD, wintypes.BOOL + create_job = native("CreateJobObjectW", handle, ctypes.c_void_p, wintypes.LPCWSTR) + set_info = native("SetInformationJobObject", boolean, handle, ctypes.c_int, ctypes.c_void_p, dword) + assign = native("AssignProcessToJobObject", boolean, handle, handle) + terminate = native("TerminateJobObject", boolean, handle, wintypes.UINT) + query = native("QueryInformationJobObject", boolean, handle, ctypes.c_int, + ctypes.c_void_p, dword, ctypes.c_void_p) + create_event = native("CreateEventW", handle, ctypes.c_void_p, boolean, boolean, wintypes.LPCWSTR) + set_event = native("SetEvent", boolean, handle) + close = native("CloseHandle", boolean, handle) + + def checked(ok): + if not ok: + raise ctypes.WinError(ctypes.get_last_error()) + return ok + + job = checked(create_job(None, None)) + event, process, assigned = None, None, False + try: + limits = ExtendedLimits() + limits.Basic.Flags = 0x2000 # JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + checked(set_info(job, 9, ctypes.byref(limits), ctypes.sizeof(limits))) + name = "Local\\research-replay-" + uuid.uuid4().hex + event = checked(create_event(None, True, False, name)) + # A name collision must not attach to an already-signaled event. + if ctypes.get_last_error() == 183: + raise ValueError("replay ownership gate name already exists") + process = subprocess.Popen( + # The gate itself must not run sitecustomize/PYTHONPATH startup + # code before ownership. The requested command retains env/argv. + # Windows accepts Unicode argv directly. ASCII JSON escaping can + # expand an otherwise valid command past its UTF-16 launch limit. + [sys.executable, "-I", "-S", "-c", _WINDOWS_GATE, name, + json.dumps(argv, ensure_ascii=False)], + cwd=cwd, env=env, start_new_session=True) + checked(assign(job, int(process._handle))) + assigned = True + checked(set_event(event)) + try: + return process.wait(timeout=timeout) + except subprocess.TimeoutExpired: + raise ValueError("verification timed out") from None + finally: + try: + if process is not None: + if assigned: + checked(terminate(job, 1)) + # Termination is asynchronous. Require the job to report + # no active processes before releasing its ownership handle. + deadline = time.monotonic() + 10 + while True: + state = Accounting() + checked(query(job, 1, ctypes.byref(state), ctypes.sizeof(state), None)) + if not state.ActiveProcesses: + break + if time.monotonic() >= deadline: + raise ValueError("verification process cleanup timed out") + time.sleep(0.01) + elif process.poll() is None: + # Assignment failed before the gate opened: no command child + # exists, so only this known gated launcher needs stopping. + process.kill() + process.wait(timeout=10) + finally: + try: + if event: + checked(close(event)) + finally: + checked(close(job)) + +def run_command(argv, cwd, timeout): + # Assertion checks must not disappear because of the caller's environment. + # Keep explicit manifest options (including deliberate -O controls) intact. + # PYTHONPATH precedes the standard library on a child's sys.path, so a stray + # same-named module could end a verifier with exit code 0 before any check. + env = os.environ.copy() + env.pop("PYTHONOPTIMIZE", None) + # Ambient inspection can turn a verifier exception into a successful REPL + # exit at terminal EOF. Keep explicit -i and the caller's stdin unchanged. + env.pop("PYTHONINSPECT", None) + env.pop("PYTHONPATH", None) + if os.name == "nt": + code = run_windows_command(argv, cwd, timeout, env) + require(code == 0, f"verification command exited {code}") + return + previous_handler = None + terminating = False + waiting = False + process = None + + def terminate(signum, frame): + nonlocal terminating + if not terminating: + terminating = True + # Only interrupt the protected wait. Launch/assignment and cleanup + # defer delivery; repeated signals cannot interrupt group cleanup. + if waiting: + raise SystemExit(128 + signum) + + # Own a group for POSIX cleanup of this command and its descendants. + try: + if (threading.current_thread() is threading.main_thread() + and signal.getsignal(signal.SIGTERM) == signal.SIG_DFL): + previous_handler = signal.SIG_DFL + signal.signal(signal.SIGTERM, terminate) + if terminating: + raise SystemExit(143) + process = subprocess.Popen(argv, cwd=cwd, env=env, start_new_session=True) + try: + try: + waiting = True + if terminating: + raise SystemExit(143) + code = process.wait(timeout=timeout) + finally: + waiting = False + except subprocess.TimeoutExpired: + raise ValueError("verification timed out") from None + require(code == 0, f"verification command exited {code}") + finally: + try: + if process is not None: + if hasattr(os, "killpg"): + # The leader may have exited while descendants hold its group. + try: + os.killpg(process.pid, signal.SIGKILL) + except ProcessLookupError: + pass + elif process.poll() is None: + subprocess.run(["taskkill", "/F", "/T", "/PID", str(process.pid)], capture_output=True) + process.wait() + finally: + if previous_handler is not None: + signal.signal(signal.SIGTERM, previous_handler) + if terminating: + raise SystemExit(143) + + +def isolated_python_argv(argv, cache_directory): + """Place our cache option after interpreter flags, before the entry point. + + -B prevents writes, not reads of existing .pyc files. A fresh namespace + keeps preexisting source-adjacent and externally configured caches out of + this registered interpreter. It is not inherited by child interpreters. + """ + require(argv and argv[0] == sys.executable, "cache isolation requires the registered Python interpreter") + index = 1 + options = [argv[0]] + while index < len(argv): + option = argv[index] + if option in ("-", "--") or not option.startswith("-"): + break + if option == "--check-hash-based-pycs": + require(index + 1 < len(argv), "missing Python hash-cache option argument") + options.extend(argv[index:index + 2]) + index += 2 + continue + if option.startswith("--"): + options.append(option) + index += 1 + continue + # Python permits combined short flags, attached -c/-m payloads, and + # attached or separate -W/-X arguments. Stop scanning an option once + # its argument begins: letters inside that argument are not flags. + entry = False + consumed = 1 + replace_cache = False + for offset, flag in enumerate(option[1:], start=1): + if flag in "cm": + entry = True + break + if flag in "WX": + if offset == len(option) - 1: + require(index + 1 < len(argv), "missing Python interpreter option argument") + consumed = 2 + value = argv[index + 1] if consumed == 2 else option[offset + 1:] + if flag == "X" and value.split("=", 1)[0] == "pycache_prefix": + replace_cache = True + if offset > 1: + options.append(option[:offset]) + break + if entry: + break + if not replace_cache: + options.extend(argv[index:index + consumed]) + index += consumed + # CPython's effective prefix can use the first duplicate -X value even + # when sys._xoptions shows the last. Remove only earlier cache-prefix + # settings; script arguments remain untouched, even when spelled -X. + return [*options, "-X", "pycache_prefix=" + str(cache_directory), *argv[index:]] + + +def run_python_replay(argv, cwd, timeout): + with tempfile.TemporaryDirectory(prefix="research-replay-pycache-") as cache: + run_command(isolated_python_argv(argv, cache), cwd, timeout) + + +def run_package(root, package, install=False): + task, manifest = validate(root, package) + require(sys.version_info[:2] == (3, 12), "run replay with Python 3.12 (metadata check is version-independent)") + if install and manifest["requirements"]: + run_command([sys.executable, "-m", "pip", "install", "--disable-pip-version-check", *manifest["requirements"]], root, 300) + for requirement in manifest["requirements"]: + name, version = requirement.split("==") + require(importlib.metadata.version(name) == version, "dependency version mismatch: " + requirement) + # Coverage labels are UTF-8 metadata but a redirected Windows stdout may + # use cp1252. Escape unrepresentable display characters instead of aborting + # before replay. This changes only our stream, not child encoding or env. + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(errors="backslashreplace") + for command in manifest["commands"]: + argv = [sys.executable if item == "{python}" else item.replace("{package}", package) for item in command["argv"]] + print("CHECK " + command["checks"], flush=True) + run_python_replay(argv, root, command["timeout_seconds"]) + print("PASS_REGISTERED_RESEARCH_PACKAGE " + package + " outcome=" + task["outcome"]) + + +def planning_manifests(root, tracked): + """Include deliberate sparse omissions without disguising deletions or drift. + + The fallback follows the chooser's PR3102 contract: one stage-zero regular + skip-worktree index entry must match the exact HEAD mode and blob. This is + declaration discovery only, not permission to replay absent package code. + """ + committed = {name for name in tracked + if name.startswith("research/") and name.count("/") == 2 + and name.endswith("/VERIFY.json")} + # Explicit scandir retains error detection; only an absent directory is OK. + try: + with os.scandir(root / "research") as entries: + local = {Path(entry.path) / "VERIFY.json" for entry in entries} + except FileNotFoundError: + local = set() + present, missing = {}, set() + for path in sorted(local | {root / name for name in committed}): + name = path.relative_to(root).as_posix() + symlink_free_relative_path(root, name) + try: + mode = path.lstat().st_mode + except (FileNotFoundError, NotADirectoryError): + if name in committed: + missing.add(name) + continue + require(stat.S_ISREG(mode), "non-regular replay manifest: " + name) + present[name] = path + fallback = {} + if missing: + indexed, objects = {}, {} + for record in git(root, "ls-files", "-t", "--stage", "-z", "--", "research").split(b"\0"): + if not record: + continue + header, raw_name = record.split(b"\t", 1) + name = raw_name.decode("utf-8") + if name in missing: + indexed.setdefault(name, []).append(header.split()) + for record in git(root, "ls-tree", "-r", "-z", "HEAD", "--", "research").split(b"\0"): + if not record: + continue + header, raw_name = record.split(b"\t", 1) + name = raw_name.decode("utf-8") + if name in missing: + objects[name] = header.split() + for name in sorted(missing): + metadata = objects.get(name, []) + require(len(metadata) == 3 and metadata[0] in (b"100644", b"100755") + and metadata[1] == b"blob" + and indexed.get(name) == [[b"S", metadata[0], metadata[2], b"0"]], + "missing replay manifest is not an unchanged sparse HEAD input: " + name + + "; materialize it or commit its intended deletion") + # A regular file occupying the package directory is not an omission. + require(not (root / name).parent.exists() or (root / name).parent.is_dir(), + "non-directory replay package: " + name) + names = sorted(missing) + with closing(git_blobs(root, [objects[n][2].decode("ascii") for n in names])) as rows: + for (oid, raw), name in zip(rows, names): + require(oid.encode("ascii") == objects[name][2], "sparse manifest blob mismatch: " + name) + fallback[name] = raw + # Preserve Path's component ordering: package "a" precedes "a-extra". + for name in sorted(present.keys() | fallback.keys(), key=lambda name: PurePosixPath(name).parts): + yield root / name, fallback[name] if name in fallback else present[name].read_bytes(), name in fallback + + +def plan_details(root, changed): + """One selection pass with exact causes; no replay or acceptance decision.""" + changed = set(changed) + # A removed input is absent from HEAD, including an old case-only rename + # spelling on case-insensitive filesystems. Git's exact changed paths remain + # eligible so its consumer is selected, rather than rejected or skipped. + head_tracked = tracked_input_paths(root) + tracked = head_tracked | changed + replay_changed = sorted(changed.intersection({ + "scripts/research_package.py", ".github/workflows/research-packages.yml", + ".github/workflows/research-package-batch.yml"})) + # Group literal Git paths once. Keep removed paths and exact spellings. + changed_packages = {} + for name in sorted(changed): + parts = name.split("/", 2) + if len(parts) == 3 and parts[0] == "research": + changed_packages.setdefault("/".join(parts[:2]), []).append(name) + packages = [] + sparse_count = 0 + for path, raw, sparse in planning_manifests(root, head_tracked): + sparse_count += sparse + package = path.parent.relative_to(root).as_posix() + manifest = json.loads(raw.decode("utf-8")) + # Parse only for selection; validate fully in the isolated replay job. + inputs = manifest.get("inputs", []) + require(isinstance(inputs, list), "declare input paths for CI invalidation") + for name in inputs: + # Do not require existence: a deleted input must still trigger replay. + symlink_free_relative_path(root, name) + require_tracked_input(name, tracked) + causes = { + "declared_inputs": sorted(changed.intersection(inputs)), + "package_files": changed_packages.get(package, []), + "shared_controls": replay_changed, + } + if any(causes.values()): + packages.append({"package": package, "causes": causes, + "manifest_sha256": hashlib.sha256(raw).hexdigest()}) + if sparse_count: + print(f"Planning includes {sparse_count} unchanged sparse manifests from HEAD; " + "materialize packages and inputs before replay.", file=sys.stderr) + return packages + + +def plan(root, changed): + return [row["package"] for row in plan_details(root, changed)] + + +def write_plan_explanation(path, base, head, changed, details): + """Publish a complete sidecar before stdout/GitHub matrix publication.""" + matched = {name for row in details for names in row["causes"].values() for name in names} + report = { + "schema_version": 1, "base": base, "head": head, + "mode": "all-tracked" if base is None else "commit-diff", + "scope": "Selection from working or authenticated sparse HEAD manifests, not replay or mathematical acceptance. " + "Manifest hashes bind the declarations read; checkout must remain stable.", + "changed_paths": sorted(set(changed) - {""}), "selected": details, + "unmatched_paths": sorted(set(changed) - matched - {""}), + } + fd, temporary = tempfile.mkstemp(prefix=".replay-plan-", dir=path.parent) + try: + with os.fdopen(fd, "w", encoding="utf-8") as stream: + json.dump(report, stream, indent=2) + stream.write("\n") + os.replace(temporary, path) + finally: + Path(temporary).unlink(missing_ok=True) + + +def replay_matrix(packages, batch_size=None): + """Retain the flat CLI format, or group complete coverage into bounded matrices.""" + if batch_size is None: + return {"include": [{"package": p} for p in packages]} + require(type(batch_size) is int and 1 <= batch_size <= 256, + "matrix batch size must be 1..256") + batches = [packages[i:i + batch_size] for i in range(0, len(packages), batch_size)] + require(len(batches) <= 256, + "more than 256 matrix batches required; no packages were truncated") + return {"include": [{"packages": batch} for batch in batches]} + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--package") + ap.add_argument("--check-only", action="store_true") + ap.add_argument("--print-card", metavar="TASK_ID", + help="print the exact pinned catalogue card object to copy into TASK.json original_card") + ap.add_argument("--install-deps", action="store_true", help="explicit opt-in to install manifest's allowed pinned dependencies") + ap.add_argument("--changed-since", help="plan affected opt-in packages relative to this Git commit") + ap.add_argument("--github-output", type=Path) + ap.add_argument("--explain-plan", type=Path, + help="write exact replay-selection causes to JSON; matrix output stays unchanged") + ap.add_argument("--matrix-batch-size", type=int, + help="group planned packages for bounded reusable-workflow matrices (1..256)") + args = ap.parse_args() + if args.explain_plan and (not args.changed_since or args.package or args.print_card): + ap.error("--explain-plan requires --changed-since without --package or --print-card") + if args.explain_plan and args.github_output and args.explain_plan.resolve() == args.github_output.resolve(): + ap.error("--explain-plan and --github-output must use different files") + try: + if args.print_card: + require(args.package, + "--print-card needs --package research/ to resolve its pinned catalogue") + sys.stdout.write(json.dumps(catalogue_card(ROOT, args.package, args.print_card), indent=2) + "\n") + elif args.changed_since: + if set(args.changed_since) == {"0"}: + base = None + changed = git(ROOT, "ls-files", "-z").decode().split("\0") + else: + base = git(ROOT, "rev-parse", "--verify", args.changed_since + "^{commit}").decode().strip() + # A rename must invalidate both the removed input and its new path. + changed = git(ROOT, "diff", "--no-renames", "--name-only", "-z", base, "HEAD").decode().split("\0") + details = plan_details(ROOT, changed) + packages = [row["package"] for row in details] + matrix = json.dumps(replay_matrix(packages, args.matrix_batch_size)) + if args.explain_plan: + head = git(ROOT, "rev-parse", "--verify", "HEAD^{commit}").decode().strip() + write_plan_explanation(args.explain_plan, base, head, changed, details) + print(matrix) + if args.github_output: + with args.github_output.open("a", encoding="utf-8") as out: + out.write(f"matrix={matrix}\ncount={len(packages)}\n") + elif args.package: + if args.check_only: + validate(ROOT, args.package) + print("PASS_PACKAGE_METADATA_AND_PINS (not a mathematical acceptance check)") + else: + run_package(ROOT, args.package, args.install_deps) + else: + ap.error("use --package, --print-card or --changed-since") + except (OSError, ValueError, KeyError, TypeError, subprocess.SubprocessError, importlib.metadata.PackageNotFoundError) as exc: + ap.exit(1, str(exc) + "\n") + + +if __name__ == "__main__": + main()