Skip to content

Commit f4b8926

Browse files
os-zhuangclaude
andcommitted
feat(platform-objects): lock studio & account apps with ADR-0010 full protection
Studio and Account shipped without a protection block, so tenant overlay writes to PUT /api/v1/meta/app/{studio,account} succeeded — the NavigationSyncEffect bug in objectui demonstrated the hazard by writing auto-navigation into these system apps. Both are core platform UI in the same class as Setup (which already carries lock: 'full'): - studio: the metadata workbench — a broken overlay locks implementers out of the very surface used to repair metadata - account: every authenticated user's only self-service security surface (sessions / API keys / linked accounts) The loader translates protection into the _lock envelope; objectui's isNavigationSyncableApp already filters on _lock ∈ {full, no-overlay}, so client behavior is automatically correct. Setup-style navigation contributions are unaffected (registration-time in-memory merge, not overlay writes). Adds a regression test pinning lock: 'full' on all three platform apps. Verified end-to-end on a rebuilt showcase stack: GET /api/v1/meta/app now serves _lock: "full" for setup, studio, and account. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 8950204 commit f4b8926

3 files changed

Lines changed: 38 additions & 1 deletion

File tree

packages/platform-objects/src/apps/account.app.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ export const ACCOUNT_APP: App = {
3838
isDefault: false,
3939
// Surface via the avatar dropdown, not the App Switcher — see App.hidden.
4040
hidden: true,
41+
// ADR-0010 §3.7 — author-facing protection block. Loader translates
42+
// this into the `_lock` envelope at registration time. Every
43+
// authenticated user depends on this app to manage their own
44+
// sessions / API keys / linked accounts, so a tenant overlay that
45+
// breaks it is a security-relevant outage — same class as Setup.
46+
protection: {
47+
lock: 'full',
48+
reason: 'Core self-service security UI shipped by @objectstack/platform-objects — see ADR-0010.',
49+
docsUrl: 'https://docs.objectstack.ai/adr/0010-metadata-protection',
50+
},
4151
branding: {
4252
primaryColor: '#0ea5e9', // sky-500 — distinct from Setup's slate
4353
},

packages/platform-objects/src/apps/studio.app.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ export const STUDIO_APP: App = {
3434
icon: 'hammer',
3535
active: true,
3636
isDefault: false,
37+
// ADR-0010 §3.7 — author-facing protection block. Loader translates
38+
// this into the `_lock` envelope at registration time. Same rationale
39+
// as Setup: tenant overlay edits to the metadata workbench can lock
40+
// implementers out of the very surface used to repair metadata.
41+
protection: {
42+
lock: 'full',
43+
reason: 'Core developer workbench shipped by @objectstack/platform-objects — see ADR-0010.',
44+
docsUrl: 'https://docs.objectstack.ai/adr/0010-metadata-protection',
45+
},
3746
// Studio is the metadata-authoring host, so its ambient copilot is
3847
// pinned to the schema-architect agent. Resolved by the ambient chat
3948
// endpoint via `app.defaultAgent` — no UI-side `?agent=` override

packages/platform-objects/src/platform-objects.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
SysMetadataHistoryObject,
2929
} from './metadata/index.js';
3030
import { SysSetting } from './system/index.js';
31-
import { SETUP_APP, SETUP_NAV_CONTRIBUTIONS } from './apps/index.js';
31+
import { ACCOUNT_APP, SETUP_APP, SETUP_NAV_CONTRIBUTIONS, STUDIO_APP } from './apps/index.js';
3232
import { AppSchema } from '@objectstack/spec/ui';
3333

3434
const systemObjects = [
@@ -156,6 +156,24 @@ describe('@objectstack/platform-objects', () => {
156156
});
157157
});
158158

159+
describe('platform app protection (ADR-0010 §3.7)', () => {
160+
// All three platform apps are core UI shipped by this package: a
161+
// tenant overlay that breaks Setup/Studio locks admins/implementers
162+
// out of the repair surface, and Account is every user's only
163+
// self-service security surface. The loader translates `protection`
164+
// into the `_lock` envelope; clients (e.g. objectui's
165+
// isNavigationSyncableApp) rely on `_lock` to skip automatic
166+
// navigation writes into these apps.
167+
it.each([
168+
['SETUP_APP', SETUP_APP],
169+
['STUDIO_APP', STUDIO_APP],
170+
['ACCOUNT_APP', ACCOUNT_APP],
171+
])('%s declares a full lock', (_name, app) => {
172+
expect(app.protection?.lock).toBe('full');
173+
expect(() => AppSchema.parse(app)).not.toThrow();
174+
});
175+
});
176+
159177
describe('SETUP_NAV_CONTRIBUTIONS (ADR-0029 D7)', () => {
160178
const shellGroupIds = new Set(
161179
(SETUP_APP.navigation ?? []).map((n) => n.id),

0 commit comments

Comments
 (0)