Skip to content

Latest commit

 

History

History
98 lines (85 loc) · 5.95 KB

permissions.md

File metadata and controls

98 lines (85 loc) · 5.95 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. Its adopted module-source contract registers scoped definitions separately from finite grants; no IModule API is implied. Identity's foundation definitions/Role templates are implemented. Customization keys and their live consumers require their owning operation decision; these examples register or grant nothing.

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 and have finite source admission. CustomizationOperationPolicySource binds four authoring commands and four verification queries to Seed authority. The two default initializers admit only their declared Seed or transport-owned event-initializer source/request bindings under the live contract. Hub provisioning emits the completed-tenant event and does not directly invoke a Customization HTTP writer. The planned Studio editors below own human-facing permission keys and admission.

Every command takes its tenant from context rather than request input. RLS and Write_With_Foreign_TenantId_Is_Rejected_By_WithCheck independently protect the database write. Finite source admission registers no assignable permission key. The Phase 03 Status block owns current source-bound qualification.

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.