ADR-0009: Portal x Chain Matrix as Access Control Model
Status: ACCEPTED Date: 2026-03-22 Supersedes: N/A Superseded by: N/A
Context
The platform serves four distinct portal types (SYSTEM, TENANT, PARTNER, CONSUMER) with two request chains (WEB for browser-based sessions, API for programmatic access). Each combination has different endpoints, authentication flows, and permission models. A unified model was needed to classify and control endpoint access.
Source: DevPortal design spec (Portal x Chain Matrix), slaunchx-infra-gateway/docs/MODULE-SPEC.md, slaunchx-app-prometheus/docs/domain/gateway/DESIGN-gateway-validation-chain.md
Decision
Access control is modeled as a Portal x Chain matrix. Each controller declares its portal scope via @GatewayValidation(portals = {...}) and its chain type via chainType. The WEB chain uses JWT + RBAC permissions; the API chain uses API Key + HMAC-SHA256 + scope-based authorization (@ApiScope). The gateway framework assembles a different validation chain per chain type, with portal-specific handlers executing in a defined priority order.
Current matrix:
- SYSTEM (10010101): WEB chain only (206 endpoints)
- TENANT (10010102): WEB chain only (209 endpoints)
- PARTNER (10010103): WEB chain only (103 endpoints)
- CONSUMER (10010104): WEB + API chain (87 WEB + API endpoints with scopes)
Consequences
- Clean separation of concerns: portal identity and chain type are orthogonal dimensions.
- API chain extensibility: any portal can add API endpoints by annotating controllers with
@ApiScope. - Per-portal controller isolation (ADR-0011) becomes natural: each portal gets its own controller package.
- The matrix is the definitive reference for documentation generation (DevPortal parser uses it directly).
Alternatives Considered
- Single chain with role-based differentiation: Rejected because WEB (session-based) and API (key-based) authentication are fundamentally different protocols that cannot be unified without compromising security.
- Portal-agnostic endpoints with runtime portal filtering: Rejected because it hides the portal scope from the source code, making audits unreliable.