Context
nanopub-query #129 adds server-side support for revoking and re-assigning space roles via authorization-scoped latest-wins (the same model as preset assignments, #302). The materializer side is implemented on branch feat/issue-129-role-revocation (design: doc/design-role-revocation.md). This issue tracks the Nanodash changes needed to publish and surface these operations.
Two new, previously-unused vocabulary terms drive it; nothing existing is reinterpreted, so this is fully back-compat and needs no re-ingest (effective on the next periodic rebuild after the server redeploys).
1. New vocabulary (add to KPXL_TERMS)
gen:RevokedRoleInstantiation (class)
gen:forSpace, gen:forAgent (predicates on the revocation node)
gen:detachedRole (predicate)
(gen:hasRole and gen:AdminRole already exist as KPXL_TERMS.HAS_ROLE / ADMIN_ROLE_TYPE.)
2. New publishing actions / templates
a. Revoke a role instantiation (admin revokes a member/maintainer/observer; or a member leaves)
:r a gen:RevokedRoleInstantiation ;
gen:forSpace <spaceIRI> ;
gen:forAgent <agentIRI> ; # whose role is revoked
gen:hasRole <roleIRI> . # the version-pinned role being revoked
- Admin revocation uses
gen:hasRole gen:AdminRole (the admin tier has no per-role IRI; the server keys admin revocation on gen:AdminRole). This is the one convention worth getting exactly right.
- Self-leave is just a revocation signed by the agent whose role it names (
forAgent == signer), allowed at any tier — except root admins, who are constitutional and cannot be revoked or leave at runtime (server enforces; the UI should not offer "leave" to a root admin).
- Authorization (server-enforced; UI should only offer what will take effect): admins revoke anyone (incl. admin peers); maintainers revoke members/observers; members revoke observers; self always (non-root).
b. Detach a role from a space (per-role opt-out, incl. preset-supplied roles)
<spaceIRI> gen:detachedRole <roleIRI> .
- Admin-authored. Removes the role's availability in the space — direct and preset-derived attachments, cascading to the instantiations anchored on the role.
- Non-sticky: a newer
gen:hasRole attachment or preset (re)assignment re-attaches.
3. Hard requirements on the published nanopubs
- Every role assign / revoke / attach / detach nanopub must stamp
dct:created in pubinfo. It is the latest-wins key. A nanopub lacking it sorts as epoch (always loses) — so a re-grant without dct:created would be silently dropped against an older revocation. (nanopub-query review finding; the server treats missing timestamp as epoch by design.)
gen:detachedRole nanopubs must be recognizable by type — publish them as a single-predicate assertion (only the gen:detachedRole triple) or carry an explicit npx:hasNanopubType gen:detachedRole. Same convention gen:hasRole attachments already rely on; a detachment buried among other assertion triples with no type marker is not picked up.
4. Consumer queries — NO change needed ✅
Verified against the published queries: the read path already reflects revocation/detachment with no query edit:
get-space-members (RAo0c4UNoD…) computes ?validated via an EXISTS over the current space-state graph; a revoked member's state-graph gen:RoleInstantiation is removed, so the member flips to ?validated = false (still listed from the raw graph, flagged unvalidated).
get-space-roles (RAKJFw-xIQ2r…) reads gen:RoleAssignment from the state graph; a detached role's row is removed, so the role disappears from the listing.
- Admin listings read
npa:inverseProperty gen:hasAdmin rows; a revoked (non-root) admin disappears, a root admin sticks.
(Confirmed by local read-path canary tests in the server branch. The full fleet read-path canary per doc/canary-checklist-spaceref-1.15.md still applies at deploy time.)
5. UI
- Space management: offer Revoke on each member/maintainer/observer/admin (subject to the authorization matrix and the root-admin exemption), a Leave action for the current user's own non-root role, and Detach role for admins (per-role, including preset-supplied roles).
- Members already render with a validation flag — a revoked member simply shows unvalidated; no display change strictly required, but consider hiding or clearly marking revoked rows.
6. Known limitation to be aware of (server, model-level — shared with #302)
Revocation authorization is evaluated against the current materialized role set each cycle. For a negative assertion this fails open: if the revoker later loses the tier that authorized the revocation (e.g. an admin who kicked someone then leaves), the suppression lifts and the revoked grant can reappear on the next rebuild — unless the grant's own granter has also lost authority. The same property exists in #302 preset deactivation. The threat model treats admin↔admin churn as low-risk (mutually-trusting admin set). Flagging so the UX doesn't imply revocation is irreversible-by-departure; eliminating it would need sticky revocations or authorization snapshotting (a larger design change to decide separately).
References
Context
nanopub-query #129 adds server-side support for revoking and re-assigning space roles via authorization-scoped latest-wins (the same model as preset assignments, #302). The materializer side is implemented on branch
feat/issue-129-role-revocation(design:doc/design-role-revocation.md). This issue tracks the Nanodash changes needed to publish and surface these operations.Two new, previously-unused vocabulary terms drive it; nothing existing is reinterpreted, so this is fully back-compat and needs no re-ingest (effective on the next periodic rebuild after the server redeploys).
1. New vocabulary (add to
KPXL_TERMS)gen:RevokedRoleInstantiation(class)gen:forSpace,gen:forAgent(predicates on the revocation node)gen:detachedRole(predicate)(
gen:hasRoleandgen:AdminRolealready exist asKPXL_TERMS.HAS_ROLE/ADMIN_ROLE_TYPE.)2. New publishing actions / templates
a. Revoke a role instantiation (admin revokes a member/maintainer/observer; or a member leaves)
gen:hasRole gen:AdminRole(the admin tier has no per-role IRI; the server keys admin revocation ongen:AdminRole). This is the one convention worth getting exactly right.forAgent == signer), allowed at any tier — except root admins, who are constitutional and cannot be revoked or leave at runtime (server enforces; the UI should not offer "leave" to a root admin).b. Detach a role from a space (per-role opt-out, incl. preset-supplied roles)
gen:hasRoleattachment or preset (re)assignment re-attaches.3. Hard requirements on the published nanopubs
dct:createdin pubinfo. It is the latest-wins key. A nanopub lacking it sorts as epoch (always loses) — so a re-grant withoutdct:createdwould be silently dropped against an older revocation. (nanopub-query review finding; the server treats missing timestamp as epoch by design.)gen:detachedRolenanopubs must be recognizable by type — publish them as a single-predicate assertion (only thegen:detachedRoletriple) or carry an explicitnpx:hasNanopubType gen:detachedRole. Same conventiongen:hasRoleattachments already rely on; a detachment buried among other assertion triples with no type marker is not picked up.4. Consumer queries — NO change needed ✅
Verified against the published queries: the read path already reflects revocation/detachment with no query edit:
get-space-members(RAo0c4UNoD…) computes?validatedvia anEXISTSover the current space-state graph; a revoked member's state-graphgen:RoleInstantiationis removed, so the member flips to?validated = false(still listed from the raw graph, flagged unvalidated).get-space-roles(RAKJFw-xIQ2r…) readsgen:RoleAssignmentfrom the state graph; a detached role's row is removed, so the role disappears from the listing.npa:inverseProperty gen:hasAdminrows; a revoked (non-root) admin disappears, a root admin sticks.(Confirmed by local read-path canary tests in the server branch. The full fleet read-path canary per
doc/canary-checklist-spaceref-1.15.mdstill applies at deploy time.)5. UI
6. Known limitation to be aware of (server, model-level — shared with #302)
Revocation authorization is evaluated against the current materialized role set each cycle. For a negative assertion this fails open: if the revoker later loses the tier that authorized the revocation (e.g. an admin who kicked someone then leaves), the suppression lifts and the revoked grant can reappear on the next rebuild — unless the grant's own granter has also lost authority. The same property exists in #302 preset deactivation. The threat model treats admin↔admin churn as low-risk (mutually-trusting admin set). Flagging so the UX doesn't imply revocation is irreversible-by-departure; eliminating it would need sticky revocations or authorization snapshotting (a larger design change to decide separately).
References
feat/issue-129-role-revocation;doc/design-role-revocation.md.