Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions docs/adr/0126-packaged-metadata-customization-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,49 @@ read at each runtime's own consult point; an empty ledger changes nothing anywhe
they never write the ledger — the ledger records the customer's **choices**, and no upgrade
un-makes a choice. The converse holds too: disabling never blocks or edits the base's upgrade
stream. The two never meet: the base's version history and the ledger share no columns.

> **Note (2026-10-23, ruling of record `6259332195` on #24443, items 1–2, 4–5) — wall 3 is true
> in practice for the access class.** Two stages of #24446 make this wall hold for sharing rules,
> permission sets, positions and capabilities. They add no spec key and no rename spelling.
>
> - **The boot report** (U1, #25311, `54df640b38`). At `kernel:bootstrapped`, plugin-security
> compares the customer's access state with the registry the boot holds. That state is the
> activation-ledger rows, the environment-authored permission sets and positions (clones
> included) and the `sys_sharing_rule` rows. The report lists orphans, suspected re-enablements,
> sharing-rule collisions, broken references and field denies a customer set lacks. It writes and
> refuses nothing.
> - **The marketplace refusal** (U2, #25355, `448374d3f8`). On a re-install over an installed
> package, before its first write, `POST /api/v1/marketplace/install-local` runs the same
> comparison through the `security.accessUpgradeCheck` service. It runs over the registry and
> over a projection with the incoming manifest. Only the delta refuses: 409
> `ACCESS_UPGRADE_CONFIRMATION_REQUIRED`, naming each finding, and nothing is written. A finding
> that predates the upgrade does not block it; the boot report keeps raising it. A check that
> cannot read its sources answers 503 and writes nothing.
> - **The confirm token.** The refusal carries a `findingSet` token. The same call with
> `confirmAccessUpgrade` set to that token installs. The install's own entry in the marketplace
> install ledger, not the activation ledger, records the confirmation (`findingSet`,
> `confirmedBy`, `confirmedAt`). The token confirms exactly the findings it names: if they
> change, the call is refused again with the fresh set, and a bare `true` confirms nothing. The
> CLI sends it as `os package install … --confirm-access-upgrade`.
> - **Who is told.** Under `single`, the administrator. The boot report notifies the Default
> Organization's `admin_full_access` holders and any `OS_PLATFORM_OWNER_EMAIL` address, once per
> finding set. The refusal answers the administrator who installs (`manage_metadata`). Under a
> wall (`group`, `isolated`), the operator. The boot report is one `error` line in the operator's
> log and writes into no organization (seat ruling `6353787407`, option A). Only the operator
> reaches the refusal: ADR-0131 D12 answers a tenant administrator 403 first, with no finding.
> A tenant administrator is never the fallback.
> - **Two accepted limits**, both following from §9 (a clone records no base and no link) and from
> the bare `(metadata_type, name)` ledger key. A re-enablement is only *suspected*: nothing
> records which name replaced which, so a package with several unswitched names yields several
> candidates. The field-deny check reads "grants the same object", not "copied from that set",
> so it can name an unrelated customer set and is a review item.
> - **Two doors are still open**, each with its card. `POST /api/v1/packages` with `overwrite`
> replaces an installed package without this check (#25382). A cloud-fetched marketplace
> install of a package shipping a catalog name an environment-authored item holds writes its
> install entry, and the next cold boot refuses; it is to be refused before the first write, not
> confirmed (#25366).
> - **The other classes follow after v18** (ruling item 4), as does the retirement of
> `package-upgrade.zod.ts`. Until then this wall holds in practice for the access class only.
4. **The paper protocol is not the model.** The three-layer
`metadata-customization.zod.ts` surface (#12057 — exported, documented, zero consumers) is
**superseded by this ADR as a matter of record**: nothing may build against it, and its
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,27 @@ The `single` anchor's own disposition is #11979's (Choice 4B), which this record
> outside `os serve` over a database built before 18.x, it let the deployment's only platform
> administrator be banned. That anchor is retired.

> **Note (2026-10-22, #25286, `09e07c259a`; landed record `6353078154` on #25271) — under `single`,
> no slug change makes another organization the Default Organization.** Under `single`, a
> `sys_organization` slug changed to `default` is admitted only on the organization that is already
> the Default Organization. Every other such write is refused 403 `PERMISSION_DENIED` by the
> last-admin guard's `sys_organization` `beforeUpdate` hook, for every context: better-auth's
> `organization/update`, seeds and plugins alike, `isSystem` included. A read the guard cannot
> complete refuses too. Before this hook, such a re-slug made that organization the Default
> Organization, and every holder of a grant it owned a platform administrator. Walled postures are
> unchanged, because no grant anchors there.
>
> - **Removed, with no in-place replacement:** moving the Default Organization among several
> organizations. Its second write, another organization re-slugged to `default`, is exactly the
> refused capture. To move the anchor, move the grant, not the slug: a grant re-owned by the
> Default Organization anchors.
> - **The stranding it leaves.** Re-slugging the Default Organization away while other
> organizations exist is still admitted when an administrator stays standing (ADR-0135 D5.2,
> unchanged). After it, no organization is the Default Organization and none can take `default`,
> the previous holder included, until the store holds one organization again or declares a wall.
> The tenancy census already reports that shape at error level
> (`SINGLE_POSTURE_MANY_ORGANIZATIONS`), with those two ways out.

### D6 — Managed definitions are sealed; customization is environment-level state beside them, never an edit and never per organization

**Two install modes.** The package **declares** the modes it permits (`installModes`, default
Expand Down
Loading