The data model

On this page

dembrane’s system of record is Directus on PostgreSQL - 49 collections. The schema is versioned in echo/directus/sync/snapshot/ (collections, fields, relations as JSON), managed with directus-sync. This page maps the collections you’ll work with day to day and how they hang together. For how data is created and processed see the processing pipeline; for how access to it is decided see roles & policies in code.

Note

The snapshot under echo/directus/sync/snapshot/collections/ is the source of truth for the schema. When you add or change a collection, update the snapshot and follow database migrations - pushing schema to prod has sharp edges (the is_indexed pitfall, drop-index ordering).

The spine: org → workspace → project → conversation

The core hierarchy, top to bottom:

org
 └─ workspace                 (an org has many workspaces)
     └─ project               (a workspace has many projects)
         └─ conversation      (a project collects many conversations)
             └─ conversation_chunk   (a recording arrives as chunks)

Each level carries its own membership and settings, and access is computed by walking this spine plus the inheritance rules (see below and roles & policies).

Organisation

Workspace

Project

Conversation

Chat

Standard versus agentic chat is covered in chat & the agent service.

Reports

Reports are produced in two phases (fan-out summaries → generate). See the processing pipeline.

Agentic runs

Library & analysis

These power the library & analysis surface (Changemaker+).

Billing, tiers and seats

Seats are computed, not stored as a count: seat_capacity.py derives the effective seat state from membership rows. See roles & policies in code.

People, trainings and verification

Cross-cutting

How access is derived (not stored)

A user’s effective role in a workspace is computed, not just read from a row. inheritance.py folds together: their direct workspace_membership row, org-admin auto-join (gated by the workspace’s visibility), org-member inheritance, and sticky-removal records. The result feeds policies.py, which expands the role into a policy set and checks the requested action with has_policy(...). So when debugging “why can this person see this?”, trace inheritance.derive_workspace_rolepolicies.get_effective_policies, don’t just look at the membership table.


Related

Related

Comments