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.
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
falseon/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), tenantnorthwindas plain org owner, Playwright./_console/homeshows, verbatim: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/configcarries afeaturesmap. On the framework side, two of its keys are derived from what is really mounted, not declared:features.marketplace— frameworkpackages/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 — cloudapps/objectos-ee/test/composed-cloud-surfaces-declined.e2e.test.tsassertsfeatures.marketplace === falseagainst a real boot, andcomposed-install-local-declined.e2e.test.tsdoes the same forinstallLocal, 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:490documents 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 totrue, 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, honouringfeatures.aiStudiohere would not have hidden this card, because the server was sayingtrue.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 (frameworkplugin-security/src/security-plugin.tsexcludessetup.access/setup.writefrom 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:
features.marketplace(alreadyfalse— a missed read)features.installLocal/features.marketplace(alreadyfalse— a missed read)features.aiStudio, and/orstudio.accessfrom/auth/me/permissionssetup.access— but see above; needs a product ruling firststudio.accessis available on the same/auth/me/permissionspayload this app already consumes throughuseCapabilityGate(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=offmarketplace 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.