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

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).

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.

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.

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 end state the shape asks for: a plain tenant sees Ask + their installed business apps, and nothing that authors metadata.

⛔ 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.

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