You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(core): anchor PLATFORM_ADMIN on a verified OS_PLATFORM_OWNER_EMAIL match, inside the one derivation site (#13146)
* wip: platform-admin config anchor (L2)
* wip: tests for the config anchor
* wip: changeset, vitest alias, tsconfig paths
* test(core): make the platform-admin-config double refuse top-level combinators
check:where-matcher graded the new fixture's matches() as silently wrong:
with no combinator branch it read $or as a field name, compared row.$or
(undefined) against the array and excluded the row, leaving the suite
asserting on an empty result with nothing erroring.
Refuses instead of implementing, which is what most of this repo's
conforming doubles do and what the sibling batch-equivalence double
already spells. $in stays supported: it is a per-field value operator the
resolver really issues, not a top-level combinator.
* docs(core): the sys_permission_set standing reason named the position, not the row
Two defects in one sentence, both pre-existing on main:
- it said the row `platform_admin` is resolved by name; the row is
`admin_full_access` and `platform_admin` is the POSITION that row derives
(resolve-authz-context.ts:594 matches the row, :665-666 unshifts the
position);
- 'un-makes every platform admin at once' stopped being true for a
configured deployment: the config anchor sets the same standing off the
caller's own sys_user row and never reads this table.
A flat replacement would only swap which half is wrong, so the reason is now
conditional and states the condition -- true whether or not
OS_PLATFORM_OWNER_EMAIL is declared. Reason string only; role, columns and
every executable path are untouched.
---------
Co-authored-by: Claude <noreply@anthropic.com>
`PLATFORM_ADMIN` can now be anchored on deployment CONFIGURATION instead of a stored grant row: an account whose `sys_user.email` is on `OS_PLATFORM_OWNER_EMAIL`**and** whose `email_verified` reads verified resolves `PLATFORM_ADMIN` with the declared `admin_full_access` capability set, derived live on each authorization resolution (#11663 leg L2, design accepted 2026-08-25 as bundle 1A/2B/3A/4A/5A/6A/7A).
7
+
8
+
**Additive — nothing is revoked.** The legacy unscoped `admin_full_access` grant still confers exactly as it did; a holder whose standing rests on the row alone now gets a once-per-process pointer at the configuration line that re-anchors them. A deployment that has declared no administrators resolves byte-identically to before: the config list is empty, the derivation answers "not an admin" before it reads any row, and the pinned batch-equivalence query multiset is unchanged.
9
+
10
+
**The variable takes a list.**`OS_PLATFORM_OWNER_EMAIL` accepts one address or a comma-separated list of them — one normalization (`trim().toLowerCase()`), duplicates collapsed, blank entries dropped. ⛔ Any entry that is not an address **fails the whole variable closed** with a loud refusal naming it, rather than being skipped: silently dropping a typo would leave a narrower administrator set than the operator declared, with nothing anywhere to notice. Unset, blank or refused all mean **zero** config-derived administrators.
11
+
12
+
**Verified-email match only.** An unverified account holding a configured address confers nothing, and an ABSENT `email_verified` column reads unverified. The match reads the caller's own **stored**`sys_user` row, never the caller-supplied session email.
13
+
14
+
New exports from `@objectstack/core`: `resolvePlatformAdminEmails`, `parsePlatformAdminEmails`, `matchesConfiguredPlatformAdmin`, `normalizePlatformAdminEmail`, `PLATFORM_ADMIN_EMAIL_SEPARATOR`, `ADMIN_STANDING_NON_TABLE_INPUTS` and the test hooks beside them. `@objectstack/core` now depends on `@objectstack/types` (measured acyclic: `types` depends only on `spec`).
15
+
16
+
`@objectstack/plugin-auth`'s break-glass guard follows the derivation, as it must: `ADMIN_STANDING_SURFACE.sys_user` is reclassified `derives`, the last-administrator enumeration counts config-derived administrators through the resolver's own predicate, and a fifth write shape is judged — a change of address or an `email_verified` reset that would leave the environment with no administrator is refused, naming the configuration as the remedy. An ordinary profile write still costs the guard no reads.
0 commit comments