From 25b72baa8aa97ea1aab6ee3e604dbd4111772119 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Sat, 29 Aug 2026 15:51:25 -0600 Subject: [PATCH] =?UTF-8?q?fix(recover):=20single-instance=20lock=20?= =?UTF-8?q?=E2=80=94=20the=20driver=20is=20not=20parallel-safe=20and=20now?= =?UTF-8?q?=20refuses=20instead=20of=20corrupting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three properties compose into tree corruption under concurrency: (a) assert_write_set measures a GLOBAL git status, so a concurrent run's writes read as THIS run's blast-radius violation and abort it; (b) an abort does NOT restore the stage edits already on disk; (c) gate_stage's commit is a deliberately broad 'git add -u src/' — and it must be, since propagation touches many overlays and a narrower filename glob once DROPPED four R22-verified banks — so a concurrent --commit sweeps the aborted run's half-applied edits into its commit. Measured today: xargs -P 4 over 33 binaries put 696 broken lines of ov_MAIN_012 into md_MAIN_026's +1 bank commit; check-all went 212/213 and the wave bank was blocked behind it (R59). Narrowing the gate's git add was the WRONG fix (it would restore defect (c)'s predecessor). Instead the driver enforces its own contract: flock on .run/recover/.driver.lock, refuse loudly (R43). Control: with the lock held -> rc 1 REFUSED; lock free -> rc 0 and the probe runs normally. --- tools/recover_integration.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tools/recover_integration.py b/tools/recover_integration.py index baa39dfe32..a0f5a2f961 100644 --- a/tools/recover_integration.py +++ b/tools/recover_integration.py @@ -44,7 +44,7 @@ SESSION-16 (cookbook §65) added the mode this was missing and the blocker it co --auto pulls leaf-MATCH candidates from the backlog (status capped / near-close-0, reach>=2), filtered to those STILL a stub in the binary AND STILL match_one-MATCH on their best draft (drift-safe, R14). """ -import argparse, collections, glob, json, os, re, shutil, subprocess, sys +import argparse, collections, fcntl, glob, json, os, re, shutil, subprocess, sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import backlog, corpus, gate_stage @@ -235,6 +235,25 @@ def main(): "§55b bans --auto-from regardless). Use --no-propagate, then propagate a genuinely " "shared bank with a targeted `dedup_propagate --addr`.") + # ---- SINGLE-INSTANCE LOCK (P31 S65). This driver is NOT parallel-safe and now says so instead + # of corrupting the tree: (a) assert_write_set measures a GLOBAL `git status`, so a concurrent + # run's writes look like THIS run's blast-radius violation and abort it; (b) an abort does NOT + # restore the stage edits already on disk; (c) gate_stage's commit is a deliberately broad + # `git add -u src/` (it must be — propagation touches many overlays, and a narrower glob once + # DROPPED four R22-verified banks), so a concurrent run's --commit sweeps the aborted run's + # half-applied edits into its own commit. Measured: `xargs -P 4` over 33 binaries put 696 broken + # lines of ov_MAIN_012 into md_MAIN_026's +1 bank commit and took check-all to 212/213 (R59: a + # gate commits only its own block). Refuse loudly rather than mishandle (R43). + lock_path = os.path.join(REPO, ".run/recover", ".driver.lock") + os.makedirs(os.path.dirname(lock_path), exist_ok=True) + _lock_fh = open(lock_path, "w") + try: + fcntl.flock(_lock_fh, fcntl.LOCK_EX | fcntl.LOCK_NB) + except OSError: + sys.exit("REFUSED: another recover_integration.py is running (%s). This driver is NOT " + "parallel-safe — run it SERIALLY. See the single-instance-lock note above." % lock_path) + _lock_fh.write("%d\n" % os.getpid()); _lock_fh.flush() + run_dir = f".run/recover/{a.run_id}" dd = f"{run_dir}/drafts" shutil.rmtree(os.path.join(REPO, run_dir), ignore_errors=True)