Permissions
tCAD's nineteen ACE keys, what each one gates, which ones reach civilian and judicial records, and a starter server.cfg block per role.
Every capability tCAD checks is an ACE object, granted in server.cfg. There is nowhere else to grant one.
Authority is checked server-side, every time, on the acting player's source. The NUI is sent no permission set at all, so hiding a button is decoration and reaching a screen is not a grant.
How a method is gated
Two things are true of every operation:
- It requires
Config.permissions.use—tcad.use— on top of whatever else it asks for. - A method cannot be registered without naming a permission or explicitly declaring itself open. The registration call raises otherwise. So the authority check is structural rather than remembered: there is no path by which somebody adds an operation and forgets the gate.
ACE inheritance applies, and every key shares the tcad. prefix deliberately — so add_ace group.admin "tcad" allow grants the whole tree.
The nineteen keys
Opening the CAD
| Key | ACE object | Gates |
|---|---|---|
use | tcad.use | Required for every method. Also Config.acePermission, which gates opening the panel |
Calls
| Key | ACE object | Gates |
|---|---|---|
createCall | tcad.calls.create | File a new call |
editCall | tcad.calls.edit | Change a call's details |
holdCall | tcad.calls.hold | Put a call on hold |
cancelCall | tcad.calls.cancel | Cancel a call |
closeCall | tcad.calls.close | Close a call |
Adding a comment to a call is open beyond use. It is the one call action with no key of its own: a unit noting what they saw is not an administrative act, and gating it produces a timeline with gaps in it.
Dispatch
| Key | ACE object | Gates |
|---|---|---|
dispatchSelf | tcad.dispatch.self | Attach yourself to a call |
dispatchUnit | tcad.dispatch.unit | Attach or detach somebody else |
Two keys rather than one because attaching yourself is a much smaller capability than assigning another unit. Detaching yourself is open beyond use — a unit must always be able to clear.
Setting your own status needs no key at all. Its authority is by state: the actor must be on duty in that department.
Records — ⛔ these gate personal data
Every civilian, vehicle and warrant operation comes in two forms: one scoped to the caller's own records, which needs nothing beyond use, and one that reaches every record, which needs the key below. A player managing their own character is not an administrative act; a player reading somebody else's medical notes is.
| Key | ACE object | Gates |
|---|---|---|
viewCivilian | tcad.civilian.view | Read any civilian's record, their vehicles and their warrants |
editCivilian | tcad.civilian.edit | Edit or delete any civilian's record, and set the LEO vehicle flags (stolen, impounded, BOLO, repo) |
runNcic | tcad.ncic.run | Run an NCIC person or plate query |
viewCivilian is wider than it looks: it also gates the subject's SSN on a warrant. A warrant is readable by anyone with tcad.use — it is a public court record — but the identifying number on it needs this key. A judge without it can approve and deny warrants and will not see which person each one names beyond their name, which for two characters with the same name is a judge ruling on the wrong person. That is a worse outcome than the disclosure the key controls, so if you grant tcad.warrants.approve or tcad.warrants.deny to a group, grant tcad.civilian.view to the same group.
A dispatcher deliberately does not need it: dispatch works calls and units, and a civilian's identifying number is not part of that. A dispatcher opening a warrant sees it in full apart from that field.
editCivilian is separate from viewCivilian because reading a record and rewriting somebody else's are different acts — and because an owner must not be able to clear their own stolen-vehicle flag, which is exactly what collapsing the two would allow.
runNcic has its own key because every query is logged against the officer who ran it. A CAD where everyone can run a name is a CAD where nobody can be held to it.
Warrants
| Key | ACE object | Gates |
|---|---|---|
createWarrant | tcad.warrants.create | Request a warrant |
approveWarrant | tcad.warrants.approve | Approve a requested warrant |
denyWarrant | tcad.warrants.deny | Deny a requested warrant |
serveWarrant | tcad.warrants.serve | Mark a warrant served |
recallWarrant | tcad.warrants.recall | Withdraw a warrant, and the extradition hold |
Five keys rather than one, because the separation is the point of a judicial view: an officer requests and serves, a judge approves or denies. Granting approve to the requesting officer removes the review step entirely.
⛔ Administration
| Key | ACE object | Blast radius |
|---|---|---|
manageAgency | tcad.admin.agency | Agencies and departments — structure. Retiring a department takes its members off duty in it |
manageCatalogue | tcad.admin.catalogue | The lookup catalogues: call types, priorities, statuses, dispositions, status codes, warrant types, licence types, weapon models. Wrong values are annoying and recoverable |
viewAudit | tcad.admin.audit | Read who changed what. Separate because it is a read, and a supervisor can reasonably audit without being able to change anything |
Three keys rather than one, split by blast radius rather than by screen.
There is deliberately no key for deleting a catalogue entry as distinct from editing one. Delete is refused by the storage layer for every catalogue whose rows history points at — that is a correctness rule, not a permission — so a separate key would imply a capability that does not exist.
A starter block
Adapt the group names to your framework. These are illustrative roles, not a recommendation about who on your server should hold what.
# Everybody who uses the CAD at all
add_ace group.dispatcher "tcad.use" allow
add_ace group.officer "tcad.use" allow
add_ace group.judge "tcad.use" allow
# Dispatch: calls and units. Deliberately NOT tcad.civilian.view.
add_ace group.dispatcher "tcad.dispatch.unit" allow
add_ace group.dispatcher "tcad.calls.create" allow
add_ace group.dispatcher "tcad.calls.edit" allow
add_ace group.dispatcher "tcad.calls.hold" allow
add_ace group.dispatcher "tcad.calls.cancel" allow
add_ace group.dispatcher "tcad.calls.close" allow
# Patrol
add_ace group.officer "tcad.dispatch.self" allow
add_ace group.officer "tcad.calls.create" allow
add_ace group.officer "tcad.civilian.view" allow
add_ace group.officer "tcad.ncic.run" allow
add_ace group.officer "tcad.warrants.create" allow
add_ace group.officer "tcad.warrants.serve" allow
# Judiciary. tcad.civilian.view is granted WITH approve/deny, for the
# reason under Records above — without it a judge cannot tell two
# same-named subjects apart.
add_ace group.judge "tcad.civilian.view" allow
add_ace group.judge "tcad.warrants.approve" allow
add_ace group.judge "tcad.warrants.deny" allow
add_ace group.judge "tcad.warrants.recall" allow
# Administration. The whole subtree, and an audit-only grant.
add_ace group.admin "tcad" allow
add_ace group.supervisor "tcad.use" allow
add_ace group.supervisor "tcad.admin.audit" allowNote what is not in that block: tcad.civilian.edit is granted to nobody. Rewriting another player's record is worth deciding on deliberately rather than inheriting from a starter config.
Refusing an action a grant allows
A grant is the floor, not the ceiling. Config.denyMethod in config/sv_functions.lua is called after the gate has already said yes, and the only thing it can do is turn that into a no — on duty state, time of day, your own rules.
No hook can grant a permission. Grants are ACE objects in server.cfg, where an auditor looks. See Customisation.
Checking what a key resolves to
tcad_config
in the server console prints the whole permission map with its resolved ACE object names, which is the fastest way to catch a renamed value that no longer matches your add_ace lines.
Was this page helpful?
TIMMYG Studios