feat: declare module permissions
Establishes the one canonical PermissionDeclaration contract (packages/contracts/src/authorization.ts) — id/baseline/description, strict, unknown-property-rejecting — consumed by both Core's authorization registry and module manifests, replacing Core's previously separate interface. Bumps the canonical module manifest to manifestVersion 3: permissions become complete declarations instead of bare identifier strings, with manifest-owned namespace and duplicate-id enforcement. Version 2 and bare string-array permissions are rejected outright; there is no compatibility adapter. Adds composePlatformPermissionRegistry (core/src/modules/permissions.ts), a pure, synchronous composer that builds one platform permission registry from Core's own declarations plus every already-discovered module's, in deterministic order, reusing createPermissionRegistry and rejecting any module-contributed permission in the reserved core.* namespace. core/src/authorization remains independent of core/src/modules; only the module integration layer depends on authorization. Updates the module template, module-development docs, authorization docs, and existing manifest fixtures for manifestVersion 3. Does not wire the combined registry into apps/api — that is pass 2.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { permissionIdSchema } from "./identifiers.js";
|
||||
import type { EkklesiaUserId } from "./identity.js";
|
||||
import { parseEkklesiaUserId } from "./identity.js";
|
||||
|
||||
@@ -51,6 +52,44 @@ export const permissionBaselineSchema = z.enum(["public", "authenticated", "rest
|
||||
|
||||
export type PermissionBaseline = z.infer<typeof permissionBaselineSchema>;
|
||||
|
||||
/**
|
||||
* The one authoritative shape of a *registered* permission declaration — what both Core's
|
||||
* authorization registry (`core/src/authorization/permissions.ts`) and a module manifest's
|
||||
* `permissions` array parse into, so a permission a module declares and a permission Core's
|
||||
* registry enforces are provably the same structure, never two independently maintained
|
||||
* interfaces that could quietly drift apart. `.strict()` rejects any property beyond these three —
|
||||
* there is no room for a module or a future caller to smuggle extra, unvalidated metadata into a
|
||||
* declaration Core treats as trusted.
|
||||
*
|
||||
* This schema says nothing about *registration*: parsing a value into a `PermissionDeclaration`
|
||||
* only proves it is shaped correctly, not that any registry has accepted it. Registration remains
|
||||
* exclusively `PermissionRegistry`'s responsibility (`core/src/authorization/permissions.ts`) —
|
||||
* built by composing exactly the declarations a trusted caller supplies, Core's own plus every
|
||||
* discovered module's.
|
||||
*/
|
||||
export const permissionDeclarationSchema = z
|
||||
.object({
|
||||
id: permissionIdSchema,
|
||||
baseline: permissionBaselineSchema,
|
||||
/** Operator/audit-facing text only. Authorization never reads, parses, or otherwise
|
||||
* interprets this value — changing it can never change what a permission allows. */
|
||||
description: z.string().trim().min(1).max(240),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type PermissionDeclarationInput = z.input<typeof permissionDeclarationSchema>;
|
||||
export type PermissionDeclaration = z.output<typeof permissionDeclarationSchema>;
|
||||
|
||||
export function parsePermissionDeclaration(input: unknown): PermissionDeclaration {
|
||||
return permissionDeclarationSchema.parse(input);
|
||||
}
|
||||
|
||||
export function safeParsePermissionDeclaration(
|
||||
input: unknown,
|
||||
): z.ZodSafeParseResult<PermissionDeclaration> {
|
||||
return permissionDeclarationSchema.safeParse(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Distinguishes a grant an operator/system created directly (`explicit`) from one a module
|
||||
* created as a side effect of its own business operation, attributed to some source the module
|
||||
|
||||
Reference in New Issue
Block a user