diff --git a/.changeset/one-app-rule-adr-0019-citation.md b/.changeset/one-app-rule-adr-0019-citation.md new file mode 100644 index 0000000000..233a4f248b --- /dev/null +++ b/.changeset/one-app-rule-adr-0019-citation.md @@ -0,0 +1,17 @@ +--- +"@objectstack/spec": patch +--- + +fix(spec): the one-app-per-package refusal cites the record it means, `ADR-0019 (app-as-consumer-unit) D3` + +`ADR-0019` names **two** records in this repository — `0019-app-as-consumer-unit` (D3 = a `type: 'app'` package defines at most one app) and `0019-approval-as-flow-node` (D3 = deprecating `ApprovalProcessSchema`). Both have a D3, and `stack.zod.ts` cited the bare number for both, so an author following the refusal's own citation was as likely to reach the wrong decision record as the right one. + +The three citations of the app-cap rule now name the record: + +- the `STACK_SINGLE_APP_VIOLATION` message — the only one an app author ever sees; +- the `validateSingleApp` docblock; +- the `StackSingleAppViolationError` docblock. + +Only the message tail changed: `An 'app' package must define at most one app, but found N (…)` is untouched, so any consumer matching on that prefix is unaffected. The rule, the refusal's condition and `defineStack`'s behaviour are unchanged. + +The approvals-side citations are deliberately left bare — repo-wide ADR-number disambiguation is tracked separately. diff --git a/content/docs/ui/audience-based-interfaces.mdx b/content/docs/ui/audience-based-interfaces.mdx index a8bdee9672..acdfdfe235 100644 --- a/content/docs/ui/audience-based-interfaces.mdx +++ b/content/docs/ui/audience-based-interfaces.mdx @@ -18,6 +18,8 @@ description: The same data serves different audiences. Give end users a curated The built-in permission sets already encode this split: `member_default` and `viewer_readonly` do **not** carry `studio.access` / `manage_metadata`, so Studio and schema-design surfaces are invisible to them; `admin_full_access` carries both, while `organization_admin` gets `setup.access` only — the Setup shell is reachable but Studio and schema design stay hidden from org-scoped admins (see [`default-permission-sets.ts`](https://github.com/objectstack-ai/objectstack/blob/main/packages/plugins/plugin-security/src/objects/default-permission-sets.ts)). +**One package, one app.** A package of `type: 'app'` exposes **exactly one** App — `defineStack` refuses a second at load, per ADR-0019 (app-as-consumer-unit) D3. More audiences therefore never mean more apps: they are navigation groups **inside** that one app, gated by `requiredPermissions` exactly as below. + ### 1. Gate the app and its navigation ```ts @@ -68,6 +70,6 @@ This is the lesson from Airtable's surfaces: the **Automations tab is visible to ## See also -- Decision records: **ADR-0047** (run modes), **ADR-0066** (unified authorization, dual-surface gates). +- Decision records: **ADR-0019** (app-as-consumer-unit — one app per package), **ADR-0047** (run modes), **ADR-0066** (unified authorization, dual-surface gates). - [Who can see data / automation / interface](/docs/permissions/access-recipes). - Guide: [Security](/docs/permissions); reference: [App schema](/docs/references/ui/app), [Permission schema](/docs/references/security/permission). diff --git a/packages/spec/src/stack.zod.ts b/packages/spec/src/stack.zod.ts index 34043ef7ce..9a0875337d 100644 --- a/packages/spec/src/stack.zod.ts +++ b/packages/spec/src/stack.zod.ts @@ -1460,7 +1460,7 @@ function validateNamespacePrefix(config: ObjectStackDefinition): string[] { } /** - * Validate the "at most one App per package" rule — ADR-0019 (D1/D3). + * Validate the "at most one App per package" rule — ADR-0019 (app-as-consumer-unit) D1/D3. * * A consumer package (`manifest.type === 'app'`) must not define **more than * one** app — that is the banned "suite contains apps" shape. Fold the apps @@ -1479,7 +1479,7 @@ function validateSingleApp(config: ObjectStackDefinition): string[] { const names = apps.map((a) => a.name).join(', '); return [ `An 'app' package must define at most one app, but found ${apps.length} (${names}). ` + - `Fold them into one app with multiple tabs, or split into separate packages (ADR-0019 D3).`, + `Fold them into one app with multiple tabs, or split into separate packages — ADR-0019 (app-as-consumer-unit) D3.`, ]; } @@ -1943,7 +1943,8 @@ class StackNamespacePrefixInvalidError extends StackRefusalError { /** * [ADR-0112 · #15963] An `app` package declares more than one app — the banned - * "suite contains apps" shape, ADR-0019 D3 — {@link validateSingleApp}. + * "suite contains apps" shape, ADR-0019 (app-as-consumer-unit) D3 — + * {@link validateSingleApp}. * Spelled `_VIOLATION` like the ledger's other rule-violation refusals * (`UNIQUE_VIOLATION`, `EXTERNAL_SCHEMA_MODE_VIOLATION`). */