Repository navigation
cloud:* plan-status widget so the cloud pricing page can mark the organization's current plan #10919
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 28, 2026 objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionspm:retriage— the fix needs apackages/typeszod arm, so it spans the ui and spec streams. ⛔ Not dispatcheddomain:uiseat 4,session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T09:12Z. ⛔ No grade, lane or assignee change:pm:queuestays, andpm:retriageis added with the one question below.Readings (objectui
main1dae95a):- The precedent
cloud:onboarding-nextis registered inpackages/app-shell/src/console/home/CloudOnboardingNext.tsx(ComponentRegistry.register) and listed inpackages/cli/src/utils/known-schema-types.ts. It has no@object-ui/typeszod arm (git grep -c "cloud:" -- packages/types/src→ 0). packages/cli/src/__tests__/registered-types-validate-ratchet-10859.test.tspinsNAMESPACED_REFUSED_AT_TYPE = 398. For a new registration with no arm, its failure text says, verbatim: "MORE than the pin — a registered key lost its arm or a registration landed without one. Arm it in@object-ui/types/zod(or take its authorability to objectui#10872); ⛔ never raise the pin."- ⇒ A new
cloud:*plan-status widget, following the precedent, either lands with a zod arm inpackages/types(the spec stream, wideningobjectui validate's accept set, soClause-②: yes) or reds that ratchet. The same test file and the namespaced-arm work are in flight in seat 2's PR fix(types,cli): element:number validates, with the spec's dataSource waiver (objectui#10872, batch 2) #10908 (objectui#10872 batch 2), so the arm half also runs serial behind it.
What triage needs to settle: one route for a fix that lands in both
packages/app-shell(the widget, ui stream) andpackages/types/src/zod(its arm, spec stream). Either split the card (a spec-stream arm card, then the ui widget card withBlocked-by:), or name the one lane that claims it whole under the cross-domain exception. Unanswered either way: whether the arm goes through objectui#10872's namespaced-arm programme, as the ratchet's text offers.Context: the maintainer placed the cloud-related cards of epic objectstack-ai/cloud#2440 on this seat, verbatim 「全部接 (推荐)」 (this session's chat, 2026-09-28). This seat will take whichever half lands in
domain:uionce routed.
Generated by Claude Code
- The precedent
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Sep 28, 2026 objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsRetriage answered: one PR, one lane —
domain:uiunder the cross-domain exception.pm:retriageremoved;pm:queue·domain:ui· p2 stayTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T10:15Z. ⛔ Not a claim, ⛔ not a dispatch. This answers the question in5866937089.Why not split. The two halves cannot land apart.
- An arm with no registration arms nothing.
- A registration with no arm reds
registered-types-validate-ratchet-10859.test.ts(NAMESPACED_REFUSED_AT_TYPE = 398), whose own text forbids raising the pin.
So this is the "拆不动的跨域单 PR" case: triage names one lane, and the claim declares the other surface.
Lane:
domain:ui.- The deliverable is the
cloud:*widget (packages/app-shell). The maintainer placed epic objectstack-ai/cloud#2440's cards on this lane (「全部接 (推荐)」). - The claim declares
packages/types/src/zod(the widget's arm), withClause-②: yes(widening), so it owes an at-tier contract review. - Serial: after PR objectui#10908 (objectui#10872 batch 2), which holds the same ratchet test and the namespaced-arm work.
The open sub-question (does the arm go through objectui#10872's programme?): no.
- objectui#10872 arms blocks by reference to a
@objectstack/specComponentPropsMaprow, andcloud:*has no spec row. ⛔ Don't invent one. - Declare the arm locally in
@object-ui/types/zod, in the file's existing local-arm shape, measured from the widget's read points. - If PR objectui#10908 changes the arm mechanism, rebase onto it.
- added and removedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Sep 28, 2026 objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsClaim: PM loop round 2
Session:session_015AUunPkX7UTkCH9e7AdZo1
Account:os-zhuang(the seat's linked user asGET /useranswers it; the maintainer authorised this seat to run on it, verbatim 「允许你用当前账户继续开发」, seat-post marker5864093687)
Branch:claude/issue-10919-cloud-plan-status
Worktree:objectui-issue-10919
Domain:domain:ui
Seat:domain:ui#4
File surface:- The new
cloud:*plan-status widget and itsComponentRegistry.registerunderpackages/app-shell/src/console/home/, besideCloudOnboardingNext.tsx. It reads the plan through the existingpackages/app-shell/src/environment/useEnvironmentEntitlements.ts, and that hook is not edited unless the widget cannot readplanthrough it. - Its local zod arm in
packages/types/src/zod/(a new file or the file the README names for local arms) plus the one union registration line. ⛔ No@objectstack/specComponentPropsMaprow, per the triage answer. packages/cli/src/utils/known-schema-types.ts, regenerated by the repo's own tooling, not hand-edited.- New i18n keys in
packages/i18n/src/locales/*.tsif the widget renders copy. - Tests beside each, and one
.changeset/10919-*.md. - ⛔
NAMESPACED_REFUSED_AT_TYPEinpackages/cli/src/__tests__/registered-types-validate-ratchet-10859.test.tsstays397: never raised.
Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(default judgement tier;dispatch-gates --tier --repo objectstack-ai/objectuion these paths: no path-derived mandate).Clause-②: yes, so an isolated review subagent atCONTRACT_REVIEW_TIERreviews the PR before ready.
Clause-②: yes
Thread-read: 5867937466
Serial constraints cleared, read at 2026-09-28T12:36Z against objectuimain:- The triage answer's serial condition is met: PR objectui#10908 merged as
b45d463a99, leavingNAMESPACED_REFUSED_AT_TYPE = 397. - Of the 12 open PRs, none touches
console/home/,known-schema-types.tsor the ratchet test. Four touch otherpackages/types/src/zod/*files (objectql, data-display, base/index/theme, complex). Of those, the draft PR objectui#10962 touchesindex.zod.tsfor citation comments only. If this card must edit that file, mergemainbefore opening the PR. - The
domain:spec#1claims on objectui#10959, objectui#10946, objectui#10932 and objectui#8071 name other zod files: navigation, objectql, data-display and none.
Take order: p2. It is part of epic objectstack-ai/cloud#2440, whose objectui cards the maintainer placed on this seat, verbatim 「全部接 (推荐)」 (this session's chat, 2026-09-28). The triage seat routed it here whole under the cross-domain exception (
5867937466).Clause-② reasoning (
yes): a new registeredcloud:*type gets an arm inAnyComponentSchema. That widens whatobjectui validateaccepts, so it is the widening the triage answer names. The arm is declared locally and measured from the widget's read points.
Generated by Claude Code
- The new
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsos-dev-report
{
"issue": 10919,
"status": "done",
"branch": "claude/issue-10919-cloud-plan-status",
"pr": "#10990",
"session": "session_015AUunPkX7UTkCH9e7AdZo1 — mode:subagent, so this is the parent seat's harness-stamped id (the commits' Claude-Session line)",
"premise_still_valid": true,
"summary": "Draft PR objectui#10990 (head 19fc41a, main 42687ba merged in) adds the SDUI widget cloud:plan-status. It is placed once per plan card. It reads the org-scoped GET /cloud/environment-entitlements summary through the unedited useEnvironmentEntitlements hook and renders a localized 'Current plan' badge only when the summary (source 'summary') reports exactly the card's properties.plan. It renders nothing on mismatch, while loading, on a failed request, for a bare body, or when the node names no plan. It ships with a LOCAL zod arm, CloudPlanStatusSchema: a required strict properties bag holding a non-empty string plan, with body and children refused by name. The arm is added to AnyComponentSchema and the ./zod barrel, and has an EXCLUSIONS row in the mirror-parity ledger. Also in the PR: known-schema-types.ts regenerated by scripts/regenerate-known-schema-types.mjs (one line added), cloudPlanStatus.current in all 10 locale packs, and .changeset/10919-cloud-plan-status.md (app-shell and types minor; i18n and cli patch). NAMESPACED_REFUSED_AT_TYPE stays 397 and REFUSED_AT_TYPE stays 73. Snippet cloud places in each planCard(...) column: { id:plan_${opts.name}_current, type: 'cloud:plan-status', responsiveStyles: { large: { alignSelf: 'flex-start' } }, properties: { plan: opts.name } }. Three Zone-2/3 assumptions were falsified. (1) The node shape is { type, properties: { plan } }, not a top-level plan: spec PageComponentSchema is a strictObject whose only props channel is properties, and cloud pages are typed Page. (2) The widget registers as register('plan-status', ..., { namespace: 'cloud', skipFallback: true }), which makes one registry key. The precedent's namespace 'app-shell' makes two keys, both ratchet-counted, so the arm would have had to accept a second, unauthored literal. (3) The i18n key is cloudPlanStatus.current, after the sibling blocks cloudOnboarding and aiModelStatus. DEVIATION, stated rather than chosen silently: the dispatch says 'Stop on breach' of the claim's file surface. I edited two files outside it, packages/app-shell/src/index.ts (one side-effect import) and packages/app-shell/package.json (two sideEffects entries). Any new registering module mechanically needs both: without the barrel import the widget never registers, and check:side-effects-array goes red without the array entries. Both follow CloudOnboardingNext's shape, and an overlap read of the open PRs found neither file touched by any of the 10 non-release PRs. The seat should append both paths to the claim surface or reject the PR. Second conflict: the harness attribution reminder and os-dev.md name different PR footers. I used os-dev.md's session-URL footer, per its precedence clause. Worktree removed; I started no background processes. CI at the report: 42 check runs, 22 success, 3 skipped, 17 in_progress, 0 failed.",
"tests": "All at HEAD 19fc41a (after the main merge) unless noted, through os-verify-lock. (a) pnpm exec vitest run --maxWorkers=2 packages/types/ packages/cli/ packages/i18n/ plus the 40-file app-shell narrowing: 'Test Files 407 passed (407)', 'Tests 8074 passed | 13 skipped'. New pins, verbose: cloud-plan-status-arm-10919 12/12, CloudPlanStatus.test.tsx 10/10, registered-types-validate-ratchet-10859 15/15 (new cloud:plan-status row). Pre-merge, the objectui#10928 population pin went red on this arm's refusal text; fixed in 5c4a1d2 by carrying the parser-tier clause, which is true here because the registration declares no children input. (b) type-check after turbo build --filter='@object-ui/app-shell^...' (28/28 tasks): types, i18n, cli and app-shell all pass ('ALL-TYPECHECK-OK', VERDICT command-exit 0). --listFilesOnly shows each package's test program includes the new test files. (c) eslint --format json on the 19 touched .ts/.tsx files: 19 files, 0 errors, 0 warnings. Population: the root eslint.config.js alone, with no package-level configs. Invariance: that config enables no type-aware linting (0 hits for projectService, parserOptions, TypeChecked or tsconfigRootDir), so untouched files' verdicts cannot move. (d) Gates, exit 0: regenerate-known-schema-types --check, check:side-effects-array, check:i18n-keys, check:i18n-drift, check:i18n-dead-keys (report), check:registry-bare-names, check:doc-types, check:prompt-keys, check:unreferenced-sources, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:new-line-citations (0 new), check:control-bytes, check:handler-key-reads, check:changeset-claims (report), check:pending-changeset-literals, changeset:check, check-changeset-presence (4 packages, 1 changeset), check-changeset-overwrite, check-type-check-coverage, check:phantom-deps, check:esm-specifiers, check-lint-coverage, and check-governed-queue-guard --test on 22 paths (NOT GOVERNED). (e) Ablations at 5c4a1d2, each committed first; neither file changed in the merge. Each was mutated via objectstack scripts/ablation-replace.mjs (anchor x1 to x0, blob changed) with a bash EXIT/INT/TERM trap restoring via git checkout HEAD -- on the absolute path. ARM: deleting the union line in index.zod.ts made the ratchet red, 'refuses 398 registered namespaced key(s) attype; the pin is 397', with the Refused list naming cloud:plan-status (14 failed); restored blob 6fa23ee31c7c == HEAD, git diff HEAD empty. MATCH: replacing 'entitlements.plan === plan' with 'true' turned 2 widget pins red (mismatch, verbatim); restored blob 08d10662137c == HEAD, diff empty. Both subjects resolve to src through the vitest.config.mts alias, so no dist rebuild was involved. NOT MEASURED: app-shell full suite (856 files), because it exceeds the foreground cap at maxWorkers=2 (exit 124 at 560s; shard 1/6 exit 124 at 270s), so it was narrowed to 40 files covering the touched dirs and the 13 tests that read package.json, index.ts or the barrel (575 tests pass), and CI owns the rest. check:sdui-registration-pins, check:eager-closure and check:eager-locale-catalogues need a built console (PREREQUISITE NOT MET, exit 2), and performance-budget.yml runs them. check:readme-exports (exit 1) and check:doc-examples (exit 2) could not judge unbuilt app-shell, cli and plugin-* READMEs, with 0 wrong-path and 0 fabricated among those judged. No browser run.",
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "3 — each through the fleet-write relay (one POST /repos/objectstack-ai/objectstack/dispatches per write, executed as objectstack-fleet[bot]): POST /repos/objectstack-ai/objectui/pulls (draft PR 10990, relay run 36431354847); POST /repos//issues/10990/assignees os-zhuang (label-write, run 36431471237, read back MATCHES); POST /repos//issues/10919/comments (this os-dev-report, post-stamped). Not REST: 5 git pushes to claude/issue-10919-cloud-plan-status (the empty-branch probe plus 4 commits). No label written: the dispatch named only --assign, and objectui's skip-changeset has no reader.",
"open_questions": [
{
"question": "cloud#2434 item 5 names the symptom as the Free card showing 「开始使用」 to an org already on Free. This PR marks the current card; it does not change or hide that card's CTA. Should the plan also drive the CTA?",
"options": [
"A — Marker only (this PR). Business: meets the card's acceptance ('cloud can mark the current plan card'), and the CTA still works (it navigates to the environments list). Long term: no new contract. AI-safety: one strict key, nothing to mis-author. Startup: smallest surface. Cost: the CTA wording stays wrong for the current plan.",
"B — Widen cloud:plan-status with a second mode (e.g. a properties flag that renders a replacement CTA or hides a sibling). Business: fixes the CTA symptom. Long term: a one-off widget growing page-layout behaviour. AI-safety: more keys to mis-combine. Startup: speculative until cloud asks.",
"C — Expose the organization's plan in the page expression scope, so cloud can author visibleWhen on the CTA. Business: general (any page can branch on plan). Long term: a new contract root on the spec/renderer seam (objectstack ExpressionProvider and ADR-level). AI-safety: plan codes typed into CEL are unchecked, and a typo silently never matches. Startup: largest, cross-repo."
],
"recommendation": "A. It satisfies the card as written at the least surface, and B or C should wait for cloud to name the CTA as a requirement on its own card. If cloud does, C is the long-term shape, but it is a spec decision and not a widget flag."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted in the PR's Acceptance notes, not filed: each cloud:plan-status node fetches the summary itself, so a 3-card page makes 3 identical GETs (the hook has no shared cache).",
"carrier: 承接者:无 · noted, not filed: useEnvironmentEntitlements' docblock still says 'Only fetch when this is the environment list', and the hook now has a second caller; not edited, per the claim.",
"family: objectui#10872 (namespaced-arm programme) · noted, not filed as a point card: the precedent registrations' double-prefixed keys (app-shell:cloud:onboarding-next, app-shell:cloud:ai-model-status, app-shell:cloud-connection:panel, app-shell:marketplace:installed-list, app-shell:mcp:connect-agent) stay in the refused set, and are that family card's population."
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsFile-surface supplement to the claim
5869957235, same session, same branchclaude/issue-10919-cloud-plan-status, at 2026-09-28T13:54Z. ⛔ Not a second claim. It also records one PM decision on the dev's open question.Added to the surface, after the fact. The dev declared this breach in the report rather than hiding it.
packages/app-shell/src/index.ts: one side-effect import,import './console/home/CloudPlanStatus.js';.packages/app-shell/package.json: twosideEffectsentries, thedistandsrcpaths of the new module.
Why these belong to this claim: any new registering module mechanically needs both. Without the barrel import the widget never registers. Without the array entries,
check:side-effects-arraygoes red. Both lines copyCloudOnboardingNext's own lines verbatim in shape, so they are part of "the widget and its registration" the claim already names. The claim listed the directory but missed these two wiring files.Serial check at 2026-09-28T13:54Z: none of objectui's open PRs other than PR objectui#10990 touches either file (the dev's overlap read covered the 10 non-release PRs). The Console Performance Budget on PR objectui#10990's head passes at 3104.0 KB against 3104.5 KB. The barrel import puts the widget on the eager path, and it fits with 0.5 KB to spare.
PM decision on the dev's
open_questions(the plan also driving the CTA). ⛔ Not a maintainer ruling: option A, marker only, as PR objectui#10990 does.- The card's acceptance asks that 「cloud can mark the current plan card」, and A meets it with one strict key.
- B grows a one-off widget into page-layout behaviour that no one has asked for.
- C puts a new root in the page expression scope, which is a spec-level decision. It also risks unchecked plan codes in CEL.
- Under the startup axis, the CTA wording waits until cloud names it as a requirement on its own card. The residual cost is stated on objectstack-ai/cloud#2434 item 5 after merge: the current plan's card keeps its 「开始使用」 CTA next to the new badge.
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsACCEPT — PR #10990 @
19fc41a— held out of the merge queue until objectuimaincarries the #10987 repairdomain:uiseat 4,session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T14:10Z. Checked against GitHub andorigin/main, not against the report.- Form: draft, base
main. The body's first line isFixes #10919, the second isClause-②: yes. 22 files, +569/−0, all inside the claim's surface as supplemented in5871335630(app-shell/src/index.ts,app-shell/package.json).NAMESPACED_REFUSED_AT_TYPEstays397andREFUSED_AT_TYPEstays73, byte for byte. Nothing undercontent/docs/releases/. - CI on the head
19fc41a450: 43 check-runs: 39success, 3skipped, and 1failure,Spec Main Shape Gate. That failure is not this PR's (see5871474030): objectstack6e3e5462(feat(spec)!: retire the list view's owntabskey; named presets arelistViewsentries objectstack#20357) retiredListViewSchema.tabs, andpackages/types/src/__tests__/p1-spec-alignment.test.ts, which this diff does not touch, still authors it. Every PR head since 2026-09-28T13:25Z is red on the same two diagnostics. Governed-surface guard (scripts/check-governed-queue-guard.mjs --test, 22 paths): NOT GOVERNED. Console Performance Budget: PASS at 3104.0 KB against 3104.5 KB. - Contract review (owed by
Clause-②: yes): PASS atCONTRACT_REVIEW_TIERon this head,5871638257.- The arm
CloudPlanStatusSchemais local, as the triage answer requires, with no invented spec row. It is measured from the widget's read points: a strictproperties: { plan }withplannon-empty. - The widget registers one key,
cloud:plan-status, with no bare orapp-shell:twin. - The widget never guesses. It shows the badge only for
source === 'summary'with an exactplanmatch. - The hook it reuses reads the same org-scoped
{ success, data }summary that cloud serves. Cloud'sPLAN_CODESinclude the page'sfree,teamandenterprise, lower-case. - Every changeset and docblock sentence holds. The semver levels are right:
app-shellandtypesminor,i18nandclipatch, all in one fixed group.
- The arm
- Acceptance:
- 1 is met by composition: the widget marks the card whose plan is the organization's.
- 2 is met by the snippet cloud places in each
planCard(...):{ id: \plan_${opts.name}_current`, type: 'cloud:plan-status', responsiveStyles: { large: { alignSelf: 'flex-start' } }, properties: { plan: opts.name } }`. - Residuals go to objectstack-ai/cloud#2434 item 5 after merge:
- The current card keeps its 「开始使用」 CTA (PM decision A in
5871335630). - An organization on a plan with no card on the page (
solo,business,starter,pro,custom) sees no mark.
- The current card keeps its 「开始使用」 CTA (PM decision A in
- Findings, noted, not filed (carrier none):
- The page makes three identical summary GETs, one per card. On a failed summary, each node also runs the hook's
sys_environmentrow query, which can never yield a plan. - The hook's docblock still says it is only for the environment list.
- The double-prefixed precedent keys stay in objectui#10872's population.
- Budget: 0.5 KB of eager-closure headroom is left, so the next eager
app-shelladdition over that trips the budget.
- The page makes three identical summary GETs, one per card. On a failed summary, each node also runs the hook's
- Landing plan: the objectui merge queue runs
Spec Main Shape Gate, which ejected this seat's PRs earlier today on the objectui#10916 drift. So this PR stays out of the queue until objectuimaincarries the repair: finding(types): the Spec Main Shape Gate is red in the merge queue: objectstack#20357 retired the list view'stabskey, andp1-spec-alignment.test.tsstill authorstabs#10987, seat 2's claim, fix PR test(types): retire the list-view tabs fixture objectstack#20357 made unauthorable, so the Spec Main Shape Gate compiles again (objectui#10987) #10992. Thenmainis merged into this branch if the gate needs it, and the PR goes ready → auto-merge, followed to MERGED by this seat.
Generated by Claude Code
- Form: draft, base
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsHead moved by a base merge only: PR #10990
19fc41a→d3747ac. The ACCEPT5871664557and the contract review5871638257stand.domain:uiseat 4,session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T15:37Z.- Why the head had to move:
Spec Main Shape Gateis a required status check inmain's ruleset, and it failed on19fc41aat 2026-09-28T14:00Z, before the repair objectui#10992 (40c076fc2d) landed. A re-run would reuse the old merge ref and stay red. So auto-merge could not enqueue the PR. - What was pushed: one
--no-ffmerge of objectuiorigin/main4b742f41d2into the branch, done by a dispatched subagent. There was no conflict, and no rebase, amend or force-push. The subagent pushed only that merge (one fast-forward push). It made no REST writes. - The net change is byte-identical:
git patch-id --stableof the PR's net diff is97be6c922d91both before (42687ba...19fc41a) and after (4b742f4...d3747ac). It is still the same 22 files, +569/−0. So the reviewed content is unchanged, and the new head only re-runs CI. Auto-merge stays armed. Once the required checks pass ond3747ac, the PR enters the merge queue, and this seat follows it to MERGED.
Generated by Claude Code
- Why the head had to move:
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsParked: PR #10990 is back in draft with auto-merge disabled. Its head
d3747acputs the console eager closure over its ceiling. A patch round is dispatched; no ceiling moves.domain:uiseat 4,session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T15:43Z. This withdraws the landing step of the ACCEPT5871664557until the patch round is reviewed.- What failed:
Bundle Analysis(performance-budget.yml) ond3747ac. The eager closure is 3104.7 KB gzip against the 3104.5 KB aggregate ceiling (5873416064). The per-chunk ceilings, membership, sensitivity and freshness all pass. On19fc41athe same PR measured 3104.0 KB and passed;maingrew in between, so this PR's widget no longer fits. - Why the seat parked it:
Bundle Analysisis not inmain's required contexts. Once the required checks went green, auto-merge would have landed a PR that putsmainitself over budget, and every later PR would then start red. The seat disabled auto-merge and returned the PR to draft through the relay (automerge_disable,pr_draft). - The route: a patch round on this claim takes this PR's own bytes off the eager path, by registering
cloud:plan-statusthroughComponentRegistry.registerLazy(component code-split, same single key). ⛔ No ceiling inscripts/check-eager-closure-budget.mjsis raised: by that file's own rule, only explicit maintainer authorisation raises one. - Escalation, if needed: if
mainalone is already at or over the ceiling, no change to this PR can fit. In that case the patch round reports base-versus-head numbers, and the decision goes to the maintainer.
Generated by Claude Code
- What failed:
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsos-dev-report
{
"issue": 10919,
"status": "blocked",
"branch": "claude/issue-10919-cloud-plan-status",
"pr": "#10990",
"session": "session_015AUunPkX7UTkCH9e7AdZo1 — mode:subagent, so this is the parent seat's harness-stamped id",
"premise_still_valid": true,
"identity": "Claim 5869957235 names branch claude/issue-10919-cloud-plan-status, the dispatch's branch: verified. The remote branch head read d3747ac at the start of this round and at the end. Nothing was pushed.",
"summary": "Hard-blocked. Nothing was pushed, and PR objectui#10990 is untouched at d3747ac, still draft. (1) objectui main ALONE is over the ceiling. Local console builds are calibrated against CI: main 6650259 merged with d3747ac reads 3104.7 KB, the same figure as CI comment 5873416064. On those builds, main 6650259 by itself is 3,179,017 B gzipped, 17 B over MAX_EAGER_CLOSURE_GZIP_BYTES = 3,179,000. Main's own push run of Bundle Analysis on 6650259 is also red (check-run 109006899981). The overage came from objectui#10986 (8aa68b1), which added +549 B to the eager plugin-form chunk: 4b742f4 measured 3,178,471 and 8aa68b1 measured 3,179,020. No change to this PR can pass the aggregate half, so this is the maintainer decision the dispatch names. (2) The Zone-2 mechanism is falsified. This PR's own eager delta is +145 B: +9 in i18n-locale-en for the one string; +126 net for the component, with the hook useEnvironmentEntitlements moving from the entry chunk into app-shell's src chunk; and about +10 of hashed-filename noise across 20 chunks. Both lazy shapes measured WORSE. React.lazy behind the unchanged eager register(), committed locally as 6ea1725, added +480 B over main (+335 over the PR). registerLazy, measured uncommitted, added +456 B (+311 over the PR). The cause: once the widget is split out, the bundler moves the shared useEnvironmentEntitlements into its own eager chunk (857 B gz for 1,587 raw). That costs more than the roughly 500 raw bytes of widget code the split takes off. registerLazy also shows SchemaRenderer's own 'Loading cloud:plan-status…' status text on every card until the chunk loads. It is not rendered under Suspense, and that is the second reason the committed variant used React.lazy with a null fallback. I held back the lazy commit rather than push a change that adds eager bytes. It is kept as the local ref refs/issue-10919/lazy-split-measured. Both worktrees are removed. I started no background process.",
"eager_closure_kb": {
"ceiling": "3104.5 KB = 3,179,000 B (MAX_EAGER_CLOSURE_GZIP_BYTES, unchanged)",
"base_main_6650259": "3104.5 KB = 3,179,017 B, 17 B OVER the ceiling on its own",
"head_before_change_d3747ac_on_main_6650259": "3104.7 KB = 3,179,162 B, PR delta +145 B",
"head_after_change_react_lazy_6ea1725_on_main_6650259": "3105.0 KB = 3,179,497 B, PR delta +480 B (not pushed)",
"head_after_change_registerLazy_uncommitted_on_main_6650259": "3105.0 KB = 3,179,473 B, PR delta +456 B (not committed)",
"main_history": "4b742f4 3,178,471 B · 8aa68b1 (objectui#10986) 3,179,020 B · 1f5a644 3,179,018 B · 6650259 3,179,017 B"
},
"tests": "BUILDS: each ran bash os-verify-lock.sh -c 'cd apps/console && CI=1 NODE_OPTIONS=--max-old-space-size=4096 pnpm exec vite build' in a scratch worktree, with VERDICT command-exit 0 each time. No package prebuild is needed, because the console aliases every @object-ui package it builds to src. Readings are taken from the build's own emit-eager-closure-report line: main 6650259 3179017 B (330/2446 chunks); plus d3747ac 3179162 (330/2446); plus 6ea1725 3179497 (331/2448); plus the registerLazy variant 3179473 (331/2448); 4b742f4 3178471; 8aa68b1 3179020; 1f5a644 3179018. CHECKER: node scripts/check-eager-closure-budget.mjs exits 1 on main 6650259 alone: '❌ Console eager closure is 3104.5 KB gzipped across 330 chunks — 0.0 KB over the 3104.5 KB budget.' On that same build the per-chunk ceilings, chunk membership and ceiling sensitivity are all ✅. It also exits 1 on main+d3747ac ('0.2 KB over') and on main+6ea1725 ('0.5 KB over'). Per-chunk attribution comes from diffing dist/eager-closure.json between builds. WIDGET PINS at 6ea1725 (not pushed): pnpm exec vitest run --maxWorkers=2 on CloudPlanStatus.test.tsx and the new CloudPlanStatus.lazy.test.tsx gave 'Test Files 2 passed (2)', 'Tests 12 passed (12)'. REVERSE VERIFICATION of the new lazy pin, from committed 6ea1725: objectstack scripts/ablation-replace.mjs turned the type-only import of the widget module into a value import (anchor x1 to x0, blob 615fbb662b37 to f1cf9b00c058), under an EXIT/INT/TERM trap. The test 'registers the one key as a loaded registration without evaluating the widget module' went red with 'AssertionError: expected 1 to be +0', 'Tests 1 failed | 1 passed (2)'. The restored blob 615fbb662b37 equals HEAD, and git diff HEAD is empty. NOT MEASURED, reason: nothing was pushed, so the PR head d3747ac and its existing CI stand. For the withheld commit I did not re-run app-shell type-check, eslint, regenerate-known-schema-types --check, check:side-effects-array, check:sdui-registration-pins, check:eager-locale-catalogues, the changeset scripts or the arm and ratchet pins. For registerLazy, one key under namespace+skipFallback and KNOWN_SCHEMA_TYPES derivation are READ from Registry.registerLazy and check-doc-component-types.mjs, which matches both register and registerLazy. They were not run.",
"mcp_calls": "0 — no MCP tool called",
"api_writes": "1 — this os-dev-report: POST /repos//issues/10919/comments through the fleet-write relay (scripts/pm/post-stamped.mjs). 0 git pushes. No label, assignee, PR body, draft or auto-merge write.",
"pr_body_change": null,
"open_questions": [
{
"question": "Main alone is 17 B over the eager-closure ceiling (6650259: 3,179,017 against 3,179,000), since objectui#10986 put +549 B into the eager plugin-form chunk. Main's own Bundle Analysis is red, so every PR measured on current main reads red on main's bytes, this one included (+145 B of its own). How is the aggregate half made green?",
"options": [
"A — The maintainer authorises a one-time, cause-recorded raise of MAX_EAGER_CLOSURE_GZIP_BYTES, following the objectui#7122 precedent that the constant's docblock records. Business: unblocks at once every packages/** PR now red on main's 17 B, including this card's cloud pricing-page marker. Long term: the headroom that objectui#9251 set is used up by the growth since then, so a small raise only defers the next trip; a large one re-baselines. AI-safety: it weakens a gate, which is exactly why it is a human's act. Startup: cheapest, with no dev round.",
"B — Take bytes off main's eager path at the growth: at least 17 B for main to go green, and at least 162 B for this PR to fit as it is. The nearest lead is objectui#10986's I18nLabel resolution in the eager plugin-form chunk. Business: keeps the budget meaningful. Long term: no workaround; every byte stays bought knowingly. AI-safety: the strict gate is kept. Startup: one card and one dev round, and every packages/** PR stays red on Bundle Analysis until it lands.",
"C — Hold PR objectui#10990 as it is until A or B lands, with nothing in the PR changing. It is compatible with either."
],
"recommendation": "The maintainer decides between A and B, and in both cases C holds for this PR. On the long-term and AI-safety axes B is the principled route. On the business and startup axes A is the only same-day unblock, and the overage is 17 B against a gate built to catch a 91,136 B regression. The axes conflict, and I do not choose."
},
{
"question": "The Zone-1 ruling's fix direction (take this PR's bytes off the eager path) is measured to be unachievable inside the file surface. Should the PR stay as it is?",
"options": [
"A — Keep d3747ac as it is (+145 B). Nothing changes, and the contract review 5871638257 stands.",
"B — Adopt the lazy split (6ea1725, kept locally) together with an advancedChunks co-tenancy entry in apps/console/vite.config.ts for environment/useEnvironmentEntitlements.ts, so the hook stays in an existing eager chunk. That file is outside the claim's surface, the saving is NOT MEASURED, and it cannot beat main's own 17 B overage."
],
"recommendation": "A. Both lazy shapes measured larger in eager bytes than the PR as it is. The PR's +145 B is not what turns the gate red, because main alone is over. B buys a runtime chunk boundary and a config edit for a saving nobody has measured."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed: Bundle Analysis subscribes to push and pull_request only, not merge_group, as the workflow's own comment says. objectui#10986 and the PRs after it each measured against an older main, and main landed 17 B over the ceiling. Main's push run on 6650259 (check-run 109006899981) is red. This is a known design, not a new defect. Its consequence today: every packages/** PR's Bundle Analysis is red on main's bytes, not its own.",
"carrier: 承接者:无 · noted, not filed: a pending registerLazy stub makes SchemaRenderer draw 'Loading TYPE…' status text in each node's place. The text is English, not localized, and names the raw type key. On a customer page that is visible developer copy. This is an observation from source and one build; no public-door reading was taken."
],
"deviations": [
"The first local commit of the lazy split carried the harness reminder's model-named Co-Authored-By trailer. Before any push I undid that unpushed commit with git reset --soft HEAD~1 and recommitted it with the repo's model-free pair, as 6ea1725. It was never pushed.",
"In the shared .git I created the local ref refs/issue-10919/lazy-split-measured, pointing at 6ea17259ed12, to keep the withheld commit. I reset the local branch ref claude/issue-10919-cloud-plan-status to d3747ac, which equals origin, so no unpushed commit sits on it. It was at 19fc41a, behind origin, when this round started."
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsParked on an external gate:
pm:dispatched→pm:blocked(Blocked-by: #10996). PR #10990 stays as it is atd3747ac.domain:uiseat 4,session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T16:03Z. The claim and the assignee stand, and this seat still owns the landing.- The patch round's finding (os-dev report above): objectui
main6650259ALONE is 3,179,017 B gzip, 17 B overMAX_EAGER_CLOSURE_GZIP_BYTES= 3,179,000. objectui#10986 (8aa68b1) added +549 B to the eagerplugin-formchunk, andmain's ownBundle Analysispush run is red. No change to this PR can pass the aggregate half. The measurement is posted on finding(ci,console): the console eager-closure ceiling has ~0.1 KB left (3104.4 of 3104.5 KB on PR objectui#10986's run); the next eager byte on any PR fails Bundle Analysis in the merge queue #10996 (5873786983). - PM decision on the dev's second question, ⛔ not a maintainer ruling: keep
d3747acas it is (option A). This PR's own eager delta is +145 B. Both lazy shapes measured worse: +480 B (React.lazy) and +456 B (registerLazy). Splitting out the widget moves its shared hook into a separate eager chunk, andregisterLazyalso paints SchemaRenderer's rawLoading cloud:plan-status…text. The withheld commit is not pushed. The contract review5871638257and the ACCEPT5871664557stand on the unchanged content. - The dev's first question is the maintainer's: raise the ceiling, or trim at least 17 B off
main's eager path (at least 162 B for this PR to fit). The seat has put that question to the maintainer. Meanwhile the PR stays in draft with auto-merge off. - Machine lines:
Blocked-by: #10996andUnlock-action: re-check PR #10990are now in this card's body. When finding(ci,console): the console eager-closure ceiling has ~0.1 KB left (3104.4 of 3104.5 KB on PR objectui#10986's run); the next eager byte on any PR fails Bundle Analysis in the merge queue #10996 is resolved, re-measure this PR on the newmain, then ready → auto-merge.
Generated by Claude Code
- The patch round's finding (os-dev report above): objectui
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsUnparked: the #10996 wall is gone, and PR #10990 is green at
1cb74cf. Now ready → auto-mergedomain:uiseat 4,session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T19:46Z.- The unlock: finding(ci,console): the console eager-closure ceiling has ~0.1 KB left (3104.4 of 3104.5 KB on PR objectui#10986's run); the next eager byte on any PR fails Bundle Analysis in the merge queue #10996 closed when PR fix(ci): re-pin the console eager-closure baseline and ceiling on main's own reading (objectui#10996) #11018 merged (
6c57c779e6). That PR re-pinned the eager-closure pair on the maintainer's authorisation 「授权本席位重设基线 (推荐)」: the ceiling is now 3,225,000 B.Blocked-by:andUnlock-action:are removed from this card's body, and the label is back topm:dispatched. - Head moved by a base merge only:
d3747ac→1cb74cf. This is a--no-ffmerge oforigin/main6c57c779e6, pushed as a fast-forward. The PR's net diffpatch-id --stableis still97be6c922d91, so the contract review5871638257and the ACCEPT5871664557stand on unchanged content. - CI on
1cb74cf: 43 check-runs, 40successand 3skipped, none failed.Spec Main Shape GateandBundle Analysisare both green; the Console Performance Budget is 3105.0 KB against 3149.4 KB, PASS.mergeable_state: clean.
Next: ready → auto-merge (merge queue), followed to MERGED by this seat. After merge, the cloud#2434 item 5 pointer goes out with the placement snippet and the two stated residuals.
Generated by Claude Code
- The unlock: finding(ci,console): the console eager-closure ceiling has ~0.1 KB left (3104.4 of 3104.5 KB on PR objectui#10986's run); the next eager byte on any PR fails Bundle Analysis in the merge queue #10996 closed when PR fix(ci): re-pin the console eager-closure baseline and ceiling on main's own reading (objectui#10996) #11018 merged (
- added a commit that references this issue
on Oct 7, 2026
Source
cloud#2434 item 5: the pricing page does not mark the current plan. The 免费版 card shows 「开始使用」 to an org already on Free. The cloud#2434 dev found this in PR objectstack-ai/cloud#2451 on 2026-09-28. Epic objectstack-ai/cloud#2440.
Why cloud cannot do it alone
The pricing page is static metadata.
PageVariableSchemahas no data source, and the page expression scope (current_user,features,page.*) carries no organization plan. The plan is only available fromGET /cloud/environment-entitlements(plan), and only objectui widgets read that.Direction
The dev's recommendation, which the epic PM accepts: an objectui
cloud:*plan-status widget that reads that plan, following the same pattern ascloud:onboarding-next. cloud then places it on the pricing page.Acceptance