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
Sibling P2 issues in this series: 031-05-T02 (P2 AccessScope / PermScope discriminated + only-narrowing Intersect + AppKey
entitlement shapes), 031-05-T09 (P2 cross-tenant / view-as +
AccessGrant primitives). Filed in parallel.
Problem statement
Wangkanai.Foundation.Domain ships usable DDD bases today — Entity<T>, AuditableEntity<T>, UserAuditableEntity<T> (Created /
Updated / Deleted + By), IAggregateRoot, IRepository / IAsyncRepository + UoW, DomainEvent types, Result<T> — plus a
full Audit module and EF integration. Wangkanai.Federation is
positioned (per its README "Planned Features" list) to ship the OIDC /
Identity / Permission / Tenant vocabulary on top of those bases.
What is missing from the public ecosystem today is a worked example
of layering the two together for a multi-tenant authz / permission
matrix. As research 009 §3 row 10 (consensus MEDIUM, Investigator E7
web search S9) documents:
No evidence of external/public consumers successfully combining
Foundation.Domain + Federation packages; no samples layering them
for multi-tenant scoped permissions + OIDC.
For a consuming platform trying to build a hosted multi-tenant OIDC
identity provider on top of Foundation + Federation, this gap is the onboarding cliff — every consumer has to re-derive the
layering pattern from scratch, with no canonical reference, and most
get the FK-scalar convention, the composite-unique identity, or the
scope-narrowing semantics wrong on the first attempt. Research 009
§8 P2 row makes this explicit: the gap is "Zero public samples
combining Foundation.Domain + Federation auth".
What RiverSync needs (and what the spike demonstrated)
RiverSyncAuditableEntity (or a generic platform wrapper) extending Foundation.Domain.AuditableEntity<T> — adds platform-wide audit
semantics without re-implementing the Foundation audit trail.
AccessScope (a PermScope-shaped discriminated value type with OrgWide | Region(id) | Site(id) cases, plus an Intersect
only-narrowing contract) implemented as a Common.Domain value
type in feature 029-03 — generalized scope/claim primitives
— and the bridge to the existing PermScope claim contract in Common.Claims.
TenantInfo (a non-persisted projection: Guid Id, display name, string TenantType with canonical values Internal | Customer | Partner, optional scope context) implemented in feature 029-04 — non-persisted type spike
— the spike that demonstrates the layering works end-to-end
without taking a dependency on Account.Domain.
External consumption demonstration in Common/test/Unit per 029-04-U2 AC1:
a test file that references TenantInfowithout a project
reference to Account.Domain.
The 030 thin shapes — TenantInfo, PermissionInfo, RolePermissionInfo, ApplicationInfo as Common.Domain value
types with no EF mapping — are the consumer-side proof point that
this layering works.
The current state of RiverSync's authz wiring (worked-example data point)
The RiverSync account IdP today implements the multi-tenant authz /
permission matrix with raw POCOs (no inheritance from Foundation.Domain):
Account/src/Domain/Authorization/Permission.cs:9 — bare entity,
no AuditableEntity base.
Account/src/Domain/Authorization/RolePermission.cs:22-71 — bare
POCO with Allowed + ScopeRegionId + ScopeSiteId + one-way
narrowing contract.
Account/src/Domain/Tenancy/Tenant.cs:14-23 — bare POCO with Guid Id, TenantType TenantType, string Name.
Account/src/Domain/Identity/ApplicationUser.cs:28-33 — bare
POCO with scalar Guid TenantId.
The repeated FR-006 scalar-FK convention and the
"RolePermission_Has_No_ClaimsCompiler_Logic" test pattern across
epics 013-018 are exactly the layering convention the OSS example
needs to document. Without it, every Foundation + Federation consumer
re-invents the wheel.
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 layering example matters to us — and why
we are asking for it rather than quietly forking.
Thin Common foundation (epic 029, IMPLEMENTED). Epic 029 — Common OSS Foundation Layer
establishes RiverSync.Common.Domain as a thin shell layered on Wangkanai.Domain + Wangkanai.System + Wangkanai.Foundation.Domain
Wangkanai.Audit.Domain (alpha-02 pair). It introduces RiverSyncAuditableEntity (extending Foundation.Domain.AuditableEntity<T>)
and generalized scope / claim primitives in 029-03
the 029-04 TenantInfo spike.
This is the Option A recommendation from research 009 §7: thin
extraction in Common, not a full internal fork.
Thin federation overlay with consumable value types (epic 030, IN PROGRESS). Epic 030 — Extract Federation Domain Models To Common
pushes consumable model shapes (TenantInfo, PermissionInfo, RolePermissionInfo, ApplicationInfo) into Common.Domain as
thin value types — non-persisted, no EF mapping, stringly-typed
where it must be (e.g. string TenantType) for WASM/RP consumer
safety. The 030 thin shapes are the demonstration that the upstream layering is consumable; the
worked example upstream is what makes the demonstration canonical.
Foundation spike plan in 029.
Per epic 029 plan §3
(action 3 — "Smoke-adopt the Foundation bases in Common.Domain shell"):
"add Wangkanai.Foundation.Domain + Wangkanai.Audit +
Wangkanai.EntityFramework.Postgres pins (if needed) to
Directory.Packages.props; create a thin RiverSyncAuditableEntity
or similar in Common.Domain and migrate one non-persisted type as
spike. Rationale: proves layering (F5) with almost zero blast;
surfaces real gaps early." The spike is exactly the
Foundation + multi-tenant authz wiring the OSS ask in this issue
requests as a public example.
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 (TenantInfo, AccessScope, PermScope, RP wiring, ViewAsContext); it does NOT host a
token issuer.
Even if upstream publishes a Foundation + Federation layering
example, Account remains the only writer of identity and the
only writer of the persisted permission matrix. The OSS
example is consumed (read-only projection in RP apps, write
path only in Account), never re-implemented per-app.
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 layering
example. 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.Foundation.Domain dependency (or Wangkanai.Federation) even after this issue is resolved. The
thin Common strategy in 029/030 is OSS-agnostic and is being
executed on the alpha-02 pair today; any deeper Federation
adoption is contingent on legal/architectural review on our side
(license clarity first — see P0 issue wangkanai/federation#40).
No licensing-philosophy request. This issue is filed under
the assumption that the P0 license work in wangkanai/federation#40 is
resolved first. The ask here is for worked example + docs, not
for a license change of philosophy. The layering example is
authored in Markdown + code snippets, and would land under the
existing Foundation license (Apache-2.0, per research 009 §10) —
no new license surface.
Request
We respectfully ask the Wangkanai.Foundation (and cross-linked Wangkanai.Federation) maintainers to publish a layering example +
guidance doc demonstrating Foundation.Domain + Federation auth for
a multi-tenant authz / permission matrix. The ask decomposes into three
coordinated deliverables:
RiverSyncEntity<T>-style platform wrapper extending it.
A Tenant aggregate with Guid Id + TenantType (Internal /
Customer / Partner enum or string discriminator) + Name.
A scalar-FK User.TenantId foreign-key convention (the build-
parallelism pattern documented in 013-02 spec line 36-37 AC2).
A composite (Email, TenantId) UNIQUE identity invariant (the
multi-tenant email invariant from 013-05-U2 AC1).
A RolePermission with Allowed + ScopeRegionId + ScopeSiteId
one-way narrowing contract.
The AccessScope discriminated value type with OrgWide | Region | Site cases and an Intersect only-narrowing contract.
A non-persisted TenantInfo projection demonstrating the
thin-overlay shape.
The sample should be compilable (with the platform RiverSync*
prefix swapped out for upstream namespaces) and independently
consumable — i.e. a downstream project referencing only Foundation.Domain (and Federation, if available) should be
able to compile against it without taking a dependency on Account.Domain or any other RiverSync-private package.
A guidance document (docs/layering.md or equivalent)
covering the conventions the example demonstrates:
Why the FK is scalar (User.TenantId : Guid) and not a
navigation property — build parallelism, WASM-safety, layered
projection overlay.
Why the composite UNIQUE(Email, TenantId) replaces the
default Identity index — multi-tenant email invariant.
Why the scope narrowing contract is one-way (no widening) —
least-privilege intersection.
Why "no compiler logic in domain entities" is enforced as a
guard (e.g. the RolePermission_Has_No_ClaimsCompiler_Logic test pattern) —
keeps domain pure, isolates token emission in the Application
layer.
Why the TenantInfo projection is non-persisted and stringly-
typed (e.g. string TenantType) — WASM/RP consumer path
without server-side dependencies.
A documented recipe for migrating an existing raw-POCO entity
(the shape of Account/src/Domain/Authorization/Permission.cs:9
and RolePermission.cs:22-71 today) to the layered pattern. The
recipe should be small enough to be a "1-day spike" (per research
009 §8 P2 row rightmost column: "1-day spike in Common.Domain;
then migrate timestamps; surfaces real gaps") and produce a
runnable before/after diff.
These three deliverables together close the "Zero public samples
combining Foundation.Domain + Federation auth" gap on the OSS side and
give downstream consumers (RiverSync + any similar multi-tenant OIDC
platform) a canonical reference for the layering pattern.
Why now
This is a pre-existing gap, but the timing is right for three
converging reasons that make the platform's multi-tenant authz wiring
visible upstream:
The thin Common strategy is already in flight (epic 029
IMPLEMENTED, epic 030 IN PROGRESS), with the 029-03 scope /
claim primitives and the 029-04 TenantInfo spike acting as the consumer-side proof point that the layering works. The OSS
worked example is what makes that proof point canonical rather
than RiverSync-internal.
The cross-tenant ViewAsContext precedent (epic 018) plus
the 013-05 composite-unique invariant make the multi-tenant
authz wiring a real, shipped pattern on our side — not a roadmap
sketch. That makes the layering example a current-day
reference need rather than a future-tense one.
The P0 license issue (wangkanai/federation#40) is filed
and being driven in parallel. Until that resolves, this issue
is filed against Wangkanai.Foundation first
(Apache-2.0 per research 009 §10 — license clarity already
exists for Foundation) and can land independently of the
federation-side P0 work. Once P0 resolves, the same pattern
can be extended into Wangkanai.Federation's docs.
Per research 009 §8, this P2 row is adjacent to the other P2
rows: P2 AccessScope/PermScope discriminated + only-narrowing
Intersect + AppKey entitlement shapes (031-05-T02); P2 cross-tenant /
view-as + AccessGrant primitives (031-05-T09). The three P2 issues
together cover the "what does a Foundation + Federation layered
multi-tenant IdP actually look like?" question that no public sample
answers today.
Drafted by RiverSync for upstream filing against Wangkanai.Foundation
(with a cross-link note on 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.
Layering example + guidance: Foundation.Domain + multi-tenant authz/permission matrix
Problem statement
Wangkanai.Foundation.Domainships usable DDD bases today —Entity<T>,AuditableEntity<T>,UserAuditableEntity<T>(Created /Updated / Deleted + By),
IAggregateRoot,IRepository/IAsyncRepository+ UoW,DomainEventtypes,Result<T>— plus afull
Auditmodule and EF integration.Wangkanai.Federationispositioned (per its README "Planned Features" list) to ship the OIDC /
Identity / Permission / Tenant vocabulary on top of those bases.
What is missing from the public ecosystem today is a worked example
of layering the two together for a multi-tenant authz / permission
matrix. As research 009 §3 row 10 (consensus MEDIUM, Investigator E7
For a consuming platform trying to build a hosted multi-tenant OIDC
identity provider on top of Foundation + Federation, this gap is
the onboarding cliff — every consumer has to re-derive the
layering pattern from scratch, with no canonical reference, and most
get the FK-scalar convention, the composite-unique identity, or the
scope-narrowing semantics wrong on the first attempt. Research 009
§8 P2 row makes this explicit: the gap is "Zero public samples
combining Foundation.Domain + Federation auth".
What RiverSync needs (and what the spike demonstrated)
The thin Common strategy being executed in
epic 029 — Common OSS Foundation Layer
(IMPLEMENTED 2026-06-18) and
epic 030 — Extract Federation Domain Models To Common
(IN PROGRESS) needs a canonical worked example upstream that
demonstrates:
RiverSyncAuditableEntity(or a generic platform wrapper) extendingFoundation.Domain.AuditableEntity<T>— adds platform-wide auditsemantics without re-implementing the Foundation audit trail.
AccessScope(aPermScope-shaped discriminated value type withOrgWide | Region(id) | Site(id)cases, plus anIntersectonly-narrowing contract) implemented as a Common.Domain value
type in
feature 029-03 — generalized scope/claim primitives
— and the bridge to the existing
PermScopeclaim contract inCommon.Claims.TenantInfo(a non-persisted projection:Guid Id, display name,string TenantTypewith canonical valuesInternal | Customer | Partner, optional scope context) implemented infeature 029-04 — non-persisted type spike
— the spike that demonstrates the layering works end-to-end
without taking a dependency on
Account.Domain.Common/test/Unitper029-04-U2 AC1:
a test file that references
TenantInfowithout a projectreference to
Account.Domain.The 030 thin shapes —
TenantInfo,PermissionInfo,RolePermissionInfo,ApplicationInfoasCommon.Domainvaluetypes with no EF mapping — are the consumer-side proof point that
this layering works.
The current state of RiverSync's authz wiring (worked-example data point)
The RiverSync account IdP today implements the multi-tenant authz /
permission matrix with raw POCOs (no inheritance from
Foundation.Domain):Account/src/Domain/Authorization/Permission.cs:9— bare entity,no
AuditableEntitybase.Account/src/Domain/Authorization/RolePermission.cs:22-71— barePOCO with
Allowed+ScopeRegionId+ScopeSiteId+ one-waynarrowing contract.
Account/src/Domain/Tenancy/Tenant.cs:14-23— bare POCO withGuid Id,TenantType TenantType,string Name.Account/src/Domain/Identity/ApplicationUser.cs:28-33— barePOCO with scalar
Guid TenantId.The repeated FR-006 scalar-FK convention and the
"RolePermission_Has_No_ClaimsCompiler_Logic" test pattern across
epics 013-018 are exactly the layering convention the OSS example
needs to document. Without it, every Foundation + Federation consumer
re-invents the wheel.
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 layering example matters to us — and why
we are asking for it rather than quietly forking.
Thin Common foundation (epic 029, IMPLEMENTED).
Epic 029 — Common OSS Foundation Layer
establishes
RiverSync.Common.Domainas a thin shell layered onWangkanai.Domain+Wangkanai.System+Wangkanai.Foundation.DomainWangkanai.Audit.Domain(alpha-02 pair). It introducesRiverSyncAuditableEntity(extendingFoundation.Domain.AuditableEntity<T>)and generalized scope / claim primitives in
029-03
029-04 TenantInfo spike.
This is the Option A recommendation from research 009 §7: thin
extraction in Common, not a full internal fork.
Thin federation overlay with consumable value types (epic 030, IN PROGRESS).
Epic 030 — Extract Federation Domain Models To Common
pushes consumable model shapes (
TenantInfo,PermissionInfo,RolePermissionInfo,ApplicationInfo) intoCommon.Domainasthin value types — non-persisted, no EF mapping, stringly-typed
where it must be (e.g.
string TenantType) for WASM/RP consumersafety. The 030 thin shapes are the
demonstration that the upstream layering is consumable; the
worked example upstream is what makes the demonstration
canonical.
Foundation spike plan in 029.
Per
epic 029 plan §3
(action 3 — "Smoke-adopt the Foundation bases in Common.Domain shell"):
"add Wangkanai.Foundation.Domain + Wangkanai.Audit +
Wangkanai.EntityFramework.Postgres pins (if needed) to
Directory.Packages.props; create a thin
RiverSyncAuditableEntityor similar in Common.Domain and migrate one non-persisted type as
spike. Rationale: proves layering (F5) with almost zero blast;
surfaces real gaps early." The spike is exactly the
Foundation + multi-tenant authz wiring the OSS ask in this issue
requests as a public example.
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 (TenantInfo,AccessScope,PermScope, RP wiring,ViewAsContext); it does NOT host atoken issuer.
example, Account remains the only writer of identity and the
only writer of the persisted permission matrix. The OSS
example is consumed (read-only projection in RP apps, write
path only in Account), never re-implemented per-app.
No timeline promises (explicit)
To be unambiguous about what this issue is not asking:
any particular release date for adopting any upstream layering
example. 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.Foundation.Domaindependency (orWangkanai.Federation) even after this issue is resolved. Thethin Common strategy in 029/030 is OSS-agnostic and is being
executed on the alpha-02 pair today; any deeper Federation
adoption is contingent on legal/architectural review on our side
(license clarity first — see P0 issue
wangkanai/federation#40).the assumption that the P0 license work in
wangkanai/federation#40isresolved first. The ask here is for worked example + docs, not
for a license change of philosophy. The layering example is
authored in Markdown + code snippets, and would land under the
existing Foundation license (Apache-2.0, per research 009 §10) —
no new license surface.
Request
We respectfully ask the
Wangkanai.Foundation(and cross-linkedWangkanai.Federation) maintainers to publish a layering example +guidance doc demonstrating
Foundation.Domain + Federation authfora multi-tenant authz / permission matrix. The ask decomposes into three
coordinated deliverables:
A worked code sample combining:
Foundation.Domain.AuditableEntity<T>(audit trail + soft-deletetimestamps).
RiverSyncEntity<T>-style platform wrapper extending it.Tenantaggregate withGuid Id+TenantType(Internal /Customer / Partner enum or string discriminator) +
Name.User.TenantIdforeign-key convention (the build-parallelism pattern documented in 013-02 spec line 36-37 AC2).
(Email, TenantId)UNIQUE identity invariant (themulti-tenant email invariant from 013-05-U2 AC1).
RolePermissionwithAllowed+ScopeRegionId+ScopeSiteIdAccessScopediscriminated value type withOrgWide | Region | Sitecases and anIntersectonly-narrowing contract.TenantInfoprojection demonstrating thethin-overlay shape.
The sample should be compilable (with the platform
RiverSync*prefix swapped out for upstream namespaces) and independently
consumable — i.e. a downstream project referencing only
Foundation.Domain(andFederation, if available) should beable to compile against it without taking a dependency on
Account.Domainor any other RiverSync-private package.A guidance document (
docs/layering.mdor equivalent)covering the conventions the example demonstrates:
User.TenantId : Guid) and not anavigation property — build parallelism, WASM-safety, layered
projection overlay.
UNIQUE(Email, TenantId)replaces thedefault Identity index — multi-tenant email invariant.
least-privilege intersection.
guard (e.g. the
RolePermission_Has_No_ClaimsCompiler_Logictest pattern) —keeps domain pure, isolates token emission in the Application
layer.
TenantInfoprojection is non-persisted and stringly-typed (e.g.
string TenantType) — WASM/RP consumer pathwithout server-side dependencies.
A documented recipe for migrating an existing raw-POCO entity
(the shape of
Account/src/Domain/Authorization/Permission.cs:9and
RolePermission.cs:22-71today) to the layered pattern. Therecipe should be small enough to be a "1-day spike" (per research
009 §8 P2 row rightmost column: "1-day spike in
Common.Domain;then migrate timestamps; surfaces real gaps") and produce a
runnable before/after diff.
These three deliverables together close the "Zero public samples
combining Foundation.Domain + Federation auth" gap on the OSS side and
give downstream consumers (RiverSync + any similar multi-tenant OIDC
platform) a canonical reference for the layering pattern.
Why now
This is a pre-existing gap, but the timing is right for three
converging reasons that make the platform's multi-tenant authz wiring
visible upstream:
IMPLEMENTED, epic 030 IN PROGRESS), with the 029-03 scope /
claim primitives and the 029-04 TenantInfo spike acting as the
consumer-side proof point that the layering works. The OSS
worked example is what makes that proof point canonical rather
than RiverSync-internal.
ViewAsContextprecedent (epic 018) plusthe 013-05 composite-unique invariant make the multi-tenant
authz wiring a real, shipped pattern on our side — not a roadmap
sketch. That makes the layering example a current-day
reference need rather than a future-tense one.
wangkanai/federation#40) is filedand being driven in parallel. Until that resolves, this issue
is filed against
Wangkanai.Foundationfirst(Apache-2.0 per research 009 §10 — license clarity already
exists for Foundation) and can land independently of the
federation-side P0 work. Once P0 resolves, the same pattern
can be extended into
Wangkanai.Federation's docs.Per research 009 §8, this P2 row is adjacent to the other P2
rows: P2 AccessScope/PermScope discriminated + only-narrowing
Intersect + AppKey entitlement shapes (031-05-T02); P2 cross-tenant /
view-as + AccessGrant primitives (031-05-T09). The three P2 issues
together cover the "what does a Foundation + Federation layered
multi-tenant IdP actually look like?" question that no public sample
answers today.
References
layering absence) +
§6 action feat: update README.md paths and clean up Directory.Build.props #3 (Foundation spike plan) +
§7 Option A (thin extraction in Common) —
agile/research/009-refactoring-shared-federation-domain-from-account/report.mdAccount/src/Domain/Authorization/Permission.cs:9— bare POCOAccount/src/Domain/Authorization/RolePermission.cs:22-71—Allowed+ScopeRegionId+ScopeSiteId+ one-way narrowingAccount/src/Domain/Tenancy/Tenant.cs:14-23— bare POCOAccount/src/Domain/Identity/ApplicationUser.cs:28-33— scalarTenantIdFKARCH:VI(sole identity writer; RP-onlyfederation) —
.claude/rules/architecture/federation-auth.mdAccessScope/PermScopediscriminated + only-narrowingIntersect+ AppKey entitlement shapeswangkanai/federation#40—P0 license clarity (filed)
Tenant + (email, tenant), permission scope codec
github.com/wangkanai/federation
github.com/wangkanai/foundation
Drafted by RiverSync for upstream filing against
Wangkanai.Foundation(with a cross-link note on
Wangkanai.Federation); not yet filed. Oncefiled, the upstream URL will replace this header note and the tracker
row in
docs/oss-contributions.mdwillbe updated to
Filed.