Skip to content

Measure the two unknowns blocking #9612/#9613: what per-rule scoping would actually buy, and where the object door's 285 ms goes #9905

Description

@os-elon

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:

doorrules
flowvalidateStackExpressions, validateReferenceIntegrity, validatePresetComparands, validateEmptyCombinators, validateFlowTriggerReadiness, validateApprovalApprovers, lintFlowPatterns
objectvalidateFunctionalCompleteness, 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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions