Skip to content

Data model

Okatana stores domain, framework, queue, and integration state in the selected SQL database. Principal domain identifiers are ULIDs; most pivot tables use auto-incrementing numeric keys plus composite uniqueness.

Domain topology

User
 ├─ OrganizationMember(role) ─ Organization
 │                              ├─ Project
 │                              │   ├─ ProjectMember ─ User
 │                              │   ├─ Board
 │                              │   ├─ Label
 │                              │   ├─ Tag
 │                              │   ├─ Ticket
 │                              │   │   ├─ assignees / labels / tags / mentions
 │                              │   │   ├─ Attachment
 │                              │   │   ├─ TicketRevision
 │                              │   │   └─ TicketComment
 │                              │   │       ├─ mentions / attachments
 │                              │   │       └─ TicketCommentRevision
 │                              │   └─ project Documents
 │                              ├─ organization Documents
 │                              ├─ Invitation
 │                              ├─ AuditLog
 │                              ├─ WebhookEndpoint ─ WebhookDelivery
 │                              ├─ ApiCredential
 │                              └─ DataTransfer
 └─ notifications / email verification security state

Framework and identity tables

Table Purpose/key constraints
users ULID identity; unique email; password, avatar, preferences, email/TOTP state; encrypted TOTP cast
password_reset_tokens standard email-keyed reset storage; no reset routes currently exposed
email_verification_codes one row per user; hashed code, expiry, send time, attempt count
sessions database session payloads keyed by string session ID
notifications Laravel UUID notifications, read time, indexed per user/read/created
cache, cache_locks default database cache and locks
jobs, job_batches, failed_jobs default queue lifecycle

Organization and project tables

Table Important fields/invariants
organizations ULID; globally unique slug; JSON settings; soft delete
organization_members unique organization/user; role string; role enforcement in application
projects ULID; organization; key unique within organization; archive and soft delete
project_members unique project/user explicit access
boards ULID; project-local unique slug; position, color, WIP, done/hidden; soft delete
labels ULID; project-local unique name; color
tags ULID; project-local unique normalized name; display name

Foreign keys generally cascade pivots/children when a parent is physically deleted. Application soft deletion avoids those cascades during normal user operations.

Ticket collaboration tables

Table Purpose
tickets project/board, local number, rich description, priority/position/dates, creator/updater, archive/soft delete
ticket_assignees unique ticket/user
ticket_labels unique ticket/label
ticket_tags unique ticket/tag with reverse lookup index
ticket_mentions unique ticket/user direct mention
ticket_comments ULID, kind comment/movement, rich body, user/snapshot/edit time, soft delete
ticket_comment_mentions unique comment/user
attachments project plus ticket or comment relationship, uploader metadata, private disk/path/size
ticket_revisions immutable ticket version/snapshot/editor, unique ticket/version
ticket_comment_revisions immutable comment version/snapshot/editor, unique comment/version

tickets has unique (project_id, number) and indexes for board/position and priority/due queries. Board deletion is restricted by the ticket foreign key, so controllers move tickets before soft-deleting a populated board.

Attachments encode either ticket_id or ticket_comment_id by application convention. The database does not declare a check constraint requiring exactly one, so service/controller construction must preserve the invariant.

Document tables

Table Purpose
documents organization, optional project, author, title/caption/body, status/published/archive/soft-delete timestamps
document_editors unique document/user plus adding user
document_favorites unique document/user
document_comments ULID rich comments with snapshot/edit/soft-delete
document_tags organization-scoped display and normalized names
document_tag_assignments unique document/tag
document_mentions unique document/user
document_comment_mentions unique document-comment/user

The database permits a project ID from another organization at the foreign-key level; DocumentController/external API constrain the project through the selected organization. Preserve that validation in all new write paths and import code.

Audit and integration tables

Table Purpose
audit_logs append-only organization/project event, actor/subject snapshots, JSON changes/metadata, request context
webhook_endpoints organization/optional project, encrypted secret, selected events, active/rotation state
webhook_deliveries stored payload, status/attempts/response/error/times per endpoint
api_credentials organization, public ID, SHA-256 secret hash, JSON scopes, creator/use/expiry/revoke times
invitations organization/optional project, email/role, one-way token hash, lifecycle times
data_transfers import/export direction/scope/status/summary/errors/timing

Audit identifiers for organization/project are intentionally plain indexed ULIDs rather than foreign keys. This preserves history across domain deletion. Update/delete triggers provide database-level immutability.

Identifier and secret classes

Value Storage/behavior
Domain primary keys generated ULIDs
Ticket human ID project key + project-local integer
Invitation token plaintext delivered once; SHA-256 hash stored
API token public ID stored + secret SHA-256 stored; full token once
Webhook secret encrypted at rest because signing requires recovery
TOTP secret encrypted cast
Email verification code Laravel password hash; plaintext encrypted inside queued notification payload
Password Laravel hashed cast

Delete semantics

Soft-deleted: organizations, projects, boards, tickets, ticket comments, documents, document comments.

Immutable: audit logs, ticket revisions, ticket comment revisions (model-level for revisions; model plus database for audit).

Hard-deleted during normal actions: many pivots/memberships, attachments after file removal, verification code after success. Integration revoke normally timestamps/deactivates rather than removing records.

Indexing and query patterns

Indexes support:

  • project workflow ordering;
  • project priority/due filtering;
  • ticket comment chronology;
  • organization/project audit chronology and subject lookup;
  • endpoint active/delivery status history;
  • organization credential revocation lookup;
  • notification unread chronology;
  • mention recipient chronology;
  • normalized tag reuse and usage lookup;
  • document organization/status/publish, project/status, author/update.

When adding filters, examine query plans across SQLite, MySQL, and PostgreSQL. Case-insensitive search currently uses LOWER(...) LIKE in several server directories, which may need functional/search indexing at larger scale.

Migration guidance

  • Write explicit forward/backward schema behavior for all supported drivers.
  • Do not mutate existing audit rows during migration.
  • Consider active and soft-deleted rows for new unique constraints.
  • Backfill before making a column required.
  • Preserve ULID column types across foreign keys.
  • Add composite unique constraints to new pivots.
  • Test SQLite in the standard suite and MySQL/PostgreSQL in CI/acceptance when changing driver-specific SQL.
  • Update this model reference, exports/imports, personal export, and OpenAPI when persistence changes affect contracts.