Persist the Organization to AR namespace mapping on the Rails side
Summary
The monolith owns and persists the Organization to AR-namespace mapping, populated at provisioning. This keeps AR's API free of Organization concepts and removes per-request round-trips to resolve an organization to its namespace.
Decided. Captured in ADR-022, ADR-014, and ADR-007 via handbook!20291, with the internal API surface in ADR-009 via handbook!20309. This issue tracks the Rails-side implementation.
Not a data-ownership violation
The mapping holds only the minimal reference the monolith needs to reach its entry point into AR: the AR namespace id for an organization. This is a pointer to a dependency (like a foreign key to a downstream service), not a copy of AR's data. AR stays the sole owner; the monolith resolves anything further by the namespace id, never by organization.
Background
AR is decoupled from Organizations (ADR-022): the namespaces table references its owner only through an opaque soft FK (entity_type, entity_id), with no foreign key to organizations. AR owns the id (uuid PK) and slug. The current schema already guarantees:
iduuid primary key (stable, AR-owned).slugglobally unique and immutable (a DB trigger rejects updates).(platform, entity_type, entity_id)unique: one namespace per owner, per platform.
Consumers needing org to namespace today: role assignments (#602144 (closed)), the AR UI, AR settings.
Problem
The earlier proposal had AR expose organization_id -> AR namespace ID/slug. That puts an Organization concept into AR's API and makes the monolith and UI depend on AR at runtime for a lookup outside AR's domain.
Decision
Persist the mapping in the monolith, populated from the provisioning response:
- Rails provisions via
POST /api/internal/v1/namespaceswith the owner anchor and slug. The call is idempotent on the owner anchor(platform, entity_type, entity_id); a lost-response retry returns the same namespace. - AR returns the namespace, including its
id(uuid). - Rails stores
(organization_id, ar_namespace_id)withunique(organization_id). - Rails resolves the
slugandstatusby UUID viaGET /api/internal/v1/namespaces/:uuidand caches them. Only the UUID is persisted.
sequenceDiagram
participant Rails
participant AR as Artifact Registry
Rails->>AR: POST /api/internal/v1/namespaces { slug, entity_type, entity_id }
AR-->>Rails: 201 { id (uuid), slug, ... }
Rails->>Rails: persist (organization_id, ar_namespace_id)
Note over Rails,AR: later, on demand
Rails->>AR: GET /api/internal/v1/namespaces/:uuid
AR-->>Rails: 200 { slug, status, ... }
Rails->>Rails: cache slug + statusThereafter the monolith holds the namespace id and passes it on every applicable call. AR's API speaks only AR namespace ids.
Properties
- Idempotent provisioning: the
POSTis idempotent on(platform, entity_type, entity_id), already a unique index in AR. A lost-response retry returns the same namespace, so activation is retry-safe. The Rails-sideunique(organization_id)is the complementary guard. No idempotency-key header is needed. - Merge-safe, no AR change: if organization A absorbs B, B's namespace keeps
entity_id = B, so it does not collide under AR's unique index. "A has two namespaces" lives only in the Rails table (two rows, sameorganization_id). One-to-many later is a Rails-side relaxation (dropunique(organization_id)); AR needs no migration. - Cached slug and status, safe when stale: both change only on AR's authority (the slug through the emergency escape hatch, the status through service conditions; ADR-007). AR enforces the status on every request, so a stale cached status only affects display until the next refresh. A stale slug can leave broken links until the next refresh; slug changes are rare emergency events.
Scope
In: the mapping table, populating it at provisioning, routing AR calls by ar_namespace_id, resolving and caching slug and status by UUID. Out: teaching IAM the data hierarchy (separate data-sync ADR); the legal slug-transfer flow.
Open questions
Cache the slug, or resolve on demand?Resolved: cache slug and status (see Properties); persist only the UUID.- Where in the activation flow (#593818) does the write happen, and what is the rollback if AR succeeds but Rails fails? (idempotent retry mitigates)
Idempotency implicit onResolved: implicit on the owner anchor (ADR-009).(platform, entity_type, entity_id), or an explicit idempotency-key header?
References
- ADR-022
- ADR-014
- ADR-007
- ADR-009
- handbook!20291 (mapping decision in ADR-022/ADR-014/ADR-007)
- handbook!20309 (internal API surface in ADR-009)
- ADR-015 (WIP)
- GraphQL wrapper in Rails for the IAM Relationsh... (#602144 - closed)
- Create Org Owner role assignments for organisat... (#601665 - closed)
- #593818+