Repository navigation
Add the generic capability system and /admin capabilities - #34
Merged
Merged
Conversation
…ubcommand groups admins.yaml is now just Discord IDs. Every admin must also have a members.yaml entry, which holds their membership level, capabilities and note, so admins are members as well as admins. Pixel refuses to start if one is missing. The store refuses to change an admin's level but lets their entry hold capabilities. Commands can now have subgroups: /admin capabilities grant. Access is a floor at every level and the dispatcher checks all of them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e|list Capabilities are named permissions for individual people, declared in code (none yet). A command can require one on top of a minimum tier and the dispatcher enforces both. Nothing implies one: not a tier, not a role, not being an admin. A guest never passes. Admins grant, revoke and list them through the audited access store. Names in the file that no longer exist are ignored, with a warning and a Sentry report. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8 tasks done
admins.yaml and members.yaml now share one shape: each entry has an id like discord:<id>, so other platforms can be told apart later and an admin entry matches its members entry exactly. The old discordId key and bare IDs are refused with a clear message. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Both access files now describe a person with ids, a list of platform-prefixed ids (discord:<id>), so one person can have an id per platform. An admin entry's ids must all belong to one members entry, and only the listed ids are admin. An id may appear in only one entry per file. Replaces the single id key from the previous commit. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #28. Builds on the access store (#25) and
/admin set-level(#26).What changed
src/features/capabilities.ts. It's empty: no real capabilities ship. Admins can only grant names listed there, and a command that requires an unknown name stops startup.access: { minTier: "member", capability: "front-door" }. The dispatcher is the only place it's checked, both must hold, and the refusal is the same generic message as any other (the log recordsreason: capability).Principal.capabilitiesis empty for guests. So demoting someone takes effect straight away./helphides commands the caller can't run. A capability command is registered with at least friend tier (guest tier is rejected at startup)./admin capabilities grant | revoke | list(admin, private, audited through the store). Capability is picked from the registry. Grant is refused for guests and bots, and everything says so when nothing would change. With nothing registered they reply "No capabilities are registered yet."/admin capabilities grantneeds a subgroup, so the command framework now supports command → subgroup → subcommand. Access is checked at every level, the registry rejects a looser child, and/helplists full names.admins.yamlis a list of people, and each admin must also have amembers.yamlentry holding the same ids (membership level, capabilities, note). Pixel refuses to start without one. The store still refuses to change an admin's level, but an admin's entry can hold capabilities, so an admin can grant themselves one (audited). The "also listed as admin" warning is gone./admin whoisshows "Also listed as" for admins.ids: ["discord:<id>"], a list, so one person can have an id per platform later (Secure cross-platform account linking #16). An id may appear in only one entry per file, and an admin entry's ids must all belong to one members entry. Only the ids listed inadmins.yamlare admin; another account in the same members entry keeps the person's level but isn't an admin. Onlydiscord:is accepted for now, and inside Pixel lookups still use the bare Discord user ID. Writing a new members entry keeps the format (ids: ["discord:…"], quoted, on one line)./admin reload.AGENTS.md,docs/identity-and-access.md(including the threat model: a compromised Discord can't grant a capability) anddocs/architecture.mdare updated.Breaking change: the file formats
Both files change, so local and deployed copies need migrating before this runs against them. Pixel refuses the old formats and says which entry is wrong, without echoing the id.
members.yaml:discordId: "<id>"becomesids: ["discord:<id>"].admins.yaml: from- name: …/discordId: "<id>"objects to- ids: ["discord:<id>"]. The name moves to thenoteof the admin's members entry.tier: memberis fine).Needs a human reviewer
Touches
core/access,core/capabilities,core/identity,core/dispatcher,core/registryand the access store (AGENTS.md rule 12): the two-part check, the guest guard, the admin-entry rule, and the startup validation.Tests
Capability registry;
checkAccess(tier and capability, admin without a grant refused, guest refused even with one); identity (guests get none); command registry (unknown capability and guest-tier capability stop startup, including in subcommands and subgroups); dispatcher (member with, member without, friend below the tier, guest, admin without, admin with, subcommands, instant effect,/helphiding, generic reply plus logged reason); nested groups (registry, dispatcher, Discord JSON and parsing, the handler);/admin capabilitiesthrough the real dispatcher and store (persistence, audit, guards, empty registry, stale names); admins and members asidslists (prefix required, several ids per person, duplicates and cross-person admin entries refused, only listed ids admin); and end-to-end throughbuildCore.just checkpasses: lint, types, 728 tests, coverage thresholds.🤖 Generated with Claude Code