docs: correct module authorization comments
CI / verify (pull_request) Successful in 26s
CI / infrastructure-smoke (pull_request) Successful in 20s
CI / migration-smoke (pull_request) Successful in 27s

This commit is contained in:
ekklesia
2026-07-24 17:59:48 -05:00
parent 9fa051be61
commit 510d76f7c2
4 changed files with 27 additions and 16 deletions
+5 -3
View File
@@ -17,9 +17,11 @@ function sanitizedDenial(c: Context, status: 403 | 503, code: string): Response
}
/**
* Builds a Hono middleware that enforces `permissionId`, resolved exclusively from Core's own
* trusted, server-side permission registry — a route never supplies its own baseline, and no
* request input can influence which baseline is checked. The principal is resolved exclusively
* Builds a Hono middleware that enforces `permissionId`, resolved exclusively from the injected,
* trusted, server-side `PermissionRegistry` — in production, the complete platform registry
* `startApi` composes from Core's own declarations plus every installed module's, never a
* Core-only fallback. A route never supplies its own baseline, and no request input can influence
* which baseline is checked. The principal is resolved exclusively
* through the injected identity runtime (`resolvePrincipal`), the same one `/api/v1/principal`
* already uses; nothing here ever trusts a client-supplied claim about identity, ownership, or
* grants. A permission id that is not registered fails closed (403) exactly like a denied
+5 -3
View File
@@ -891,9 +891,11 @@ describe.sequential("live API — platform owner and delegated grant management"
const ownerEmail = "[email protected]";
const managerEmail = "[email protected]";
const password = "correct horse battery staple";
// Module permission registration does not exist yet, so `core.authorization.grants.manage` —
// the one permission `CORE_PERMISSION_REGISTRY` actually declares — stands in for "some
// registered permission" throughout this scenario's explicit-grant lifecycle.
// This scenario intentionally proves out the explicit-grant lifecycle (create, list, use,
// revoke) against Core's own grant-management permission rather than a module's — a module
// permission would exercise the exact same route and evaluator code, adding coverage breadth
// without coverage depth. The "live API — installed module permission end-to-end" scenario
// below is where a real installed module's declared permission is proven end-to-end instead.
const testPermission = "core.authorization.grants.manage";
// Well-formed (passes `PermissionId` shape validation) but never registered — used to prove the
// live route rejects it even for a caller who otherwise has full grant-management authority.
+7 -2
View File
@@ -25,8 +25,13 @@ const subjectUserId = parseEkklesiaUserId("subject_1");
// (or, for listing, still return) an unregistered permission id rather than assuming registration.
const permission = parsePermissionId("calendar.events.read");
// A permission `CORE_PERMISSION_REGISTRY` actually declares, so tests that must reach
// `store.createGrant`/`store.revokeGrant` can do so without also depending on module permission
// registration, which does not exist yet.
// `store.createGrant`/`store.revokeGrant` can do so. This suite exercises `createGrantsRoutes` in
// isolation and intentionally injects `CORE_PERMISSION_REGISTRY` directly rather than a composed
// platform registry — the routes have no notion of "Core" versus "module" permissions, they only
// ever consult whichever registry they are given. Coverage proving a module-declared permission
// works through the exact same route code lives in
// `apps/api/tests/authorization-middleware.test.ts` (unit) and the "installed module permission
// end-to-end" scenario in `apps/api/tests/api-http.integration.ts` (live).
const registeredPermission = CORE_AUTHORIZATION_GRANTS_MANAGE;
const grantId = parseGrantId("11111111-1111-1111-1111-111111111111");
+10 -8
View File
@@ -46,9 +46,10 @@ export class PermissionRegistryError extends Error {
* Builds an immutable permission registry from a fixed list of declarations, rejecting any
* duplicate permission id outright at composition time. Deliberately the smallest mechanism that
* distinguishes "registered" from "well-formed": no module discovery, no dynamic registration, no
* network or filesystem I/O — a future module registration mechanism can merge its own
* declarations into a combined list and still call this same constructor, inheriting duplicate
* detection for free instead of reimplementing it.
* network or filesystem I/O. The module integration layer's composer
* (`composePlatformPermissionRegistry`, `core/src/modules/permissions.ts`) merges Core's own
* declarations with every discovered module's into one combined list and calls this same
* constructor, inheriting duplicate detection for free rather than reimplementing it.
*/
export function createPermissionRegistry(
declarations: readonly PermissionDeclaration[],
@@ -98,11 +99,12 @@ export const CORE_PERMISSION_DECLARATIONS: readonly PermissionDeclaration[] = Ob
]);
/**
* Core's own permission registry — the trusted source of truth `apps/api`'s authorization
* enforcement consults so an HTTP route can never supply its own baseline. Contains exactly the
* one Core-owned permission this slice needs; a future module registration mechanism is
* deliberately out of scope here and will compose its own declarations through
* `createPermissionRegistry` rather than through this constant.
* Core's own permission registry, containing exactly the Core-owned permissions declared above.
* Production `apps/api` does not consult this constant directly: it enforces requests against the
* composed platform registry (`composePlatformPermissionRegistry`,
* `core/src/modules/permissions.ts`), which merges these same declarations with every installed
* module's. `CORE_PERMISSION_REGISTRY` remains for Core-only callers and tests that need Core's
* permissions in isolation, without composing in any module.
*/
export const CORE_PERMISSION_REGISTRY: PermissionRegistry = createPermissionRegistry(
CORE_PERMISSION_DECLARATIONS,