Remove review material byte caps and enable complete readonly evidence inspection

This commit is contained in:
Takeshi
2026-10-13 23:21:02 +09:00
parent be3047979b
commit e31233685b
44 changed files with 22269 additions and 2 deletions
@@ -0,0 +1,15 @@
{
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
"elapsed_seconds": 16.212463750038296,
"actual_readonly_checker_deficits": [
"OS proof: unsafe artifact path",
"case off-positive: invalid literal for int() with base 10: 'Vector3i(0'",
"case off-refusal: invalid literal for int() with base 10: 'Vector3i(0'",
"case on-positive: invalid literal for int() with base 10: 'Vector3i(6'",
"case on-refusal: invalid literal for int() with base 10: 'Vector3i(-12'",
"final regression: unsafe artifact path"
],
"full_game_acceptance": false,
"original_unit_completed": false,
"index": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p08-02-native-mixed-r05/index.json"
}
@@ -0,0 +1,70 @@
[
{
"case": "off-positive",
"original_world_revisions": {
"world": 144,
"occupancy": 144,
"walkability": 99,
"excavation": 0,
"support": 142
},
"checkpoint_world_revisions": {
"world": 150,
"occupancy": 150,
"walkability": 99,
"excavation": 0,
"support": 150
}
},
{
"case": "on-positive",
"original_world_revisions": {
"world": 144,
"occupancy": 144,
"walkability": 99,
"excavation": 0,
"support": 142
},
"checkpoint_world_revisions": {
"world": 150,
"occupancy": 150,
"walkability": 99,
"excavation": 0,
"support": 150
}
},
{
"case": "off-refusal",
"original_world_revisions": {
"world": 144,
"occupancy": 144,
"walkability": 99,
"excavation": 0,
"support": 142
},
"checkpoint_world_revisions": {
"world": 144,
"occupancy": 144,
"walkability": 99,
"excavation": 0,
"support": 142
}
},
{
"case": "on-refusal",
"original_world_revisions": {
"world": 144,
"occupancy": 144,
"walkability": 99,
"excavation": 0,
"support": 142
},
"checkpoint_world_revisions": {
"world": 144,
"occupancy": 144,
"walkability": 99,
"excavation": 0,
"support": 142
}
}
]
@@ -0,0 +1,23 @@
{
"current_checker_candidate": "1545bd42d689a9a6382e2b003ee0550fa197f6e4",
"actual_native_data_candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
"scope": "read-only diagnostic preflight against actual retained data; NOT final exact-source-map acceptance",
"rows": [
{
"case": "off-positive",
"passed": true
},
{
"case": "off-refusal",
"passed": true
},
{
"case": "on-positive",
"passed": true
},
{
"case": "on-refusal",
"passed": true
}
]
}
@@ -0,0 +1,5 @@
{
"bytes": 492771,
"sources": 18,
"size_guard_pass": true
}
@@ -0,0 +1,61 @@
[
{
"first_label": "original",
"original_requests": [
"supply.logistics_request.000000000001",
"supply.logistics_request.000000000002"
],
"active_inventory_ids": [
"cargo:staff_gina",
"facility.inventory:facility_00000005"
],
"requests": [
{
"request_id": "supply.logistics_request.000000000001",
"source_inventory_id": "facility.inventory:facility_00000005",
"destination_inventory_id": "facility.inventory:facility_00000006",
"item_requirements": {
"ingredient/raw_food": 4
},
"priority": 50,
"created_at": 728,
"deadline": -1,
"total_weight": 4,
"reserved_stack_ids": [
"stack_00000001"
],
"reservation_ids": [
"reservation_00000001"
],
"assigned_carrier_id": "staff_gina",
"state": "DELIVERING",
"retry_count": 0,
"state_reason": "",
"state_changed_at": 735
},
{
"request_id": "supply.logistics_request.000000000002",
"source_inventory_id": "facility.inventory:facility_00000005",
"destination_inventory_id": "facility.inventory:facility_00000006",
"item_requirements": {
"ingredient/raw_food": 4
},
"priority": 50,
"created_at": 729,
"deadline": -1,
"total_weight": 4,
"reserved_stack_ids": [
"stack_00000001"
],
"reservation_ids": [
"reservation_00000002"
],
"assigned_carrier_id": "",
"state": "RESERVED",
"retry_count": 0,
"state_reason": "NO_AVAILABLE_PORTER_OR_ROUTE",
"state_changed_at": 729
}
]
}
]
@@ -0,0 +1,12 @@
{
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
"scope": "Root readonly prototype only. Candidate/checker/evidence files unchanged, no formal acceptance.",
"basis": "Authoritative SupplyLogisticsBridge.validate_crosslinks lines508-554 resolves facility inventory endpoints by facility ownership and allows unassigned RESERVED request with valid reservation; destination inventory materializes upon delivery.",
"remaining_deficits": [
"OS proof: unsafe artifact path",
"final regression: unsafe artifact path",
"unapproved typed migration delta off-positive/systems/runtime/world/revisions/support",
"unapproved typed migration delta on-positive/systems/runtime/world/revisions/support"
],
"elapsed_seconds": 78.40071395831183
}
@@ -0,0 +1,8 @@
{
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
"actual_complete_packet_bytes": 449115,
"source_count": 18,
"sources_omitted": 0,
"size_guard_pass": true,
"acceptance": false
}
@@ -0,0 +1,10 @@
{
"decision": {
"action": "run",
"task_id": "P08-02",
"reason": "状態はrepair_readyで、実行できるactionはrunかwaitのみ。Rootのread-only checkerによる確定診断は3件。Rootのread-only prototypeは全native出力のうち、残りが支持(support)のwhitelist 2ケースとOS・回帰だけであることを示している。repairの入力も揃っている(ROOT_REPAIR_INPUT-r04.json、12020B)。修正範囲は検証側に限定できるので、通常のrepair runを行う。used5は保持し、上限・分母・仕様は変えない。\n\n【workerへの具体指示】\n0) 最初にdocs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.jsonを全文読む。実際のサンプル・counter値・根拠行もここから取る。既存の成果は再実装しない。tools/check_p08_migration_evidence.pyは該当する関数だけ読む。domain契約はscripts内のSupplyLogisticsBridge 508-530行とWorldService 652-670行だけを読み取り専用で確認する。\n1) 型付き値のparse: Vector3i/Vector2i/Vector3/Vector2などのGodot型付き文字列を、構築子名・次元数・各成分の数値で厳密にparseする。eval、ast.literal_eval、括弧を外すだけの処理は使わない。次の不正はすべて検出して欠陥として出す:\n・構築子名の誤り\n・成分数の誤り\n・整数型に非整数(float)が入っている\n・空の成分\n・後ろに余計な文字が続く\n2) 物流の相互参照: facility.inventory:<facility_ID>形式のcanonical endpointは、宛先inventoryが初入庫まで作られない(lazy)場合でも有効とする。ただし、その施設IDがactual stateに実在することが条件。状態ごとに次を確認する:\n・RESERVED: carrierは空で、reservationが要求・荷物・数量と相互に一致する\n・DELIVERING: carrierが実在のactorを指し、そのactorが荷物を持ち、reservationと一致する\n・孤立したendpoint、予約の欠損、状態とcarrierの組合せ矛盾は欠陥とする\n3) 支持(support)のepoch: world.supportのrevisionは独立した+1 counterではなく、WorldServiceのglobal revision epochとして導出する。導出条件は根拠に基づいて厳密に書く。例えば、after側のsupport revisionがafter側のworld revisionと等しく、old側の値以上であり、占有の遷移と整合していること。根拠の実測値はold(world144/occupancy144/support142)とafter(150/150/150)。拒否ケースではworld/occupancy/supportが全部不変であることを要求する。wildcard、基準の緩和、キーを外す対応は禁止。\n4) tools/test_p08_migration_evidence.pyに追加するテスト:\n・現実形式(r04のactualサンプル)の正常例が通る\n・次の局所的な否定例がそれぞれ固有の欠陥で失敗する:不正Vector、孤立endpoint、RESERVEDの予約欠損、DELIVERINGのcarrier不整合、支持epoch矛盾、拒否時のrevision変化\n5) 変更しないもの: fixture、Runtime、native collector、原入力、6条件、30/88、README(試験中も含む)、run_all_tests.py、既存のmarker。\n6) 編集が終わったらsourceを固定し、python -Bでast.parseと局所testsを実行する。metadataを同期する場合は、テスト証拠が及ぶ範囲を明記する。古いSHAを最終版へ黙って付け替えない。既存の版を再利用するなら、input一致の証明と実SHAの保持を必須とする。\n7) statusはsubmittedで返す。未完了の項目を列挙すること:支持whitelist 2ケース、Rootによるwriterのsemantic早期検査、全native最終版1回、通常OS操作(manual path user://saves/manual_ui、private_project 2 fieldの可逆overrideのみ)、最終全回帰、独立Opus review、統合。自分で合格と宣言しない。",
"codex_model": "gpt-6-astra",
"codex_model_reason": "今回はgpt-6-astraを選ぶ。理由は3つ。\n・意味的な検査を複数同時に厳密に書く必要がある:RESERVED/DELIVERINGの状態別crosslink、lazy endpointの有効性条件、global revision epochからの導出条件。evalを使わない型付きparseも含む。\n・修正はすでに5回目。checkerの判断を誤ると、高コストなAPFS clone全native validateをもう1回無駄にする。\n・失敗を避けるには、基準を緩めずに厳密な条件を書けることが最も重要。\n修正範囲は検証側の2ファイルに限られ、入力もcompactなので、時間とusageの増加は限定的と判断した。"
},
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/c0b5e6a2085c4190a5ed3f6c3d67557c"
}
@@ -0,0 +1,10 @@
{
"decision": {
"action": "run",
"task_id": "P08-02",
"reason": "状態はrepair_readyで、上限はnull(used6)。正規validate db010bb7はreject済みで、原因は実旧nativeのsource-map/commit不一致と、OS・final regressionの未実施。これらはRoot側の工程なので、workerの残作業は1545bd42上のmetadataが古い問題の解消だけ。同じworktreeで通常runを行い、成果を保持する。\n\n【範囲】\n変更してよいのは docs/plans/original-completion-2026-10-02/execution/p08-02/ 配下だけ。対象は implementation.json、ROOT_REPAIR_INPUT-r04.json(今回はhandoffへの追記のみ)、CONDITION_MATRIX.md(必要な箇所だけ)。\ntools、tests、fixtures、README、.uid、completion_marker_contracts.json、Runtime、collector はすべて変更禁止。\n\n【手順】\n1) 最初に ROOT_REPAIR_INPUT-r04.json(約2683B)を読み、metadataの正しい出典として扱う。\n2) implementation.json を同期する。\n (a) outputs に残っている旧checkerのSHAを、1545bd42上の tools/check_p08_migration_evidence.py と tools/test_p08_migration_evidence.py の現在のraw SHA-256に置き換える。git show 1545bd42:path の生bytesで再計算すること。\n (b) local_checks の「2 tests 33.523」を、実際のAstra run結果「4 tests 61.547s・source固定PASS」に置き換え、scopeは局所checker testsに限ると明記する。\n (c) current generator ref のSHAを現在の値に更新する。\n (d) 旧inline exampleの全文は削除せず、Git 1545bd42 の raw SHA を参照する形で完全に保持する。そのうえで generator参照へのcompact化は可。\n (e) historical links にある歴史SHAは書き換えない。\n3) ROOT_REPAIR_INPUT-r04.json には次を記録する。\n - 2 toolの新SHA(exact)\n - Astra run と tests 61.547s の scope\n - 旧native 592 で retained 8 PASS(not final と明記)\n - 現在の1545実データでの preflight 4 PASS(not acceptance と明記)\n - Git 1545 に保全済みの全文の input SHA\n - 正規validate db010bb7 のreject理由:source-map/commit不一致と、未OS・未reg。旧SHAの付け替えはしていないことも明記する。\n4) CONDITION_MATRIX.md は、古い counter・crosslink・型の説明がある場合に限り、その説明文だけを同期する。条件、分母、判定は変えない。\n5) 新入力のSourceMapは docs を除外するので、metadata-only の変更では source refs が変わらないことを確認して記載する。\n\n【workerが行う検証】\n- 変更したJSONが全てparseできること\n- 記載したhashが git show の生bytesと一致すること\n- generator refs が実在すること\n- 完全な18 source packetが500k未満であることを実測すること\n\nchecker、native、full regression、Godot はworkerで重複実行しない。gitへの書き込みも禁止。\n\n【提出後(Root)】\nwriter semantic preflight → 最終native全体を1回 → OS収集 → final regression → independent review。受入分母・仕様・予算・main統合は変えない。",
"codex_model": "gpt-6.1-sol",
"codex_model_reason": "今回はmetadata-onlyのdoc同期で、作業はJSONのSHAと数値の置換、記述の小修正、hashの再計算に限られる。コードの設計や不具合解析は不要。難しい部分(checker修正と実データ検証)はAstraが1545bd42で完了済み。手順が明示的で機械的に検証できるので既定のsolで十分であり、時間と使用量も抑えられる。astraを選ぶ根拠はない。"
},
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/79a52e42ac7940148fcb6c79308b235f"
}
@@ -0,0 +1,10 @@
{
"decision": {
"action": "wait",
"task_id": "P08-02",
"reason": "状態はsubmittedのまま変えない。オーナー指示どおり、今回はwaitで計画だけを返す。Rootは592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9のnative ON収集を続ける(OFF側success/refusal writer/reader 4件はPID 76298/76782/77272/77769でPASS済み)。formal validateは、ON・OS proof・最終full regressionがすべて揃ってから行う。\n\nOS手順の計画:\n(0) 事前確認(読取のみ): 実ソースのscenes/main/main.gdとSaveLoadMenuで次を確認し、manifestに行番号と抜粋のハッシュを記録する。読込▾のid1「旧寸法を修正して読み込む」、dimension_migration_dialog、「戻る」/「修正して読込」、manual保存先user://saves/manual。食い違いがあれば推測せずに止めて報告する。\n(1) stage: Controller native_OSのcopy-sourceでstageを作る。stage全体のSHAがcandidateのsource mapと完全一致することを確認し、current_source_pinsとも照合する。observerのP24-02 passive_os_controls_recorder_r05.gdはstageの外に置く。godot --scriptのSceneTreeとして起動してmain.tscnを実instantiateし、project.godot・autoload・製品sceneは変更しない。observerは別Nodeとして_input/_process、save_operation_completed、snapshot_publishedを受動的に記録し、root frameのPNGとModel bytesのハッシュを読むだけにする。UIやRuntimeのメソッドは呼ばない。\n(2) profile: fresh private profileを作り、protected default profileにはinventory before/afterで触れていないことを示す。native caseの旧保存Aの正式4memberを、新private profileのuser://saves/manualへコピーし、full SHAを照合する。別に保全した旧保存BのSHAも記録する。setup=controlled_old_saveと明示し、自然な製品経路とは書かない。\n(3) 起動: Godotは1プロセスだけ。新しいworkerは起動しない。PID、version、開始時刻、終了時刻、exit codeを記録する。\n(4) 入力: 実ウィンドウのPIDを対象に、CGEvent/osascriptでmouseとkeyboardを送る。各送信のtarget PIDとtimestamp_nsを記録し、observerの_input receiptと時刻窓で突き合わせる。操作順は以下で、各前後にscreen、Model hash、formal member hashを実ファイルで取る。\n - preview: 読込▾ → id1 → dialog表示。\n - cancel: 「戻る」。previewとcancelの前後でformal hashが同一であること。\n - confirm: 再度previewしてから「修正して読込」。formal saveとbackupの変化、save_operation_completedとsnapshot_publishedを記録する。\n - changed_source_rejection: 新しいprivate profileでAをpreviewした後、Rootがprivate manualを保全済みのBへ差し替える。差し替えはcontrolled inputとして記録する。OS confirmの直前と直後でBのformal hashが同一であること、main error statusが出て読込が起きない(load由来のsnapshot_publishedなし、Model不変)ことを記録する。\n(5) 後始末と結合: 自分が起動したprocessを片付け、終了後にinventoryを取り直す。manifestにcurrent commit、source map、input_method=native_os、actions順、各receipt/screen/model/formalのパスとSHAを入れ、p08-evidence index.jsonのOS proof欄に結合する。既存のnative receipt形式を再利用し、新しいobserverは作らない。\n\n許容性: observerは診断用helperであり、candidateの製品ソースもallowed_pathsも変更しない。ただしsource overrideにはしない。--scriptはstageの外から読み込み、stage全体のSHAとは別にobserverのSHAをmanifestに記録する。proofのPASSは自己申告のbooleanではなく、実ファイルとPIDで示す。全ケースが同じcandidate SHAで取られていることも確認する。signalで発火させたUIテストはhandlerの証拠としてのみ扱う。仕様、受入分母、予算、mainへの統合は変更せず、提案にも含めない。",
"codex_model": "gpt-6.1-sol",
"codex_model_reason": "今回はRootでの収集を待つだけで、Codex workerの実行も修理もない。上位モデルが必要になる難しさの根拠がないため、品質・時間・使用量を考えて既定のgpt-6.1-solのままにする。"
},
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/7af9f711058745788b0459e3db4859bf"
}
@@ -0,0 +1,7 @@
{
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
"manual_path_actual": "user://saves/manual_ui",
"source": "scenes/main/main.gd:29",
"private_profile": "Reuse approved private_project helper; only two private userdata project settings changed in isolated stage, whole original bytes restored and all other source verified. Record explicit reversible override, not canonical unchanged project setting claim.",
"observer": "Root diagnostic outside stage, passive read/capture only; no UI/Runtime mutation."
}
@@ -0,0 +1,9 @@
{
"source": "/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p08-02-native-mixed-r05",
"registered_allocation": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p08-02/.runtime/P08-02/build/p08-evidence",
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
"method": "APFS CoW separate inodes; every manifest file whole SHA/size verified; exact index bytes retained",
"files": 2882,
"elapsed_seconds": 34.907463249750435,
"accepted": false
}
@@ -0,0 +1,79 @@
{
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
"scope": "Root readonly decoder prototype diagnostic; no candidate code or actual evidence altered; no acceptance",
"observations": [
{
"case": "off-positive",
"jobs": [
{
"id": "construction_00000015",
"state": "BUILDING",
"progress": 1266
},
{
"id": "construction_00000016",
"state": "PLANNED",
"progress": 0
}
],
"starting_job": "construction_00000016"
},
{
"case": "off-refusal",
"jobs": [
{
"id": "construction_00000015",
"state": "BUILDING",
"progress": 1266
},
{
"id": "construction_00000016",
"state": "PLANNED",
"progress": 0
}
],
"starting_job": "construction_00000016"
},
{
"case": "on-positive",
"jobs": [
{
"id": "construction_00000015",
"state": "BUILDING",
"progress": 1266
},
{
"id": "construction_00000016",
"state": "PLANNED",
"progress": 0
}
],
"starting_job": "construction_00000016"
},
{
"case": "on-refusal",
"jobs": [
{
"id": "construction_00000015",
"state": "BUILDING",
"progress": 1266
},
{
"id": "construction_00000016",
"state": "PLANNED",
"progress": 0
}
],
"starting_job": "construction_00000016"
}
],
"remaining_deficits": [
"OS proof: unsafe artifact path",
"case off-positive: orphan cargo endpoint",
"case off-refusal: orphan cargo endpoint",
"case on-positive: orphan cargo endpoint",
"case on-refusal: orphan cargo endpoint",
"final regression: unsafe artifact path"
],
"elapsed_seconds": 16.21374141704291
}
@@ -0,0 +1,37 @@
"""Execute unchanged submitted P08 collector with storage-only closed-phase hook."""
from pathlib import Path
import importlib.util,argparse,json,sys,hashlib
sys.dont_write_bytecode=True
parser=argparse.ArgumentParser();parser.add_argument('--commit',required=True);parser.add_argument('--output',type=Path,required=True);args=parser.parse_args()
root=Path('/Users/korizatou114/Projects/TheTree-managed-worktrees/p08-02/P08-02-implement')
coordinator=Path(__file__).resolve().parent
def load(name,path):
s=importlib.util.spec_from_file_location(name,path);m=importlib.util.module_from_spec(s);s.loader.exec_module(m);return m
driver=load('submitted_p08_collector',root/'tools/run_p08_mixed_migration.py')
storage=load('closed_phase_storage',coordinator/'native_storage_guard.py')
original_module=driver.module
traces=[]
def module(name,path):
m=original_module(name,path)
if name=='p08_standard_runner':
original=m.execute_phase
def execute(*values,**kwargs):
row=original(*values,**kwargs)
print(json.dumps({'phase':values[3],'passed':row['passed'],'actual_pid':row.get('actual_pid'),'exit':row.get('exit_code'),'error':row.get('validation_error'),'guard_error':row.get('guard_error')}),flush=True)
if row['passed']:
changes=storage.compress_closed_phase(args.output,row)
traces.append({'phase':values[3],'ended_native_pid':row.get('actual_pid'),'storage_only_changes':changes})
print(json.dumps({'phase':values[3],'closed_proofs_preserved_and_compressed':len(changes)}),flush=True)
return row
m.execute_phase=execute
return m
driver.module=module
native=argparse.Namespace(project_root=root,output=args.output.resolve(),commit=args.commit,godot='/Applications/Godot.app/Contents/MacOS/Godot',collect=True)
code=driver.collect(native)
record={'target_commit':args.commit,'native_exit':code,'storage_helper_sha256':hashlib.sha256((coordinator/'native_storage_guard.py').read_bytes()).hexdigest(),'collector_sha256':hashlib.sha256((root/'tools/run_p08_mixed_migration.py').read_bytes()).hexdigest(),'snapshots_or_timepoints_omitted':0,'worker_source_changed':False,'phase_traces':traces,'original_unit_accepted':False}
(coordinator/'NATIVE_STORAGE_COLLECTION_TRACE.json').write_text(json.dumps(record,ensure_ascii=False,indent=2)+'\n')
raise SystemExit(code)
@@ -0,0 +1,36 @@
"""Coordinator storage only: transparently compress closed, owned phase proof files.
No snapshot/timepoint filtering. No changes to worker code or native outputs.
Call only after execute_phase returned and actual engine_ended is true.
"""
from pathlib import Path
import hashlib,json,os,subprocess
def compress_closed_phase(out, phase_row):
if phase_row.get('engine_ended') is not True:
raise RuntimeError('Owned native child must actually have ended')
out=Path(out).resolve()
rows=[]
candidates=[p for p in sorted((out/'pairs').rglob('*')) if p.is_file() and not p.is_symlink() and p.suffix in ('.var','.json') and p.stat().st_size>=65536 and not p.stat().st_flags&32] if (out/'pairs').exists() else []
for offset in range(0,len(candidates),128):
probe=subprocess.run(['/usr/sbin/lsof','-t','--',*[str(p) for p in candidates[offset:offset+128]]],capture_output=True,text=True)
if probe.stdout.strip(): raise RuntimeError('Owned proof has open handles')
for p in candidates:
if not p.is_file() or p.is_symlink() or p.suffix not in ('.var','.json'):
continue
old=p.stat()
if old.st_flags & 32 or old.st_size < 65536: continue
data_sha=hashlib.sha256(p.read_bytes()).hexdigest()
temp=p.with_name(p.name+'.owned-hfs-temporary')
if temp.exists(): raise RuntimeError('Storage temporary collision')
subprocess.run(['/usr/bin/ditto','--hfsCompression','--noclone',str(p),str(temp)],check=True)
now=temp.stat()
if hashlib.sha256(temp.read_bytes()).hexdigest()!=data_sha or (now.st_size,now.st_mode,now.st_uid,now.st_gid,now.st_mtime_ns)!=(old.st_size,old.st_mode,old.st_uid,old.st_gid,old.st_mtime_ns):
raise RuntimeError('Content/metadata preservation failed')
if hashlib.sha256(p.read_bytes()).hexdigest()!=data_sha:
raise RuntimeError('Original proof changed before replacement')
os.replace(temp,p)
if hashlib.sha256(p.read_bytes()).hexdigest()!=data_sha:
raise RuntimeError('Post-replacement content mismatch')
rows.append({'path':str(p.relative_to(out)),'bytes':old.st_size,'sha256':data_sha,'physical_before':old.st_blocks*512,'physical_after':p.stat().st_blocks*512,'whole_bytes_and_mode_owner_mtime_preserved':True})
return rows