diff --git a/docs/design/u2_m2_u1_differential_2faf035.json b/docs/design/u2_m2_u1_differential_2faf035.json new file mode 100644 index 00000000..15c34a7a --- /dev/null +++ b/docs/design/u2_m2_u1_differential_2faf035.json @@ -0,0 +1,151 @@ +{ + "base": { + "path": "/Users/maxghenis/PolicyEngine/_clones/md-u2-diff-base-9cee2423", + "head": "9cee2423f048683e15838aa5fee330afd072f85f" + }, + "candidate": { + "path": "/Users/maxghenis/PolicyEngine/_clones/md-u2-diff-cand", + "head": "2faf03565cb55ab56256317aebefab2a2ce82efd" + }, + "python": "/Users/maxghenis/PolicyEngine/_clones/md-u2-diff-venv-pandas306/bin/python", + "environment": { + "added_after_run": "read from the interpreter named in `python`, under the run's environment variables; scripts/u2_u1_differential.py does not write this block", + "python_version": "3.14.7", + "pandas_version": "3.0.6", + "numpy_version": "2.5.4", + "future_infer_string": true, + "install": "uv pip install -e \".[dev]\" \"pandas==3.0.6\" \"numpy==2.5.4\"", + "psid_root": "an empty directory (POPULACE_DYNAMICS_PSID_DIR and HOME)" + }, + "seed": 20260924, + "tolerance": "zero: byte equality", + "excluded_json_paths": [ + "/run/git_head", + "/run/date" + ], + "ratified_u1_block_sha256": "aed6e724053a44da76e4d08069930c88fe6c978a5ff58fce42beffd3a78785eb", + "dry_run": { + "result_json_equal": true, + "results_md_equal": true, + "result_json_sha256": { + "base": "9f4e4e7f1c466db7b375de6909ea85a2201a7d2361f101a35fc2514befffd35f", + "candidate": "9f4e4e7f1c466db7b375de6909ea85a2201a7d2361f101a35fc2514befffd35f" + }, + "results_md_sha256": { + "base": "5906f196f71c09e48163e12abcf36f70083ec5674874aaa440474ceddb03c0df", + "candidate": "5906f196f71c09e48163e12abcf36f70083ec5674874aaa440474ceddb03c0df" + }, + "first_json_difference": null, + "first_md_difference": null, + "run": { + "base": { + "date": "2026-10-24", + "invented_seed": 20260924, + "git_head": "9cee2423f048683e15838aa5fee330afd072f85f", + "git_clean": true, + "python": "3.14.7", + "command": "python scripts/track_u_dry_run.py --output-dir /var/folders/9l/_wztzgbx7mgc7l1r0416cy7m0000gn/T/u1-differential-tjve5_7o/same-output --seed 20260924" + }, + "candidate": { + "date": "2026-10-24", + "invented_seed": 20260924, + "git_head": "2faf03565cb55ab56256317aebefab2a2ce82efd", + "git_clean": true, + "python": "3.14.7", + "command": "python scripts/track_u_dry_run.py --output-dir /var/folders/9l/_wztzgbx7mgc7l1r0416cy7m0000gn/T/u1-differential-tjve5_7o/same-output --seed 20260924" + } + } + }, + "refusal_expectations": { + "base": { + "met": true, + "failures": {} + }, + "candidate": { + "met": true, + "failures": {} + } + }, + "loader_file_accesses_empty": true, + "contract_checks": { + "equal": true, + "sha256": { + "base": "49e47726948dc6faf4f39e0ae1ff60054e178b5feb4701cbb8b84400b6bdccd9", + "candidate": "49e47726948dc6faf4f39e0ae1ff60054e178b5feb4701cbb8b84400b6bdccd9" + }, + "first_difference": null, + "checked": [ + "actual_loader", + "age67.WAVES", + "input_frames_sha256", + "observation_plan", + "pending_decisions", + "refusal_parity", + "rows.MAX_RULINGS", + "runner.NAMED_DELTAS", + "uniform_cut_tabulation.COMPARATOR_COLUMN", + "uniform_cut_tabulation.REPORT_ROWS" + ], + "refusal_parity": { + "existing_artifact": { + "class": "ValueError", + "message": "/INVENTED/u1-refusal-parity/replication_boomers2004_uniform_cut_v1.json already exists: the registered run is one-shot", + "module": "builtins" + }, + "existing_sidecar": { + "class": "ValueError", + "message": "/INVENTED/u1-refusal-parity/replication_boomers2004_uniform_cut_v1.json already exists: the registered run is one-shot", + "module": "builtins" + }, + "invalid_provenance": { + "class": "TrackURunError", + "message": "data_provenance must be one of ('invented', 'registered_real')", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "invalid_registration_url": { + "class": "TrackURunError", + "message": "a registered run needs an issue #42 comment URL as its registration pointer (https://github.com/PolicyEngine/microcosm-dynamics/issues/42#issuecomment-)", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "invented_labeled_registered": { + "class": "TrackURunError", + "message": "a registered run reads the staged PSID through age67.load_age67_inputs; these inputs record 'invented'", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "invented_with_pointer": { + "class": "TrackURunError", + "message": "an invented run carries no registration pointer", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "u2_seed_under_u1": { + "class": "ValueError", + "message": "seed_wave_rule must be earliest_presence_wave", + "module": "builtins" + }, + "wrong_registered_headline": { + "class": "ValueError", + "message": "the registration names headline row U0, but the staged PSID gives U0-F under the fallback rule (WEALTH1 refused for waves [2005, 2007]): the staging changed after registration, so this is not the registered run", + "module": "builtins" + }, + "wrong_ssi_hash": { + "class": "TrackURunError", + "message": "a registered run needs the committed SSI capture; the SSI parameters record {'kind': 'policyengine_us_capture', 'sha256': '0000000000000000000000000000000000000000000000000000000000000000'}", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "wrong_threshold_hash": { + "class": "TrackURunError", + "message": "a registered run needs the committed, pinned Census threshold capture (adjusted_poverty.THRESHOLDS_SHA256 is 'dc21a78736f5085872cf56c9cf291258034c1293572b77455cba689a1ed0eec5'); the thresholds record {'kind': 'census_capture', 'sha256': '0000000000000000000000000000000000000000000000000000000000000000'}", + "module": "populace_dynamics.uniform_cut_track_u.runner" + } + }, + "loader_opened_under_fake_root": { + "base:supplements_refused": [], + "base:supplements_staged": [], + "candidate:supplements_refused": [], + "candidate:supplements_staged": [] + } + }, + "all_equal": true, + "dry_run_skipped": false, + "all_checks_passed": true +} diff --git a/docs/design/u2_preregistration_evidence.json b/docs/design/u2_preregistration_evidence.json new file mode 100644 index 00000000..8ee0dfa9 --- /dev/null +++ b/docs/design/u2_preregistration_evidence.json @@ -0,0 +1,148 @@ +{ + "cited_sources_sha256": { + "PSID/documentation/capture1/cross_sec_weights_13.pdf": "77c084c40210484b5d33f484fe5ce1049406b98b19d2f365b796aed0627e0070", + "PSID/documentation/capture1/cross_sec_weights_15.pdf": "c872cdc9d26db324bc6a580af47fa1c465547f8ff25a0e7f686242846c4f4f02", + "PSID/documentation/capture1/cross_sec_weights_17.pdf": "b35b5d9c99e74983c84d519383ba2b586c5db33f6b666d16370c54dbdb7a5438", + "PSID/documentation/capture1/cross_sec_weights_19.pdf": "6ef4795c5af79828aacb6f549bef58c9deffbf67f87651050bc03db4e991fc84", + "PSID/documentation/capture1/cross_sec_weights_21.pdf": "b7b128e71233f60998fa265e3cc08e6c1f4990c26d977f5d5ca9c59c3a012d4c", + "PSID/documentation/capture1/q2013.pdf": "5f966b9268ed428acd9454263296b31006220c69b33b6b36b8f6364436332c0d", + "PSID/documentation/capture1/q2015.pdf": "f3a4613bc8c1017f7cb40b30db4034d5f9a9234aad8022679ecd5c399f42e4e8", + "PSID/documentation/capture1/q2017.pdf": "733a9be8591080e09179857e672808d8cbf9b9ff351b2adf2c9e8200d893d9dc", + "PSID/documentation/capture1/q2019.pdf": "e848a2a8ea5bb7cfb14f87f98410a33b4ca7de85c51916acb7c8a49503c92835", + "PSID/documentation/capture1/q2021.pdf": "6c639ec32bfdd2176b5823d31815e31ac0db732a46ed05f22aa6a892cc1c31fb", + "PSID/documentation/capture1/q2023.pdf": "adc9975e1d7e901eb7192e6c6498f53be0eda62e0393ef1b06de0c85c511cad8", + "PSID/family/2013/FAM2013ER.sps": "526595b50a2087adc635da3c1288a396aa7179b075c11506f10338ab6348a0b5", + "PSID/family/2013/FAM2013ER_codebook.pdf": "40cc2de6af801b94b93518f7252e24c7c95098e713fc97f9ead1af74c4ce25d6", + "PSID/family/2015/FAM2015ER.sps": "6500c8e88a2353e7a6c5322a898f3879662228fdefa329e0901eb254fc9e3fff", + "PSID/family/2015/FAM2015ER_codebook.pdf": "7dcb71f823e309a286b904531410243d58cde48b8f50079401856de9ac762238", + "PSID/family/2017/FAM2017ER.sps": "24abae319022d4e3da9bce8052455dfee6b00b7b9c642004eddcfa06cb8d59e5", + "PSID/family/2017/FAM2017ER_codebook.pdf": "19bfba9f7a3fecc82a18711466e2b2a5f00b49c9d3db0f9f8587795fad2bf8c1", + "PSID/family/2019/FAM2019ER.sps": "c9f8ba8698779aeaf239c0eea4b1f98d32975b1c77767acfd35601fcb883a5b0", + "PSID/family/2019/fam2019er_codebook.pdf": "ce637e37e0f5e306dd7f10455e85069b56c9b13d5c138d3eb4b9c8d6a8452406", + "PSID/family/2021/FAM2021ER.sps": "50526906dafd86271074ba15fd426ab7030c2b62f29acf3d819b577fdc9442e2", + "PSID/family/2021/FAM2021ER_codebook.pdf": "e3b88ef394952fa05b9b98eb7f05bf9a99896840515942562aa37399e1bcd462", + "PSID/family/2021/FAM2021ER_formats.sps": "863f151a4cab1282060aa3bfe4f6648bbbc1822e6f739bcfd502ff13b40a952c", + "PSID/family/2023/FAM2023ER.sps": "915755685965a41743e96e42abbbfdddd7eab1df33234e462b0813b25d4df169", + "PSID/family/2023/FAM2023ER_codebook.pdf": "85a4078ebb79146d023df3d9d4e2d2ef6017eced849656ca39f4b740cb5a5688", + "PSID/family/2023/FAM2023ER_formats.sps": "89e4f2be4bea66fe83e0f11682bdcfab2590c01310bd697833846377d64a59f2", + "PSID/ind2023er/IND2023ER.sps": "92d32d0efd65d50e984e971c681f6e9231b720973444cdc8f20f397039c9a462", + "PSID/ind2023er/IND2023ER_codebook.pdf": "4878100887dce1a7b7831af272a9f19b01ec33dcea091a5c7352625ecd464308", + "PSID/ind2023er/IND2023ER_formats.sps": "b42b3560fb3116af0cc6bf0740824cbc24b0ff94d7a1fd4891e40c7a12f93dc4", + "PSID/mh85_23/MH85_23.sps": "6f4a194b74c2dc7b95f2a163aeab19244384099ac0966dbe735157a60c1953ad", + "PSID/mh85_23/MH85_23_Codebook.pdf": "d7b7d0f4b7b6a2653a45f38665482f9447fdcd59ccc7a678b3c4d23546273034", + "PSID/mh85_23/MH85_23_formats.sps": "f7db069e55329eb1e2272dd4163244dfa7a05e323726144c0854b70f14c1186d", + "tests/data/track_u2/psid_sources/cross_sec_weights_17.pdf": "b35b5d9c99e74983c84d519383ba2b586c5db33f6b666d16370c54dbdb7a5438", + "tests/data/track_u2/psid_sources/cross_sec_weights_23.pdf": "58411f56001e6089226ce5fc9ebcd132e85f9e917571d3fe54dcdc5139e63983" + }, + "frame_environment": { + "future_infer_string": true, + "pandas_version": "3.0.6" + }, + "input_frames_sha256": "d37d0379e56884fec4d0d883a1b655e6b66f53f03fe4c1c229dff087b2cb5de4", + "psid_files_bundle_sha256": "0dec885e58d97e2cd6e7e95cba6b6b5019e77306718a7b57eda2233e453ae374", + "psid_files_sha256": { + "family/1968/FAM1968.sps": "a2adbebfb2af38cafb2669e2aebef489f24e9e1d0c1b5e8cd2b23287dcadaaa5", + "family/1968/FAM1968.txt": "9fb3f8b872e139f23afc158cc205440af385a1e619a5cc9a95f83fa61b85bb78", + "family/1969/FAM1969.sps": "94a07c16e393cae68538d715d6a722a3c0de6e02dc8c9e56c5e658725db622c2", + "family/1969/FAM1969.txt": "f564d78850ce6a5e262389df9dc20fae52a3e63f5ae6b141bba787dd04c72fdd", + "family/1970/FAM1970.sps": "0212c4fd983a19b9c1cdd71feb4d92beed9d3906f807b485ef209ebd9f6d75f0", + "family/1970/FAM1970.txt": "8d28224213dc9d2dd31e1ac70ee9775ff257350926c0f595782de95f5ad4d84c", + "family/1971/FAM1971.sps": "8a6ccfc9ba6110dabe8147b528c2e0b2ada2d701889ea9bb03aa6c9bf0d479ae", + "family/1971/FAM1971.txt": "97360718a1f76e910ff2f2941dc0c65d6022016a570d53435b8ede97cef0b73c", + "family/1972/FAM1972.sps": "04b8594dda71404ff2e27734ff20ba8c7f9b982d2a4c834e61d59958000a5b9e", + "family/1972/FAM1972.txt": "5b1148272096ea8b50351ae4a98c1792be666b9ab140b344ee72434abd867055", + "family/1973/FAM1973.sps": "10f4b5e346529c6f5f4705842dfaa25186d514725fac6cc3a6b50dfabe3c33c4", + "family/1973/FAM1973.txt": "ae656b7f4b689f82259bec0e392e425df61eb8ab446b1bcb4187e25de568d011", + "family/1974/FAM1974.sps": "e043119eca809a3a32cbd086b5a17db114da72305446cb54ffa6e134e5ca0082", + "family/1974/FAM1974.txt": "579d98750c34753bfd3ee16149bb1124dd8cbd531b97c65a41e9fe0ae7e7f6cf", + "family/1975/FAM1975.sps": "a120da243300e5684f946f98acc25414f62579ec8c9c675267bbcce48c88f6e3", + "family/1975/FAM1975.txt": "26f6c07de11b9cbfc1ca0ff2942851b73f9e2f89df1abd0b77f2cda5fc097776", + "family/1976/FAM1976.sps": "f1884fe637284802ee2c4d0d147ed5f42a048e5f9d30a9c8c05bc53142d61771", + "family/1976/FAM1976.txt": "b35387e2b04bb9515f5edc1a31cc4b55a5bf018f9aa102ead4d7399e5d8b0824", + "family/1977/FAM1977.sps": "2367ae4b09af04ae0b69ef4cd65a1b47303f7526e24a16483ff1edd21fabbe53", + "family/1977/FAM1977.txt": "6314e1ae1784a6d5267ec863c96432c11a8b22a45a09170d7c7322ff0faaa1d8", + "family/1978/FAM1978.sps": "2a0eaa0fab5a17dc02a3c344b65599d3169d0a95bab1a2d6b9129454c4886239", + "family/1978/FAM1978.txt": "39764f180bf8431e7947027ef656c7910e832c01facfb86c8534735bdf42e12d", + "family/1979/FAM1979.sps": "3c1041d482cde534562f251f7cf6a9a7369980fdf5e4b691fa7413c5fa7e58ed", + "family/1979/FAM1979.txt": "6666f099d276fe353180b0e1d3881e06be70f656bda87d4f05a04ef7ed80e529", + "family/1980/FAM1980.sps": "acba02dc6d341d2cc4a8cd0a8b1365edf9f1d2c90dba32b4ddc83667b6750d67", + "family/1980/FAM1980.txt": "a1e28e6e689cbe990ae0cf9d4bbc268c75c5a30a21e0ff7297a970f69ac8a054", + "family/1981/FAM1981.sps": "a914dd7d130fca11745d2f099f5181d3e347d4a1206706a4524982f1bc5d4509", + "family/1981/FAM1981.txt": "b38c3093711aaa9e74875a610d874f155be6ed4b3a2bf67badc740a7373a149e", + "family/1982/FAM1982.sps": "e99097b4fad8dbd1fc99740ee6632db2cfb678dd3dbd7142f62f50bb4b1ff05a", + "family/1982/FAM1982.txt": "dc3510d641a9b878528977bdaa1173c46e9cb89db2e0003575213db80cc6476b", + "family/1983/FAM1983.sps": "685e7c076eb68a79a5a7c5e4f6a6bbe94fa7769ba5c5883775978b7b2ece0f3e", + "family/1983/FAM1983.txt": "acf81d4911373984974e02fce4707bb4779c72f00e30d01fbc7c16d6cc68a140", + "family/1984/FAM1984.sps": "ad90da0506c5c556ef8b527e1d6c75a057b30bd05ba5e09588df514053630368", + "family/1984/FAM1984.txt": "bc92bb4d845241db96864b038476a6524ae5f43311db708972dd8bd8084bb9ef", + "family/1985/FAM1985.sps": "07b156f52c91dbcc66944b983f601553d9ddb94bb343248434d55e3d01eb7c2f", + "family/1985/FAM1985.txt": "db0daa1860e69fce5c66567a5d64b0bd07be3d31e0c70046bc7e474d22119f51", + "family/1986/FAM1986.sps": "a8d57b14f4efc36170cc2577174ab336b026a01e61f0639f8e3b8d1f82d930da", + "family/1986/FAM1986.txt": "f78ba17a5e50df3860b65e957777ad778bd0a49308b1ab684ad7ca9893a8d3c8", + "family/1987/FAM1987.sps": "3f1816e8bd169ba55ddb2c42cc5488d00adb8913f17687d7885cec8fe5f812bd", + "family/1987/FAM1987.txt": "b0512acd98ef280878271840229af34401f30702e01d8d646191f049e4bdcba0", + "family/1988/FAM1988.sps": "a2971802f1e81107e27cf7c426986f387db8bed9481026a4cf876bcd9bad6f69", + "family/1988/FAM1988.txt": "e30bd0ead4bba2238108a51eb2e24eee935a4a38e0d4257efdd1cff1bd23389c", + "family/1989/FAM1989.sps": "bf034bb3e45dc8ff5ec6a25c34d81f7888e922686e66362f2fa57bfd7c452058", + "family/1989/FAM1989.txt": "524d0627d471a66dcfa73ace35389e8117d426d56d6c7804df742e91ab7be728", + "family/1990/FAM1990.sps": "8eb30ab1a766c3b60e0e84aab24f197992c2cd0947c33acfb6d34684fc28fe67", + "family/1990/FAM1990.txt": "318fc47527ec48f8ebc8bc7114649a5a85b978e591a964fd46a8735935c0dc95", + "family/1991/FAM1991.sps": "dec4e504682dc08014a82307e3dd78d2833cc0f16ae8a3eaee1693f2fdf11e32", + "family/1991/FAM1991.txt": "01e39177b72be26da8eeb62d193ac0c5e47265a530a8a484dc271687e45fd3cb", + "family/1992/FAM1992.sps": "0c512da1ad8bc7ffb764b07376edf5200a5a166287ca4c4070a4b371010c9bb5", + "family/1992/FAM1992.txt": "d392f84b96b5f76bff8cccc0dde3defa8fc02a1646d3fcf607e0f5e91d3698b7", + "family/1993/FAM1993.sps": "acf13fda9d02d5a2cfdcf4a180a3bffbb4dcd2a64770b6312d0933cf7b111a08", + "family/1993/FAM1993.txt": "76ac2c830d9912cba11c3187a6c1c47612142894592f955ac1f03aabb8e3153e", + "family/1994/FAM1994ER.sps": "c5ae483c602e0cb1ce5cb280c4704d9ee49bd09ba5fd2ff1747c571abd693c43", + "family/1994/FAM1994ER.txt": "326dfb30ed4a7193c9aa41527c646ec9e58e44165175f70db32b31d7ee94eae3", + "family/1995/FAM1995ER.sps": "5487bb3e0f782124d0b872ab43d09ee424c15b801868605121e3719483dce3a1", + "family/1995/FAM1995ER.txt": "c24d9982413259fce6bf386ff84195b5deb77ebb0e834705cdffe8987cb6a0db", + "family/1996/FAM1996ER.sps": "9aa5ad480b2f80b300af58d457c15e5f8c2c027dcb1a78ed1aecc9821c665067", + "family/1996/FAM1996ER.txt": "8b607a5f0f3aeaa706c5036c0240e46c684d083c66da31a38b2c835e3adfdbb3", + "family/1997/FAM1997ER.sps": "a40333ad1795c7f19d194797137e340732b093d4a4c0108589dd1512b66f4b99", + "family/1997/FAM1997ER.txt": "1fb8b8bee3fdd1a2e8f6637b6c3e434019d8dca5eacb94478e0419d5fbcfc8e9", + "family/1999/FAM1999ER.sps": "7afad10e0da6969cd434a588f7d336923451d7a2fa8a71959c6edf376b023a9b", + "family/1999/FAM1999ER.txt": "92d7bef0eb5794cccb9137a4fe177dadfb2ba92709628b5cbc4004f8e7d786a0", + "family/2001/FAM2001ER.sps": "c6dc0e81131ece42ebeb70a67ee58082b939049815bd359724e745780d0afc12", + "family/2001/FAM2001ER.txt": "cc55d16f2fd44bcc89c1ab812e21f3f4f7bc26b15645b3170e590f55d1320ca5", + "family/2003/FAM2003ER.sps": "09f96449a9410a2478c0c24c042b5510ea0a75e7c97d83102a60d1faab747ac3", + "family/2003/FAM2003ER.txt": "f06914b61c017d34c471343d9667d2c19ad5d59f78890ee26ff613d465555ef1", + "family/2005/FAM2005ER.sps": "9a3440f8c3ba8af3ab2a40bde19b3a7382e20b82b0d91bd7b4ccf5e36ee7ec0d", + "family/2005/FAM2005ER.txt": "29e9d9a3eb7cdf33df8831cc55f27ab6e230e8202b4cdd982da79b759c0a749f", + "family/2007/FAM2007ER.sps": "4c8bd5b1d7bdce8a9d596e191f2791004ceb3413bf0b88c68cadccd33277977d", + "family/2007/FAM2007ER.txt": "f811e277303fe90a42edef602ad1a92405e8df0c9ee0dd36eff4ff531202ebbf", + "family/2009/FAM2009ER.sps": "4faa45e30b5a4719a3fa12f9d5d48568f644f315457ee3eed3e4ff8872459e4c", + "family/2009/FAM2009ER.txt": "59cf31106cf4c3d2e62903bd841b1689c9229e15674bdc5fd896453286b085f2", + "family/2011/FAM2011ER.sps": "6a994d8b65803a515eaa3bf62c74e278dd5943b8b578428ef4fd462d96cba45c", + "family/2011/FAM2011ER.txt": "3253586567a76a43d3e4a568dd5ca8af0f8a3c69db829d6a55bdf0942c60992f", + "family/2013/FAM2013ER.sps": "526595b50a2087adc635da3c1288a396aa7179b075c11506f10338ab6348a0b5", + "family/2013/FAM2013ER.txt": "0406aed042d660f51712d0f18a6e80f939717c0c2fc68b8d3a8d6dcbf48180e1", + "family/2015/FAM2015ER.sps": "6500c8e88a2353e7a6c5322a898f3879662228fdefa329e0901eb254fc9e3fff", + "family/2015/FAM2015ER.txt": "e4a64b275a9c6dbe8e7b562be7ed0906d4082b0c49cad79d764c69b8de318ec2", + "family/2017/FAM2017ER.sps": "24abae319022d4e3da9bce8052455dfee6b00b7b9c642004eddcfa06cb8d59e5", + "family/2017/FAM2017ER.txt": "c4220fe0569f3dbb8e110029a9d4d896c41635c50c86b7cb9edd0e7a5de82b0f", + "family/2019/FAM2019ER.sps": "c9f8ba8698779aeaf239c0eea4b1f98d32975b1c77767acfd35601fcb883a5b0", + "family/2019/FAM2019ER.txt": "80602015789c552cff3b75dfec6813204e918837f767f2e71744f51c07a9056d", + "family/2021/FAM2021ER.sps": "50526906dafd86271074ba15fd426ab7030c2b62f29acf3d819b577fdc9442e2", + "family/2021/FAM2021ER.txt": "c8a7a0589962bd3e6300e99595be16cb198ef48b3bb2b9b626743fe44cd185ae", + "family/2023/FAM2023ER.sps": "915755685965a41743e96e42abbbfdddd7eab1df33234e462b0813b25d4df169", + "family/2023/FAM2023ER.txt": "384c63924b6667ecebd7b3ca797bfae94fcea4c0de1708c46ab25659a16432b2", + "ind2023er/IND2023ER.sps": "92d32d0efd65d50e984e971c681f6e9231b720973444cdc8f20f397039c9a462", + "ind2023er/IND2023ER.txt": "d68554ac828a690491572f37323eba1d8488e8b0001ed2aa89df4d4ade26c869", + "mh85_23/MH85_23.sps": "6f4a194b74c2dc7b95f2a163aeab19244384099ac0966dbe735157a60c1953ad", + "mh85_23/MH85_23.txt": "05297c541ae3cc62abd834b0c8e06aa189df4712e56f3507bb64c15e05b38ff7" + }, + "registries_sha256": { + "design": "56181372d412811af6d06f5c0d8d1e071049f7fb73c34bd394aaff8f5e0c2fd6", + "income": "79b8f953eda2111389b762608cea1025fb45d96ceb0468169eaf9ed405b0d4eb", + "individual": "283c65c46ccfbc6b3a5b700ec7d0d1783e61c931352455b842828cc0186cedda", + "pension": "7cc17ff1f6f4a5896b86cafa5edc59028102d28bfcdfa42840d55b628074f3dd", + "roles": "f10c4a7dff38f74a29291fcec4a96c8c7b31d58561798a10c53094d6dcf393b7", + "support": "279515f5dab832155583829aec312078547a28d7152bcd07e90a2cf9bb863b8a", + "wealth": "c5c8fa1ddf283c07ac4dcac4490afb9754d336ef3618a159e028ce2b39f874a1", + "weights": "47c9f2d1b4b95f8259107d589aadc370b835dc807d45fa5268cf88abd72cc3be" + }, + "schema_version": "populace_dynamics.track_u2_preregistration_evidence.v2", + "specification": "boomers2004_1946_55_uniform_cut", + "target_id": "U2" +} diff --git a/scripts/track_u2_dry_run.py b/scripts/track_u2_dry_run.py index 7ae97fa6..a4d959b2 100644 --- a/scripts/track_u2_dry_run.py +++ b/scripts/track_u2_dry_run.py @@ -73,6 +73,7 @@ import platform import subprocess import sys +import tempfile from pathlib import Path from typing import Any @@ -127,6 +128,17 @@ def _refusal(call: Any) -> dict[str, Any]: return {"refused": False} +def _refusal_without_evidence() -> dict[str, Any]: + """The registered preflight's read of the evidence record, forced + absent through an explicit missing path in a fresh temporary + directory: the committed record stays in place.""" + with tempfile.TemporaryDirectory() as directory: + missing = Path(directory) / "absent.json" + if missing.exists(): + raise RuntimeError(f"{missing} exists in a fresh directory") + return _refusal(lambda: loader.read_preregistration_evidence(missing)) + + def _invented_individual_raw( inputs: cohort.U2Inputs, registries: sources.RegistrySet ) -> pd.DataFrame: @@ -208,9 +220,7 @@ def _round_1_refusals( "undocumented_relationship_rule": _refusal( lambda: registry_roles.rule(2015, 99) ), - "registered_preflight_without_evidence": _refusal( - loader.read_preregistration_evidence - ), + "registered_preflight_without_evidence": _refusal_without_evidence(), "evidence_from_invented_inputs": _refusal( lambda: loader.preregistration_evidence(inputs) ), diff --git a/tests/track_u2/test_u2_loader.py b/tests/track_u2/test_u2_loader.py index d3c56ebf..fc6cefd3 100644 --- a/tests/track_u2/test_u2_loader.py +++ b/tests/track_u2/test_u2_loader.py @@ -23,8 +23,8 @@ invented frames; * for a frozen load, ``loader.read_preregistration_evidence`` returns the invented evidence record the load is given, standing in for the - committed record (none is committed yet; a frozen load refuses any - other record, ``test_a_frozen_load_needs_the_committed_record``). + committed record (a frozen load refuses any other record, + ``test_a_frozen_load_needs_the_committed_record``). An audit hook refuses any open under the PSID directory -- the default one and the one under the account's real home, which differ when HOME is @@ -1036,8 +1036,9 @@ def test_a_frozen_load_needs_the_committed_record( against the committed pre-registration record, so a fabricated record, however well-formed, cannot stand in for the authorization an unfrozen load needs. It refuses before any PSID path is resolved or any file - opened: while no record is committed, and when the committed one - differs.""" + opened: when no record can be read (forced here through an explicit + missing path; the committed record stays in place), and when the + committed one differs.""" import sys @@ -1056,7 +1057,14 @@ def spy(event, args): "_resolve_data_dir", lambda data_dir: resolved.append(data_dir) or original(data_dir), ) - assert not loader.PREREGISTRATION_EVIDENCE_PATH.exists() + read = loader.read_preregistration_evidence + missing = tmp_path / "no-record" / "absent.json" + assert not missing.exists() + monkeypatch.setattr( + loader, + "read_preregistration_evidence", + lambda *args, **kwargs: read(missing), + ) with pytest.raises( loader.U2FrozenIdentityMismatch, match="no pre-registration evidence" ): @@ -1206,13 +1214,40 @@ def test_an_absent_or_unpinned_evidence_record_refuses( loader.read_preregistration_evidence(path) -def test_the_committed_evidence_record_is_absent_so_registration_refuses(): - """At this commit the section 20 step-4 pass has not run: there is no - committed evidence record, so the registered run cannot bind one.""" +def test_the_committed_evidence_record_is_the_pinned_one(tmp_path): + """Section 20 step 5: the committed evidence record is the one the + step-4 pass wrote, byte for byte, and the reader accepts it. An + absent record and a copy one byte away from it still refuse.""" - assert not loader.PREREGISTRATION_EVIDENCE_PATH.exists() + path = loader.PREREGISTRATION_EVIDENCE_PATH + assert path.is_file() + raw = path.read_bytes() + assert ( + hashlib.sha256(raw).hexdigest() + == loader.PREREGISTRATION_EVIDENCE_SHA256 + == "7bc5c3c26ff014271061a8228068c6e71103430ec299048462570476a510b845" + ) + assert loader.read_preregistration_evidence() == ( + loader.check_preregistration_evidence(json.loads(raw)) + ) + absent = tmp_path / "absent.json" + assert not absent.exists() with pytest.raises(loader.U2LoaderRefusal, match="no pre-registration"): - loader.read_preregistration_evidence() + loader.read_preregistration_evidence(absent) + # One hex digit of the frame digest changes: the copy stays a + # well-formed record, so only the pin refuses it. + key = b'"input_frames_sha256"' + at = raw.index(b'"', raw.index(key) + len(key)) + 1 + altered = bytearray(raw) + altered[at] = ord("1") if raw[at] == ord("0") else ord("0") + assert sum(a != b for a, b in zip(raw, altered, strict=True)) == 1 + assert loader.check_preregistration_evidence(json.loads(bytes(altered))) + copy_path = tmp_path / "evidence.json" + copy_path.write_bytes(bytes(altered)) + with pytest.raises( + loader.U2FrozenIdentityMismatch, match="not the committed record" + ): + loader.read_preregistration_evidence(copy_path) def test_evidence_is_recorded_only_from_loader_sealed_inputs( diff --git a/tests/track_u2/test_u2_scripts.py b/tests/track_u2/test_u2_scripts.py index ff66f748..608261d5 100644 --- a/tests/track_u2/test_u2_scripts.py +++ b/tests/track_u2/test_u2_scripts.py @@ -989,14 +989,24 @@ def test_binding_covers_every_registered_identity(registered): ) != registered.binding_sha256("d" * 64, evidence) -def test_preflight_refuses_while_the_evidence_record_is_absent(registered): +def test_preflight_refuses_while_the_evidence_record_is_absent( + registered, tmp_path, monkeypatch +): """Section 14: execution rechecks the frozen identities, so without - the committed pre-registration evidence record (none exists at this - commit) the preflight refuses before comparing the binding.""" + the committed pre-registration evidence record (forced absent here + through an explicit missing path; the committed record stays in + place) the preflight refuses before comparing the binding.""" from populace_dynamics.uniform_cut_track_u2 import loader - assert not loader.PREREGISTRATION_EVIDENCE_PATH.exists() + read = loader.read_preregistration_evidence + missing = tmp_path / "absent.json" + assert not missing.exists() + monkeypatch.setattr( + loader, + "read_preregistration_evidence", + lambda *args, **kwargs: read(missing), + ) with pytest.raises(loader.U2LoaderRefusal, match="no pre-registration"): _preflight(registered, evidence=None) with pytest.raises(loader.U2LoaderRefusal, match="registries other"):