docs: reclaim storage while preserving acceptance evidence bytes

This commit is contained in:
Takeshi
2026-10-14 21:57:01 +09:00
parent 4da2bb492b
commit 5f9d7c49e8
15 changed files with 284 additions and 14 deletions
@@ -1,26 +1,25 @@
{
"plan_id": "P08-03",
"status": "in_progress",
"candidate": "47750010b8694c72737f47078f925b2b2e22ce59",
"executed_native_source": "31aa7706d28a92e313c432d5a62e35d19503dcc3",
"candidate": "fd643f55a271e63c554b94681454d357117c0678",
"executed_native_source": "fd643f55a271e63c554b94681454d357117c0678",
"formal_acceptance": false,
"independent_review": "INDEPENDENT_SEMANTIC_REVIEW_7E2368F5.json",
"review_status": "changes_requested",
"missing": [
"Main must display real rejection reason consistently",
"changed-product native/OS recollection after batch freezes",
"all mandatory regression suites on exact final candidate; derive actual roster after added targeted test",
"independent raw-proof read and all-five-condition review",
"integration, current final verification and ledger closure"
"Complete ordinary OS evidence for all five conditions on fd643",
"All176 mandatory regression suites on exact final candidate",
"Independent raw-proof review of all five conditions",
"Integration, final verification and ledger closure"
],
"next_unit": "P08-03",
"denominator_packages": 30,
"denominator_units": 88,
"completed_delta": 0,
"formal_validator": "7e2368f5 only historical after changed Main/checker; current 4775001 not accepted",
"next_action": "actual Opus RUN dispatched; one6.1-sol worker repair18 on accepted input7297a4c; no native/full until frozen",
"formal_validator": "Pending for fd643; historical reviews are not acceptance for changed candidate.",
"next_action": "Continue ordinary OS verification on frozen repair18; reuse exact-source R10 import/parse/native writer+reader. R12 partial OS collection preserved and stopped for user-requested storage work. Then all176 mandatory suites, actual independent Opus all-five review and integration.",
"semantic_triage": "OPUS_PRE_FULL_SEMANTIC_TRIAGE_7E2368F5.json (read-only, no acceptance)",
"capacity": "cleanup complete: 7.94GB free; 580 closed stages archived losslessly; current R09 retained; no cleanup permission pending",
"capacity": "Followup complete: 10.32GB (9.61GiB) free; 42967 files byte-preserved; errors0. See ../storage-20261014/FOLLOWUP_FINAL_RESULT.json. No permission pending.",
"implemented_not_accepted": [
"readable demolition outcome/refund from real result",
"checker rejects preview/command disagreement; 128 local Python controls executed"
@@ -36,11 +35,15 @@
"submitted": "12f04d6e8bfdba59e44d81e8e34c57b67efbefeb",
"repairs": 2,
"native_result": "four guarded suites pass 80/67/105/50, all eight native purity pairs unchanged",
"next": "accepted-main dependency adoption and existing P08 UI connection",
"next": "Supporting component already completed; continue original P08 UI OS evidence, mandatory176 regression and independent all-five review.",
"review": "INDEPENDENT_RUNTIME_SUPPORT_REVIEW_12F04D6.json",
"integration": "dbf5cb8f1bec88b259d010d8f3ce0486b1cfeaa3"
},
"accepted_input_commit": "7297a4c050f0293df331c2c51bb32def248bcb6b",
"repair_attempt": 18,
"editable_paths_added": 0
"editable_paths_added": 0,
"worker_state": "terminal_submitted; gpt-6.1-sol repair18 ended; no worker restarted",
"local_tests": "131 passed; local tests do not close original unit",
"native_handler_evidence": "artifacts/original-completion-2026-10-02/p08-03-native-os-fd643f5-r10/index.json",
"os_partial_evidence": "artifacts/original-completion-2026-10-02/p08-03-native-os-fd643f5-r12-framed; no acceptance claim"
}
@@ -23,7 +23,7 @@
"scope_relationship": "RP24 bounded-shell acceptance is independent; not a dependency of Codex functional implementation"
},
"Claude_auth": "existing native subscription client automatically renewed; actual authenticated request success, no user login wait or API fallback",
"P08_03_next": "Exact4a17 native complete; real OS bootstrap/full actions remain pending and require capacity500MiB before spawn",
"P08_03_next": "fd643 repair18 submitted; exact-source R10 native pair completed. Ordinary OS evidence partial and preserved; storage maintenance requested by user is current work. Resume one original P08-03 task without new worker.",
"current_game_sources_changed": false,
"permission_request": "still pending and not applied; independent source work does not require marker command",
"UI_worker_method": "Codex functional implementation selected by actual Opus; Antigravity read-only visual review",
@@ -130,5 +130,11 @@
"session": 3533,
"submitted": "4a17d0e0f589fa76d81f1d4630ec45c63bac21ab",
"local_tests": 56
}
},
"latest_candidate": "fd643f55a271e63c554b94681454d357117c0678",
"current_worker_state": "terminal_submitted; no active worker",
"latest_model": "gpt-6.1-sol",
"original_unit_completed_delta": 0,
"current_storage_work": "../storage-20261014/TRANSPARENT_COMPRESSION_RESULT.json and SMALL_RESULT; all retained content SHA verified",
"historical_records_note": "Earlier run fields below are retained history. latest_candidate/current_worker_state/P08_03_next govern current continuation."
}
@@ -0,0 +1,9 @@
{
"cloned_files": 26808,
"unique_contents": 16138,
"free_before_bytes": 10291994624,
"free_after_bytes": 10304966656,
"errors": [],
"elapsed_seconds": 417.76647186279297,
"method": "APFS copy-on-write clones, not hardlinks. Paths and content SHA preserved; later writes remain independent."
}
@@ -0,0 +1,26 @@
# 容量の追加整理と増加原因
2026-10-14。今回の追加整理で空き容量は3.66GB(3.41GiB)から10.32GB(9.61GiB)へ回復した。追加純回復は6.66GB(6.20GiB)。今日の最初の測定0.38GBからの純回復は9.94GB。背景のOS処理も動くため、ファイル表示量の合計を物理回収量とは扱わない。
| 対象 | 追加整理前の表示量 | 対応・注意 |
| --- | ---: | --- |
| 制作側の証跡 | 約19.9GiB | 状態スナップショット・試験記録・配布候補が多数。閉じたソース580コピーの整理後も生証跡が残っていた。 |
| 旧配布ビルド | 約4.7GiB | 内容と通常パスを維持して透過圧縮。 |
| Runner作業場所 | 約15.3GiB | 履歴ビルド・レビュー資料にも重複コピーがある。透過圧縮。 |
| OSスワップ | 9GiB確保、約8.1GiB使用 | 使用中のため直接削除せず保持。 |
| シミュレータ端末データ | 約5.9GiB | iPhone17が起動中。保持。 |
| OS更新パッケージ | 約2.5GiB | 更新処理が動作中。保持。 |
APFSの共有領域とマウントされたシミュレータは、表示量に重複がある。これらを単純合計しない。Dataのローカルスナップショットは0件。整理中にApp Storeの開いたパッケージに約3.08GBの割当を実測し、その後このキャッシュは0となった。原測定はFOLLOWUP_CAUSE_MEASUREMENTS.jsonとCONCURRENT_APPSTORE_DOWNLOAD.jsonに保存した。
以前のアプリ削除直後のディレクトリ別測定がないため、過去の20GB全部を正確に特定の原因へ割り当てたとは主張しない。ただし、検証のコピー・状態記録の蓄積、旧ビルド、OSスワップ、更新書込を現在の増加源として実測した。アプリ本体を消しても、これらの別領域は残る。
42,967ファイルをAPFS透過圧縮した。ファイル名、通常の読込、バイト内容を維持し、各ファイルの圧縮前・圧縮後・置換前・置換後のSHAを照合した。内容不一致0、処理エラー0。使用中ファイルと直近書込は除外した。目安128MiB以下、最大512ファイルの小分けで処理し、一時コピーは各バッチ終了時に除去した。
同一内容26,808件への共有コピー処理もSHA照合エラー0。ただし追加純回収は約13MB、所要約7分で効率は低かった。今後の必須工程にはしない。容量回収は透過圧縮、終了した生成キャッシュの整理、不要な常設コピーの防止を先に行う。
処理一覧自体も約32.9MBから約3.9MBへgzip圧縮し、全解凍内容のSHA一致を検証した。表示用にはこの分析要約を残し、監査用の一覧は圧縮保管する。有効な受入証跡の内容、原本、完成素材、ライセンス、プレイヤーの保存は失っていない。使用中モデル、ツール用ランタイム、起動中シミュレータ、OS更新は保持した。
P08-03は同一候補fd643f55a271e63c554b94681454d357117c0678が提出済み。131局所試験とR10の同一版native writer/readerを再利用できる。R12の通常OS入力の途中記録は保持し、容量整理を優先するため専用収集プロセスを終了した。通常OS入力の全条件、最終176回帰、独立Opusレビュー、統合と最終確認は未完了。新しい実装担当や重複全回帰は起動していない。
原30パッケージ/88成果単位の完了加算は0、正式完了11件を維持する。次に閉じる原項目はP08-03。容量整理をゲームの受入完了の代わりにしない。
@@ -0,0 +1,40 @@
{
"time": "2026-10-14T21:46:02.469061",
"free_at_followup_start_bytes": 3661324288,
"observed_storage_kib": {
"production_artifacts_before_followup": 20862552,
"production_build": 4908804,
"managed_worktrees": 15993364,
"user_simulator_device_data": 6236360,
"hf_active_model_cache": 7367464,
"codex_bundled_runtime": 1597860,
"system_update_packages": 2610044
},
"vm_swap_total_MiB": 9216,
"vm_swap_used_MiB": 8325.5,
"confirmed_growth_mechanisms": [
"Repeated native snapshots and raw state records",
"Old release builds held concurrently",
"Runner runtime and independent review material contain repeated source/build records",
"Operating system swap files; 9 GiB allocated, 8.1 GiB currently used",
"Observed active App Store package allocated 3.08 GB during earlier cleanup; no longer in that cache"
],
"historical_20GB_attribution": "No per-directory measurement immediately after the user app deletions; do not claim exact attribution of all historical20GB. Current causes and current reclaimed free space are measured.",
"measurement_limit": "du totals include APFS shared extents and may include mounted simulator volumes; not physically additive.",
"retained": [
"Game original specifications, assets, saves, accepted and pending acceptance evidence byte contents",
"Active model and bundled tool runtime",
"Booted simulator and system updates"
],
"cleanup_permission": "User explicitly authorized storage recovery and evidence summarization. No new permission wait.",
"apfs_main_container_free_bytes_observed": 10695372800,
"apfs_volumes_consumed_bytes": {
"System": 12638564352,
"Preboot": 8976842752,
"Recovery": 1284190208,
"Data": 201700458496,
"VM": 9666527232
},
"data_local_snapshot_count": 0,
"simulator_volume_warning": "17.6 GB iOS simulator volume is mounted; do not double count its unpacked mounted contents as additional host filesystem use."
}
@@ -0,0 +1,37 @@
{
"time": "2026-10-14T21:54:39.509973",
"free_before_followup_bytes": 3661324288,
"free_after_followup_bytes": 10323677184,
"net_followup_free_increase_bytes": 6662352896,
"earliest_today_free_bytes": 381820928,
"net_free_increase_since_earliest_today_bytes": 9941856256,
"unique_compressed_file_paths": 42967,
"compression_errors": 0,
"content_mismatches": 0,
"receipt_archives": [
{
"file": "TRANSPARENT_COMPRESSION_FILES.jsonl.gz",
"original_bytes": 1272112,
"compressed_bytes": 165049,
"uncompressed_sha256": "6bc3d384f06bfee0edfd38affb9d78e666f8e968aed5e432de1a205f56f7faf6",
"roundtrip_verified": true
},
{
"file": "TRANSPARENT_COMPRESSION_SMALL_FILES.jsonl.gz",
"original_bytes": 16905515,
"compressed_bytes": 2377146,
"uncompressed_sha256": "3647776c7378d880f21cc23e2f22caa03451cc4f3635076eb8b3c9289e88c9ad",
"roundtrip_verified": true
},
{
"file": "CONTENT_CLONE_DEDUP_FILES.jsonl.gz",
"original_bytes": 14732080,
"compressed_bytes": 1352979,
"uncompressed_sha256": "0381806948fd7e32306df084124c10e472d86003e616b75c8113e9379613fe42",
"roundtrip_verified": true
}
],
"byte_contents_removed_from_acceptance_evidence": 0,
"game_original_unit_completed_delta": 0,
"next_game_original_unit": "P08-03"
}
@@ -15,3 +15,13 @@
今後の保管方針: 終了した検証でソース一式を毎回常設しない。同じ候補・設定・入力は一つの凍結参照を使う。独立した実行結果と生入力・計測は残す。旧コピーを整理する際はこの方式で内容保持を先に検証し、原路径からの復元一覧を残す。最新の受入証跡は要約だけで置換しない。
30パッケージ/88成果単位と受入条件は変更しない。容量整理はゲームの受入完了ではない。
## 追加整理: 透過圧縮と共有
旧証跡・旧配布ビルド・終了したRunner資料は、APFS透過圧縮を使って同じパスと内容SHAを維持する。毎ファイルの圧縮前、圧縮後、置換前、置換後を照合する。使用中ファイルと直近書込は除外する。大量の小ファイルは128MiB以下を目安にまとめ、512ファイルごとに検証して一時コピーを片付ける。
同一内容の閉じた証跡はAPFSのcopy-on-write cloneで共有する。ハードリンクで常設共有しない。変更したコピーが元を変更しないことを実際の書込で確認した。通常の読み込み、SHA検証、既存証跡のパスを維持する。最新の受入条件や原30/88を変更しない。
TRANSPARENT_COMPRESSION_RESULT.json / SMALL_RESULT.json と CONTENT_CLONE_DEDUP_RESULT.json に実測結果を保存する。FILES.jsonl.gzは内容SHAと対象パスの最小記録であり、分析の要約は FOLLOWUP_ANALYSIS.md に保存する。検証のために常設するソースの重複コピーは増やさず、終了時に生成キャッシュを整理し、必要な生証跡を圧縮する。
実測の注意: 共有コピー整理の追加純回収は約13MBで、約7分を要した。今後は常用の必須工程にせず、容量回収はまず透過圧縮と終了した生成キャッシュの整理を優先する。今回の実行記録はgzipで全内容の復元SHAを検証し、表示用には分析要約だけを残す。
@@ -0,0 +1,16 @@
{
"started": "2026-10-14T21:33:08.578964",
"ended": "2026-10-14T21:35:51.517976",
"candidate_files": 3812,
"compressed_files": 3802,
"content_mismatches": [],
"errors": [],
"logical_bytes_preserved": 2627197170,
"allocated_before_sum": 2633801728,
"allocated_after_sum": 316567552,
"free_before_bytes": 3661324288,
"free_after_bytes": 5176586240,
"elapsed_seconds": 162.93938517570496,
"method": "APFS transparent compression; same paths and byte content; every file SHA checked before copy, after compressed copy, before atomic replacement and after replacement",
"physical_measurement_note": "Use net free-space increase, not summed APFS file blocks, as observed physical recovery."
}
@@ -0,0 +1,15 @@
{
"started": "2026-10-14T21:37:12.794504",
"ended": "2026-10-14T21:46:36.790384",
"candidate_files": 39956,
"compressed_files": 39165,
"errors": [],
"logical_bytes_preserved": 8853292891,
"allocated_before_sum": 8932560896,
"allocated_after_sum": 2114166784,
"free_before_bytes": 5183709184,
"free_after_bytes": 10297393152,
"elapsed_seconds": 564.000155210495,
"method": "APFS transparent compression in bounded batches; original paths and bytes maintained; per-file SHA verified before compression, compressed output, original before replacement and final file. Temporary hardlinks removed after every batch.",
"physical_measurement_note": "Net free-space change only; file-block totals can double count APFS shared extents."
}
@@ -0,0 +1,28 @@
import pathlib,os,hashlib,json,subprocess,shutil,time,gzip
R=pathlib.Path('/Users/korizatou114/Projects/とりにく工房ゲーム制作部');O=R/'docs/plans/original-completion-2026-10-02/execution/storage-20261014'
def sha(p):
h=hashlib.sha256()
with p.open('rb') as f:
for b in iter(lambda:f.read(1048576),b''):h.update(b)
return h.hexdigest()
start=time.time();before=shutil.disk_usage(R).free;canonical={};cloned=[];errors=[]
with (O/'CONTENT_CLONE_DEDUP_FILES.jsonl').open('w') as log:
for name in ['TRANSPARENT_COMPRESSION_FILES.jsonl','TRANSPARENT_COMPRESSION_SMALL_FILES.jsonl']:
for line in (gzip.open(O/(name+'.gz'),'rt') if not (O/name).exists() else (O/name).open()):
r=json.loads(line);p=pathlib.Path(r['path']);p=p if p.is_absolute() else R/p;key=r['sha256'];prior=canonical.get(key)
if prior is None:canonical[key]=p;continue
if prior==p or p.suffix in {'.exe','.wav','.pck'}:continue
tmp=p.with_name(p.name+'.storage-clone-tmp')
try:
if sha(prior)!=key or sha(p)!=key:raise RuntimeError('source changed; retain original')
subprocess.run(['cp','-c','-p',str(prior),str(tmp)],check=True,capture_output=True)
if sha(tmp)!=key:raise RuntimeError('clone content mismatch')
original=p.stat();os.chmod(tmp,original.st_mode&0o777);os.utime(tmp,ns=(original.st_atime_ns,original.st_mtime_ns))
os.replace(tmp,p)
if sha(p)!=key:raise RuntimeError('post-clone mismatch')
row={'path':str(p),'canonical':str(prior),'sha256':key,'verified':True};cloned.append(row);log.write(json.dumps(row)+'\n')
except Exception as e:
errors.append({'path':str(p),'error':str(e)})
if tmp.exists():tmp.unlink()
if len(cloned) and len(cloned)%2000==0:print(json.dumps({'cloned':len(cloned),'free_GiB':round(shutil.disk_usage(R).free/2**30,2)}),flush=True)
result={'cloned_files':len(cloned),'unique_contents':len(canonical),'free_before_bytes':before,'free_after_bytes':shutil.disk_usage(R).free,'errors':errors,'elapsed_seconds':time.time()-start,'method':'APFS copy-on-write clones, not hardlinks. Paths and content SHA preserved; later writes remain independent.'};(O/'CONTENT_CLONE_DEDUP_RESULT.json').write_text(json.dumps(result,indent=2));print(json.dumps(result),flush=True)
@@ -0,0 +1,37 @@
import pathlib,os,hashlib,subprocess,json,time,shutil,datetime
R=pathlib.Path('/Users/korizatou114/Projects/とりにく工房ゲーム制作部'); A=R/'artifacts'; O=R/'docs/plans/original-completion-2026-10-02/execution/storage-20261014'; O.mkdir(exist_ok=True)
EXT={'.json','.var','.varbytes','.variant','.bin','.log','.txt','.jsonl','.csv'}
def sha(p):
h=hashlib.sha256()
with p.open('rb') as f:
for b in iter(lambda:f.read(1024*1024),b''):h.update(b)
return h.hexdigest()
opened=subprocess.run(['lsof','-n','-F','n'],capture_output=True,text=True).stdout.splitlines(); opened={p[1:] for p in opened if p.startswith('n/')}
start=time.time();free=shutil.disk_usage(R).free;rows=[];errors=[];candidates=[]
for p in A.rglob('*'):
if p.suffix not in EXT or p.is_symlink() or not p.is_file():continue
s=p.stat()
if s.st_size<262144 or s.st_blocks*512<s.st_size*.5 or s.st_mtime>start-120 or str(p) in opened or s.st_nlink>1:continue
candidates.append((s.st_size,p))
candidates.sort(reverse=True);print(json.dumps({'candidate_files':len(candidates),'logical_bytes':sum(x[0] for x in candidates),'free_before':free}),flush=True)
receipt=O/'TRANSPARENT_COMPRESSION_FILES.jsonl'
with receipt.open('w') as log:
for _,p in candidates:
tmp=p.with_name(p.name+'.storage-compress-tmp')
try:
s=p.stat();before=sha(p)
subprocess.run(['ditto','--hfsCompression',str(p),str(tmp)],check=True,capture_output=True)
t=tmp.stat()
if sha(tmp)!=before:raise RuntimeError('compressed content mismatch')
now=p.stat()
if now.st_mtime_ns!=s.st_mtime_ns or now.st_size!=s.st_size or sha(p)!=before:raise RuntimeError('source changed')
if t.st_blocks*512>=s.st_blocks*512*.9:tmp.unlink();continue
os.replace(tmp,p)
if sha(p)!=before:raise RuntimeError('post-replacement mismatch')
row={'path':str(p.relative_to(R)),'sha256':before,'bytes':s.st_size,'allocated_before':s.st_blocks*512,'allocated_after':p.stat().st_blocks*512,'mtime_ns':s.st_mtime_ns,'content_verified':True};log.write(json.dumps(row)+'\n');log.flush();rows.append(row)
except Exception as e:
errors.append({'path':str(p),'error':str(e)})
if tmp.exists():tmp.unlink()
if len(rows) and len(rows)%100==0:print(json.dumps({'completed':len(rows),'free_GiB':round(shutil.disk_usage(R).free/2**30,2),'elapsed_s':round(time.time()-start)}),flush=True)
result={'started':datetime.datetime.fromtimestamp(start).isoformat(),'ended':datetime.datetime.now().isoformat(),'candidate_files':len(candidates),'compressed_files':len(rows),'content_mismatches':[e for e in errors if 'mismatch' in e['error']],'errors':errors,'logical_bytes_preserved':sum(r['bytes'] for r in rows),'allocated_before_sum':sum(r['allocated_before'] for r in rows),'allocated_after_sum':sum(r['allocated_after'] for r in rows),'free_before_bytes':free,'free_after_bytes':shutil.disk_usage(R).free,'elapsed_seconds':time.time()-start,'method':'APFS transparent compression; same paths and byte content; every file SHA checked before copy, after compressed copy, before atomic replacement and after replacement','physical_measurement_note':'Use net free-space increase, not summed APFS file blocks, as observed physical recovery.'}
(O/'TRANSPARENT_COMPRESSION_RESULT.json').write_text(json.dumps(result,indent=2));print(json.dumps(result),flush=True)
@@ -0,0 +1,43 @@
import pathlib,os,hashlib,subprocess,json,time,shutil,datetime,itertools,tempfile
R=pathlib.Path('/Users/korizatou114/Projects/とりにく工房ゲーム制作部');O=R/'docs/plans/original-completion-2026-10-02/execution/storage-20261014'
roots=[R/'artifacts',R/'build',pathlib.Path('/Users/korizatou114/Projects/TheTree-managed-worktrees'),pathlib.Path('/Users/korizatou114/.codex/sessions'),pathlib.Path('/Users/korizatou114/Library/Logs/com.openai.codex')]
EXT={'.json','.var','.varbytes','.variant','.bin','.log','.txt','.jsonl','.csv','.exe','.pck','.wav'}
def sha(p):
h=hashlib.sha256()
with p.open('rb') as f:
for b in iter(lambda:f.read(1048576),b''):h.update(b)
return h.hexdigest()
opened={s[1:] for s in subprocess.run(['lsof','-n','-F','n'],capture_output=True,text=True).stdout.splitlines() if s.startswith('n/')}
start=time.time();free=shutil.disk_usage(R).free;rows=[];errors=[];candidates=[]
for p in itertools.chain.from_iterable(q.rglob('*') for q in roots):
if p.suffix not in EXT or p.is_symlink() or not p.is_file():continue
s=p.stat()
if s.st_size<16384 or s.st_blocks*512<s.st_size*.5 or s.st_mtime>start-120 or str(p) in opened or s.st_nlink>1:continue
candidates.append(p)
print(json.dumps({'candidate_files':len(candidates),'logical_bytes':sum(p.stat().st_size for p in candidates),'free_before':free}),flush=True)
with (O/'TRANSPARENT_COMPRESSION_SMALL_FILES.jsonl').open('w') as log:
cursor=0
while cursor<len(candidates):
with tempfile.TemporaryDirectory(prefix='tree-compression-batch-',dir='/private/tmp') as scratch:
inp=pathlib.Path(scratch)/'input';out=pathlib.Path(scratch)/'compressed';inp.mkdir();batch=[];size=0
while cursor<len(candidates) and len(batch)<512 and size<128*1024*1024:
p=candidates[cursor];cursor+=1
try:
s=p.stat();digest=sha(p);name=str(len(batch));os.link(p,inp/name);batch.append((p,s,digest,name));size+=s.st_size
except Exception as e:errors.append({'path':str(p),'error':str(e)})
subprocess.run(['ditto','--hfsCompression',str(inp),str(out)],check=True,capture_output=True)
for p,s,digest,name in batch:
t=out/name
try:
now=p.stat()
if sha(t)!=digest:raise RuntimeError('compressed content mismatch')
if now.st_mtime_ns!=s.st_mtime_ns or now.st_size!=s.st_size or sha(p)!=digest:raise RuntimeError('source changed')
if t.stat().st_blocks*512>=s.st_blocks*512*.9:continue
os.replace(t,p)
if sha(p)!=digest:raise RuntimeError('post-replacement mismatch')
row={'path':str(p),'sha256':digest,'bytes':s.st_size,'allocated_before':s.st_blocks*512,'allocated_after':p.stat().st_blocks*512,'mtime_ns':s.st_mtime_ns,'content_verified':True};log.write(json.dumps(row)+'\n');rows.append(row)
except Exception as e:errors.append({'path':str(p),'error':str(e)})
log.flush()
print(json.dumps({'processed':cursor,'compressed':len(rows),'free_GiB':round(shutil.disk_usage(R).free/2**30,2),'elapsed_s':round(time.time()-start)}),flush=True)
result={'started':datetime.datetime.fromtimestamp(start).isoformat(),'ended':datetime.datetime.now().isoformat(),'candidate_files':len(candidates),'compressed_files':len(rows),'errors':errors,'logical_bytes_preserved':sum(r['bytes'] for r in rows),'allocated_before_sum':sum(r['allocated_before'] for r in rows),'allocated_after_sum':sum(r['allocated_after'] for r in rows),'free_before_bytes':free,'free_after_bytes':shutil.disk_usage(R).free,'elapsed_seconds':time.time()-start,'method':'APFS transparent compression in bounded batches; original paths and bytes maintained; per-file SHA verified before compression, compressed output, original before replacement and final file. Temporary hardlinks removed after every batch.','physical_measurement_note':'Net free-space change only; file-block totals can double count APFS shared extents.'}
(O/'TRANSPARENT_COMPRESSION_SMALL_RESULT.json').write_text(json.dumps(result,indent=2));print(json.dumps(result),flush=True)