Skip to content

Permissions

Overview

Access control in METIS has five layers:

  1. Django built-ins — is_superuser and is_staff grant global edit access. Some module-specific features, such as Coherence, still require their own group unless noted below.
  2. Trusted broad editors — users in the trusted_editors group get broad non-admin editing access.
  3. 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.
  4. Config-flag roles — semantic roles declared as flags on a JourneyStep's configuration (see Config flags below).
  5. 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.