Permissions & Job Gates
Two separate systems, and they answer different questions.
| Question | Checked | |
|---|---|---|
| Staff | May this player write? | Server, on open and on every verb |
| Access | May this player read this leaf? | Server before a body is sent, client before a signpost is drawn |
Staff: three independent checks
Any one passing is enough, and all three are consulted.
| Check | What it reads | Works on |
|---|---|---|
| ACE | rm_guidebook.press, or anything in staff.aces | Every framework, and none |
| Framework group | admin, superadmin, owner by default | Frameworks that expose groups |
| Framework permission call | The core's own answer | Only frameworks that have one |
Why three
The frameworks disagree about what an administrator is. Relying on only one of them breaks on the other two.
The ACE route works everywhere:
add_ace group.admin rm_guidebook.press allow
add_principal identifier.license:XXXXXXXXXXXXXXXX group.adminBoth lines are needed
The commonest cause of a refused press is the missing add_principal, not the ace.
To use different group names:
-- local/overrides.lua
RmGuidebook.Local = { staff = { groups = { 'admin', 'owner', 'developer' } } }Remember that lists replace wholesale — that means those three and no others.
Which checks actually do anything on your framework is in Frameworks.
The console
Source 0 always clears the staff check, so /almanac_resync and /almanac_migrate can be driven from rcon or a scheduled task.
RmGuidebook.Local = { staff = { allowConsole = false } }The eight verbs
Each verb can be given an ACE of its own. A player holding it may use that verb whether or not they are staff — so this widens access rather than narrowing it.
RmGuidebook.Local = {
staff = { verbs = { entry_write = 'rm_guidebook.scribe' } },
}add_ace group.moderator rm_guidebook.scribe allowThat is how you let a moderator write entries without letting them strike chapters.
| Verb | Lets them |
|---|---|
chapter_write | Create and edit chapters |
chapter_strike | Delete a chapter — and its entries with it |
entry_write | Create and edit entries |
entry_strike | Delete an entry |
signpost_write | Place and edit signposts |
signpost_strike | Remove a signpost |
hand | /almanac_hand — open the almanac on someone else's screen |
ferry | Carry a player to a signpost |
Striking a chapter strikes its entries
There is no separate confirmation for the entries that go with it. chapter_strike is the verb to be careful about delegating.
Access: job gates
Any chapter, entry or signpost can be limited to certain jobs at a minimum grade.
access = { on = true, jobs = { { job = 'police', grade = 1 } } }Grade is a minimum — grade 1 means grade 1 and above.
The rule runs in two places, and that is the point:
- On the server, before a body is handed over. A restricted entry is not merely hidden; it cannot be opened.
- On the client, before a signpost is drawn. A restricted signpost is not drawn at all for a player the rule refuses — no prompt, no title, no blip.
Hidden is not restricted
Two different things, and choosing the wrong one is a common mistake.
| Hides from the index | Blocks reading | Use for | |
|---|---|---|---|
| Shown = off | Yes | No | A page you reach from a signpost or a link |
| Access | Yes | Yes | Anything that actually matters |
Anyone with a link or a signpost can still open a shown = off page. Use Access for anything that actually matters.
Search respects both
Three deliberate rules:
- Hidden entries are not searchable. They are reachable by slug and by signpost, but not discoverable. That is the difference between unlisted and secret.
- Restricted entries are not searchable by anyone who could not read them.
- The query is matched against the text, not the markup. Searching for
<strong>finds nothing, which is the point.
Testing a gate
access.staffSeesAll defaults to true, so staff see everything — which makes a gate look broken when it is working.
RmGuidebook.Local = { access = { staffSeesAll = false } }That lets you test a job-gated chapter without leaving the admin group. The alternative is a second account.
"Everyone can see a page they should not"
Check three things, in this order:
access.enabledistruein your settings.- The chapter and the entry both have the access block set. An open chapter containing a restricted entry still hides the entry — but the chapter is visible.
- You are not testing as staff. See above.
Where the job list comes from
The list offered in the access picker is merged from what the framework reported and your own jobs table, and yours wins.
On VORP and RSG the framework reports a real list with grade labels. On RedEM:RP and standalone there is no registry, so your jobs table is where the labels come from entirely.
RmGuidebook.Local = {
jobs = {
vallaw = { label = 'Valentine law', grades = { [0] = 'Recruit', [1] = 'Deputy', [2] = 'Sheriff' } },
},
}/almanac_resync re-reads the list, so a job added since start-up appears without a restart.
Reader and route gates
Two coarser switches, both nil by default, meaning everyone:
readers = { ace = 'rm_guidebook.reader' }, -- who may open the almanac at all
route = { ace = 'rm_guidebook.navigator' }, -- who may ask for directionsStaff always pass either way.
What the server actually enforces
Worth being precise, because it is better than most:
- The press is re-checked on every verb, not just on open. A client that flips its own staff flag achieves nothing.
- A restricted body is never sent. The client cannot leak what it was not given.
- A player naming an entry they may not read is told the leaf is missing, not that it exists.
/almanac_handapplies the target's permissions, not yours — handing a page out is not a way around the gate.
Turn on the press trace and every refusal is logged with the name of whoever was refused:
RmGuidebook.Local = { debug = { enabled = true, log = { press = true } } }