Maintainer adjudication 2026-08-19: measure before committing to an implementation. #9612 and #9613 stay parked until this lands, then are re-scoped from these numbers.
This card produces two numbers and a written finding. ⛔ It ships no optimisation.
Why these two, and not an implementation card
Both remaining routes for #9612/#9613 need numbers that do not exist yet, and one of the two most load-bearing facts about the problem was only established on 2026-08-19.
Unknown 1 — "scoping saves 83–85%" is an UPPER BOUND, not an estimate
#9851 measured that ablating validateStackExpressionsoff the runtime-publish surface entirely removes 83–85% of the real-shape flow-publish bill (and only 36% of the stub-shape bill — the asymmetry is what makes it a mechanism rather than a correlation).
⛔ That is the saving from deleting the rule, not from scoping it. Scoping keeps the rule and hands it less: the reachable references it actually needs rather than the whole stack. The saving is somewhere in (0, 85%] and depends entirely on how much smaller the reachable closure is than the full stack — which nobody has measured.
Treating 85% as scoping's payoff is the same error class this repo has now paid for twice in one round: scaling a total by a ratio it was not measured against. #9859 forbids the identical move for #4716's delta. Do not repeat it here.
Unknown 2 — the object door's cost has never been profiled at all
Verified on origin/main at a065e4655, from the registry:
| door | rules |
|---|
| flow | validateStackExpressions, validateReferenceIntegrity, validatePresetComparands, validateEmptyCombinators, validateFlowTriggerReadiness, validateApprovalApprovers, lintFlowPatterns |
| object | validateFunctionalCompleteness, validateManagedApiMethods, validatePresetComparands, lintAutonumberFormats, validateSecurityPosture, validateRuleCompilability, validateRuleSchemaFormats |
⭐ Both hot rules are runtimeTypes: ['flow']. Neither is on the object door. The only rule on both doors is validatePresetComparands.
So every profile either card carries — the 86%-at-gate concentration, validateStackExpressions at 70–85%, validateReferenceIntegrity at ~11% — describes flow publishing. The object door costs 16.48 ms @ 21 → 285.08 ms @ 420 on real-shape stacks (#9851's measurement) and its cost driver is unidentified.
That matters for prioritisation, in the maintainer's terms: publishing a flow is a low-frequency action; saving an object or field edit is what a Studio user does dozens of times a day, and #4716's own body calls it the hottest metadata path in the product — the reason P1 deliberately stopped at flow. #4716 has since widened that door from 2 rules to 7. The high-frequency path is the one we have never looked at.
Deliverable 1 — what would scoping actually buy
For the two flow-door hot rules, measure how much of the stack each one actually needs versus what it is handed:
- Compute, for a real written item against a real-shape stack at several N, the transitive closure of references the rule must resolve, and report
|closure| / N. If a flow publish at N=420 only reaches 6 objects, scoping is worth doing; if it reaches 300, it is not. - Then obtain a timed figure, not only a size ratio: feed the rule the closure instead of the full stack and time it. A throwaway measurement prototype is fine and is the only way to get a real number — but it is measurement scaffolding, not a fix: it must not ship as production code, and this PR must not change any rule's behaviour on any surface.
- Report the answer as a range with its method, and say plainly which part is measured and which is projected.
⚠️ Do not stop at the size ratio. A closure that is 5% of the stack does not automatically mean 95% saving — the rule's cost may not be linear in input size. Time it.
Deliverable 2 — profile the object door
Using the landed instrument (scripts/bench/runtime-publish-gate.bench.mts --type object), attribute the 285 ms @ 420 real-shape across the 7 object-door rules. Report a per-rule breakdown at N=21 and N=420 on both shapes.
Anti-vacuity, mandatory: the per-rule shares must account for the measured total (say what fraction is unattributed rather than rounding it away), and removing the top contributor must move the total by roughly its stated share. If it does not, the attribution method is wrong and that is the finding.
Note validatePresetComparands sits on both doors — if it is a large share of the object door too, that is a single change benefiting both, and worth calling out explicitly.
Standing rule from #9851 — applies to every number in this card
Quote the stack shape with every figure. A number given as "X ms at N objects" without its shape is not reproducible; real and stub differ ~25× at identical N. The instrument prints both by default; keep it that way in anything you report.
Scope fences
Rollback clause
If either measurement falsifies its premise — the closure turns out to be the whole stack (scoping buys nothing), or the object door's 285 ms turns out not to reproduce — STOP and report. That is a valid and valuable answer; do not convert the card into an implementation to salvage it.
Priority
Low, and deliberately bounded. Maintainer, verbatim: 「目前也没有相关的真实用户」, alongside the standing 「objectstack cloud 还没有正式上线,简化开发」. Nobody is waiting on a slow save today. A short PR reporting two numbers honestly is the intended outcome — do not pad it toward looking proportionate to the discussion that produced it.
Refs: #9612 · #9613 · #9851 / PR #9853 (the instrument, squash fb62bd19e) · #9859 · #9873 · #4716.
Maintainer adjudication 2026-08-19: measure before committing to an implementation. #9612 and #9613 stay parked until this lands, then are re-scoped from these numbers.
This card produces two numbers and a written finding. ⛔ It ships no optimisation.
Why these two, and not an implementation card
Both remaining routes for #9612/#9613 need numbers that do not exist yet, and one of the two most load-bearing facts about the problem was only established on 2026-08-19.
Unknown 1 — "scoping saves 83–85%" is an UPPER BOUND, not an estimate
#9851 measured that ablating
validateStackExpressionsoff the runtime-publish surface entirely removes 83–85% of the real-shape flow-publish bill (and only 36% of the stub-shape bill — the asymmetry is what makes it a mechanism rather than a correlation).⛔ That is the saving from deleting the rule, not from scoping it. Scoping keeps the rule and hands it less: the reachable references it actually needs rather than the whole stack. The saving is somewhere in
(0, 85%]and depends entirely on how much smaller the reachable closure is than the full stack — which nobody has measured.Treating 85% as scoping's payoff is the same error class this repo has now paid for twice in one round: scaling a total by a ratio it was not measured against. #9859 forbids the identical move for #4716's delta. Do not repeat it here.
Unknown 2 — the object door's cost has never been profiled at all
Verified on
origin/mainata065e4655, from the registry:validateStackExpressions,validateReferenceIntegrity,validatePresetComparands,validateEmptyCombinators,validateFlowTriggerReadiness,validateApprovalApprovers,lintFlowPatternsvalidateFunctionalCompleteness,validateManagedApiMethods,validatePresetComparands,lintAutonumberFormats,validateSecurityPosture,validateRuleCompilability,validateRuleSchemaFormats⭐ Both hot rules are
runtimeTypes: ['flow']. Neither is on the object door. The only rule on both doors isvalidatePresetComparands.So every profile either card carries — the 86%-at-gate concentration,
validateStackExpressionsat 70–85%,validateReferenceIntegrityat ~11% — describes flow publishing. The object door costs 16.48 ms @ 21 → 285.08 ms @ 420 on real-shape stacks (#9851's measurement) and its cost driver is unidentified.That matters for prioritisation, in the maintainer's terms: publishing a flow is a low-frequency action; saving an object or field edit is what a Studio user does dozens of times a day, and #4716's own body calls it the hottest metadata path in the product — the reason P1 deliberately stopped at
flow. #4716 has since widened that door from 2 rules to 7. The high-frequency path is the one we have never looked at.Deliverable 1 — what would scoping actually buy
For the two flow-door hot rules, measure how much of the stack each one actually needs versus what it is handed:
|closure| / N. If a flow publish at N=420 only reaches 6 objects, scoping is worth doing; if it reaches 300, it is not.Deliverable 2 — profile the object door
Using the landed instrument (
scripts/bench/runtime-publish-gate.bench.mts --type object), attribute the 285 ms @ 420 real-shape across the 7 object-door rules. Report a per-rule breakdown at N=21 and N=420 on both shapes.Anti-vacuity, mandatory: the per-rule shares must account for the measured total (say what fraction is unattributed rather than rounding it away), and removing the top contributor must move the total by roughly its stated share. If it does not, the attribution method is wrong and that is the finding.
Note
validatePresetComparandssits on both doors — if it is a large share of the object door too, that is a single change benefiting both, and worth calling out explicitly.Standing rule from #9851 — applies to every number in this card
Quote the stack shape with every figure. A number given as "X ms at N objects" without its shape is not reproducible; real and stub differ ~25× at identical N. The instrument prints both by default; keep it that way in anything you report.
Scope fences
surfaces/runtimeTypes/surfaceReason. The how remains an open maintainer decision, and one option in runtime gate: full runtime-safe rule snapshot for the publish door — the expensive half split out of the object-gating card #9612's body (a size threshold) is a deliberate fail-open at scale that is explicitly not a dev's pick.scripts/ablation-dist-preflight.mjsin both directions.content/docs/releases/**.scripts/**is inside the ROOT package's tsc scope, whose program is defined by exclusion — sodispatch-gates.mjswill not namecheck:type-check-debtfor your paths. Runpnpm check:type-check-debt(both legs) regardless of what the union derives. Baselines may only shrink: if the root entry drifts up, fix your own errors — raising the ledger is maintainer-only and is not authorised. Background: finding: dispatch-gates cannot name check:type-check-debt for a scripts/** card — the root package's tsc scope is defined by EXCLUSION, so no path literal will ever describe it #9873. This cost the previous card a CI round-trip.Rollback clause
If either measurement falsifies its premise — the closure turns out to be the whole stack (scoping buys nothing), or the object door's 285 ms turns out not to reproduce — STOP and report. That is a valid and valuable answer; do not convert the card into an implementation to salvage it.
Priority
Low, and deliberately bounded. Maintainer, verbatim: 「目前也没有相关的真实用户」, alongside the standing 「objectstack cloud 还没有正式上线,简化开发」. Nobody is waiting on a slow save today. A short PR reporting two numbers honestly is the intended outcome — do not pad it toward looking proportionate to the discussion that produced it.
Refs: #9612 · #9613 · #9851 / PR #9853 (the instrument, squash
fb62bd19e) · #9859 · #9873 · #4716.