Thalix VaultDocumentation
Browse documentation
Architecture
publishedimplementedEdit this page

Domain model

The data model distinguishes logical files, immutable content versions, mutable current classification, immutable classification history, and access policy. For definitions of Root Folder, subfolder, file, group, metadata definition, tag, access grant, and audit event, see Glossary.

erDiagram
    ORGANIZATION ||--o{ ORGANIZATION_MEMBERSHIP : has
    USER ||--o{ ORGANIZATION_MEMBERSHIP : joins
    USER ||--o{ GROUP_MEMBERSHIP : belongs_to
    GROUP ||--o{ GROUP_MEMBERSHIP : contains
    USER ||--o{ GROUP : owns
    GROUP ||--o{ GROUP_INVITATION : offers
    USER ||--o{ GROUP_INVITATION : invited
    USER ||--o{ FOLDER : owns
    FOLDER ||--o{ FILE : classifies
    USER ||--o{ FILE : owns
    FILE ||--|{ FILE_VERSION : contains
    FILE ||--o{ FILE_METADATA_VALUE : has
    METADATA_DEFINITION ||--o{ FILE_METADATA_VALUE : validates
    FILE ||--o{ FILE_METADATA_REVISION : records
    USER ||--o{ ACCESS_GRANT : grants
    FILE ||--o{ ACCESS_GRANT : scopes
    FOLDER ||--o{ ACCESS_GRANT : scopes
    USER ||--o{ AUDIT_EVENT : acts_in

Logical modules

RecordPurposeKey invariant
organizationsExplicit ownership boundaryOne seeded organization in MVP
usersIdentity and account statusDeactivated users lose session access
organization_membershipsOrganization roleRole is global_admin, admin, or user
groups / group_membershipsUser-owned team grant subjectsCreator is sole owner; ≥1 active owner always; per-user role owner/manager/member; name unique per owner
group_invitationsGated group membership`pending -> accepted \declined \revoked \expired`; ≤1 pending row per group/email; only invitee resolves it
foldersCanonical Root Folder/subfolder classification, owned by one memberRoots unique per owner; siblings unique per parent (both case-insensitive, all statuses); owner_user_id/parent_folder_id/root_folder_id immutable; depth 0-32
filesStable logical file identityOpaque ID, owner, Root Folder, current version, archive state
file_versionsImmutable content factsVersion number, opaque storage key, size, checksum, uploader, time
file_metadata_revisionsImmutable classification historyBefore/after values, actor, time, optional reason
metadata_definitionsControlled custom-field schemaStable key; supported type and validation rules
file_metadata_valuesValidated current custom valuesEach value conforms to its active definition
access_grantsDirect or group accessSubject, file/Root Folder scope, level, grantor, lifecycle
audit_eventsSecurity and transaction accountabilityAppend-only and secret-free

File identity

A logical file has one stable opaque ID. Uploading new bytes creates a new file_versions row and object; it does not replace the logical file or overwrite a prior object. The current version pointer advances only after the new object and catalog record are verified.

Changing name, Root Folder, description, custom metadata, or archive state modifies the logical file while also adding an immutable metadata revision. It does not create a content version.

Root Folder ownership and hierarchy

Every Root Folder has exactly one owner: the active member who created it (for a root) or that root's owner (for a subfolder, inherited transitively down the tree regardless of who ran the create command). Ownership implies manage on the Root Folder without a stored access_grants row -- the same "ownership implies manage, never a materialized grant" convention files already use. A root's trimmed name is unique case-insensitively per owner (active or deactivated); different owners may reuse the same name freely, since root uniqueness is scoped to ownership, not the whole organization.

A folder additionally carries a nullable parent_folder_id (null for a root), an immutable root_folder_id (equal to its own ID for a root, resolved once and never recomputed as the tree changes), and an integer depth (0 for a root, capped at 32). Parent, child, and root must share one organization and one root tree; a root cannot become another folder's child through the creation surface. A subfolder's trimmed name is unique case-insensitively among its immediate siblings -- active or deactivated -- independently of the organization-wide root-name scope above. Creating a subfolder requires the actor to hold live manage on the immediate parent at the moment of creation; a parent that is missing, cross-tree, or merely unmanageable is indistinguishable to the caller.

Group ownership, roles, and invitations

Every group is created by one active member, who becomes its sole owner; group_memberships carries a per-user, per-group role of owner, manager, or member rather than an undifferentiated flat list. A group must always retain at least one active owner -- no role change, member removal, or deactivation may leave it with zero. A trimmed group name is unique case-insensitively per owner (active or deactivated), independent of the organization-wide Root Folder name scope; a different owner may reuse the same name freely.

group_invitations gates membership: a pending row is created by an owner/manager inviting an existing active member, and only the invited user can move it to accepted or declined; an owner or manager can move it to revoked; the current time passing expires_at reads it as expired. At most one pending invitation may exist per (group, invited email) pair -- a resend refreshes that same row rather than inserting a duplicate. Inviting an email that does not resolve to an existing active member in the caller's organization returns the identical success shape a real invitation would, while inserting no row at all -- membership existence and organization membership are both anti-enumeration surfaces here, the same as file existence is elsewhere. Removing a member deletes only their group_memberships row; any access_grants row created for them directly (never through the group) is untouched and independently revocable.

Controlled metadata

Root Folder is a dedicated entity rather than a generic custom field. New files and metadata edits may reference only active Root Folders. Historical records remain readable when a Root Folder or custom field is deactivated.

Custom field types are limited to text, number, boolean, date, and choice. Field keys remain stable; labels, allowed choices, required state, and active state may evolve under server validation.

Constraint strategy

Database constraints enforce uniqueness, referential integrity, sequential relationships, and valid structural states where possible. Server-side authorization and workflow checks remain authoritative for rules involving identity, ownership, effective grants, storage verification, or cross-record policy.