Uh oh!
There was an error while loading. Please reload this page.
fix(app-shell,i18n): gate Home's authoring CTAs on the manage_metadata capability - #4654
Merged
Merged
Conversation
…a capability "Build an app" and "Start with a template" were gated on `useIsWorkspaceAdmin()`, a ROLE check. On the EE single-database multi-tenant deployment a workspace owner is `org_owner` + `organization_admin` yet deliberately holds no `manage_metadata`, so the most prominent CTA on their home page led into /studio, a filled-in new-package dialog, and a raw capability refusal at submit. The missing grant is intended (maintainer ruling 2026-08-13: hosted tenants do not author metadata), so the fix is presentational. Both cover cards are now disabled with the reason shown on screen and in the tooltip; the marketplace shortcut in the apps strip goes with them (same route, so leaving it live would make the gate cosmetic), as does the "Build with AI" hero CTA (its output is draft metadata that cannot be published without the capability). The zero-app admin empty state explains the posture instead of directing the owner to build. The gate consumes the answer /api/v1/auth/me/permissions already returns via the permissions provider; no permission logic is re-derived client-side. Unknown capabilities fail OPEN, the doctrine useCapabilityGate states for ADR-0066 gates: MePermissionsProvider collapses an absent `systemPermissions` into the same `[]` an empty one produces, and reading that as denial would strip the product's front door from every admin on a backend predating ADR-0066. New key `home.build.noCapability` in all ten locale packs. The de pack's pinned quote-pairing counts move 50 -> 51 for the German value's „Metadaten verwalten“ span; the invariant itself (open === close, rdq 0) holds. Part of objectstack-ai/objectstack#8270 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnQd8iMMUwXQEV1crFmQiQ
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
The catch in PackageFormDialog mapped 409 to a localized key and passed every other server message straight to the banner. For a session without the ADR-0066 `manage_metadata` capability that message is the server's own English sentence, "Managing packages requires the `manage_metadata` capability." — measured verbatim in a zh console. Adds a capability arm beside the existing 409 arm, probed the same way (the status when the transport reports one, the message when it does not), rendering the new `engine.packages.noCapability` string. The deployment withholds the capability deliberately (maintainer ruling 2026-08-13), so the copy states the posture rather than inviting a retry. The string is added to both tables of the metadata-admin module-local i18n table (ENGINE_STRINGS_EN / ENGINE_STRINGS_ZH), which is the locale surface this `engine.*` namespace uses — it is a two-locale table, not the ten-pack @object-ui/i18n system that carries `home.build.noCapability`. Tests pin the localized refusal in both locales, the message-probe fallback, that an unrelated 500 still surfaces the server's reason, and that the 409 arm still wins ahead of it. Part of objectstack-ai/objectstack#8270 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnQd8iMMUwXQEV1crFmQiQ
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 14, 2026 16:48
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixesobjectstack-ai/objectstack#8270
Implements both halves of the maintainer ruling of 2026-08-13: the capability gate on the console home, and the localized refusal at submit. The second half landed in a patch round after the PM lifted this card's file fence for one file.
Premise: verified, still valid
The dispatch flagged that the defect was measured on a stale 2026-08-12 EE deploy and that
HomePage.tsx:58already carried a cloud#816 capability check. Checked againstorigin/mainata99233fff:agentHasCapability(agents, a.name, 'authoring')asks whether an AI agent with theauthoringcapability is deployed, driving the "Build with AI" CTA. It never reads the user's permissions.HomePage.tsx:405-446on main) were gated onisAdminalone, anduseIsWorkspaceAdmin(packages/auth/src/useIsWorkspaceAdmin.ts:42) reads roles —org_owneris in itsADMIN_ROLESset. So the EE workspace owner from the report isisAdmin === trueand saw both CTAs, exactly as filed.The premise holds; nothing here is stale-deploy residue.
Half 1 — the home gate (
c13a37f92)The gate consumes the answer the server already gives —
GET /api/v1/auth/me/permissions→systemPermissions, surfaced throughMePermissionsProvider— viausePermissions().hasCapabilities(['manage_metadata']). No permission logic is re-derived client-side.title. A vanished primary CTA reads as a broken page; a dimmed one explains the deployment's posture.home.build.noCapabilityin all ten@object-ui/i18nlocale packs.Half 2 — the localized refusal at submit (
2f8631f2d)PackageFormDialog's catch mapped 409 to a localized key and passed every other servermessagestraight to the banner. For a session without the capability that message is the server's own English sentence, "Managing packages requires themanage_metadatacapability." — measured verbatim in a zh console, which is the untranslated half of the report.A capability arm now sits beside the 409 arm, probed the same way (the status when the transport reports one, the message when it does not). The copy states the posture rather than inviting a retry, since the deployment withholds the capability deliberately.
One deviation from the patch-round instruction, which asked for "all ten packs": this
engine.*namespace does not use the ten-pack system. It resolves through the metadata-admin module-local table, which is a two-locale table (ENGINE_STRINGS_EN/ENGINE_STRINGS_ZH) read byt(key, locale). The key was added to both, matching every otherengine.packages.*string — and zh is precisely the locale the defect was reported in. Routing this one key through@object-ui/i18ninstead would have split the namespace across two i18n systems.The one judgement call worth review
Unknown capabilities fail OPEN.
MePermissionsProvidercollapses an absentsystemPermissions(a backend predating ADR-0066) and a genuinely empty one into the same[], so a barehasCapabilitiesgate would strip the product's front door from every admin on such a deployment — silently. This follows the doctrineuseCapabilityGatealready states for ADR-0066 gates (framework#3923): the server enforces regardless, and hiding a permitted user's button on missing client data is the worse failure. The ruled case is unaffected — the EE owner's set is non-empty (manage_org_users,setup.access,setup.write), so it gates closed. Pinned by the "fail OPEN" case in the test file. Teaching the provider to preserve absent-vs-empty is a separate follow-up the PM seat owns;packages/permissionsis untouched here.Verification — all at
2f8631f2d(the final commit)Correcting the first round's report: it listed
pnpm --filter ... typecheckas exit 0. That script name does not exist in these packages (it istype-check), so pnpm matched nothing and the run was vacuous — it proved nothing. The hyphenated command above is a real pass, and it needed the dependency closure built first (a fresh worktree has nodist/, and without ittscfails on TS6305 for@object-ui/core).Reverse verification, both halves, direction predicted before running:
true(refactor and testids kept, so only the gate is under test): the 4 ruled-case cases plus the zero-app no-capability case should redden and the capability-held / fail-open / zero-app-held cases stay green. Observed exactly: 5 failed, 3 passed.false: the two localization cases plus the message-probe case should redden, while the over-broadness guard (an unrelated 500 still surfaces the server's reason) and the 409-ordering guard stay green. Observed exactly: 3 failed, 4 passed.Both restored with
git checkoutfrom their commits; ablation markers grepped clean andgit statusempty each time.The de pack's pinned quote-pairing counts move 50 to 51 for the German value's „Metadaten verwalten“ span. The invariant itself is untouched:
open === close,rdq === 0, and the mismatch scan stays empty — only the literal ledger moved, with provenance added alongside the existing entries.Generated by Claude Code