|
| 1 | +--- |
| 2 | +'@objectstack/core': patch |
| 3 | +'@objectstack/plugin-auth': patch |
| 4 | +'@objectstack/plugin-security': patch |
| 5 | +'@objectstack/service-settings': patch |
| 6 | +--- |
| 7 | + |
| 8 | +refactor(core, plugin-auth, plugin-security, service-settings): one definition each for the `admin_full_access` anchor rule and the organization-reach rule, exported from core security |
| 9 | + |
| 10 | +Clause-②: yes (widening) |
| 11 | +Metadata-protocol: none |
| 12 | + |
| 13 | +Two security rules were each spelled more than once. A change to one copy could leave the other behind. Each now has one definition in `@objectstack/core`'s security module, and every reader calls it. No behaviour change: every reader answers exactly as before. |
| 14 | + |
| 15 | +- **The anchor rule (ADR-0131 D5).** New export: `organizationAnchorsPlatformStanding(grantOrganizationId, { walled, defaultOrganizationId })`, with the type `PlatformStandingAnchorFacts`. It decides which `admin_full_access` grant anchors platform-admin standing: |
| 16 | + - behind a wall, no grant does; |
| 17 | + - under `single`, the grant owned by the Default Organization does; |
| 18 | + - an organization-less grant never does, and neither does any grant when no Default Organization can be named. |
| 19 | + |
| 20 | + Three readers call it: |
| 21 | + - the authorization resolver passes the memoised Default Organization; |
| 22 | + - the last-administrator guard in `@objectstack/plugin-auth` passes the Default Organization as the pending write would leave it; |
| 23 | + - the first-user promotion's holder scan in `@objectstack/plugin-security` passes the Default Organization it promotes into, and keeps its own system-user exclusion. |
| 24 | + |
| 25 | + Each reader keeps its own normalizer for a grant row's organization. |
| 26 | +- **The organization-reach rule (ADR-0131 D8/D9).** New exports: |
| 27 | + - `resolveOrganizationReach({ callerOrganizationId, wallStands, deriveOrganization })`; |
| 28 | + - `organizationReachAdmits(reach, rowOrganizationId)`; |
| 29 | + - `organizationReachRowOwner(reach)`; |
| 30 | + - `organizationIdOf(value)`; |
| 31 | + - `UNOWNED_ORGANIZATION_REACH`; |
| 32 | + - the types `OrganizationReach` and `OrganizationReachFacts`. |
| 33 | + |
| 34 | + The rule decides whose `sys_setting` rows a caller may draw on: |
| 35 | + - the caller's own organization; |
| 36 | + - for a caller naming none where no wall stands, the organization D9 derives; |
| 37 | + - nothing behind a wall; |
| 38 | + - rows with no organization, only in a composition that registers no organization object. |
| 39 | + |
| 40 | + `SettingsService` and the resolver's direct localization fallback both call it. Each still supplies its own facts: |
| 41 | + - the service: the posture in force (the `tenancy` service), with a failed read propagated to its caller; |
| 42 | + - the fallback: the requested posture (`OS_TENANCY_POSTURE`), with a failed read degraded to the built-in defaults. |
| 43 | + |
| 44 | + These two differences are kept by design: they make the fallback, which runs only when the service has failed, the stricter reader. |
| 45 | + |
| 46 | +No configuration, metadata or HTTP behaviour changes. Code that does not import the new exports needs no action. |
0 commit comments