Documentation Index
Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
Use this file to discover all available pages before exploring further.
Version: 1.0.0
Last Updated: 2026-01-15
Constitution Reference: Section 5 (Database Naming & Environment Guardrails)
Overview
This document defines standard column definitions, naming conventions, and data patterns used across the Encore Health OS platform. All database schema designs MUST follow these standards to ensure consistency, maintainability, and proper multi-tenant isolation.
Related Documents:
constitution.md §5 - Database naming and guardrails
AGENTS.md - Database rules quick reference
specs/_templates/SPEC_TEMPLATE.md - Data Model section
specs/_templates/db-preflight.md - Database pre-flight checklist
Standard Column Definitions
Identity Columns
These columns are REQUIRED on all business entity tables:
| Column | Type | Required | Default | Description | Example |
|---|
id | UUID | Yes | gen_random_uuid() | Primary key, auto-generated | 550e8400-e29b-41d4-a716-446655440000 |
organization_id | UUID | Yes | - | Tenant identifier, FK to pf_organizations | Required for multi-tenant isolation |
site_id | UUID | Conditional | - | Site identifier, FK to pf_sites | Required for site-scoped entities |
Usage Notes:
organization_id is ALWAYS required for multi-tenant isolation
site_id is required when data is site-specific (e.g., beds, rooms)
- Both enable RLS policies for data isolation
Audit Columns
These columns track record lifecycle and are RECOMMENDED on all business entities:
| Column | Type | Required | Default | Description | Auto-Updated |
|---|
created_at | TIMESTAMPTZ | Yes | now() | Record creation timestamp | On INSERT only |
updated_at | TIMESTAMPTZ | Yes | now() | Last modification timestamp | Via trigger on UPDATE |
created_by | UUID | Recommended | - | User who created, FK to pf_profiles | Set by application |
updated_by | UUID | Recommended | - | User who last modified, FK to pf_profiles | Set by application |
deleted_at | TIMESTAMPTZ | Conditional | - | Soft delete timestamp (null = not deleted) | Set by application |
Update Trigger Pattern:
-- Trigger to auto-update updated_at
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_update_timestamp
BEFORE UPDATE ON {table_name}
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
Extensibility Columns
| Column | Type | Required | Default | Description | When to Include |
|---|
custom_fields | JSONB | Conditional | '{}' | Organization-specific metadata | Business entities only |
When to Include custom_fields:
- ✅ Business entity tables (employees, residents, invoices, forms)
- ✅ Configuration tables that organizations might extend
- ❌ Junction/mapping tables (e.g.,
hr_employee_skills)
- ❌ Audit/log tables (e.g.,
pf_audit_logs)
- ❌ Line item tables (use parent’s
custom_fields)
- ❌ System configuration tables
Required Comment Pattern:
COMMENT ON COLUMN {table_name}.custom_fields IS
'Organization-specific metadata (e.g., {"external_id": "EXT-123", "badge_number": "B-456"})';
Status Columns
Common patterns for tracking entity state:
| Pattern | Type | Description | Example Values |
|---|
status | TEXT | Current lifecycle state | 'draft', 'active', 'archived' |
is_active | BOOLEAN | Simple active/inactive flag | true, false |
deleted_at | TIMESTAMPTZ | Soft delete indicator | null (active), timestamp (deleted) |
Prefer status TEXT over is_active BOOLEAN when entity has more than 2 states.
Prefer deleted_at TIMESTAMPTZ over is_deleted BOOLEAN for soft deletes (enables audit trail).
Standard Enum Values
Common Status Values
| Domain | Values | Description |
|---|
| General Lifecycle | 'draft', 'active', 'inactive', 'archived' | Standard entity lifecycle |
| Approval Workflow | 'pending', 'approved', 'rejected', 'cancelled' | Approval state machine |
| Processing State | 'queued', 'processing', 'completed', 'failed' | Async processing |
| Publication State | 'draft', 'review', 'published', 'deprecated' | Content publishing |
Employment Status (HR)
| Value | Description |
|---|
'active' | Currently employed |
'inactive' | Temporarily inactive (leave, etc.) |
'terminated' | Employment ended |
'pending' | Pending onboarding |
Episode Status (RH)
| Value | Description |
|---|
'pending' | Awaiting admission |
'active' | Currently in residence |
'discharged' | Completed stay |
'transferred' | Transferred to another facility |
Transaction Status (FA)
| Value | Description |
|---|
'draft' | Not yet finalized |
'pending' | Awaiting approval |
'posted' | Finalized to ledger |
'voided' | Cancelled/reversed |
Column Naming Conventions
Standard Patterns
| Pattern | Example | Description |
|---|
{entity}_id | employee_id, resident_id | Foreign key reference to entity |
{entity}_count | bed_count, task_count | Denormalized count (use sparingly) |
{action}_at | submitted_at, approved_at | Timestamp when action occurred |
{action}_by | submitted_by, approved_by | User who performed action (FK to pf_profiles) |
is_{state} | is_active, is_primary | Boolean state flag |
has_{feature} | has_attachments, has_comments | Boolean existence flag |
total_{entity} | total_amount, total_hours | Calculated/aggregated total |
{entity}_number | invoice_number, employee_number | Human-readable identifier |
{entity}_date | hire_date, admission_date | Date (no time) field |
Foreign Key Naming
-- Standard FK pattern
{referenced_entity}_id UUID REFERENCES {core}_{referenced_table}(id)
-- Examples
employee_id UUID REFERENCES hr_employees(id)
organization_id UUID REFERENCES pf_organizations(id) ON DELETE CASCADE
site_id UUID REFERENCES pf_sites(id)
Index Naming
| Pattern | Example | Purpose |
|---|
idx_{table}_{column} | idx_hr_employees_org | Single column index |
idx_{table}_{col1}_{col2} | idx_hr_employees_org_status | Composite index |
idx_{table}_{column}_partial | idx_hr_employees_active | Partial index |
uniq_{table}_{columns} | uniq_hr_employees_number | Unique index |
Reserved Column Names
These columns have platform-wide meaning and MUST NOT be repurposed:
| Column | Reserved For | Core |
|---|
id | Primary key | All |
organization_id | Multi-tenant isolation | All |
site_id | Site-level scoping | All |
custom_fields | Organization extensibility | All |
created_at | Audit: creation time | All |
updated_at | Audit: last modified time | All |
created_by | Audit: creator reference | All |
updated_by | Audit: modifier reference | All |
deleted_at | Soft delete marker | All |
Data Type Guidelines
Identifiers
| Type | Use For | Example |
|---|
UUID | Primary keys, foreign keys | id, organization_id |
TEXT | Human-readable identifiers | employee_number, invoice_number |
SERIAL/BIGSERIAL | Sequence numbers (internal only) | Avoid for business keys |
Text Fields
| Type | Use For | Notes |
|---|
TEXT | Variable-length strings | Preferred over VARCHAR |
VARCHAR(n) | Fixed-max-length strings | Only when constraint needed |
TEXT[] | String arrays | For tags, keywords |
Numeric Fields
| Type | Use For | Notes |
|---|
INTEGER | Counts, small numbers | -2B to +2B |
BIGINT | Large counts | When INTEGER may overflow |
NUMERIC(p,s) | Money, precise decimals | NUMERIC(12,2) for currency |
DECIMAL | Alias for NUMERIC | Use NUMERIC for consistency |
Money Pattern:
-- Always use NUMERIC for currency
amount NUMERIC(12, 2) NOT NULL DEFAULT 0.00
Date/Time Fields
| Type | Use For | Notes |
|---|
TIMESTAMPTZ | Timestamps with timezone | Always use for timestamps |
DATE | Date only (no time) | Use for birth dates, hire dates |
TIME | Time only (no date) | Rare; use for schedules |
INTERVAL | Time durations | For calculated durations |
Always Use TIMESTAMPTZ (not TIMESTAMP) for timestamps to handle timezone correctly.
JSON Fields
| Type | Use For | Notes |
|---|
JSONB | Structured metadata | Preferred over JSON (indexable) |
JSON | Avoid | Use JSONB instead |
Constraint Guidelines
CHECK Constraints
Use CHECK constraints for:
- ✅ Enum-like values:
CHECK (status IN ('active', 'inactive'))
- ✅ Numeric ranges:
CHECK (age >= 0 AND age <= 150)
- ✅ String patterns:
CHECK (email ~ '^[^@]+@[^@]+\.[^@]+$')
NEVER use CHECK constraints for:
- ❌ Time-based logic:
CHECK (expiry_date >= CURRENT_DATE) - Will fail as time passes!
- ❌ Complex business rules - Use application-level validation
- ❌ Cross-table relationships - Use foreign keys or application logic
-- ✅ CORRECT: Static enum check
CHECK (status IN ('pending', 'approved', 'rejected'))
-- ❌ WRONG: Time-based check (NEVER DO THIS)
CHECK (expiry_date >= CURRENT_DATE)
Foreign Key Constraints
-- Standard FK with CASCADE delete (for child records)
organization_id UUID NOT NULL
REFERENCES pf_organizations(id)
ON DELETE CASCADE
-- FK with RESTRICT (prevent orphans)
employee_id UUID NOT NULL
REFERENCES hr_employees(id)
ON DELETE RESTRICT
-- Optional FK (nullable)
supervisor_id UUID
REFERENCES hr_employees(id)
ON DELETE SET NULL
Unique Constraints
-- Organization-scoped uniqueness
UNIQUE(organization_id, employee_number)
-- Composite uniqueness
UNIQUE(organization_id, site_id, bed_number)
RLS Policy Patterns
Standard Policy Set
Every business table needs these RLS policies:
-- Enable RLS
ALTER TABLE {core}_{entity} ENABLE ROW LEVEL SECURITY;
-- SELECT: Organization access
CREATE POLICY "{core}_{entity}_select" ON {core}_{entity}
FOR SELECT
USING ({core}_has_org_access(organization_id, auth.uid()));
-- INSERT: Organization access with check
CREATE POLICY "{core}_{entity}_insert" ON {core}_{entity}
FOR INSERT
WITH CHECK ({core}_has_org_access(organization_id, auth.uid()));
-- UPDATE: Organization access with BOTH clauses (CRITICAL!)
CREATE POLICY "{core}_{entity}_update" ON {core}_{entity}
FOR UPDATE
USING ({core}_has_org_access(organization_id, auth.uid()))
WITH CHECK ({core}_has_org_access(organization_id, auth.uid()));
-- DELETE: Admin only
CREATE POLICY "{core}_{entity}_delete" ON {core}_{entity}
FOR DELETE
USING ({core}_is_org_admin(organization_id, auth.uid()));
⚠️ CRITICAL: UPDATE policies MUST include WITH CHECK to prevent organization_id changes that could breach tenant isolation.
Security Definer Functions
-- Standard org access check function
CREATE OR REPLACE FUNCTION {core}_has_org_access(org_id UUID, user_id UUID)
RETURNS BOOLEAN
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
RETURN EXISTS (
SELECT 1 FROM pf_user_role_assignments
WHERE pf_user_role_assignments.organization_id = org_id
AND pf_user_role_assignments.user_id = user_id
AND (pf_user_role_assignments.expires_at IS NULL
OR pf_user_role_assignments.expires_at > now())
);
END;
$$;
Table Categories
Business Entity Tables
Primary data tables that represent core business objects.
Requirements:
- ✅
organization_id (required)
- ✅
site_id (where applicable)
- ✅
custom_fields JSONB (required)
- ✅ All audit columns
- ✅ RLS enabled with full policy set
Examples: hr_employees, rh_residents, fa_invoices, fw_forms
Junction/Mapping Tables
Tables that create many-to-many relationships.
Requirements:
- ✅
organization_id (required)
- ✅ Composite primary key or unique constraint
- ❌
custom_fields (not needed)
- ✅ RLS enabled
Examples: hr_employee_skills, fw_form_assignments
Audit/Log Tables
Tables that record historical events for compliance.
Requirements:
- ✅
organization_id (required)
- ✅
created_at (required)
- ❌
updated_at, deleted_at (logs are immutable)
- ❌
custom_fields (not needed)
- ✅ Append-only (no UPDATE/DELETE policies)
Examples: pf_audit_logs, fa_journal_entry_lines
Configuration Tables
Tables that store module or organization settings.
Requirements:
- ✅
organization_id (required)
- ✅ All audit columns
- ❌
custom_fields (settings ARE the custom fields)
Examples: hr_module_settings, fa_module_settings
Versioning
| Version | Date | Changes |
|---|
| 1.0.0 | 2026-01-15 | Initial version |
See Also
constitution.md §5 - Database naming and guardrails
AGENTS.md - Database rules quick reference
specs/_templates/SPEC_TEMPLATE.md - Data Model section
specs/_templates/db-preflight.md - Database pre-flight checklist
specs/_templates/DATA_MODEL_TEMPLATE.md - Data model documentation template
Document Owner: Platform Foundation
Review Cadence: Quarterly
Next Review: 2026-04-15