Repository navigation
docs(adr): ADR-056 §3.1–3.6 ownership overrides + multi-org membership; ADR-059 app-level entity registry - #677
seanspeaks wants to merge 5 commits into
Conversation
…hip; ADR-059 entity registry Stacked on #670 (ADR-056). ADR-056 §3 gains: scope resolution request → binding → integration → app entity type → app → primary-derived default; ownership by scope instead of user.primary; explicit integration ownership; mixed-ownership list fields (scope, owner, organizationId, access); proxy scopes; and users in several organizations (membership relation, one active org per request, individual entities and integrations paired with their organization). ADR-059: optional appDefinition.entities registry keyed by entity type, merged with inference from integrations; one registry function replaces the five derivations; `$infer: false` makes registration required. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
There was a problem hiding this comment.
If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.
Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.
…t-mode semantics Record what F4 (#681) implemented: no backfill migration; User.organizationId is a legacy pointer read as an implicit membership; orgRole defaults to MEMBER; strictMembership refuses any non-member of a pre-existing org. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
…ner-only, list/access rule) From the Graphite review on #677: under 'all' the old text still paired every individual entity with an org, so the opt-in could not work. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
| is no backfill: an existing person's org is honoured at read time, and the table holds only | ||
| memberships added since. This replaces an earlier plan for the migration to write one row per | ||
| existing `organizationId`. That would mean a data migration run against every adopter's live | ||
| database, and it buys nothing the read-time rule does not. |
There was a problem hiding this comment.
Legacy membership cannot be revoked
High Severity
User.organizationId is a permanent implicit membership and is never moved, while OrganizationMembership has no removed state. Leaving the first organization has nothing to delete, and deleting a later row for that org makes the implicit membership apply again. The original org membership therefore comes back after removal.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit b090b64. Configure here.
| ``` | ||
| owner = caller AND (organizationId = active org | ||
| OR (individualAcross = 'all' AND organizationId IS NULL)) | ||
| ``` |
There was a problem hiding this comment.
List rules drop admin visibility
Medium Severity
§3.4 returns every record the caller may see under the permissions table, including an org admin's view access to other members' individual entities. §3.6 then restricts both lists and access checks to individual entities whose owner is the caller. An admin's metadata access and the view access value cannot both be implemented.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit b090b64. Configure here.
…wrapper takes entities The first implementation lists, reads and deletes an entity of an unregistered type and refuses new ones, but does not emit "registered": false yet. Says so, and how such an entity answers today. getModulesDefinitionFromIntegrationClasses is kept as a deprecated wrapper (#679 now restores it) with an optional entities argument. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
…ntegrations deferred §3.6 gains "Organization roles": the shared secret always manages; an adopter token's role claim wins, else the stored membership orgRole, else MEMBER; a Frigg bearer token keeps its pre-roles behaviour unless user.orgAdminGateBearerTokens is set. The stored membership role is read for authorization as the fallback. The integration ownership table now says what is implemented: mixed organization + individual integrations are refused with 400 MIXED_ENTITY_SCOPES and global entities give 404 at integration create. Both mixed rows, and the workspace/me example that depends on them, are deferred until ORPHANED and member removal exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8eda42e. Configure here.
| itself. | ||
|
|
||
| The membership row is therefore read for authorization, not only written: it is the fallback when | ||
| the token is silent, never an override of what the token says. |
There was a problem hiding this comment.
Stored role outlives IdP demotion
Medium Severity
An adopter JWT role claim wins so an IdP demotion applies on that request, but the membership row keeps the first token's role and is the fallback whenever a later token omits the claim. A demoted caller who next presents a silent token is authorized as their original role again.
Reviewed by Cursor Bugbot for commit 8eda42e. Configure here.
|
ADR reconciliation needed after Stack A/B — drafted with Claude Code at Sean's request Severity: must-reconcile. The scope vocabulary ( ADR-056
ADR-059
Merge order: this PR merges into |





Stacked on #670 (ADR-056). Driven by Left Hook's internal MCP server, where each staff member connects their own Google, Xero and HubSpot in an organization-primary Frigg app, with no integration around those connections.
ADR-056 §3, new subsections
user.primary, so apps that set nothing keep today's meaning.user.primary. Today anx-frigg-appuserid-only request to an org-primary app resolves to anundefinedowner. A request missing the identity its scope needs now gets a 400.individualentity./api/v2/entities,/credentialsand/integrationsreturn mixed ownership, and each record carriesscope,owner,organizationIdandaccess.managementApi.proxy.scopes, default['individual'].GetUserFromXFriggHeadersre-links an individual to whichever org a request names, because membership is a singleorganizationIdcolumn, so a person in two orgs flips membership on every request.orgRole.organizationIdcolumn".ADR-059 (new)
appDefinition.entitiesmap, keyed by entity type name and merged with today's inference from integrations. Entities can then exist without an integration: agents and MCP servers, connect-first products, global entities.getEntityTypeRegistryreplaces the five call sites that derive module definitions from integrations: router, backend-utils, app-definition-loader, encryption schema registry and health. The encryption schema therefore covers registry-only types.$infer: falsemakes registration required for apps that want one explicit list.Review
Daniel. These are the decisions the stacked implementation PRs follow, so comments here change those PRs.
🤖 Generated with Claude Code
https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
Generated by Claude Code
Note
Low Risk
Documentation-only ADR updates; no runtime code changes in this PR, though accepted decisions will drive substantial 2.1 auth and schema work later.
Overview
ADR-056 is revised (2026-10-15) by folding former ADR-058 into Decision §3 as §3.1–§3.6, so tenancy, scope resolution, and multi-org behavior are one document.
§3.1–§3.5 specify how scope is resolved (request → binding → integration → app entity type → app → default), that ownership follows resolved scope instead of
user.getId()/user.primary, optional explicit integration ownership, v2 list payloads withscope/owner/organizationId/access, and entity-proxy scope defaults viamanagementApi.proxy.scopes.§3.6 replaces single-column org membership with
OrganizationMembership, per-request active org, andorganizationIdonEntityandIntegration(reversing the earlier “noorganizationIdonEntity” note).orgRolemoves to membership; org-admin rules differ by auth mode. Integration mixed-scope ownership rows are marked deferred for 2.1’s first cut (400MIXED_ENTITY_SCOPES, global entities 404 on tenant API).ADR-059 (new) adds an optional
appDefinition.entitiesregistry merged with today’s integration inference,getEntityTypeRegistryto replace five derivation call sites, optional$infer: false, and v2/api/v2/entity-typesplus authorize without an integration.The ADR index lists ADR-056’s revision date and ADR-059.
Reviewed by Cursor Bugbot for commit 8eda42e. Bugbot is set up for automated code reviews on this repo. Configure here.