Skip to content

Latest commit

 

History

History
97 lines (84 loc) · 5.96 KB

permissions.md

File metadata and controls

97 lines (84 loc) · 5.96 KB

Customization — Permission Matrix

Per Permission Standards, which names this file. Part of the module spec.

P02d-4 Step 3 consumer implemented — 2026-10-04. Education consumes this internal projection under its public boundary. Customization adds no HTTP route, permission key or audit intent; field requiredness is internal metadata, and schema validation remains write-only.

P02d-3 Step 3 implemented — 2026-10-02; both review rounds passed. ICustomizationDefinitionProjectionReader is internal and unrouted, uses trusted ambient scope and adds no HTTP endpoint or permission key. Public admission belongs to P02d-4. The module contract owns its scope.

No permission keys yet. The matrix below is a forward declaration in the {module}.{resource}.{action} form with the closed action set of Permission Standards. Registration runs through IModule.RegisterPermissions(IPermissionRegistry), and neither type exists in backend/src yet; the catalogue lands with the Identity module in Phase 03, together with Role, Permission and the lighting-up of the AuthorizationBehavior shell.

Resource read write delete admin Default role grants
ContentType ✓ ✓ ✓ – tenant-admin: read+write+delete; editor: read
ContentTypePublication ✓ ✓ – – tenant-admin: read+write; editor: read
LevelTaxonomy ✓ ✓ ✓ – tenant-admin: read+write+delete; editor: read
LevelTaxonomyPublication ✓ ✓ – – tenant-admin: read+write; editor: read

Publication is a sub-resource, because the action set is closed. Authoring a schema and making it the live answer for a key are two decisions — the module models them as two commands for exactly that reason — and they carry different blast radii. A wrong draft constrains nothing: it exists, it is addressable, and no content row is validated against it. A wrong publication retires the incumbent in the same transaction, and every subsequent write of that content type is validated against the new shape.

The way to say that is Permission Standards § Closed Action Set's own: there is no publish action, and "if a verb does not fit, model the verb as a distinct sub-resource" — its first worked example being "publish a course" → education.course_publication.write. So customization.content_type.write covers Register and the additive revision, and customization.content_type_publication.write covers Publish. An earlier version of this matrix put publication on admin instead, which read as the same split and was not one: admin means "full control including ownership transfer", no default role held it, and the result was a matrix in which nobody could publish anything.

delete is a real action here, and it is soft. A Deprecated revision is never removed — content rows pin the version they were written against — but a Draft is removable, and the partial index is partial on deleted_at IS NULL precisely so that a retired definition releases its key to a successor. Permission Standards defines delete as "remove or soft-delete the resource", and audit.md classifies the same operation as MUST-audit; a matrix with no delete disagreed with both. Publication has none, because unpublishing is not deletion: the successor's publish is what retires a revision.

Six write commands are unrouted. The current route set exposes none of them, and permission enforcement remains Phase 03. The four authoring commands are called by the Seeder. The two default-initialization commands are shared by the Seeder and the transport-owned Customization subscriptions; they take tenant authority only from the restored trusted context. Hub provisioning emits the completed-tenant event and does not directly invoke a Customization HTTP writer. The planned Studio editors below own human-facing authorization.

Every command takes its tenant from context rather than request input. RLS and Write_With_Foreign_TenantId_Is_Rejected_By_WithCheck protect the database write; reachability is the current authorization boundary, not a new permission grant. The Step 5 record owns implementation validation/review status.

Every key in the {module}.{resource}.{action} form. The resources above are written PascalCase as the template writes them; the keys they generate are lowercase snake_case — customization.content_type.read, customization.content_type_publication.write, customization.level_taxonomy.delete. editor is Permission Standards § Built-in Roles's Editor, named there rather than invented here.

The Admin Studio editors are what make these keys load-bearing. The TenantContentType editor lands in Phase 04, the TenantLevelTaxonomy editor in Phase 05, and Phase 06 consolidates them — each one is the first caller with a human behind it, and therefore the first that needs a key rather than an absence of endpoints.

Scope is Tenant, not Organization. Both aggregates are tenant-owned tenant-wide: a content type is what the whole tenant's content looks like, and a branch publishing its own shape would make one organization's rows unreadable to another's renderer. There is no organization_id on either table and no restrictive write guard, which is Database Standards § Table classes's answer for this class rather than an omission.