Scoped Permission + RolePermission base shapes for Foundation overlays
Filing target: github.com/wangkanai/federation (or split to
github.com/wangkanai/foundation if upstream prefers
the base entity home), Priority P1 (per research
009 §8
P1 Permission + RolePermission row).
Motivated by: research
agile/research/009-refactoring-shared-federation-domain-from-account/report.md
§8 P1 row. Drafted from epic
031-oss-contribution-parallel-track/031-02-p1-scoped-permission-role-permission-models.
Problem statement
wangkanai/federation (and the underlying wangkanai/foundation base it
presumably composes) does not today expose a scope-aware RolePermission
link entity or a scope-free Permission base shape suitable for a
multi-app product platform to overlay onto a thin Common layer. The
Federation repo's published models (per 009 §5 F6) are still
NotImplementedException stubs under "Planned Features," and Foundation
ships Entity / AuditableEntity bases but no permission-shaped entities.
Downstream multi-tenant OIDC platforms that want a composable
permission/grant matrix have nothing to bind against upstream — they end
up re-declaring a private Permission and RolePermission, which is the
exact duplication this project was created to prevent.
This is the P1 sibling of the P0 license issue (F7 / 031-01) and the
Sister-P1 of the multi-tenant Tenant + (email, tenant) composite
identity ask (031-03). All three sit in the same upstream gap: the base
shapes a multi-app platform needs are either missing or planned, not
shipped.
Two half-asks, one upstream gap
The Permission + RolePermission shape is two halves of one matrix:
Permission — the grant key. A scope-free base with Id +
Name (human-readable grant key like "users.read") + optional
Description. FK-free, no tenancy, no scope. Anchor: RiverSync's own
Account/src/Domain/Authorization/Permission.cs:9-28.
RolePermission — the link entity carrying the scope-aware
Allowed + ScopeRegionId + ScopeSiteId triple and the one-way
narrowing contract that binds them. Anchor:
Account/src/Domain/Authorization/RolePermission.cs:39-70.
Both halves belong on the same upstream issue because they share the
matrix semantics; if the base Permission ships without a contract for
how scope attaches to its link, the consumer ends up inventing a
half-formed contract locally. Filing them as a pair lets the maintainer
reject, accept, or split the matrix semantics as a single decision.
RiverSync's matrix evidence (the shape we would overlay)
The downstream shape exists today, locally, in RiverSync's Account
IdP. We are NOT asking upstream to take this exact shape — only that
a base shape be available that lets a thin Common overlay express it
without a hard IdP-package dependency.
// Account/src/Domain/Authorization/RolePermission.cs:39-70 (verbatim, the link entity)
/// <summary>
/// Whether this role↔permission grant is effective. Defaults to <c>true</c> so
/// existing role/permission rows authored pre-015 continue to behave as grants.
/// <para>
/// Per FR-005 / FR-003: an explicit <c>false</c> is a <b>role/permission-layer
/// denial</b>. The scope layer (epic 015, 013-03 addendum) may further narrow
/// the grant on a per-region/per-site basis, but MUST NOT re-introduce a
/// permission that is denied at the role/permission layer — the narrowing
/// invariant is one-way.
/// </para>
/// </summary>
public bool Allowed { get; set; } = true;
/// <summary>
/// Per FR-004 (layer-5 scope override): explicit region-narrowing. <c>null</c>
/// ⇔ no region override (grant is org-wide or, if <see cref="ScopeSiteId"/>
/// is set, site-scoped). ...
/// </summary>
public Guid? ScopeRegionId { get; set; }
/// <summary>
/// Per FR-004 (layer-5 scope override): explicit site-narrowing. <c>null</c>
/// ⇔ no site override. Set to an <see cref="OrganizationSite.Id"/> to
/// narrow the grant to a single site. ...
/// </summary>
public Guid? ScopeSiteId { get; set; }
// Account/src/Domain/Authorization/Permission.cs:9-28 (verbatim, the grant-key entity)
public class Permission
{
/// <summary>Stable Guid key.</summary>
public Guid Id { get; set; }
/// <summary>
/// Human-readable permission name, e.g. <c>"users.read"</c>, <c>"billing.write"</c>.
/// The grant key in the RolePermission matrix.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>Optional human-readable description.</summary>
public string? Description { get; set; }
/// <summary>UTC timestamp of creation.</summary>
public DateTimeOffset CreatedAt { get; set; }
/// <summary>UTC timestamp of last update.</summary>
public DateTimeOffset UpdatedAt { get; set; }
}
Why this is a composition blocker, not a feature request
- No scope-free Permission base upstream. Consumers wanting a
thin PermissionInfo overlay cannot use Foundation's Entity base
alone — Entity carries Id + audit metadata but no
Name-as-grant-key shape. They have to re-declare Permission
locally. The thin-Common strategy (epic 030) wants to consume, not
re-declare.
- No
RolePermission link with one-way narrowing contract. Without
upstream committing to "scope narrows, never widens; a denied grant
cannot be re-introduced at the scope layer," consumers have to
invent the contract themselves and police it in their own resolver.
The contract is small but load-bearing.
- The DM-33 schema-only guard pattern is locally proven. A
reflection-based test
(Account/test/Unit/Authorization/RolePermissionTests.cs:39-56)
bans the runtime method names Compile / Evaluate / Build on
RolePermission so a future contributor cannot smuggle compiler
logic into the link entity. If upstream's base carried
Compile/Evaluate/Build methods, every consumer of the base
would have to write a mirror of this guard test. We'd like upstream
to ship the same shape with the same absence, so the guard becomes
a project convention rather than a per-consumer tax.
RiverSync usage context
RiverSync is a six-app product platform. Its OIDC identity-provider role
is centralized in a single application (Account); the other five
(Portal, Admin, Partners, Pipeline, Field) participate strictly
as OIDC relying parties. The following four anchors explain why a
scope-free Permission base and a scope-aware RolePermission link matter
to us — and why we are asking for the shapes 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.
-
Planned thin Common overlays on Foundation (epic 030).
Epic 030 — Extract Federation Domain Models To Common
is the follow-on that would push consumable model shapes
(PermissionInfo, RolePermissionInfo overlays on
Foundation.Entity) into Common.Domain, leaving Account as the
persisted IdP matrix and issuer. Neither 029 nor 030 pins
Wangkanai.Federation today — both are blocked on the license
question being resolved first (F7, see 031-01) AND on the
Permission/RolePermission base shapes being available upstream.
-
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 same "shape lives in Common,
writer lives in Account" pattern applies to the permission matrix
that the scope layer reads at runtime.
-
ARCH:VI — Account is the sole writer; Common provides consumption shapes.
Per architecture rule
.claude/rules/architecture/federation-auth.md
(extract ARCH:VI):
Account is the sole platform OIDC/OAuth2 identity provider — and
the only writer of the persisted role↔permission matrix.
- The other five product apps (Portal, Admin, Partners, Pipeline, Field)
participate strictly as OIDC relying parties; none runs its own
authorization server, credential store, OR role↔permission matrix.
Common provides consumable shapes (PermissionInfo /
RolePermissionInfo overlays, claims codec, RP wiring,
ViewAsContext); it does NOT host a token issuer or persist a
matrix. The base shapes in this issue are what Common would
overlay against.
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 release. 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 (or Wangkanai.Foundation) 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.
- No licensing-philosophy request. We are not asking upstream to
change their licensing philosophy — only that the raw Permission /
RolePermission base shapes be available for thin-overlay
consumption. The license of record (Apache-2.0 vs SSPL v1 vs
something else) is a project governance decision and not ours to
make; it is a sibling concern tracked in the P0 issue (F7 / 031-01).
- No scope-on-Permission request. Scope belongs on the link
entity (RolePermission.Allowed + ScopeRegionId / ScopeSiteId),
not on Permission itself. Putting scope on Permission would couple
the base to a tenancy model that Foundation doesn't otherwise own,
and would push Foundation toward IdP-matrix semantics — exactly the
shape the thin-overlay contract is trying to avoid.
Request
We respectfully ask the wangkanai/federation (and, if appropriate,
wangkanai/foundation) maintainers to consider shipping the following
two base shapes as thin, composable, schema-only POCOs. We have ordered
them so that the maintainer can accept, reject, or split them as a
single matrix decision.
1. Permission — scope-free grant-key base
A base entity (or POCO) with exactly:
Guid Id — stable key (convention with the rest of the Foundation
Entity line).
string Name — human-readable grant key, e.g. "users.read",
"billing.write". No FK leakage: Permission carries NO
RoleId, NO TenantId, NO scope FKs. Relationships are configured
at the consumer's ORM mapping, not declared on the type.
string? Description — optional human-readable description.
CreatedAt / UpdatedAt timestamps — present and explicit in the
base contract (or absent by design, with the choice documented) so
consumer overlays don't silently lose audit metadata.
Mirrors RiverSync's
Account/src/Domain/Authorization/Permission.cs:9-28.
Scope does NOT live on Permission. It belongs on the link entity
(§2 below).
2. RolePermission — scope-aware link with one-way narrowing contract
A schema-only base (or POCO) link entity with:
Guid Id — stable key.
Guid RoleId — FK scalar to ApplicationRole.Id. No navigation
property (FK-scalar convention, configurable in the consumer's
mapping).
Guid PermissionId — FK scalar to Permission.Id. No navigation
property.
bool Allowed (default true) — explicit false is a
role/permission-layer denial.
Guid? ScopeRegionId — nullable region-narrowing override. null
⇔ no region override (grant is org-wide or, if ScopeSiteId is set,
site-scoped).
Guid? ScopeSiteId — nullable site-narrowing override. null ⇔ no
site override. Setting a site implies its region.
CreatedAt / UpdatedAt — UTC timestamps.
The one-way narrowing contract (verifiable property, not just a
comment). The resolver intersects the role/permission-layer decision
with the scope override; the operation is set-intersection, not
set-union. Three properties bind:
- Scope can narrow, never widen. A scope override may further
restrict where/when a grant applies, but it cannot extend it.
- A denied grant cannot be re-introduced at the scope layer. If
Allowed = false, no combination of ScopeRegionId /
ScopeSiteId may create an effective grant.
- The resolver intersects, never unions. DM-10 + §12 resolved on
the RiverSync side; the same intersection-only contract is what
we are asking upstream to commit to.
No embedded compiler. Any claims-compiler runtime the consumer
needs lives in a separate type or package — never on the base
Permission / RolePermission POCOs. This is the
RolePermission_Has_No_ClaimsCompiler_Logic guard pattern
(Account/test/Unit/Authorization/RolePermissionTests.cs:39-56)
that bans the runtime method names Compile / Evaluate / Build
on the link entity. We would love to see upstream ship the same
shape with the same absence, so the guard becomes a project
convention rather than a per-consumer tax.
3. Documentation in a matrix contract note (companion to LICENSE-COMPATIBILITY)
Regardless of which package the base shapes live in, ship a short
PERMISSION-MATRIX.md (or equivalent) in the repo that documents:
- Which entity holds the grant key (
Permission.Name).
- Which entity holds the scope (
RolePermission.Allowed +
ScopeRegionId + ScopeSiteId).
- The one-way narrowing contract, with the three verifiable
properties above.
- The schema-only guard pattern (no
Compile / Evaluate /
Build on the link entity).
This is the same posture RiverSync needs for ARCH:VI compliance: a
written, citable contract on the upstream side that consumers can
quote in their own compliance documentation.
What we are NOT asking for
- No change to upstream's licensing philosophy (tracked separately in
the P0 issue, F7 / 031-01).
- No scope fields on
Permission itself (scope belongs on the link
entity per §2).
- No specific ORM shape (FK-scalar convention, configurable in the
consumer's mapping; no embedded navigations).
- No commitment from RiverSync to take a hard dependency on the
shipped shape once available.
Why now
This is a long-standing gap (the Federation repo's permission models
are still "Planned Features" per 009 §5 F6), but the timing is right
for three converging reasons that make the upstream base-shape gap
visible from a multi-app product platform's perspective:
- The thin Common overlay plan is concrete, not hypothetical.
Epic 030's PermissionInfo / RolePermissionInfo overlays on
Foundation.Entity are the next concrete step in the
thin-extraction plan from 009 §7 Option A. The overlay work
surfaces exactly the missing base shapes upstream needs to ship.
- The cross-tenant /
ViewAsContext precedent (epic 018) makes
the multi-tenant IdP pattern of "shape lives in Common, writer
lives in Account" a real, shipped pattern on the RiverSync side —
not a roadmap sketch. The same pattern now applies to the
permission matrix, and the upstream gap is the missing piece.
- The DM-33 schema-only guard pattern is locally proven. A
reflection-based test bans the runtime method names
Compile / Evaluate / Build on RolePermission, which
means the upstream guard is a one-paragraph copy of a pattern
that already exists in a shipping project. The cost of shipping
the same shape upstream is small; the cost of every consumer
re-writing the same guard test is cumulative.
Without these base shapes upstream, multi-app OIDC platforms either
re-declare a private Permission / RolePermission matrix (duplicating
what Foundation was created to share) or invent a scope-narrowing
contract locally with no upstream authority to anchor it. Neither is a
good place for the platform to be.
References
- Research 009, §8 P1 row (Permission + RolePermission models, scope /
narrowing contract) and §5 F6 (Federation "Planned Features" gap) —
agile/research/009-refactoring-shared-federation-domain-from-account/report.md
- Architecture rule
ARCH:VI (Account as sole identity issuer + sole
matrix writer; RP-only federation) —
.claude/rules/architecture/federation-auth.md
- Epic 029 (Common OSS foundation layer) —
agile/epics/029-common-oss-foundation-layer/epic.md
- Epic 030 (thin federation domain extraction to Common with
PermissionInfo / RolePermissionInfo overlays) —
agile/epics/030-extract-federation-domain-models-to-common/epic.md
- Epic 018 (cross-tenant view-as) —
agile/epics/018-cross-tenant-access-view-as/epic.md
- Epic 031-01 (P0 license + SaaS/hosted terms — sibling P0 issue, F7) —
agile/epics/031-oss-contribution-parallel-track/031-01-p0-license-metadata-issue/
- Federation repo (Models tree, README, LICENSE) —
github.com/wangkanai/federation
- Foundation repo (
Entity / AuditableEntity base shapes) —
github.com/wangkanai/foundation
- Source evidence —
RolePermission link entity (Allowed + scope
fields + one-way narrowing invariant) —
Account/src/Domain/Authorization/RolePermission.cs:39-70
- Source evidence —
Permission grant-key entity (Name as matrix key,
FK-free) —
Account/src/Domain/Authorization/Permission.cs:9-28
- Source evidence —
RolePermission_Has_No_ClaimsCompiler_Logic
reflection guard (DM-33 schema-only pattern) —
Account/test/Unit/Authorization/RolePermissionTests.cs:39-56
- Cross-tenant
ViewAsContext shape (Common consumption, Account
writer) —
Common/src/Web/ViewAsContext.cs
- T01 evidence notes (RolePermission half — link entity, scope fields,
one-way contract) —
docs/oss/drafts/.notes/031-02-T01-evidence.md
- T02 evidence notes (Permission half — grant-key base, FK-free,
scope-free) —
docs/oss/drafts/.notes/031-02-T02-evidence.md
Drafted by RiverSync for upstream filing against wangkanai/federation
(or wangkanai/foundation if upstream prefers the split). 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.
Scoped Permission + RolePermission base shapes for Foundation overlays
Problem statement
wangkanai/federation(and the underlyingwangkanai/foundationbase itpresumably composes) does not today expose a scope-aware
RolePermissionlink entity or a scope-free
Permissionbase shape suitable for amulti-app product platform to overlay onto a thin Common layer. The
Federation repo's published models (per 009 §5 F6) are still
NotImplementedExceptionstubs under "Planned Features," and Foundationships
Entity/AuditableEntitybases but no permission-shaped entities.Downstream multi-tenant OIDC platforms that want a composable
permission/grant matrix have nothing to bind against upstream — they end
up re-declaring a private
PermissionandRolePermission, which is theexact duplication this project was created to prevent.
This is the P1 sibling of the P0 license issue (F7 / 031-01) and the
Sister-P1 of the multi-tenant
Tenant+(email, tenant)compositeidentity ask (031-03). All three sit in the same upstream gap: the base
shapes a multi-app platform needs are either missing or planned, not
shipped.
Two half-asks, one upstream gap
The Permission + RolePermission shape is two halves of one matrix:
Permission— the grant key. A scope-free base withId+Name(human-readable grant key like"users.read") + optionalDescription. FK-free, no tenancy, no scope. Anchor: RiverSync's ownAccount/src/Domain/Authorization/Permission.cs:9-28.RolePermission— the link entity carrying the scope-awareAllowed+ScopeRegionId+ScopeSiteIdtriple and the one-waynarrowing contract that binds them. Anchor:
Account/src/Domain/Authorization/RolePermission.cs:39-70.Both halves belong on the same upstream issue because they share the
matrix semantics; if the base
Permissionships without a contract forhow scope attaches to its link, the consumer ends up inventing a
half-formed contract locally. Filing them as a pair lets the maintainer
reject, accept, or split the matrix semantics as a single decision.
RiverSync's matrix evidence (the shape we would overlay)
The downstream shape exists today, locally, in RiverSync's
AccountIdP. We are NOT asking upstream to take this exact shape — only that
a base shape be available that lets a thin Common overlay express it
without a hard IdP-package dependency.
Why this is a composition blocker, not a feature request
thin
PermissionInfooverlay cannot use Foundation'sEntitybasealone —
EntitycarriesId+ audit metadata but noName-as-grant-key shape. They have to re-declarePermissionlocally. The thin-Common strategy (epic 030) wants to consume, not
re-declare.
RolePermissionlink with one-way narrowing contract. Withoutupstream committing to "scope narrows, never widens; a denied grant
cannot be re-introduced at the scope layer," consumers have to
invent the contract themselves and police it in their own resolver.
The contract is small but load-bearing.
reflection-based test
(
Account/test/Unit/Authorization/RolePermissionTests.cs:39-56)bans the runtime method names
Compile/Evaluate/BuildonRolePermissionso a future contributor cannot smuggle compilerlogic into the link entity. If upstream's base carried
Compile/Evaluate/Buildmethods, every consumer of the basewould have to write a mirror of this guard test. We'd like upstream
to ship the same shape with the same absence, so the guard becomes
a project convention rather than a per-consumer tax.
RiverSync usage context
RiverSync is a six-app product platform. Its OIDC identity-provider role
is centralized in a single application (
Account); the other five(
Portal,Admin,Partners,Pipeline,Field) participate strictlyas OIDC relying parties. The following four anchors explain why a
scope-free Permission base and a scope-aware
RolePermissionlink matterto us — and why we are asking for the shapes 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-side federationpieces (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.
Planned thin Common overlays on Foundation (epic 030).
Epic 030 — Extract Federation Domain Models To Common
is the follow-on that would push consumable model shapes
(
PermissionInfo,RolePermissionInfooverlays onFoundation.Entity) intoCommon.Domain, leavingAccountas thepersisted IdP matrix and issuer. Neither 029 nor 030 pins
Wangkanai.Federationtoday — both are blocked on the licensequestion being resolved first (F7, see 031-01) AND on the
Permission/RolePermission base shapes being available upstream.
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 same "shape lives in Common,
writer lives in Account" pattern applies to the permission matrix
that the scope layer reads at runtime.
ARCH:VI — Account is the sole writer; Common provides consumption shapes.
Per architecture rule
.claude/rules/architecture/federation-auth.md(extract ARCH:VI):
Accountis the sole platform OIDC/OAuth2 identity provider — andthe only writer of the persisted role↔permission matrix.
participate strictly as OIDC relying parties; none runs its own
authorization server, credential store, OR role↔permission matrix.
Commonprovides consumable shapes (PermissionInfo/RolePermissionInfooverlays, claims codec, RP wiring,ViewAsContext); it does NOT host a token issuer or persist amatrix. The base shapes in this issue are what
Commonwouldoverlay against.
No timeline promises (explicit)
To be unambiguous about what this issue is not asking:
particular release date for adopting any upstream release. 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.
hard
Wangkanai.Federation(orWangkanai.Foundation) dependencyeven after this issue is resolved. The thin Common strategy in
029/030 is OSS-agnostic and is being executed on
Wangkanai.DomainWangkanai.Systemtoday; any deeper Federation adoption iscontingent on legal/architectural review on our side.
change their licensing philosophy — only that the raw
Permission/RolePermissionbase shapes be available for thin-overlayconsumption. The license of record (Apache-2.0 vs SSPL v1 vs
something else) is a project governance decision and not ours to
make; it is a sibling concern tracked in the P0 issue (F7 / 031-01).
entity (
RolePermission.Allowed+ScopeRegionId/ScopeSiteId),not on
Permissionitself. Putting scope on Permission would couplethe base to a tenancy model that Foundation doesn't otherwise own,
and would push Foundation toward IdP-matrix semantics — exactly the
shape the thin-overlay contract is trying to avoid.
Request
We respectfully ask the
wangkanai/federation(and, if appropriate,wangkanai/foundation) maintainers to consider shipping the followingtwo base shapes as thin, composable, schema-only POCOs. We have ordered
them so that the maintainer can accept, reject, or split them as a
single matrix decision.
1.
Permission— scope-free grant-key baseA base entity (or POCO) with exactly:
Guid Id— stable key (convention with the rest of the FoundationEntityline).string Name— human-readable grant key, e.g."users.read","billing.write". No FK leakage: Permission carries NORoleId, NOTenantId, NO scope FKs. Relationships are configuredat the consumer's ORM mapping, not declared on the type.
string? Description— optional human-readable description.CreatedAt/UpdatedAttimestamps — present and explicit in thebase contract (or absent by design, with the choice documented) so
consumer overlays don't silently lose audit metadata.
Mirrors RiverSync's
Account/src/Domain/Authorization/Permission.cs:9-28.Scope does NOT live on Permission. It belongs on the link entity
(§2 below).
2.
RolePermission— scope-aware link with one-way narrowing contractA schema-only base (or POCO) link entity with:
Guid Id— stable key.Guid RoleId— FK scalar toApplicationRole.Id. No navigationproperty (FK-scalar convention, configurable in the consumer's
mapping).
Guid PermissionId— FK scalar toPermission.Id. No navigationproperty.
bool Allowed(defaulttrue) — explicitfalseis arole/permission-layer denial.
Guid? ScopeRegionId— nullable region-narrowing override.null⇔ no region override (grant is org-wide or, if
ScopeSiteIdis set,site-scoped).
Guid? ScopeSiteId— nullable site-narrowing override.null⇔ nosite override. Setting a site implies its region.
CreatedAt/UpdatedAt— UTC timestamps.The one-way narrowing contract (verifiable property, not just a
comment). The resolver intersects the role/permission-layer decision
with the scope override; the operation is set-intersection, not
set-union. Three properties bind:
restrict where/when a grant applies, but it cannot extend it.
Allowed = false, no combination ofScopeRegionId/ScopeSiteIdmay create an effective grant.the RiverSync side; the same intersection-only contract is what
we are asking upstream to commit to.
No embedded compiler. Any claims-compiler runtime the consumer
needs lives in a separate type or package — never on the base
Permission/RolePermissionPOCOs. This is theRolePermission_Has_No_ClaimsCompiler_Logicguard pattern(
Account/test/Unit/Authorization/RolePermissionTests.cs:39-56)that bans the runtime method names
Compile/Evaluate/Buildon the link entity. We would love to see upstream ship the same
shape with the same absence, so the guard becomes a project
convention rather than a per-consumer tax.
3. Documentation in a matrix contract note (companion to LICENSE-COMPATIBILITY)
Regardless of which package the base shapes live in, ship a short
PERMISSION-MATRIX.md(or equivalent) in the repo that documents:Permission.Name).RolePermission.Allowed+ScopeRegionId+ScopeSiteId).properties above.
Compile/Evaluate/Buildon the link entity).This is the same posture RiverSync needs for ARCH:VI compliance: a
written, citable contract on the upstream side that consumers can
quote in their own compliance documentation.
What we are NOT asking for
the P0 issue, F7 / 031-01).
Permissionitself (scope belongs on the linkentity per §2).
consumer's mapping; no embedded navigations).
shipped shape once available.
Why now
This is a long-standing gap (the Federation repo's permission models
are still "Planned Features" per 009 §5 F6), but the timing is right
for three converging reasons that make the upstream base-shape gap
visible from a multi-app product platform's perspective:
Epic 030's
PermissionInfo/RolePermissionInfooverlays onFoundation.Entityare the next concrete step in thethin-extraction plan from 009 §7 Option A. The overlay work
surfaces exactly the missing base shapes upstream needs to ship.
ViewAsContextprecedent (epic 018) makesthe multi-tenant IdP pattern of "shape lives in Common, writer
lives in Account" a real, shipped pattern on the RiverSync side —
not a roadmap sketch. The same pattern now applies to the
permission matrix, and the upstream gap is the missing piece.
reflection-based test bans the runtime method names
Compile/Evaluate/BuildonRolePermission, whichmeans the upstream guard is a one-paragraph copy of a pattern
that already exists in a shipping project. The cost of shipping
the same shape upstream is small; the cost of every consumer
re-writing the same guard test is cumulative.
Without these base shapes upstream, multi-app OIDC platforms either
re-declare a private
Permission/RolePermissionmatrix (duplicatingwhat Foundation was created to share) or invent a scope-narrowing
contract locally with no upstream authority to anchor it. Neither is a
good place for the platform to be.
References
narrowing contract) and §5 F6 (Federation "Planned Features" gap) —
agile/research/009-refactoring-shared-federation-domain-from-account/report.mdARCH:VI(Account as sole identity issuer + solematrix writer; RP-only federation) —
.claude/rules/architecture/federation-auth.mdagile/epics/029-common-oss-foundation-layer/epic.mdPermissionInfo/RolePermissionInfooverlays) —agile/epics/030-extract-federation-domain-models-to-common/epic.mdagile/epics/018-cross-tenant-access-view-as/epic.mdagile/epics/031-oss-contribution-parallel-track/031-01-p0-license-metadata-issue/github.com/wangkanai/federation
Entity/AuditableEntitybase shapes) —github.com/wangkanai/foundation
RolePermissionlink entity (Allowed + scopefields + one-way narrowing invariant) —
Account/src/Domain/Authorization/RolePermission.cs:39-70Permissiongrant-key entity (Name as matrix key,FK-free) —
Account/src/Domain/Authorization/Permission.cs:9-28RolePermission_Has_No_ClaimsCompiler_Logicreflection guard (DM-33 schema-only pattern) —
Account/test/Unit/Authorization/RolePermissionTests.cs:39-56ViewAsContextshape (Common consumption, Accountwriter) —
Common/src/Web/ViewAsContext.csone-way contract) —
docs/oss/drafts/.notes/031-02-T01-evidence.mdscope-free) —
docs/oss/drafts/.notes/031-02-T02-evidence.mdDrafted by RiverSync for upstream filing against
wangkanai/federation(or
wangkanai/foundationif upstream prefers the split). Not yetfiled. Once filed, the upstream URL will replace this header note and
the tracker row in
docs/oss-contributions.mdwill be updated to
Filed.