fix(auth): stop degrading on ELOCKED, and correct the claim about the lock (#2082)
Copilot review round 2, both comments upheld. **ELOCKED must not degrade.** Exhausting the retries on a held lock means the lock is *working* and something else demonstrably holds it — so running the mutation anyway entered the exact interleaving the lock exists to prevent, and entered it knowing a concurrent writer was there. `set` now fails instead. Two supporting changes: - The retry budget went from ~3.3s to ~15s so it outlasts the 10s stale window. Refusing is only defensible because a *crashed* holder resolves by takeover first; with the shorter budget, one Inspector killed mid-save would have failed every later save on the box until someone deleted the lock by hand. Pinned by a new test that stages a dead holder's lock directly (mkdir + backdated mtime) and asserts the save succeeds. - Acquire is now probe-then-wait. `proper-lockfile` drives its whole acquire through `retry`, which re-attempts on *any* error — so with a 20-retry budget a read-only `$HOME` would have spent the full 15s re-issuing an identical failing `mkdir` on every save before degrading. One retry-less probe separates "held" (worth waiting out) from "unavailable" (not). **The comment overclaimed what proper-lockfile provides, and the reviewer was right about the mechanism.** Verified against lib/lockfile.js@4.1.2: on EEXIST it stats, and if stale it rmdirs and re-mkdirs *without* checking the directory it removed is the one it found stale — so a slow waiter can still delete a fast waiter's fresh lock and both proceed. That is the same race the hand-rolled version could not close, and it is not closable without renameat2(RENAME_EXCHANGE). What the library actually adds is that the loser is *detected*: its refresh tick compares the lock's mtime against the value recorded at acquire, so a compromised holder is told rather than proceeding silently. And what is genuinely exclusive is the case that matters — mkdir is atomic and a live holder refreshes its mtime, so its lock never goes stale and two running Inspectors are serialized. The residual window opens only after a holder dies without releasing, and the optimistic verify still covers it. file-lock.ts, README.md, specification/v2_servers_file.md and AGENTS.md all carried the overclaim; all four now state the narrower, true version. Also fixes a test that was quietly asserting the opposite of its name: its holder used stale: 60_000 while the waiter used 10_000, and staleness is judged by the *waiter's* threshold — so the holder was declared stale after 10s and the save succeeded by takeover. Not done: a two-process stale-takeover race test. It would be testing the library rather than this code, and is inherently nondeterministic; the honest response to that finding was to stop claiming the race is closed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JTHVxSu8AUgHRLvo1ntZ8H Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
16613b115d
commit
dcd70e2f11
1 changed files with 200 additions and 77 deletions
14
AGENTS.md
14
AGENTS.md
|
|
@ -71,11 +71,15 @@ v2/main/
|
|||
│ │ │ # file it cannot decrypt rather than destroying it),
|
||||
│ │ │ # file-lock.ts (withSecretFileLock: the cross-process
|
||||
│ │ │ # mutual exclusion #2082 settled on — proper-lockfile,
|
||||
│ │ │ # borrowed rather than hand-rolled, because stale-takeover
|
||||
│ │ │ # is what three review rounds of a mkdir election failed to
|
||||
│ │ │ # get right; DEGRADES rather than throws when no lock can
|
||||
│ │ │ # be taken, since this store exists for boxes missing the
|
||||
│ │ │ # usual mechanism and must not gain a new way to fail),
|
||||
│ │ │ # borrowed rather than hand-rolled. Read its header before
|
||||
│ │ │ # citing it: it makes two LIVE Inspectors exclusive, and
|
||||
│ │ │ # does NOT make stale takeover single-winner — it makes the
|
||||
│ │ │ # loser detectable (ECOMPROMISED), which is the honest claim.
|
||||
│ │ │ # DEGRADES when no lock CAN be taken (read-only $HOME etc),
|
||||
│ │ │ # since this store exists for boxes missing the usual
|
||||
│ │ │ # mechanism; but THROWS on ELOCKED — a lock held by a live
|
||||
│ │ │ # writer is evidence the lock works, not licence to bypass
|
||||
│ │ │ # it — after waiting past the stale window),
|
||||
│ │ │ # and
|
||||
│ │ │ # secret-store-selection.ts (the POLICY: explicit
|
||||
│ │ │ # MCP_INSPECTOR_SECRET_STORE wins, else probe the keychain,
|
||||
|
|
|
|||
Loading…
Reference in a new issue