Skip to content

Commit 2024eca

Browse files
claude[bot]claude
andauthored
fix(service-settings): supply the effective tenancy posture at the manifest gate (#16015)
* fix(service-settings): supply the effective tenancy posture at the manifest gate `SettingsServicePlugin`'s `verifiedContextFromRequest` called `resolveAuthzContext` with no `tenancyPosture`, so both posture-conditional API-key refusals stayed unreachable at the settings doors: under a wall-enforcing posture an ex-member's org-stamped key and an organization-less key were both admitted. This seam also returns `authz.tenantId` onward as the resolved settings tenant, so the key's own unvetted `active_organization_id` became the verdict the read/write path acted on. The posture is derived here through the kernel's async registry, carrying decision-1-option-A's classification: a `tenancy` service that was never registered resolves quietly to "no posture", one that was registered and failed to build raises `AuthzStoreUnavailableError`. The requested posture (`OS_TENANCY_POSTURE`) is deliberately not reused — under ADR-0093 D4/D5 it would refuse legitimate automation over a wall that is not in force. Deliberately not extracted into a shared helper: sibling repairs are in flight on this same seam, and this copies the precedent set by `@objectstack/cloud-connection`'s install-local door. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * test(service-settings): await the route handler rather than chaining on its void return Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * test(service-settings): hold the caller's bound in the fixture driver's find `check:objectql-double-limit` graded the new fixture driver twice: first limit-blind, then touching rows outside the bound because the copy ran before the slice. The bound is now applied after the filter, by presence, and before any row-touching stage; `count` no longer derives a population size from a page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent c223028 commit 2024eca

3 files changed

Lines changed: 724 additions & 2 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/service-settings': patch
3+
---
4+
5+
Fix: the settings REST doors now supply the effective tenancy posture to the shared authorization resolver, so both posture-conditional API-key refusals apply here — and the tenant this seam hands onward is a vetted one.
6+
7+
Under a wall-enforcing posture (`isolated`), an API key stamped with an organization its owner has left is refused, as is a key carrying no organization at all. Previously neither guard ran at this door, because both are conditional on a posture the caller supplies and this seam supplied none — the key's tenant was its own stored `active_organization_id`, never checked against current membership. This gate does not merely admit the principal: it returns that tenant onward as the resolved settings tenant, so an unvetted claim became the verdict the read/write path acted on. A browser session whose stored active organization is no longer backed by a membership now has that claim dropped here too, rather than passed through.
8+
9+
The posture is read from the kernel's `tenancy` service, so it is the posture in force rather than the one requested through `OS_TENANCY_POSTURE`. A deployment that registers no `tenancy` service is unchanged: there is no wall there, and no posture-conditional refusal applies. A `tenancy` service that is registered and fails to build is an outage rather than a quiet admission.

0 commit comments

Comments
 (0)