The remote guard contract only pinned refusal of an existing target-side
binary. Nothing pinned the other half: a path the backend proves absent must
stay writable, or a guard that over-reports "exists" (e.g. treating a
"missing" probe as present) would block every new .pdf/.sqlite on Docker/SSH
without a red test. Extend the kept T1 test instead of adding a function
(test budget); verified it fails 6/6 under a missing->exists mutation.
The substitution pairs were built by looping over a one-element tuple (left
over from a multi-pair version), and _bash_safe() only existed to do a
function-local import from a module already imported at the top. Import
_bash_safe_path directly and build the three pairs as a literal list.
Keep the two contracts that pin #122662: (1) write_file / patch replace /
V4A update against a binary that exists only on an unhinted non-local
backend (VercelSandboxEnvironment, scoped config claiming 'local') is
refused with bytes untouched, for .sqlite and .pdf; (2) a probe transport
failure (raise or error return) fails closed. Creation-allowed, locality
and tilde-fallback cases are covered by the first invariant's setup or by
tests/tools/test_binary_document_write_guard.py, and the salvage shape
caps a stack at two invariant tests.
_check_binary_document_write stat'ed the controller host only, so a binary
(.sqlite/.pdf/...) that existed solely in the task's execution target
(Docker/SSH/... namespace) was treated as a new file and destroyed by a
plain-text write_file/patch that even reported verified:true (#122662).
The existence decision now goes through one tri-state helper
(_target_regular_file_state: exists/absent/unavailable) that asks the LIVE
file-ops layer - the same backend the write executes on. Host-backed envs
keep today's Path.is_file semantics (OSError -> proceed); other backends are
probed via ShellFileOperations._probe_regular_file, whose missing/not_regular
answers prove absence while every other status fails closed with a retry
message. Locality comes from the live environment object
(_file_ops_uses_host_paths), never env_type strings or class-name hint tables
(VercelSandboxEnvironment is unclassified there). The PDF and generic binary
refusal messages are unchanged verbatim; opaque-document and SQLite-sidecar
unconditional refusals are untouched.
_stale_overwrite_blocker keeps its host-only probe on purpose: remote reads
never record a full_write_baseline (version stability requires host metadata),
so converting it would refuse every remote overwrite of a file the task fully
read. Tracked as follow-up.
(cherry picked from commit 8b5eb26d57d7ef7d1d975de0ef5e1e8abd9a513d)