Skip to content

Console home: the authoring / marketplace / setup cards ignore features.* and studio.access — two of them contradict a flag the server already sends as false #5521

Description

@os-zhuang

Blocked-by: objectstack-ai/cloud#1452

Part of objectstack-ai/cloud#1452 (filed by the triage seat from the cloud seat's transfer package on that thread — anchors re-verified at cloud's framework pin 907c11d2). Companion to #5519 (the /_console/studio/* route gate).

⚠️ THREE OF FOUR ROWS ARE DONE — do not implement from the body below without reading this

row state
Browse App Marketplace already fixed by PR #5517 (#5504) — before this card was ever dispatched
Start with a template already fixed by PR #5517 — before dispatch
Build an app landed — PR #5575 (features.aiStudio read; hidden, not dimmed, per the flag's declared force-hide semantics)
Setup the only live row — blocked on cloud#1452's product ruling

⛔ So this card's headline claim — "two of them contradict a flag the server already sends as false", called "the sharpest form of this bug" below — was already false at dispatch time. Anyone re-reading the body alone will find a bug that no longer exists and produce a plausible-looking diff that fixes nothing. The premise check that caught this is the highest-value thing on the card.

Also settled, ⛔ do not reopen: the studio.access half was answered rather than implemented#5576 was ruled Option B on 2026-08-22 (the library home page asks only deployment-level questions; principal-level Studio entry stays with the console route gate) and closed as not planned. holdsStudioAccess lives in apps/console, which depends on @object-ui/app-shell, so the import is structurally impossible in the required direction.

Unblock reading, for whichever seat can reach objectstack-ai/cloud — this seat's credential cannot (measured: attach returns "you don't have access", and a repository listing filtered on cloud returns empty; that is outside the credential, not merely unattached, and ⛔ "could not check" is not "checked and still blocked"):

Is objectstack-ai/cloud#1452 closed, and did it rule the "Setup" row's product question?

Closed with a ruling ⇒ this card returns to pm:queue with only the Setup row live; re-verify that row's file face on the merged ref first. Still open ⇒ it stays pm:blocked and nothing is owed.

The Console home page offers a plain tenant four authoring / marketplace / setup entry points on a deployment that refuses all of them — and for two of those cards the server is already sending the correct false on /api/v1/runtime/config. Those two are the sharpest form of this bug: the flag is right, derived, pinned by tests on the serving side, and not read here.

Measured

Composed hosted-SaaS shape (OS_TENANCY_POSTURE=isolated, OS_AI_STUDIO_AGENTS=ask, HotCRM composed artifact), tenant northwind as plain org owner, Playwright. /_console/home shows, verbatim:

  • "Build an app" — "Start from scratch — design objects, forms, automations and interfaces"
  • "Start with a template" — "Install a template app from the marketplace and customize it"
  • "Browse App Marketplace"
  • "Setup"

Behind them on that same deployment: /api/v1/meta/*403, and install-local / marketplace-browse / cloud-connection → 404 (those routes are not merely guarded, they are absent — the host declines them by identity).

Re-measured 2026-08-21 on the EE 4.1.0 release image (cloud#1510): a plain tenant user (positions: ["user"]) follows the "Build an app" card into the full authoring flow and is only refused at submit (POST /api/v1/packages → 403).

The lockdown criterion for this shape is two-part — UI entry hidden AND API refused. The backend half is green.

The cards are produced here, not upstream

Both other repos were grepped for the verbatim strings above. Cloud: zero hits. Framework: two hits, both documentation. There is no SDUI or server-rendered home payload on either side — this list is this app's.

Two of the four already have a correct server signal that is not being read

GET /api/v1/runtime/config carries a features map. On the framework side, two of its keys are derived from what is really mounted, not declared:

  • features.marketplace — framework packages/cloud-connection/src/runtime-config-plugin.ts (hasMarketplaceBrowseMount, read off the app's own route ledger)
  • features.installLocal — same file, ceiling-and-observation (a host can lower it, never raise it)

On the composed shape both are false, and that is pinned by tests on the serving side — cloud apps/objectos-ee/test/composed-cloud-surfaces-declined.e2e.test.ts asserts features.marketplace === false against a real boot, and composed-install-local-declined.e2e.test.ts does the same for installLocal, each alongside the 404 it must agree with.

So "Browse App Marketplace" and "Start with a template" render in direct contradiction to a flag the server already sends correctly. Nothing on the server side can fix those two; they are a missed read here. (⚠️ Both have since been fixed — see the status box at the top.)

The third card now has a truthful signal too

"Build an app" is the AI/metadata-authoring entry. Its declared flag is features.aiStudio, documented by the same plugin as "whether the SPA should surface AI-driven metadata authoring ('online development') affordances … set false to force-hide the authoring UI" (runtime-config-plugin.ts:490 documents the default; the composed boot previously leaked default-true).

That flag was wrong on this shape until now: the composed boot passed no aiStudio, the open plugin defaults it to true, and no plan token is resolved there to override it — so the deployment served {"installLocal":false,"marketplace":false,"aiStudio":true,"autoPublishAiBuilds":true} while its ToolRegistry held zero authoring handlers. Fixed cloud-side in objectstack-ai/cloud#1471, where it is now derived from the same resolution that decides the agent mount. Worth stating plainly: before that fix, honouring features.aiStudio here would not have hidden this card, because the server was saying true. (⚠️ This row has since landed — PR #5575.)

The fourth card is a genuine question, not an assumed defect

"Setup" is gated by setup.access — and org admins and ordinary members legitimately hold it (framework plugin-security/src/security-plugin.ts excludes setup.access/setup.write from the platform-exclusive set on purpose: "they are the Setup app shell + tenant-settings-write caps, not platform powers"). So hiding Setup is a product decision about what a walled hosted-SaaS tenant should see, not a declared-vs-enforced restoration like the other three. It is deliberately carved OUT of this card, pending a ruling recorded on cloud#1452. Do not fold it in silently.

Proposed seam

Have the home card list honour the signals the Console already fetches, per card:

card signal
Browse App Marketplace features.marketplace (already false — a missed read)
Start with a template features.installLocal / features.marketplace (already false — a missed read)
Build an app features.aiStudio, and/or studio.access from /auth/me/permissions
Setup setup.access — but see above; needs a product ruling first

studio.access is available on the same /auth/me/permissions payload this app already consumes through useCapabilityGate (ADR-0066 D4), so no new server surface is required for any row. (⚠️ The studio.access row was subsequently ruled Option B and closed — see the status box.)

⛔ Do not weaken the backend

The 403 on /api/v1/meta/* and the 404s on install-local / marketplace / cloud-connection are measured green and are the other half of the criterion. Hiding the entries is not a reason to soften any of them.

Serial constraint (for the lane)

In-flight sibling #5504 (OS_CLOUD_URL=off marketplace load-failure + home still promoting the marketplace entry) touches the same home-card surface from a different posture. Whoever claims this card should name #5504 in the serial-constraints line and sequence behind it if the file surface overlaps. (⚠️ #5504 landed as PR #5517.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:blocked

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions