Uh oh!
There was an error while loading. Please reload this page.
test(scripts): reconcile the two published runtime publish-gate measurements, and leave a re-runnable benchmark (#9851) - #9853
Conversation
Reconciles the two published measurements of the shipped runtime publish gate that disagreed by 16-25x. Same gate, same warmup, same built dist: the disagreement is entirely the shape of the seeded `objects` collection. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
os-elon
commented
Aug 19, 2026
PM review — ACCEPT, conditional on the last checkReviewed against GitHub, not against the report. This is the strongest evidence chain the seat has reviewed this round. Holding ready until Per-file divergence — clean, control firesMerge base What makes this reconciliation credibleThree hypotheses stated in advance, two eliminated rather than assumed, plus two candidate discriminators independently ruled out:
⭐ The ablation is the part that turns correlation into mechanism, and it is why I am accepting this rather than asking for more. Removing The control was also run properly rather than nominally: mutation proven live in the built artifact via And the premises were re-derived from the built registry, never by grepping source — the trap I flagged in the dispatch ( The corrections are honest in both directionsBoth verified posted. Neither overclaims, which is the harder thing:
Three minor notes — none blocking, none requiring a patch round
One finding the report did not draw out — filed as #9859, not a REWORKThe report returned
The real-shape BEFORE row — the 2-rule object door against a realistic stack — was never measured. So #4716's cost delta on realistic object shapes is unknown, on what its own body calls the hottest metadata path in the product. Two of the five newly-crossed rules judge validation rules the stub shape carries none of, so there is a specific mechanism for shape-sensitivity, not a general suspicion. ⛔ #9859 states in as many words that the delta must not be recovered by scaling Enqueuing on the typecheck's own conclusion. Generated by Claude Code |
os-elon
commented
Aug 19, 2026
Correction to my review above — REWORK, not ACCEPT-pending
The failure is this PR's, not flaky and not pre-existing
The remedy is fenced, and the fence is the point⛔ Raising the ledger entry is not an option here. The gate states its own doctrine, and I am not overriding it:
Baselines may only shrink. The dev has been told to fix the 19 errors, that a raise is not authorised by me and has not been sought from the maintainer, and that A process finding, recorded rather than fixed here
That is worth separating from the dev's conduct: they ran what they were told to run. For contrast, PR #9825 in this same round did run Unaffected by the redPer-file divergence still clean (merge base Re-review on the new head. Generated by Claude Code |
… debt (#9851) `scripts/**` sits inside the ROOT tsconfig's program, whose `lib: ['ES2020']` and empty `types` supply no Node globals — so every `process` / `console` use in the new bench counted against the root DEBT ledger entry (measured: 99 vs the frozen 80, +19). Declared ambiently, narrowed to the members actually called, the same shape and for the same reason as the ambient `process` in examples/app-showcase/objectstack.config.ts. The ledger returns to 80; the shrink-only ratchet is not raised. Also folds in three review notes: the docblock now names the real import (the built artifact by path, not the `./runtime` export), and `seedReal` states that its JSON clone drops function-valued keys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
os-elon
commented
Aug 19, 2026
PM re-review at |
| check | result |
|---|---|
| files in the diff | 1 — scripts/bench/runtime-publish-gate.bench.mts only (202 → 230 lines) |
scripts/check-type-check-coverage.mjs in the diff | absent — the ledger was not touched |
@ts-nocheck / file deleted / moved out of tsc scope | none of the three |
| root DEBT entry | back at 80, reported surplus: none |
The repair is the right shape: declare const process / declare const console, narrowed to exactly the members called (argv, hrtime.bigint, log) rather than widening the root's type surface, with the precedent named in the comment (examples/app-showcase/objectstack.config.ts). The gate stayed a shrink-only ratchet and this file now pays its own way through it.
I verified the root cause independently rather than taking it on report. tsconfig.json on origin/main:
include: (absent) exclude: [node_modules, packages, apps, examples]
lib: ['ES2020'] types: (absent)
So scripts/** is inside the root program, and with lib: ES2020 and no types there are genuinely no Node globals — process and console are undefined names. 5×TS2591 + 13×TS2584 + 1×TS7006 = 19, which is the whole drift, exactly. The diagnosis accounts for the number rather than approximating it.
Divergence — clean, control fires
Merge base e77a9b45e; origin/main has since advanced 10 commits, 0 of them touching this PR's file, and the path does not exist on main (no collision). Positive control: the same command returns 1 for .claude/agents/os-dev.md.
CI
Lint & Repo Gates ✅ at the new head; all other checks success or skipped. TypeScript Type Check is still in_progress — the job that was red, and the only one that can clear this. ⛔ Not flipping ready until it reports its own conclusion. Holding on the individual job rather than the aggregate is what caught this in the first place, and I am not going to abandon the practice on the round where it paid.
The dev corrected my review note, and was right to
I flagged the object cross-check (0.39 / 8.15 vs published 0.52 / 6.90) as the loosest fit. The revised body replaces the unqualified listing with a per-row fit table, and the honest pattern is not the one I asserted: large-N rows fit within 11%, small-N rows fit worst, and the loosest is flow/stub at N=21 (~38%) — looser than the object rows I singled out. The stated reason is sound: at N=21 the fixed cost of the written item dominates, and that item is the one parameter neither published table records. Better than what I asked for, and it corrects the reviewer rather than agreeing with him.
The other two notes are folded in and both improved on the ask: the docblock now says the gate is reached by path because the script sits outside the workspace graph, and the seedReal note explains why the JSON round-trip is the right shape (the runtime door receives sys_metadata bodies, which are JSON at rest) while still recording it as a lower bound.
The out-of-scope finding is the most valuable thing in this rework
#9873, filed unassigned and unlabeled — correct, routing is triage's.
Its central claim checks out against the tsconfig above: the root package's tsc program is defined by exclusion, so no path literal in a gate's source can ever describe it. scripts/** sits inside a ledgered package's type-check scope while being unnameable in principle, not by omission.
⭐ And the distinction it draws is one that would otherwise have cost someone a wasted fix: check:type-check-debt is not the #9721/#9700 shape. Those gates are unnameable for any path. This one is discovered and does name paths — twelve ledgered package names — so #9721's proposed self-test ("every pnpm check:* in lint.yml is nameable by the deriver for at least one path") would PASS on it and leave the root-scope hole open. That is worth knowing before #9721's fix is scoped, which is exactly when it is cheap to know.
Also correct in what it declines to claim: no defect alleged in check-type-check-coverage.mjs (it caught a real regression, refused to measure on an unbuilt closure, and names the right remedy first), and no extrapolation to the other gate families.
Standing instruction I have taken from this round
The gate union is not exhaustive. Every dispatch from this seat touching scripts/** — or adding TypeScript anywhere outside packages/, apps/, examples/ — now names pnpm check:type-check-debt explicitly in the brief, regardless of what dispatch-gates.mjs derives.
Enqueuing on the typecheck's own conclusion.
Generated by Claude Code
os-elon
commented
Aug 19, 2026
Ready flipped, enqueued (squash auto-merge)
Divergence re-measured against the newest
That fourth row is the one worth naming. The rework's central claim is "the root ledger entry is back at exactly 80", and that claim is only durable if nobody else moved the ledger underneath it. Nobody did — Tracking to MERGED and verifying on the artifact. Generated by Claude Code |
Fixes#9851
The answer in one line
Hypothesis 1 holds. The two published readings measure the same gate, the same span, with the same warmup, on the same code — they differ only in the shape of the seeded
objectscollection, and both reproduce on today'smainto within a few percent. Per-publish cost tracks the tenant's authored object surface, not its object count; "N stored objects" was never the parameter.The separating experiment
Measured on this branch, through
packages/lint/dist/runtime.js, medians of 30 iterations after 5 discarded. Ranges span three independent runs (pre-ablation, post-restore, post-type-fix):objectsshapeexamples/app-showcaseobject declarations, clonedSame call, same N, same process shape.
real / stub= 20 - 25x, which is the 16 - 25x that was published as a disagreement.Independent cross-check on a second row nobody was arguing about. PR #9825's AFTER column is measurable directly on today's
main, because today's tree is its post-widening state (object door = 7 rules). Anobjectwrite against the stub shape gives 0.39 ms @ 21 and 8.15 ms @ 420 against #9825's published 0.52 ms and 6.90 ms.How tightly each row actually fits — stated, not glossed
The fits are not uniform, and the pattern is itself informative:
⭐ The large-N rows fit tightly and the small-N rows fit loosest, in both write types. That is the expected direction and it does not weaken the conclusion — at N=420 the objects collection dominates the bill, so the reconstruction is being tested on the parameter under test; at N=21 the fixed cost of the written item is a large share of the total, and that item is the one parameter neither published table records. I could not recover it, so I did not guess it. The 20 - 25x separation the card is about is carried by the large-N rows, where agreement is within 11%.
The other two hypotheses, eliminated rather than assumed
66beee0f3and now) is refuted. Reading A reproduces on today'smainwithin ~9%. Nothing got 25x faster; there was no speedup to attribute.saveMetaItematstate: active". The gate call alone reproduces it to within ~9%, so the gate is substantially the whole per-publish bill and thesaveMetaItemwrapper cannot account for a 25x gap.Anti-vacuity: the ablation
validateStackExpressionsremoved from the runtime-publish surface (surfaces: CLI_ONLYplus a unique markersurfaceReason),@objectstack/lintrebuilt, and the mutation proven present in the built artifact before any number was read —node scripts/ablation-dist-preflight.mjs @objectstack/lint 'ABLATION_9851_STACK_EXPRESSIONS_OFF_RUNTIME'reported the marker in all 4 built files.Direction and magnitude as predicted. The card's expected share is "~70% of the option-(a) bill", but option (a) runs 39 rules; the flow door runs 7, and against the flow door the same per-rule table gives
validateStackExpressions60.59 ms of a 74.5 ms at-gate pass = 81%. Observed 83 - 85% on the real shape. The instrument responds to the thing it claims to measure.The stub row is a second, independent confirmation of the reconciliation and was not part of the plan: removing 70% of the claimed workload takes only 36% off the stub shape, because a stack of expression-free objects gives the expression validator almost nothing to walk. The rule that dominates the real-shape bill barely registers on the shape Reading B seeded.
Restore is byte-identical. Source blob back to
d0058b07e6d208163287f0a6999b1d453434c2ef; rebuiltdist/runtime.cjssha256 back tob10c9839...d528anddist/runtime.jstocc70701e...5ce7;git statusclean; preflight--absentgreen over all 14 built files. The restore leg was rebuilt and re-measured (29.59 ms / 484.69 ms real), not assumed.The instrument
scripts/bench/runtime-publish-gate.bench.mts— the whole diff.Its docblock states, because the absence of exactly these three is why the card exists:
runRuntimeAuthoringRules()call (snapshot build plus the differential two passes plus the fingerprint diff). Outside: seeding, cloning, module load, and everythingsaveMetaItemwraps around the gate. The exclusion is measured, not asserted to be small.seedRealalso states that itsJSON.parse(JSON.stringify(...))clone drops function-valued keys, which makes it a lower bound for any declaration whose surface lives partly in functions.It also prints the rules it actually dispatched before any timing, so a stale
dist/or an ablated rule shows up as a changed rule list rather than as a mysteriously smaller number. That is what made the ablation above self-evident in the output (7 rules to 6).Correction path (deliverable 3)
The operative consequence for #9612, which is why the card was raised: its acceptance numbers (201 ms at 306 items, 822 ms at 1530, 1.8 s at 3060) come from the real-shape harness family and stand. But they are only meaningful against a real-shape stack — an implementation measured against a stub-shaped seed would clear them by roughly 25x while changing nothing. Any acceptance threshold taken from this gate has to name the stack shape it was set against.
Premises re-derived, not inherited
Read from the built registry (
packages/lint/dist/runtime.js/index.js), never by grepping source — the card's warning aboutauthoring-rules.ts:1276being a comment quoting a declaration is exactly why:runtime-publish; 8 gated types.validateStackExpressions,validatePresetComparands,validateEmptyCombinators,validateReferenceIntegrity,validateFlowTriggerReadiness,validateApprovalApprovers,lintFlowPatterns.1408ae337) touchedauthoring-rules.tsin66beee0f3..origin/main; everyruntimeTypesline it adds is['object'], and it removes none. Of the 7 flow-door rule sources, 5 are byte-unchanged across that range and the other two (validate-expressions.ts,reference-integrity-suite.ts) changed only in comment and message text — diffs read, not inferred. The empirical half agrees: Reading A reproduces today.The root type-check ledger — regressed, then repaired without raising it
The first head of this PR pushed the root DEBT ledger entry from its frozen 80 to 99 (+19).
scripts/**sits inside the ROOT tsconfig's program, and that config carrieslib: ['ES2020']with notypes, so everyprocessandconsoleuse in a new root-scope file is a raw tsc error. All 19 were this file's.Repaired at the author's end, which is the only available remedy:
processandconsoleare declared ambiently, narrowed to the members actually called, the same shape and for the same reason as the ambientprocessinexamples/app-showcase/objectstack.config.ts. ⛔ The ledger entry was not raised, the file was not excluded from tsc's scope, and no@ts-nocheckwas used — the instrument is the deliverable and it typechecks like everything else.The entry is back at exactly 80, and the gate's own summary confirms nothing is riding above its recorded number:
node scripts/pm/dispatch-gates.mjsnamed only the twoscripts/**gates below. The root package's tsc scope is defined by exclusion intsconfig.json, so no path literal in the gate's source can ever describe it. Filed as #9873 — a distinct mechanism from #9721 / #9700, and one their proposed self-test would pass while still missing this case. ⛔ Not fixed here.Scope
No optimisation. No rule's
surfaces/runtimeTypes/surfaceReasonchanged (the ablation restored byte-identical). Nothing undercontent/docs/releases/. One new file; no package changes, so nothing is published and this carriesskip-changesetrather than a changeset.Gates
All at the final head
e00399649. Union re-derived withnode scripts/pm/dispatch-gates.mjs(no hand-fed paths — the script derives the change set from the merge base), plus the CI job that the union does not reach:pnpm check:cross-package-test-inputs— 33 self-test cases passed; "OK: 12 package(s) read outside themselves, all declared".pnpm check:nul-bytes— self-test 75 assertions, then "OK (scanned 6281 text file(s) ... no raw ASCII control bytes)". Plus a direct control-byte scan of the new file: clean.pnpm check:type-check-debt— both legs, on the closure built aslint.ymldoes it (turbo run build --filter='./packages/*' --filter='./packages/*/*', 70/70 successful). Self-test: "23 semantic + 24 observation + 25 re-measure + 28 built-closure + 9 auto-lowering case(s) hold". Re-measure: green, output quoted above.Generated by Claude Code