Restart-when: npx tsx scripts/bench/runtime-publish-gate.bench.mts on a then-current main reports the real-shape N=420 flow-publish median materially below this band (⇒ some later change reached this door; correct the body and close against a number), OR objectstack cloud has real tenants whose Studio publish latency is a reported symptom
⛔ Ruled 2026-08-20: record it, do not fix it. Maintainer verbatim: 「C 只记录,不修(推荐)」
This card is a live perf record, not a work item. Its number was re-measured on 2026-08-20 at 482.78 ms and is inside the band below. ⛔ Do not scope a narrowing off this card without a new ruling — the measured reason is in "Why the obvious fixes do not reach this door".
Recording a measured fact that has lived only inside a card comment (#4716 comment 5309105113, measured on origin/main @ 66beee0f3 by a throwaway harness) so it stops being invisible to every perf listing. Filed unassigned during the 2026-08-18 adjudication of that card (maintainer directed the split package: 「简化开发」→「同意」).
The measurement
Today's shipped gate costs 33.4 ms at 21 objects → 506 ms at 420 objects per flow publish. The companion per-rule profile shows the bill concentrates in at-gate rules being handed the whole stack: validateStackExpressions ≈ 70% of the option-(a) bill, validateReferenceIntegrity ≈ 11% — both already on the runtime-publish surface today.
✅ Re-confirmed twice since, on two later refs and two different machines:
⚠️ Parameterisation, which the original wording got slightly wrong: the cost is linear in the tenant's authored object SURFACE — fields, formulas, validation rules, predicates, the expression-bearing keys validateStackExpressions walks — not its object count. Count is a proxy whose constant is set by what the objects contain. At identical N a real-shape stack and a synthetic expression-free one are ~21–26× apart (measured 2026-08-20: real/stub 26.4× @21, 21.4× @420). Any number quoted from this family must name its stack shape; not doing so is what once made this card's figure look 16–25× wrong against another correct measurement of a different workload.
Why it may matter
Flow publish is a user-facing interactive action (Studio designer). Half a second of gate latency at 420 objects is already perceptible, and it grows with exactly the number that grows as a tenant succeeds.
⚠️ But scope it honestly: per the maintainer's stated priority, saving an object/field edit is the high-frequency Studio action while publishing a flow is rare. The two doors have disjoint drivers — the object door is validateRuleCompilability at 81.3%, a rule that appears in no profile on this card. ⛔ Every concentration figure here describes flow publishing only, and the object door remains unmeasured. The better-characterised half is the less important half.
⭐ Why the obvious fixes do not reach this door — measured 2026-08-20
Package-narrowing (#9612) requires two things, and a tenant-authored overlay stack has neither:
- A
packageId on the write. REST derives it solely from ?package= (packages/rest/src/rest-server.ts, where 'all'/empty is commented as "env-local overlay"), so a Studio publish yields undefined and resolveWritePackageScope returns at its first guard.
_packageId provenance on the stored objects. narrowObjectsToPackageClosure's limb 2 — if (typeof owner !== 'string' || owner === '' || owner === OVERLAY_PROVENANCE_SENTINEL) return true; — keeps any object with no provenance, and the real seeded corpus carries zero.
⇒ 📌 Fixing the missing packageId (#10063) would not, on its own, buy the saving. That is the counter-intuitive part and the reason this card stays open: without it, the plumbing fix reads like a latency win and is not one on an unpackaged corpus. Cross-noted on #10063.
The counterfactual proves the mechanism is sound rather than broken: on a provenance-stamped seed the shipped deriver narrows 421 objects to 45 (10.7%) and saves 89.5% (468.10 → 49.36 ms), with the differential verdict semantically unchanged and both positive controls live. The narrowing works; it just cannot reach this corpus.
⛔ Fences, carried and still binding
The instrument
npx tsx scripts/bench/runtime-publish-gate.bench.mts # both shapes
npx tsx scripts/bench/runtime-publish-gate.bench.mts --mode package # the closure counterfactual
Re-runnable, states its own timed span, warmup policy and seeded stack shape, prints both shapes, and names the rules it dispatched — so a stale build shows up as a changed rule list rather than as a silently wrong number. It replaces the throwaway harness this card's original reading came from, which was the real problem all along: the number was right, and nobody could re-run it to find that out.
Restart-when:
npx tsx scripts/bench/runtime-publish-gate.bench.mtson a then-currentmainreports the real-shape N=420 flow-publish median materially below this band (⇒ some later change reached this door; correct the body and close against a number), OR objectstack cloud has real tenants whose Studio publish latency is a reported symptomRecording a measured fact that has lived only inside a card comment (#4716 comment 5309105113, measured on
origin/main@66beee0f3by a throwaway harness) so it stops being invisible to every perf listing. Filed unassigned during the 2026-08-18 adjudication of that card (maintainer directed the split package: 「简化开发」→「同意」).The measurement
Today's shipped gate costs 33.4 ms at 21 objects → 506 ms at 420 objects per flow publish. The companion per-rule profile shows the bill concentrates in at-gate rules being handed the whole stack:
validateStackExpressions≈ 70% of the option-(a) bill,validateReferenceIntegrity≈ 11% — both already on the runtime-publish surface today.✅ Re-confirmed twice since, on two later refs and two different machines:
validateStackExpressionswalks — not its object count. Count is a proxy whose constant is set by what the objects contain. At identical N a real-shape stack and a synthetic expression-free one are ~21–26× apart (measured 2026-08-20: real/stub 26.4× @21, 21.4× @420). Any number quoted from this family must name its stack shape; not doing so is what once made this card's figure look 16–25× wrong against another correct measurement of a different workload.Why it may matter
Flow publish is a user-facing interactive action (Studio designer). Half a second of gate latency at 420 objects is already perceptible, and it grows with exactly the number that grows as a tenant succeeds.
validateRuleCompilabilityat 81.3%, a rule that appears in no profile on this card. ⛔ Every concentration figure here describes flow publishing only, and the object door remains unmeasured. The better-characterised half is the less important half.⭐ Why the obvious fixes do not reach this door — measured 2026-08-20
Package-narrowing (#9612) requires two things, and a tenant-authored overlay stack has neither:
packageIdon the write. REST derives it solely from?package=(packages/rest/src/rest-server.ts, where'all'/empty is commented as "env-local overlay"), so a Studio publish yieldsundefinedandresolveWritePackageScopereturns at its first guard._packageIdprovenance on the stored objects.narrowObjectsToPackageClosure's limb 2 —if (typeof owner !== 'string' || owner === '' || owner === OVERLAY_PROVENANCE_SENTINEL) return true;— keeps any object with no provenance, and the real seeded corpus carries zero.⇒ 📌 Fixing the missing
packageId(#10063) would not, on its own, buy the saving. That is the counter-intuitive part and the reason this card stays open: without it, the plumbing fix reads like a latency win and is not one on an unpackaged corpus. Cross-noted on #10063.The counterfactual proves the mechanism is sound rather than broken: on a provenance-stamped seed the shipped deriver narrows 421 objects to 45 (10.7%) and saves 89.5% (468.10 → 49.36 ms), with the differential verdict semantically unchanged and both positive controls live. The narrowing works; it just cannot reach this corpus.
⛔ Fences, carried and still binding
5339305885) — which, per the section above, does not reach this door as literally implemented.The instrument
Re-runnable, states its own timed span, warmup policy and seeded stack shape, prints both shapes, and names the rules it dispatched — so a stale build shows up as a changed rule list rather than as a silently wrong number. It replaces the throwaway harness this card's original reading came from, which was the real problem all along: the number was right, and nobody could re-run it to find that out.