Data Model
This page maps Ragz's core entities and how they relate — useful background before reading RBAC or Roles & Permissions, and the entities that Security Model's tenant isolation and ACL rules actually operate over.
The tree
Organization
└── Workspace (= a department)
├── WorkspaceMember (user + role: owner | manager | contributor | viewer)
├── Folder (navigation only — no ACL of its own)
├── Document (+ versions, + acl_group_ids)
├── Group / UserGroup (org-scoped; referenced by Document.acl_group_ids)
├── Chat
│ └── Message (tree-structured: edits/regenerates branch)
│ ├── Citation (document + version + section + page, or a web URL)
│ └── ChatAttachment
└── MetadataField (admin-defined per-workspace document metadata)
Organization
├── UsageRecord (per org/user/workspace usage ledger)
├── OrgQuota / UserQuota
└── RoleTemplate (global, superadmin-composed permission bundles)
(org-independent)
└── Secret (envelope-encrypted provider keys — see Security Model)Organizations and workspaces
An Organization is the top-level tenant boundary — every org-owned row in
Postgres, and every Qdrant point, carries an org_id, and TenantContext
(see Security Model) is what makes that boundary
actually hold at query time.
A Workspace is a department: its own embedding model, retrieval tuning
(top_k, min_score, rerank on/off), chunking strategy, system-prompt
override, and feature toggles (web search, generative UI, strict-mode
validation). Documents, chats, and folders all belong to exactly one
workspace.
Membership and roles
| Table | Purpose |
|---|---|
WorkspaceMember | Ties a user to a workspace with a role: owner, manager, contributor, or viewer |
Group / UserGroup | Org-scoped groups, independent of workspace membership — the unit that Document.acl_group_ids restricts against |
RoleTemplate / RoleTemplateVersion | Superadmin-composed, org-independent named permission bundles that org admins assign to role="user" accounts; versioned, with a draft → active → archived lifecycle |
owner and manager are the two workspace roles that act as
department admin for that workspace — document ACL management, workspace
settings, and member management. contributor can upload and chat;
viewer can chat and read but not upload. This role only ever governs
workspace-scoped actions — org-level admin/superadmin standing is separate
(see Roles & Permissions) and two permissions,
documents.acl.bypass and audit.read/audit.export, are carved out from
automatic admin grants entirely — they require an explicit role-template
grant even for an org admin.
Documents
A Document belongs to a workspace and optionally a Folder (navigation
only — folders carry no ACL of their own; each document keeps its own
independent acl_group_ids regardless of which folder it sits in).
- Versioning:
lineage_idgroups every version of "the same document";versionincrements;supersedes_document_idpoints at the version it replaces;is_current+approvedtogether decide whether retrieval can see it — per the product requirement that the latest approved version wins and superseded versions are ignored. - ACL:
acl_group_idsisNonefor an unrestricted document, or a list ofGroupIDs — only members of those groups (plus admins) can retrieve from or open it. This is the field the Security Model's vector-query ACL enforcement reads, and the field that's blanked tonullfor plain users in API responses. - Status:
queued → processing → indexed(orfailed), tracked alongside anIngestJobrow per stage (parse → chunk → embed → upsert). - Metadata: admin-defined
MetadataFields per workspace (name, version, revision date, department, doc type, or custom fields) with values stored onDocument.meta.
Chat
A Chat belongs to a workspace and a user. Its Messages form a tree,
not a list: editing a message inserts a new sibling under the same parent
(new sibling_index), and regenerating inserts a new assistant sibling under
the same user message — so every past branch stays intact and selectable,
not overwritten.
Each assistant Message carries:
grounding("documents"vs"general"— set to"general"only on a labeled general-knowledge fallback, never silently)grounding_score/completeness_score(set only when strict-mode validation ran)blocks_json(structured generative-UI blocks, when the workspace has that feature enabled)
Each Citation row is one grounding reference on a message — document ID
(nullable for a web citation), a chunk_ref, page, score, the [n]
marker used in the rendered answer, section, and the document's version
at citation time (deliberately not an FK to Document — deleting a
document must never rewrite chat history). A ChatAttachment is a file a
user attached to a specific turn (routed either inline or through retrieval).
Usage and quotas
Every billable action (chat completion, ingestion, embedding, rerank, web
search) writes one UsageRecord — org_id, user_id, an optional
workspace_id (pure reporting dimension, never part of aggregation), token
counts, and units for per-call features like rerank or web search.
OrgQuota and UserQuota set the monthly ceilings this ledger is checked
against.
Secrets
A Secret row is { name, ciphertext, nonce, key_version, fingerprint } —
provider API keys and similar, envelope-encrypted under the KEK (see
Security Model). There's no plaintext column and no
org scoping on this table itself; access to use a secret is gated by the
route/service layer, not by a tenancy column.
Where RBAC attaches
| Decision | Reads |
|---|---|
| Can this request touch org X's data at all? | TenantContext.org_id |
| Can this user act in workspace Y? | WorkspaceMember.role for that (user, workspace) pair |
| Can this user retrieve/open document Z? | Document.acl_group_ids ∩ TenantContext.group_ids (or admin) |
| Can this user perform a specific privileged action? | TenantContext.permissions, sourced from role + any assigned RoleTemplate |
See RBAC for the full authorization walkthrough and Roles & Permissions for the permission catalog.