Uh oh!
There was an error while loading. Please reload this page.
feat(scripts): mechanize verify-lock acquisition into a capped, FIFO, self-reporting entry point - #9921
Merged
Merged
Conversation
… self-reporting entry point Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeA3nU1B5Q2pgxqxgUrexd
…d self-test it in CI Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeA3nU1B5Q2pgxqxgUrexd
This was referenced Aug 19, 2026
os-warren
marked this pull request as ready for review
August 19, 2026 08:20
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9661
Draft, and it stays draft:
.claude/agents/os-dev.mdis a governed surface, so thisone is for the maintainer to merge.
What changed
A declared
-wcap that nothing enforced was losing to the agents who ignored it. Thismoves the cap out of prose and into a call site.
scripts/pm/os-verify-lock.sh(new) — the one entry point for the container'sshared heavy-verify lock:
scripts/pm/os-verify-lock.sh -c 'pnpm --filter PKG test'(or-- ARGV)--status— holder, how long it has held, and the queue--show-budget,--self-test.claude/agents/os-dev.md— rule 1 now points at the entry point instead ofdescribing free-hand
flocketiquette; rule 7's ladder is unchanged except that"name the holder" is now
--statusrather thanfuser -v. Rules 6 and 7 still routethe queue-timeout path through 干净收尾's declared-narrowing exit, verbatim and
untouched. Net line change: 0 (358 lines, ceiling 399).
.github/workflows/lint.yml— runs the entry point's 29-case--self-test.The three properties the grading comment asked for
(a) the budget cap is enforced at the call site. The entry point accepts no
-watall — passing one exits 2 with a message saying why. The single knob,
OS_VERIFY_LOCK_WAIT, can only lower the budget; anything above the cap is clampedand says so:
(b) grants are FIFO. Every call drops a ticket file named by arrival time into
(lockfile).q/; only the ticket at the head of the live queue ever callsflock,everyone else polls.
flockstays the only mutual-exclusion primitive — the ticketqueue is advisory ordering layered on top, which is what makes the coexistence
behaviour below safe.
(c) holder duration is reported. Every run ends with a VERDICT line carrying how
long it held and how long it waited, and a run past 15 minutes prints a loud block
naming itself and its command — so the next long holder (#8716's returned mechanism,
recorded in this card's evidence comment) surfaces itself instead of waiting to be found
by a starved sibling with
fuser.Coexistence during rollout, stated explicitly
A legacy free-hand
flockuser contends on the same file with the same primitive.Mutual exclusion is unaffected — a legacy caller cannot corrupt the queue, deadlock
it, or run concurrently with an entry-point holder. What it can do is win a grant ahead
of the queue head, because it never took a ticket. So during rollout the guarantee
degrades to: entry-point callers are FIFO among themselves, legacy callers behave
exactly as they do today, and nobody loses exclusion. Both directions are asserted in
the self-test.
--statusstill names an unregistered legacy holder (viafuser), itjust cannot report its duration.
A waiter that dies, is killed, or times out leaves at most one stale ticket, and a stale
ticket cannot wedge the queue: tickets are pruned by liveness (pid present and its
/procstart time unchanged, so a reused pid cannot resurrect a dead ticket) and by anabsolute age bound. If the queue directory cannot be used at all, acquisition falls back
to a plain capped
flockwith a warning — the ordering layer is best-effort, the capand the exclusion are not.
Measurement — including one prediction this falsified
Real two-process (four-process) demonstrations, all on a private lock under a temp dir,
never the shared one. Direction predicted in writing before each run.
1. The FIFO prediction was WRONG, and that is worth recording. Predicted: three
staggered, continuously-blocked waiters would acquire out of arrival order under
free-hand
flock, and in order through the entry point. Observed, 10 runs each:Linux already grants FIFO among waiters that are continuously blocked. So (b) buys no
change of observed order in that regime. What it does buy is worth keeping anyway: the
order becomes explicit and testable rather than an undocumented kernel property
(
flock(2)promises nothing), it becomes observable via--status(the kernel's waitqueue is invisible), and only the head contends, so there is no thundering herd.
2. The card's actual mechanism reproduces exactly — and it is duty cycle, not grant
order. A compliant waiter (short budget, times out, goes off to do lock-free work,
comes back) versus a non-compliant resident waiter, with the compliant one arriving
first. Free-hand, 12 runs:
Twelve out of twelve to the waiter that arrived second, purely for being present at the
handoff. That is the reported inversion, reproduced.
3. Through the entry point, the same scenario reverses completely. The would-be
resident asks for
OS_VERIFY_LOCK_WAIT=3000— the real measured non-compliant value —and still arrives second:
Residency cannot be bought, so arrival decides. The enforcement half (a) is the
load-bearing one; (b) is what makes the resulting order explicit rather than
inherited from the kernel.
Honest limitation: FIFO orders waiters that are present. It does not reserve a
place across an agent's off-cycle between two
os-verify-lock.shcalls. What removesthe measured 6x skew is that every waiter now has the same maximum residency.
Verification
Gate union re-derived on the final head and run there —
456ec7aa:node scripts/pm/dispatch-gates.mjs(no paths, off the merge base) derives 17 familiesfrom the three changed paths; 16 of 17 pass, plus
check:pm-governed-proseandscripts/pm/check-dispatch-gates.mjs.One declared narrowing:
pnpm check:type-check-debtwas not run locally. It refusesto measure without a built farm closure, and says so itself:
This diff contains no TypeScript. Its sibling
check:type-check-coverage— the samescript without
--re-measure— passes here, and lint.yml builds the closure before thisstep, so CI runs it properly.
Ablation legs (both restored,
git status --porcelainclean afterwards; this is abash script executed from source, so there is no
dist/between edit and run and therebuild precondition does not apply):
{lfd}close-in-child ⇒ the fd-hygiene case red (want: 0 got: 1),which is the guard against the orphaned-holder mechanism diagnosed in
scripts/gen-sdui-manifest.sh.Dogfooded on the real shared lock while doing this card's own work —
pnpm install(
held the lock 9s), the dependency-closure build (held the lock 2m02s) and the gateunion (
held the lock 20s), all through the entry point.Scope note
No changeset:
scripts/,.claude/and.github/workflows/publish nothing, so thiscarries
skip-changeset. #8716 remains open to its own triage — this PR does not closeit; it only adds the reporting that makes its mechanism self-announcing.
Generated by Claude Code