Permissions¶
Overview¶
Access control in METIS has five layers:
- Django built-ins —
is_superuserandis_staffgrant global edit access. Some module-specific features, such as Coherence, still require their own group unless noted below. - Trusted broad editors — users in the
trusted_editorsgroup get broad non-admin editing access. - App access — each app (Coherence, The Gathering, Outreach, Audax, Invitations) opens
only for users in that app's group. Nobody is included automatically: not superusers,
not staff, not
trusted_editors. - Config-flag roles — semantic roles declared as flags on a
JourneyStep's configuration (see Config flags below). - Object ownership — a person can always edit their own record.
Throughout this document, global edit access means the user is a superuser, is staff,
or belongs to trusted_editors.
The rule¶
You can read anything that is not private. You can change a holon's things if you can edit that holon.
Everything below is that rule applied to a particular screen. If a page shows you something, the question of whether you may change it has one answer: can you edit the holon it belongs to? A membership belongs to its holon, a relationship to either of its two ends, a journey to the single holon that carries it. There is no separate "but only if the holon is private" branch anywhere — that was a rule of its own for a while, and it meant a scoped team could see a thing on their own screen and be refused when they moved it.
Two things are deliberately not covered by the rule:
- Notes. A holon's notes and activity are its internal record, so reading them asks for edit access, not view access — whether or not the holon's class is private.
- CSV import. Bulk import has its own group,
csv_importers, because it writes far more than the screen it is launched from — and being able to edit one holon is not a reason to be able to load rows into many.
Groups¶
Seven groups control access. An administrator assigns users to them in the admin.
| Group | Grants |
|---|---|
trusted_editors |
Broad non-admin edit access across the application (global edit access). |
csv_importers |
Permission to run CSV imports. |
coherence_users |
Access to the Coherence app (conversations, transcripts, journeys). |
gathering_users |
Access to The Gathering app. |
outreach_users |
Access to the Outreach app. |
audax_users |
Access to the Audax app. |
invitations_users |
Access to the Invitations app. |
The five app groups open a door and grant no editing: what a user may change inside an app is decided by the rules below.
Config flags¶
Some permissions are configured by setting flags in the config of a JourneyStep,
rather than by group membership.
The capabilities¶
A membership resting at a step is granted whatever capabilities that step carries. There are four, and they are independent — you can give someone any one without the others:
| Capability | What it grants |
|---|---|
| Team member | Act on the holon: edit its fields, info fields, logo and media; create child holons; read its notes; add, move and remove the people on it; appear on its team roster. A team member can always see the holon, even when its class is private |
| Manage people | Edit the Person records of people who belong to the holon, or to any holon beneath it |
| View private | See holons whose class marks its composition private, without being able to act on them |
| Manage logins | Create and reset the login of a person who belongs to the holon, or to any holon beneath it. Needs Manage people on the same step to be reachable — see below |
Team member used to be two separate ticks, Edit content and Manage team. In practice they were always given together, and code that asked for one where it meant the other caused people to be offered choices the save then refused, so they are now one.
All four reach holons underneath. A capability granted on a gathering applies to its camps, and to the experiences under those. It never works the other way: standing on a camp gives you nothing on the gathering above it.
Three of these four replaced a single flag, team-active, which granted everything at
once — a camp leader who should manage their camp's schedule necessarily also got edit
rights on every person in the camp. Existing team steps were migrated to carry those, and
only those: manage logins was never part of team-active, and no step carries it until
someone ticks it. So nobody's access changed, except in one approved way: manage
people now reaches people who belong only to holons beneath the one it was granted
on, where before it stopped at the exact holon.
Setting it: capabilities are granted in the Django admin and nowhere else. Open the journey step (the step's Edit in admin button on the journey page, or journey page → pencil icon → Journey steps) and tick the capabilities. Only administrators can use Edit in admin; everyone else sees it greyed out, with a note that an administrator has to make the change. The journey editor shows every capability on every step, including the ones that are off (a conversation journey's step editor shows only the ones set). Each checkbox is labelled with what it grants and whether it reaches holons underneath. Saving leaves everything else on the step — its colours in particular — untouched.
Who holds what: a holon's admin page links to a read-only list of every person with a capability on it, the membership granting it, and whether it came from the holon itself or from an ancestor.
private_memberships (on a holon class)¶
Set in a holon class's config, not a journey step's. It marks that class's
composition private: who belongs to a holon of this class, and the workflow they are in,
are readable only by someone who holds view private on that holon (or a global
editor). Everyone else gets a 403 on the holon's page and on its Team panel, and the
holon does not appear in search results or listings.
{"private_memberships": true}
Turning it off takes the JSON boolean false, and only that. Unlike a
capability — where anything but boolean true reads as unset — this flag
fails closed: null, 0, "" and the string "true" all leave the class
private. A privacy flag that misreads as "public" is the expensive direction to
be wrong in. Remove the key entirely to go back to inheriting.
Two things follow that are easy to get wrong:
- It is inherited. A subclass of a private class is private too, whether or not it repeats the flag. Both the per-object check and the queryset filter resolve it through the class tree, so they cannot disagree about a subclass.
- It hides the holon, and grants nothing. Named for what it protects — who belongs to the holon — but enforcement covers the record too: the detail page and every partial under it return 403, and the name is withheld from search, mentions, listings and the CSV/email exports. What it never does is grant rights: it is a visibility flag, not a role.
The Outreach network and list classes use it: a network holds the operator's entire LinkedIn graph, and a list holds who they are approaching.
Not the same as internal. A holon class can also be marked "internal": true, which
keeps its holons out of search, the holon autocompletes, the event-parent list, CSV
matching and the extension's holon picker — for everyone, administrators included. That is
about noise, not privacy: an internal holon's page still opens for anyone allowed to see
it, and internal grants and withholds nothing. Tenant boundary holons are internal and
private at once; each flag does its own job. The Django admin is exempt from internal,
so an operator can always reach every holon by name there.
Display flags are not permission flags¶
Not every config flag grants access. public-visible (below) controls only what is
shown on the public site and grants no edit rights. Keep the two ideas apart: don't
reach for a capability to make something public. Asking a display flag as if it were a
permission is refused outright, so the two cannot quietly merge again.
Public visibility (public-visible)¶
This is not an in-app permission. It decides what the public site shows and grants no access to anything inside the app — which is why it lives in its own document rather than here.
The rule in one sentence: something is on the public site when the relationship
to its parent rests on a step carrying public-visible, and its parent is on the
public site too.
⚠️ The setting belongs to the step or journey, not to one holon, so marking a step of a shared journey publishes everything resting on that step.
See PUBLICATION.md for the chain, how to publish and unpublish, how people and organisations are published through what they are attached to, and what a visitor sees for something unpublished.
What each role can do¶
The table below summarises who can perform each action. "Global editor" = superuser, staff,
or trusted_editors. "Scoped team member" = a person whose membership grants the relevant
capability on the holon or on any of its ancestors.
Holons¶
One rule covers the work. Team member governs a holon's fields and relationships and its memberships — adding someone, moving them, removing them — and lets the holder see the holon even when its class is private. It passes for global editors and for team members of the holon or any of its ancestors.
View private is the read-only counterpart: it lets someone see a private holon without acting on it.
| Action | Who can do it |
|---|---|
| View a holon's notes / activity | Anyone who can edit that holon |
| View a holon's change history | Anyone who can edit that holon |
| Open a holon's configuration page (the gear tab on its page) | Any Team member of the holon or an ancestor. Each section inside keeps its own rule: Telegram and change history need edit rights, Journeys needs global edit rights, Import CSV needs the CSV importers group. Where a holon's class places Import CSV on the configuration page (every class does by default), an importer sees the button only on holons whose configuration they can open |
| See a holon's Access section (who holds which capability) | Anyone who can open that holon's configuration page |
| Edit a holon's fields, logo, and configuration | Anyone who can edit the holon's content |
| Manage a holon's team (add/remove members, edit or move a membership's journey step) | Any Team member of the holon or an ancestor |
| Add / edit / delete holon relationships, and move a relationship's journey step | Anyone who can edit the content of either endpoint of the relationship |
| Create a child holon under a holon (of a class its config allows) | Anyone who can edit the parent holon's content |
| Create an unrestricted top-level holon | Global editors only |
| Create a Coherence event | Global editors, under any holon they can pick. Anyone with Team member on a tenant itself (the tenant team's Admin step), inside that tenant only: as a house event directly in the tenant, or under any client or event in it. Nobody else |
| Delete a holon | Superusers and staff only (trusted editors cannot delete) |
| Assign journeys to a holon | Global editors only |
Because scoped rights walk up the ancestor chain, a team member of a parent holon can edit all of its child holons — a Gathering team member can manage its camps' teams and relationships, and a camp team member can do the same for the camp's experiences.
Managing a relationship from one endpoint never grants edit access to the other endpoint: relating your camp to an organisation doesn't let you edit that organisation.
People¶
| Action | Who can do it |
|---|---|
| Add a person to the CRM | Global editors, or anyone with manage people on any holon |
| Add an existing person to a holon (from the person page, the holon's Team panel, person-create, the extension, Outreach bulk-add, or the API) | Any Team member of that holon or an ancestor |
| Edit a person | Global editors; the person themselves; anyone with manage people on a holon that person belongs to, or on a holon above it |
| View a person's change history | Anyone who can edit that person |
| Delete a person | Superusers and staff only |
Adding a person asks only that you run something — a camp lead enrolling a participant, a conversation host adding a guest. It used to ask for standing on a domain-type holon, which refused camp leads: a camp is a different class, and the check was an exact class match. Handing that person a login is a separate, narrower question — see below.
Journeys and users¶
| Action | Who can do it |
|---|---|
| Manage journeys (journey editor, Journeys settings, creating and duplicating a journey in the shared catalog) | Global editors only |
| Edit a journey and its steps | Global editors, or any Team member of the single holon that carries it — provided no class offers it |
| Create or reset a person's login | Global editors, or anyone with manage logins on a holon that person belongs to, or on a holon above it — except when the person is themselves a global editor |
| Run CSV imports | Superusers and members of csv_importers |
A journey belongs to one holon, or to nobody. A scoped editor may rewrite a journey only when exactly one holon carries it and no class offers it to others — that journey is that holon's own working sequence. The moment a second holon runs it, or a class offers it to every holon of that class, it is shared: shared journeys belong to global editors, because a step renamed in one place would change what everyone else is looking at.
A brand-new journey has no holon at all, which is why creating one — and duplicating one into the shared catalog — stays with global editors. A Coherence event team duplicates through their own route instead, which attaches the copy to their event.
Manage logins, in plain terms. Ticking Manage logins on a journey step lets everyone sitting at that step create a login for, and reset the password of, anyone who belongs to their holon — and to every holon underneath it. A gathering-level grant covers its camps; a camp-level grant covers only that camp, never the gathering above it.
It needs Manage people alongside it to be usable. The Create login and Reset password controls live on a person's configuration page, and that page opens for whoever may edit that person — which is what Manage people grants. So a step with Manage logins ticked and Manage people not ticked gives its holders no way to reach the controls: they open the person and find no login section. Tick both on the same step.
It never applies to a full administrator. If the person whose password you are trying to reset is a superuser, staff, or a trusted editor, you are told "Only a full administrator can manage the login of another administrator", however much standing you have. That is deliberate: without it, a camp-level right could be turned into administrator access by resetting an administrator's password and signing in as them.
It also never applies to someone in no holon at all — there is no holon for the grant to reach, so only a global editor can give that person a login.
Nobody holds it until it is ticked. No step carries Manage logins out of the box. Until an administrator ticks it on a step, creating and resetting logins stays exactly where it was: global editors only.
Coherence¶
| Action | Who can do it |
|---|---|
| Access Coherence (conversations, transcripts, journeys) | Members of coherence_users only |
Coherence access is a deliberate opt-in: it does not follow from being a superuser,
is_staff or trusted_editors. The Coherence navigation item and all Coherence-related sections
(profile, person detail, holon detail, note links) appear only for users with Coherence
access.
Coherence access is a door, not a permission. Being in coherence_users lets you
reach the Coherence screens; what you may change once you are there is the same question
as everywhere else — can you edit the event holon the conversation belongs to. Group
membership on its own grants no editing.
What different users see¶
Global editors land on the Activity / dashboard page and see the operational navigation (Activity, Calendar, Kanban), the Journeys settings card, the Chrome extension download card, and the option to clear focus (view "All").
Scoped users (team members without global edit access) are taken to the detail page of the first holon they are a Team member of. Their focus is scoped to their team holons and those holons' ancestors. If they hold it nowhere, they remain on the landing page.