Commit 8dd5dab
authored
feat(decisioning): create_tenant_store — opinionated multi-tenant AccountStore with fail-closed isolation (#473)
* feat(decisioning): create_tenant_store — opinionated multi-tenant AccountStore with fail-closed isolation
Ports JS @adcp/sdk@6.7's createTenantStore. The headline 6.7 helper:
an AccountStore with a per-entry tenant gate baked in so cross-tenant
entries never reach adopter code on upsert/sync_governance.
Fail-closed: when resolve_from_auth(ctx) returns None (unauthenticated
or unregistered principal), every entry rejects with PERMISSION_DENIED
and list() returns []. resolve() also enforces the gate on Path-1
(operator-routed) calls — cross-tenant refs return None, hiding the
existence of accounts the caller can't see.
Immutability: gate methods are class-level and the store class uses
__slots__, so adopter code that tries store.upsert = custom_handler
gets AttributeError instead of silently bypassing isolation.
Closes #457. Part of #452.
* fix(tenant-store): Protocol conformance on resolve + per-entry callback isolation
Address code-review BLOCKER and security-review SHOULD-FIX items:
- resolve(ref, ctx) → resolve(ref, auth_info=None) matching the
AccountStore Protocol. Adopter resolve_by_ref(ref, ctx) callback
unchanged — ResolveContext is synthesized inside the public method.
- Per-entry try/except inside upsert and sync_governance: a single
callback exception no longer poisons the rest of the batch. Failed
entries surface as PERMISSION_DENIED on the wire (no exception text
leaked) and are logged server-side via logger.warning(exc_info=True).
- list now catches tenant_to_account exceptions and returns [] —
honoring the docstring's MUST-NOT-RAISE contract.
- resolve Path 1 catches resolve_by_ref exceptions, returns None +
logs WARNING (so a single adopter raise doesn't 500 the request).
- Added tests for: AccountStore isinstance check, dispatcher kwarg
call shape, four callback-raises cases.
- Class-level immutability docstring note added (instance immutability
is enforced via __slots__; class-level monkey-patching is possible
but the leading-underscore + non-export keep _TenantStore out of
adopter code paths).1 parent defa022 commit 8dd5dab
3 files changed
Lines changed: 1235 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
167 | 167 | | |
168 | 168 | | |
169 | 169 | | |
| 170 | + | |
170 | 171 | | |
171 | 172 | | |
172 | 173 | | |
| |||
318 | 319 | | |
319 | 320 | | |
320 | 321 | | |
| 322 | + | |
321 | 323 | | |
322 | 324 | | |
323 | 325 | | |
| |||
0 commit comments