Skip to content

Capabilities

Lookup reference for the capability ACL: the capability id grammar, the closed capability list, full/own/none grading, the seeded role bundles, and a summary of which routes demand which capability. The identity model behind it — tokens, Margonem-ID binding, identity linking, governance data — is owned by Roles & permissions; the authoritative per-route table is API reference §7.

Id grammar

A capability is <resource>.<action> with an optional .own ownership suffix:

<resource>.<action>[.own]     e.g. entity.read, pu.award, player.write.own
  • <resource>.all grants every action on the resource.
  • admin.all grants everything — it is the machine token's implicit set and the Namiestnik/Koordynator bundle.
  • X.own never silently satisfies X (see Grading).

Capability list

Capability Grants
entity.read Read any entity, name resolution, location/map reads, job polling — the broad shared-lore read
entity.write Create/modify/soft-delete entities, maps, and traversal links
player.read Read Gracz records and their Postacie (/people, /characters reads)
player.write / player.write.own Gracz/Postać CRUD on the dedicated /people + /characters routes (.own = the caller's own subjects)
player.declare.own Reserved for the bot's /deklaracja self-service declaration; seeded in the Gracz bundle, no route consumes it yet
session.read Session reads, integrity, participation graph, narrator profile
session.write Author/edit/open/close sessions, rebuild the graph, refresh the hash store
session.distribute Run entity-driven session distribution
pu.award Run the monthly PU batch (POST /workflows/award-pu)
pu.read / pu.read.own PU timeline/history/new-character preview and election eligibility (.own scopes the timeline, new-character preview, and eligibility to own Postacie / own Gracz; history is served unscoped)
workflow.settle Run the settlement workflow (POST /workflows/settle) — see Settlement model
workflow.map_checkup Run the map-checkup sweep (POST /workflows/map-checkup)
currency.read / currency.read.own Holdings, reports, denominations (.own filters to own holdings; reconciliation needs full)
currency.write Create/adjust holdings; apply session @Transfer directives
economy.read Economy snapshot, timeline, materialization
governance.read Read the narrator Uprawnienia table
governance.write Edit narrator Uprawnienia rows
location.override Reindex the repository after hand edits to nerthus.entities.md — Council-level (see Locations model)
identity.read GET /auth/whoami, GET /identity/{id}
identity.link Link a Discord account ⇄ Margonem ID
capability.read GET /capabilities (the caller's own effective set)
capability.admin Grant/revoke per-Person capabilities
token.manage Mint/list/revoke named tokens
log.read Log streams, audit reads, entity history, Discord delivery log, log parsing
log.fetch Game-log fetch and per-session refresh
discord.send Deliver a webhook message
events.subscribe Open the GET /events SSE stream (Bearer in the header)
sync.read Read the daemon's repo-sync state (GET /sync)
sync.run Run a repo-sync tick immediately (POST /sync)
admin.mode / admin.shutdown / admin.index / admin.migrate Control-plane: mode flip, shutdown, name-index rebuild, repository import (POST /import)
admin.all Everything (machine token, Namiestnik/Koordynator)

No seeded role bundle carries workflow.settle, workflow.map_checkup, sync.read, or sync.run — settlement spans distribution, currency, and PU at once, and sync is repo-level, so these resolve only through admin.all or a per-Person grant. .own grading is meaningless for repo-level sync — the handlers treat an own-only sync grant as no grant and refuse with 403. The daemon's own scheduled sync tick runs in-process and needs no token — design in Sync.

The machine token implicitly satisfies every capability (admin.all); it exists to authenticate local processes, never to sandbox the operator's shell. Capabilities scope end users reaching the daemon through the Discord/Margonem bot — token mechanics in Roles & permissions.

Grading (full / own / none)

When the dispatcher checks a route's required capability, the caller's grant is graded:

Grade Condition Effect
full Holds the plain capability, a matching .all, or admin.all Unrestricted handler run
own Holds only the .own variant Request marked OwnOnly — the handler scopes it to the caller's own subjects or refuses
none Neither 403 naming the missing capability

X.own never silently satisfies X. The caller's own subjects are their Gracz entity (by name or Margonem ID) and every Postać whose @należy_do is that Gracz.

Handler scoping for an OwnOnly caller, route by route:

Route (OwnOnly caller) Scoping
GET /pu/timeline Filtered to own Postacie; an explicit ?character= must be an own subject (403 otherwise)
GET /pu/new-character-count player is forced to the caller's own Gracz
GET /pu/history Served unscoped — each row carries only a batch date and a counted-session count, facts the committed nerthus.ledger.md echo already publishes to every repo clone
GET /elections/eligibility gracz is forced to the caller's own Gracz (the /wybory self-check)
GET /currency, GET /currency/report Filtered to holdings owned by the caller's Gracz/Postacie
GET /currency/{name} 403 unless the holding is the caller's own
GET /currency/reconciliation Always 403 — reconciliation is a repo-wide integrity report
PATCH/DELETE /people/{name}, /characters/{name} 403 unless the subject is the caller's own
POST /people Always 403 — a new Gracz has no ownership to scope to
POST /characters @należy_do must equal the caller's own Gracz

So a Gracz holding pu.read.own reads their PU timeline but not another player's, and player.write.own lets them edit their own Postać but not create a second Gracz.

Role bundles

Roles are named bundles of capabilities — data, not hardcoded tiers. They load from committed .nerthus/data-tables/roles.json; the daemon seeds that file only when it is absent, so a Council edit survives every boot and re-init (file location and seed rule: Data tables, Configuration). The seeded defaults are also the fallback when the file is missing:

Role Bundle (as seeded)
Gracz entity.read, player.read, player.write.own, session.read, pu.read.own, currency.read.own, player.declare.own, events.subscribe, identity.read, capability.read
Narrator entity.read, player.read, session.read, session.write, pu.award, pu.read, governance.read, currency.read, log.read, log.fetch, discord.send, events.subscribe
Radny entity.read, entity.write, player.read, player.write, session.read, session.write, pu.award, pu.read, governance.read, governance.write, token.manage, capability.admin, identity.link, log.read, currency.read, currency.write, location.override, discord.send, events.subscribe
Namiestnik / Koordynator admin.all

Namiestnik and Koordynator are two distinct roles with identical seeded bundles — roles.json keys them separately, so the Council can diverge them later. The bundles are independent lists, not strict supersets: the narrator bundle carries full pu.read/currency.read instead of the .own variants and omits identity.read; the radny bundle omits log.fetch.

A Person's effective set = union of their role bundles, plus per-Person grants, minus per-Person revokes (Grant-NerthusCapability / Revoke-NerthusCapability). Session tokens store only the role; capabilities are recomputed on every request, so a grant or revoke takes effect on live tokens immediately. Every externally-minted session token carries the gracz role — a Narrator's or Radny's extra powers attach to their Margonem ID as per-Person grants, or ride a named token minted with -Roles narrator.

Routes → capabilities (summary)

The full closed table — every route with its exact capability and Write flag — is API reference §7. The shape of the mapping:

Route area Read Write / act
/entities, /resolve, /locations, /maps, /jobs entity.read entity.write
/locations/reindex location.override
/people, /characters player.read player.write (.own graded)
/sessions, /workflows/open-session, /workflows/close-session session.read session.write
/workflows/distribute-session session.distribute
/pu/*, /elections/eligibility pu.read (.own graded) pu.award (the monthly batch)
/currency/*, /workflows/apply-transfers, /audit/ledger currency.read (.own graded) currency.write
/economy/* economy.read
/workflows/settle workflow.settle
/workflows/map-checkup workflow.map_checkup
/logs/{stream}, /logs/parse, /audit/changes, /audit/notifications, /entities/{name}/history, /discord/deliveries log.read
/logs/fetch, /sessions/{header}/logs/refresh log.fetch
/discord/send discord.send
/governance/permissions governance.read governance.write
/auth/whoami, /identity/{id} identity.read identity.link (POST /auth/link)
/capabilities capability.read capability.admin (grant/revoke)
/tokens token.manage token.manage
/sync sync.read sync.run
/events events.subscribe
/admin/mode, /admin/shutdown, /name-index/rebuild, /import admin.mode / admin.shutdown / admin.index / admin.migrate

Six routes are public (no bearer, no capability): GET /health, GET /schema, GET /routes, GET /schema/version, POST /auth/margonem, POST /auth/discord.

Note

A capability never bypasses the write gate. Even admin.all is refused when the daemon is in read-only mode or on schema drift — status codes and error ids in API reference.

Election eligibility (GET /elections/eligibility) requires pu.read because eligibility is PU-derived data; a Gracz self-checks via pu.read.own. The formula and window live in PU model.