docs(devx): measure the verify-lock gate-routing trade — four quantities, no routing - #13610
Merged
os-project-manager merged 2 commits intoAug 31, 2026
Conversation
…ies, no routing Delivers the measurement scripts/pm/os-verify-lock.sh names as unmeasured in its coverage-boundary header, and retires that sentence with a pointer to the result. Implements no routing; what the lock serialises is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
…rify-lock-throughput-measurement
This was referenced Aug 31, 2026
os-project-manager
marked this pull request as ready for review
August 31, 2026 03:30
os-project-manager
deleted the
claude/issue-12795-verify-lock-throughput-measurement
branch
August 31, 2026 03:45
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 free
to 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 #12795
Delivers the four quantities
scripts/pm/os-verify-lock.shnames as unmeasured in itsWHAT THIS LOCK DOES NOT COVERheader, and retires that sentence with a pointer to the result.⛔ No routing is implemented and nothing about what the lock serialises changed. The routing
decision this feeds is a separate, maintainer-facing card, per the triage grading on #12795.
Diff: 2 files. A new audit under
docs/audits/, and a comment-only edit to the lock script'sheader. No production code.
Answer in one line
Every routing policy measured is worse than routing nothing, on the quantity that decides it.
Today 2.0% of locked runs return exit 99 (a NOT MEASURED run). Across a 64-cell sweep of four
routing policies, four demand levels and four levels of CPU credit handed to routing, the best
cell is 4.0% and the worst is 65.7% — no cell reaches today's 2.0%.
1 — Gate-run cost distribution · MEASURED
All 116 root
check:*families, each timed aspnpm check:NAME, exit code captured before anypipe. Two hit the harness timeout and were re-run uncensored.
Whole farm serially: 1153 s. p50 = 2.3 s, p90 = 9.5 s, p95 = 23.1 s, max = 305.5 s.
Is the threshold clean? Partly — and the honest answer has two halves. 5 families (4.3%) carry
64.3% of all gate-seconds; the top 2 alone carry 46.3%. Ranks 1-5 are separated by steps of
x1.33, x2.33, x1.41, x1.83, and rank 5 stands x1.65 clear of rank 6. Below rank 6 the
distribution is smooth — every consecutive gap is x1.30 or less. So a cut exists at roughly
30 s, isolating exactly 5 families, and nothing resembling a threshold exists below it.
check:pm-dispatch-gatescheck:query-options-erasurecheck:slot-lookupcheck:stall-guardcheck:engine-double-contractOnly 6 of 116 families finish under a second and the fastest is 934 ms — because for the light
families roughly 97% of the measured wall time is pnpm's own startup, not gate work:
pnpm check:node-version(3 reps)node scripts/check-node-version.mjs— same gate, no pnpmnode -e 'process.exit(0)'— controlBoth readings are true of different things; routing needs the first, because what would be routed
is the command an agent types. The farm count has also moved:
dispatch-gates.mjsnow discovers190 families across 29 workflow files, against the card's 176.
2 — Queue-depth effect · MEASURED, and the sign is settled
One fixed CPU-bound gate at W = 1, 2, 4, 8 concurrent copies. W = 1 is the routed world.
check:objectql-double-limitrep 1check:adr-anchorscheck:objectql-double-limitrep 2(gates per minute; bold = best in row)
W = 1 is never the throughput maximum, in three sweeps out of three. On an unsaturated box
serialising costs 2.38x aggregate gate throughput; on an already-saturated box the curve is flat
and it costs approximately nothing. Even 2x over-subscription beat serialisation by 1.53x-1.84x.
The card's open possibility — that serialised-but-uncontended beats parallel-but-thrashing — did
not occur at any concurrency tested.
⛔ Per-agent end-to-end wall time is NOT MEASURED: nothing records when a card starts or ends
and no ledger field carries an agent identity. Aggregate throughput at the box bounds it.
3 — Budget fit · MEASURED, and it is decisive
Budget is 540 s (
HARD_CAP_S). Baseline from the real ledger, 99 records over 16.1 h:Two facts decide most of this before any model: the wait distribution is already flat against the
cap (p90 536 s of a 540 s budget — no headroom to spend), and the lock is idle 76.5% of the
time and still times out 2% of runs — the load is bursty and heavy-tailed, the regime where added
work is punished super-linearly.
The arithmetic that needs no model
The two heaviest families cost 305.5 + 229.0 = 534.5 s. The budget is 540 s.
Trace-driven simulation
arrival = ts - held - waitedreconstructs each run exactly. Replaying through a FIFO server withthe 540 s budget reproduces the record — exit 99: 2 simulated vs 2 observed; wait p50 151 s vs
151 s; p90 536 s vs 536 s. The residual error direction is known and favours routing: the trace
cannot contain holders it never recorded, so every routed figure is a lower bound on the harm.
Injecting gate sweeps at rate G.
fis the build service-time multiplier once gates no longer runalongside a holder — the credit routing gets for freed CPU.
f = 0.60is more credit than theconcurrency sweep supports. Build exit-99 rate; today's value is 2.0%:
No cell reaches the 2.0% baseline. The minimum over all 64 cells is 4.0% — double today's rate
— and only at the lowest demand, narrowest policy, and more CPU credit than measured.
A real derived family for an ordinary card is 7 families (measured on this PR's own diff), 16
once a changeset exists — not 116. Plausible fleet demand lands at added rho ~7-25%, the G=1 to
G=2 rows, i.e. 2x to 12x today's exit-99 rate.
⛔ Fleet gate arrival rate is NOT MEASURED and is unmeasurable with this instrument — gate runs
are unlocked, take no ticket and write no ledger row, so the ledger is blind to exactly the
population routing would add. G is swept rather than fitted; the conclusion is its robustness
across the sweep.
4 — The partial option · MEASURED. Wins its bracket, loses the question.
Routing only the heavy families does dominate routing everything, monotonically (at G=4, f=1.00:
45.5% then 40.4% then 36.4% then 33.3%). But the gain is small, for a structural reason:
Its one genuine merit is orthogonal to CPU: those 5 families are the ones whose unlocked runs most
distort a concurrent holder's timings, so routing them buys measurement hygiene while costing
measurement availability. That trade is a judgement, not a number.
Corrections to inherited premises
os-verify-lockserialises locked heavy jobs but NOT gate scripts — so every timing card measures a contended box while believing the lock gave it an idle one #12528) shipped and the throughput half was open — re-verified onorigin/mainbefore starting. Premise holds.HARD_CAP_SandDEFAULT_WAIT_Sare both still 540, and thescript argues at length that this constant cannot be raised — the budget is spent inside one
foreground turn a ~600 s ceiling kills. What [finding]
os-verify-lock's 540s wait budget is shorter than a single legitimate hold — a compliant caller times out systematically, not occasionally #12538 moved isSLOT_MAX_AGE_S(nowHARD_CAP_S * 3= 1620 s), re-based on last relinquish. Any reasoning assuming a largeracquisition budget is wrong in the other direction.
88, 93, 93, 94 ms) — an order of magnitude cheaper than the ~970 ms pnpm startup every routed
gate pays anyway. A fifth rep taken first cost 58 106 ms because a sibling held the lock; that is
contention, not mechanism.
--reportledger's/tmpresidence ([finding] Theos-verify-lockledger lives in the container's /tmp, so--reportcan never answer the question it was built for — "where does hold time go" is scoped to one shift, and says so nowhere #12783) bounds this: 16.1 h across 2 boots, onecontainer. Stated in the audit rather than left to pass silently.
Verification
19-family derived gate set (
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack,re-derived on the merged tree) plus the always-runs
os-verify-lock.sh --self-testthat covers theedited file. Run at
30bc3e773.All green except one, which self-declares its own non-result:
check-test-completeness.mjsexits 3 with "no local log to hand it, so the local reading for this gate is NOT MEASURED" and
instructs exactly that when run locally.
check:nul-bytesgreen, plus a counter-checked self-scanof both edited files (scanner proven live against a control-byte probe in the same command).
skip-changeset: the diff publishes nothing from any package — one file underdocs/audits/and acomment in
scripts/pm/.Generated by Claude Code