You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Upstream wangkanai/federation does not currently expose reusable multi-tenant primitives at the level a consuming platform needs to
build a hosted multi-tenant OIDC identity provider on top of it.
Specifically, three patterns that RiverSync treats as load-bearing
at every layer (store, domain, compiler, RP consumer) are not exposed
as a documented upstream contract — every consumer re-implements them
from scratch, with no shared vocabulary to keep the implementations
aligned.
1. The Tenant entity shape (RiverSync Account/src/Domain/Tenancy/Tenant.cs:14-23)
publicclassTenant{/// <summary>Stable Guid key. Default until persisted.</summary>publicGuidId{get;set;}/// <summary>Tenant classification (e.g. internal, customer, partner).</summary>publicTenantTypeTenantType{get;set;}/// <summary>Display name.</summary>publicstringName{get;set;}=string.Empty;/// <summary>UTC timestamp of creation.</summary>publicDateTimeOffsetCreatedAt{get;set;}/// <summary>UTC timestamp of last update.</summary>publicDateTimeOffsetUpdatedAt{get;set;}}
Three fields form the public contract: Id (Guid), TenantType
(enum), Name (string). The trailing CreatedAt / UpdatedAt are
audit plumbing and not part of the ask.
2. The TenantType enum (RiverSync Account/src/Domain/Enums/TenantType.cs)
publicenumTenantType{/// <summary>RiverSync itself — the platform-internal tenant.</summary>Internal,/// <summary>A customer tenant (paid, federated identity provider for their org users).</summary>Customer,/// <summary>A partner tenant (read-mostly, scoped to a RiverSync-issued region / segment).</summary>Partner,}
Canonical values, in declaration order: Internal, Customer, Partner. The enum is the only allowed classification field on the Tenant entity — the source XML doc explicitly states "do NOT
collapse partner tiers into a free-form string on the Tenant
entity", and "downstream membership tiers and partner-tier
subdivisions will be added as separate enum values if/when needed".
This is the enum-driven classification, not string-driven anti-pattern
that any consumer re-implementing from scratch will get wrong.
A second non-obvious modeling decision worth surfacing in OSS docs:
the internal tenant is identified by absence of a parent
(NULL Organization.TenantId denotes the internal riversync
tenant — there is no IsInternal flag). Consumers will reproduce this
decision one way or another; it is better that the upstream contract
documents the convention rather than leaving it implicit.
3. The scalar TenantId FK convention on the user entity
// Account/src/Domain/Identity/ApplicationUser.cs:28-33/// <summary>/// Foreign-key scalar to the tenancy row. The <c>Tenant</c> navigation/// property and the EF relationship are configured in 013-05; this/// property is intentionally scalar to keep 013-02 independent of 013-03./// </summary>publicGuidTenantId{get;set;}
The user entity carries TenantId as a scalar property — no
navigation property, no compile-time dependency on the Tenant type.
The FK relationship is configured later, at the persistence layer
(Infrastructure/Configurations/ApplicationUserConfiguration.cs:58-61, OnDelete(DeleteBehavior.Restrict)). The XML doc names the reason: build parallelism — keeping 013-02 (user entity) independent of
013-03 (tenant aggregate) so the two features can ship concurrently.
This is captured as a load-bearing convention, not a stylistic
choice, by 013-02 spec line 36-37 (AC2): "Given parallel feature
013-03, When ApplicationUser is authored, Then it holds TenantId
as a scalar only (no compile-time dependency on the Tenant type) —
the FK relationship is configured later in 013-05."
4. The composite (Email, TenantId) UNIQUE identity invariant
// Account/src/Infrastructure/Configurations/ApplicationUserConfiguration.cs:75-91RemoveDefaultIdentityIndex(builder.Metadata,propertyName:"NormalizedEmail");RemoveDefaultIdentityIndex(builder.Metadata,propertyName:"NormalizedUserName");// Add the composite UNIQUE(Email, TenantId) — the multi-tenant email// invariant.builder.HasIndex(user =>new{user.Email,user.TenantId}).IsUnique().HasDatabaseName("ix_application_user_email_tenant");// Add the composite UNIQUE(NormalizedUserName, TenantId) — the// multi-tenant sign-in identity invariant.builder.HasIndex(user =>new{user.NormalizedUserName,user.TenantId}).IsUnique().HasDatabaseName("ix_application_user_normalized_user_name_tenant");
The headline invariant of the entire 013 epic: a single email
address may be owned by multiple tenants, but within each tenant the (Email, TenantId) pair is unique. ASP.NET Core Identity's default
single-column NormalizedEmail UNIQUE blocks this; the configuration
strips the default indexes and replaces them with composite ones.
The sign-in path keys on NormalizedUserName, so both composites
(Email + TenantId AND NormalizedUserName + TenantId) are required
— stripping one without adding its composite lets two tenants collide.
The failure mode this guards against is documented in the same
configuration file (lines 24-31, citing report F14): without the
composite UNIQUE(Email, TenantId), two tenants cannot share an
email address — which means an account cannot be a true multi-tenant
store. The runtime consumer is TenantEmailLookup.cs:96-106 (the
multi-tenant email chooser view), which is only meaningful if the
second insert succeeds and a second row exists to render.
Every federated token carries exactly one tenant id — the user cannot
be entitled across multiple tenants on the same token. The tnt
claim is the wire-format projection of ApplicationUser.TenantId,
emitted as a Guid "D" format string (lowercase, no braces) so
that relying-party parsers do not need a full Guid deserializer.
The consuming side reads it as a plain string:
Two invariants pinned: (a) one tnt claim per token — the compiler
never emits two; (b) stable string format across versions, as part of
the public contract with every downstream RP consumer.
RiverSync usage context
RiverSync is a six-app product platform with a centralized OIDC
identity provider (Account) and five relying-party apps (Portal, Admin, Partners, Pipeline, Field). The four anchors below
explain why the upstream contract for tenant identity matters to us —
and why we are asking the question rather than quietly forking.
Thin Common foundation (epic 029). Epic 029 — Common OSS Foundation Layer
establishes RiverSync.Common.Domain as a thin shell depending on Wangkanai.Domain and Wangkanai.System, with client-side
federation pieces (claims codec, OIDC RP wiring) already extracted
into Common/src/Claims/ and Common/src/Web/ without issuer-side
leakage. This is the Option A recommendation from research 009
§7: thin extraction in Common, not a full internal fork.
Thin federation overlay with TenantInfo projection (epic 030). Epic 030 — Extract Federation Domain Models To Common,
specifically 030-01 — Core Shared Federation Shapes in Common,
introduces TenantInfo as a thin Common.Domain overlay — Guid Id, Name, and string TenantType with canonical
values Internal | Customer | Partner (the enum names from Account/src/Domain/Enums/TenantType.cs, but the Common type is string, not a reference to the Account enum). This is the
projection that lets RP apps and Field decode tenant context from
a federated token without taking a dependency on Account.Domain.
The OSS ask in this issue is for the upstream shapes that TenantInfo can be implemented against — without those, Common
has to re-derive everything from scratch.
Cross-tenant ViewAsContext precedent (epic 018). Epic 018 — Cross-Tenant Access (View-As)
established the ViewAsContext pattern — anchored at Common/src/Web/ViewAsContext.cs —
for safely traversing tenant boundaries in the platform IdP without
leaking issuer responsibilities. The tnt claim wire format and
the one-tenant-per-token invariant are what make ViewAsContext's
explicit dual-principal traversal tractable: a downstream
ViewAsSession knows it is operating as the delegating tenant's
principal against a target tenant's resource, and the wire
format must make that unambiguous.
Account is the sole platform OIDC/OAuth2 identity provider —
the only issuer of the federated token every app trusts.
The other five product apps (Portal, Admin, Partners, Pipeline,
Field) participate strictly as OIDC relying parties; none runs
its own authorization server or credential store.
Common provides consumable shapes (claims codec, RP wiring, ViewAsContext, TenantInfo); it does NOT host a token issuer.
Multi-tenant user rows are persisted exclusively in Account. No non-Account app may maintain its own ApplicationUser-equivalent store, even if the upstream shapes
it depends on would technically allow it. The OSS shapes in this
issue are consumed (read-only projection in RP apps, write path
only in Account), never re-implemented per-app.
WASM/RP consumer path (why the shapes must be stringly-typed)
The Field app is offline-first WASM/PWA. It needs to decode federated
tokens (the tnt claim, eventually the permission scopes per the
sibling 031-04 issue) without a server round-trip and without
loading any EF Core / SQL Server dependencies. That constrains the TenantInfo projection to:
string TenantType (not an enum type that drags a host-side
reference — Field cannot take a dependency on Account.Domain.Enums.TenantType).
string representation for the tnt claim value (already the
case — Guid "D" format).
Pure, DI-free value types (no ApplicationDbContext, no navigation
properties, no [ForeignKey] attributes).
This is exactly what 030-01 spec's TenantInfo(string TenantType)
shape already encodes. The upstream ask in this issue is for the federation package to expose a compatible primitive — so Common's TenantInfo is a thin projection overlay rather than a local fork.
No timeline promises (explicit)
To be unambiguous about what this issue is not asking:
No timeline promise. This issue does not commit RiverSync to
any particular release date for adopting any upstream shape.
Internal sequencing is governed separately by our own epic planning
and may change without notice to this issue. Concretely: no
timeline or commitment by RiverSync is tied to the resolution of
this issue.
No adoption commitment. RiverSync has not committed to taking
a hard Wangkanai.Federation dependency even after this issue is
resolved. The thin Common strategy in 029/030 is OSS-agnostic and
is being executed on Wangkanai.Domain + Wangkanai.System today;
any deeper Federation adoption is contingent on legal/architectural
review on our side (license clarity first — see P0 issue License consistency + explicit SaaS/hosted multi-tenant IdP terms #40).
No licensing-philosophy request. This issue is filed under
the assumption that the P0 license work in wangkanai/federation#40 is
resolved first. Until then, no RiverSync dependency is taken
against any Wangkanai.Federation release — only the thin shapes
in Common.Domain are being authored. This issue asks for documented upstream contracts, not for a license change of
philosophy.
Request
We respectfully ask the wangkanai/federation maintainers to expose
the following three coordinated upstream contracts:
A Tenant base type suitable for thin projection overlay in
consuming platforms. Minimum surface:
Id : Guid (stable key, default until persisted).
TenantType — either an enum (Internal | Customer | Partner) or a string discriminator the host can bind, with
the canonical values documented.
Name : string (display name).
Audit timestamps (CreatedAt, UpdatedAt) optional but
recommended.
Document the "NULL-FK-on-parent = internal tenant" convention
(i.e. do not require a separate IsInternal flag) so
consumers don't have to re-derive it.
A documented FK-scalar convention on the user entity — User.TenantId : Guid as a scalar property on the domain class,
with the EF Core / persistence-layer relationship configured
separately (Infrastructure, not Domain), and OnDelete(DeleteBehavior.Restrict). RiverSync wants the contract
documented, not necessarily a new API surface — the constraint is
that consumers can build user entities that stay decoupled from
the Tenant aggregate (build-parallelism + WASM-safety).
A composite (Email, TenantId) UNIQUE helper (or a documented
recipe) so consumers do not have to strip-and-replace the default
ASP.NET Core Identity indexes themselves. The minimum contract:
Replace UNIQUE(NormalizedEmail) with UNIQUE(Email, TenantId).
Replace UNIQUE(NormalizedUserName) with UNIQUE(NormalizedUserName, TenantId).
On-delete: Restrict for User.TenantId → Tenant.Id.
Optionally: a tnt claim convention — one Guid-as-string per
token, Guid "D" format (lowercase, no braces), so RP parsers do
not need a Guid deserializer.
These three contracts together are the load-bearing shape a hosted
multi-tenant OIDC platform needs from upstream to consume the package
as a thin overlay rather than re-implementing these patterns per-app.
Why now
This is a pre-existing gap, but the timing is right for three
converging reasons that make the platform's multi-tenant IdP usage
visible upstream:
The thin Common strategy is already in flight (epics 029 and
030), with 030-01 defining TenantInfo as the projection
overlay. The OSS shapes asked for in this issue are what TenantInfo will be implemented against. Without them, Common
forks the patterns locally and the "enhance our packages" goal
from the user research question remains unmet.
The cross-tenant ViewAsContext precedent (epic 018) makes
RiverSync's multi-tenant IdP usage of these primitives a real,
shipped pattern on our side — not a roadmap sketch. That makes
the contract gap a current-day gating decision for downstream
consumers building similar platforms.
The P0 license issue (wangkanai/federation#40) is filed and
being driven in parallel. Until that resolves, this issue's
shapes can be authored and reviewed in the abstract; once P0
resolves, the contracts asked for here become the immediate
next ask for any RiverSync production dependency.
Per research 009 §8, the P1 Tenant row is adjacent to the
scoped Permission/RolePermission P1 (031-02) and the permission
scope codec P1 (031-04). All three depend on the federation package
being legally consumable (P0) and on the multi-tenant primitives
being exposed (this issue).
Drafted by RiverSync for upstream filing against wangkanai/federation;
not yet filed. Once filed, the upstream URL will replace this header note
and the tracker row in docs/oss-contributions.md
will be updated to Filed.
Multi-tenant Tenant shape + scalar TenantId +
(email, tenant)composite identityProblem statement
Upstream
wangkanai/federationdoes not currently expose reusablemulti-tenant primitives at the level a consuming platform needs to
build a hosted multi-tenant OIDC identity provider on top of it.
Specifically, three patterns that RiverSync treats as load-bearing
at every layer (store, domain, compiler, RP consumer) are not exposed
as a documented upstream contract — every consumer re-implements them
from scratch, with no shared vocabulary to keep the implementations
aligned.
1. The
Tenantentity shape (RiverSyncAccount/src/Domain/Tenancy/Tenant.cs:14-23)Three fields form the public contract:
Id(Guid),TenantType(enum),
Name(string). The trailingCreatedAt/UpdatedAtareaudit plumbing and not part of the ask.
2. The
TenantTypeenum (RiverSyncAccount/src/Domain/Enums/TenantType.cs)Canonical values, in declaration order:
Internal,Customer,Partner. The enum is the only allowed classification field on theTenantentity — the source XML doc explicitly states "do NOTcollapse partner tiers into a free-form
stringon theTenantentity", and "downstream membership tiers and partner-tier
subdivisions will be added as separate enum values if/when needed".
This is the enum-driven classification, not string-driven anti-pattern
that any consumer re-implementing from scratch will get wrong.
A second non-obvious modeling decision worth surfacing in OSS docs:
the internal tenant is identified by absence of a parent
(
NULL Organization.TenantIddenotes the internalriversynctenant — there is no
IsInternalflag). Consumers will reproduce thisdecision one way or another; it is better that the upstream contract
documents the convention rather than leaving it implicit.
3. The scalar
TenantIdFK convention on the user entityThe user entity carries
TenantIdas a scalar property — nonavigation property, no compile-time dependency on the
Tenanttype.The FK relationship is configured later, at the persistence layer
(
Infrastructure/Configurations/ApplicationUserConfiguration.cs:58-61,OnDelete(DeleteBehavior.Restrict)). The XML doc names the reason:build parallelism — keeping 013-02 (user entity) independent of
013-03 (tenant aggregate) so the two features can ship concurrently.
This is captured as a load-bearing convention, not a stylistic
choice, by 013-02 spec line 36-37 (AC2): "Given parallel feature
013-03, When
ApplicationUseris authored, Then it holdsTenantIdas a scalar only (no compile-time dependency on the
Tenanttype) —the FK relationship is configured later in 013-05."
4. The composite
(Email, TenantId)UNIQUE identity invariantThe headline invariant of the entire 013 epic: a single email
address may be owned by multiple tenants, but within each tenant the
(Email, TenantId)pair is unique. ASP.NET Core Identity's defaultsingle-column
NormalizedEmailUNIQUE blocks this; the configurationstrips the default indexes and replaces them with composite ones.
The sign-in path keys on
NormalizedUserName, so both composites(
Email + TenantIdANDNormalizedUserName + TenantId) are required— stripping one without adding its composite lets two tenants collide.
The failure mode this guards against is documented in the same
configuration file (lines 24-31, citing report F14): without the
composite
UNIQUE(Email, TenantId), two tenants cannot share anemail address — which means an account cannot be a true multi-tenant
store. The runtime consumer is
TenantEmailLookup.cs:96-106(themulti-tenant email chooser view), which is only meaningful if the
second insert succeeds and a second row exists to render.
5. The
tntclaim convention (wire format)Every federated token carries exactly one tenant id — the user cannot
be entitled across multiple tenants on the same token. The
tntclaim is the wire-format projection of
ApplicationUser.TenantId,emitted as a Guid "D" format string (lowercase, no braces) so
that relying-party parsers do not need a full Guid deserializer.
The consuming side reads it as a plain string:
Two invariants pinned: (a) one
tntclaim per token — the compilernever emits two; (b) stable string format across versions, as part of
the public contract with every downstream RP consumer.
RiverSync usage context
RiverSync is a six-app product platform with a centralized OIDC
identity provider (
Account) and five relying-party apps (Portal,Admin,Partners,Pipeline,Field). The four anchors belowexplain why the upstream contract for tenant identity matters to us —
and why we are asking the question rather than quietly forking.
Thin Common foundation (epic 029).
Epic 029 — Common OSS Foundation Layer
establishes
RiverSync.Common.Domainas a thin shell depending onWangkanai.DomainandWangkanai.System, with client-sidefederation pieces (claims codec, OIDC RP wiring) already extracted
into
Common/src/Claims/andCommon/src/Web/without issuer-sideleakage. This is the Option A recommendation from research 009
§7: thin extraction in Common, not a full internal fork.
Thin federation overlay with
TenantInfoprojection (epic 030).Epic 030 — Extract Federation Domain Models To Common,
specifically
030-01 — Core Shared Federation Shapes in Common,
introduces
TenantInfoas a thinCommon.Domainoverlay —Guid Id,Name, andstring TenantTypewith canonicalvalues
Internal | Customer | Partner(the enum names fromAccount/src/Domain/Enums/TenantType.cs, but the Common type isstring, not a reference to the Account enum). This is theprojection that lets RP apps and Field decode tenant context from
a federated token without taking a dependency on
Account.Domain.The OSS ask in this issue is for the upstream shapes that
TenantInfocan be implemented against — without those, Commonhas to re-derive everything from scratch.
Cross-tenant ViewAsContext precedent (epic 018).
Epic 018 — Cross-Tenant Access (View-As)
established the
ViewAsContextpattern — anchored atCommon/src/Web/ViewAsContext.cs—for safely traversing tenant boundaries in the platform IdP without
leaking issuer responsibilities. The
tntclaim wire format andthe one-tenant-per-token invariant are what make
ViewAsContext'sexplicit dual-principal traversal tractable: a downstream
ViewAsSession knows it is operating as the delegating tenant's
principal against a target tenant's resource, and the wire
format must make that unambiguous.
ARCH:VI — Account is the sole writer.
Per architecture rule
.claude/rules/architecture/federation-auth.md(extract ARCH:VI):
Accountis the sole platform OIDC/OAuth2 identity provider —the only issuer of the federated token every app trusts.
Field) participate strictly as OIDC relying parties; none runs
its own authorization server or credential store.
Commonprovides consumable shapes (claims codec, RP wiring,ViewAsContext,TenantInfo); it does NOT host a token issuer.Account. No non-Account app may maintain its ownApplicationUser-equivalent store, even if the upstream shapesit depends on would technically allow it. The OSS shapes in this
issue are consumed (read-only projection in RP apps, write path
only in
Account), never re-implemented per-app.WASM/RP consumer path (why the shapes must be stringly-typed)
The Field app is offline-first WASM/PWA. It needs to decode federated
tokens (the
tntclaim, eventually the permission scopes per thesibling 031-04 issue) without a server round-trip and without
loading any EF Core / SQL Server dependencies. That constrains the
TenantInfoprojection to:string TenantType(not an enum type that drags a host-sidereference — Field cannot take a dependency on
Account.Domain.Enums.TenantType).stringrepresentation for thetntclaim value (already thecase — Guid "D" format).
ApplicationDbContext, no navigationproperties, no
[ForeignKey]attributes).This is exactly what 030-01 spec's
TenantInfo(string TenantType)shape already encodes. The upstream ask in this issue is for the
federation package to expose a compatible primitive — so Common's
TenantInfois a thin projection overlay rather than a local fork.No timeline promises (explicit)
To be unambiguous about what this issue is not asking:
any particular release date for adopting any upstream shape.
Internal sequencing is governed separately by our own epic planning
and may change without notice to this issue. Concretely: no
timeline or commitment by RiverSync is tied to the resolution of
this issue.
a hard
Wangkanai.Federationdependency even after this issue isresolved. The thin Common strategy in 029/030 is OSS-agnostic and
is being executed on
Wangkanai.Domain+Wangkanai.Systemtoday;any deeper Federation adoption is contingent on legal/architectural
review on our side (license clarity first — see P0 issue License consistency + explicit SaaS/hosted multi-tenant IdP terms #40).
the assumption that the P0 license work in
wangkanai/federation#40 is
resolved first. Until then, no RiverSync dependency is taken
against any Wangkanai.Federation release — only the thin shapes
in
Common.Domainare being authored. This issue asks fordocumented upstream contracts, not for a license change of
philosophy.
Request
We respectfully ask the
wangkanai/federationmaintainers to exposethe following three coordinated upstream contracts:
A
Tenantbase type suitable for thin projection overlay inconsuming platforms. Minimum surface:
Id : Guid(stable key, default until persisted).TenantType— either anenum(Internal | Customer | Partner) or astringdiscriminator the host can bind, withthe canonical values documented.
Name : string(display name).CreatedAt,UpdatedAt) optional butrecommended.
(i.e. do not require a separate
IsInternalflag) soconsumers don't have to re-derive it.
A documented FK-scalar convention on the user entity —
User.TenantId : Guidas a scalar property on the domain class,with the EF Core / persistence-layer relationship configured
separately (Infrastructure, not Domain), and
OnDelete(DeleteBehavior.Restrict). RiverSync wants the contractdocumented, not necessarily a new API surface — the constraint is
that consumers can build user entities that stay decoupled from
the
Tenantaggregate (build-parallelism + WASM-safety).A composite
(Email, TenantId)UNIQUE helper (or a documentedrecipe) so consumers do not have to strip-and-replace the default
ASP.NET Core Identity indexes themselves. The minimum contract:
UNIQUE(NormalizedEmail)withUNIQUE(Email, TenantId).UNIQUE(NormalizedUserName)withUNIQUE(NormalizedUserName, TenantId).RestrictforUser.TenantId → Tenant.Id.tntclaim convention — one Guid-as-string pertoken, Guid "D" format (lowercase, no braces), so RP parsers do
not need a Guid deserializer.
These three contracts together are the load-bearing shape a hosted
multi-tenant OIDC platform needs from upstream to consume the package
as a thin overlay rather than re-implementing these patterns per-app.
Why now
This is a pre-existing gap, but the timing is right for three
converging reasons that make the platform's multi-tenant IdP usage
visible upstream:
030), with
030-01definingTenantInfoas the projectionoverlay. The OSS shapes asked for in this issue are what
TenantInfowill be implemented against. Without them, Commonforks the patterns locally and the "enhance our packages" goal
from the user research question remains unmet.
ViewAsContextprecedent (epic 018) makesRiverSync's multi-tenant IdP usage of these primitives a real,
shipped pattern on our side — not a roadmap sketch. That makes
the contract gap a current-day gating decision for downstream
consumers building similar platforms.
wangkanai/federation#40) is filed andbeing driven in parallel. Until that resolves, this issue's
shapes can be authored and reviewed in the abstract; once P0
resolves, the contracts asked for here become the immediate
next ask for any RiverSync production dependency.
Per research 009 §8, the P1 Tenant row is adjacent to the
scoped
Permission/RolePermissionP1 (031-02) and the permissionscope codec P1 (031-04). All three depend on the federation package
being legally consumable (P0) and on the multi-tenant primitives
being exposed (this issue).
References
013-05-U2headline invariant (lines 50-53) —agile/research/009-refactoring-shared-federation-domain-from-account/report.mdAccount/src/Domain/Tenancy/Tenant.cs:14-23Account/src/Domain/Enums/TenantType.csAccount/src/Domain/Identity/ApplicationUser.cs:28-33Account/src/Infrastructure/Configurations/ApplicationUserConfiguration.cs:24-31, 58-61, 75-91Account/src/Application/Authorization/CompiledClaimTypes.cs:58-64Account/src/Application/Authorization/ClaimsCompiler.cs:146-156Account/src/Application/Authorization/PartnerTokenContext.cs:108Account/src/Application/Identity/TenantEmailLookup.cs:96-106ARCH:VI(sole identity writer; RP-onlyfederation) —
.claude/rules/architecture/federation-auth.mdagile/epics/029-common-oss-foundation-layer/epic.mdTenantInfoprojection overlay spec,
string TenantType) —agile/epics/030-extract-federation-domain-models-to-common/030-01-core-shared-federation-shapes-in-common/spec.mdagile/epics/018-cross-tenant-access-view-as/epic.mdPermission/RolePermission(allowed + Region/Site narrowing)docs/oss/drafts/.notes/031-03-T01-evidence.md(Tenant + TenantType shape)
docs/oss/drafts/.notes/031-03-T02-evidence.md(ApplicationUser scalar + composite invariant)
github.com/wangkanai/federation
Drafted by RiverSync for upstream filing against
wangkanai/federation;not yet filed. Once filed, the upstream URL will replace this header note
and the tracker row in
docs/oss-contributions.mdwill be updated to
Filed.