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:
@@ -48,7 +48,7 @@ without executing module code:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifestVersion": 2,
|
||||
"manifestVersion": 3,
|
||||
"id": "calendar",
|
||||
"name": "Calendar",
|
||||
"version": "0.1.0",
|
||||
@@ -77,7 +77,13 @@ without executing module code:
|
||||
"version": "1.0.0"
|
||||
}
|
||||
],
|
||||
"permissions": ["calendar.events.read"],
|
||||
"permissions": [
|
||||
{
|
||||
"id": "calendar.events.read",
|
||||
"baseline": "restricted",
|
||||
"description": "Read calendar events."
|
||||
}
|
||||
],
|
||||
"storage": {
|
||||
"buckets": [
|
||||
{
|
||||
@@ -99,6 +105,22 @@ has its own exact semantic version because its public contract can evolve indepe
|
||||
module release. Capability dependencies use semantic-version ranges. Optional means the provider
|
||||
may be absent; if it is installed, its capability version must be compatible.
|
||||
|
||||
`manifestVersion: 3` (`packages/contracts/src/module-manifest.ts`) is a required, exact literal — a
|
||||
version 2 manifest, and any other version, fails validation outright rather than being silently
|
||||
reinterpreted; there is no compatibility adapter or automatic migration between manifest versions.
|
||||
Each `permissions` entry is a complete declaration, not a bare identifier string: `id` (must begin
|
||||
with `<module-id>.`, exactly like a capability id), `baseline` (`public`, `authenticated`, or
|
||||
`restricted` — see [Authorization](../development/authorization.md#permission-registry) for what
|
||||
each baseline means to the evaluator), and a required, non-blank `description` up to 240 characters
|
||||
for operators and audit review only — Core's authorization layer never reads or interprets it.
|
||||
Declaring a permission here is necessary but not sufficient for it to be enforceable: the manifest
|
||||
only proves the declaration is well-formed and namespaced correctly, and does not itself register
|
||||
anything with Core's authorization registry (see
|
||||
[Authorization](../development/authorization.md#module-permission-composition) for how Core composes
|
||||
every discovered module's declarations, alongside its own, into the one platform permission
|
||||
registry). A duplicate permission id within one manifest, or a permission id outside the module's
|
||||
own namespace, fails validation the same way a duplicate or misnamed capability id does.
|
||||
|
||||
Server and optional schema entrypoints must be compiled ESM `.js` or `.mjs` files. The optional
|
||||
migration entrypoint is a directory. All entrypoints begin with `./`, remain inside the package,
|
||||
and exist. Production never imports source TypeScript directly. Modules with no tables omit
|
||||
|
||||
Reference in New Issue
Block a user