Skip to content

Reserved Column Inventory

Columns that exist on a table but have no write path yet. Implements P36.

Why this exists

Adding a column to an existing table is cheap. Adding it after the table is full of rows costs a backfill, a coordinated deploy, and a migration window. So when a feature spec describes a future capability that will need a column on an existing foundational table — break-glass, impersonation, email invitations, request correlation — we reserve the column at the table's creation migration. The column ships nullable, no write path, no consumer; the future feature lights it up.

This is a column-not-table addition pattern. It only applies to columns on existing foundational tables. New tables introduced by the feature itself (e.g., webhook_events for Layer 8 webhooks) are not reserved here — they ship with the feature that introduces them.

Inventory

audit_log

Owned by migrations/core/000001_init.up.sql. All four columns exist with partial indexes and are written by internal/core/audit/recorder.go.

ColumnTypeReserved forWrite status
request_idUUIDLog/audit correlation across one HTTP request (Layer 1.1)Written today. requestctx.RequestID middleware generates or accepts an inbound X-Request-ID; the audit recorder reads it via requestIDFromContext.
action_contextTEXTTag rows as break_glass, impersonation, gdpr_operation, or normal (P15/P16/Layer 12)Written today. Defaults to 'normal'; RequireBreakGlass middleware (Foundation 1B.11) overrides to 'break_glass' for the duration of an elevated session. 'impersonation' lights up with 1B.13; 'gdpr_operation' with the GDPR-erasure handler.
break_glass_idUUIDLogical FK to break_glass_sessions(id) (P15)Written today. Foundation 1B.11 lit up the column. The middleware calls set_app_break_glass_session_id(<session.id>) after match; audit_log_insert reads the GUC into this column. Logical FK rather than hard FK so audit rows survive cascade-deletes of the parent (rare).
impersonation_idUUIDFK to a future impersonation_sessions table (P16, Foundation 1B.13)Reserved. Same shape as break_glass_id, lit up when 1B.13 ships.

The four indexes (idx_audit_request, idx_audit_context, idx_audit_breakglass, idx_audit_impersonation) are partial — they exclude rows where the value is NULL — so they cost ~zero until rows actually carry the values.

organization_memberships

Owned by migrations/core/000002_tenancy_rbac.up.sql. Three invitation columns reserved for the future email-invite flow.

ColumnTypeReserved forWrite status
invited_atTIMESTAMPTZWhen the invite email was queuedReserved. Today every membership is created directly (auto-link on first sign-in, or POST /v1/organizations/:id/members). The future "invite member" flow will set this.
invited_byUUIDUser who issued the invite. ON DELETE SET NULL so the membership row survives if the inviter is later removedReserved. Same flow as above.
accepted_atTIMESTAMPTZWhen the invitee completed the invite flowReserved. A pending invite has invited_at set and accepted_at NULL.

last_used_at (P35) was reserved up to Layer 1.10 and is now written as of Layer 1.11 — see reference/activity-tracking.md. It moves out of the reserved inventory once it has an active write path; the row above is here for historical context.

Tables that haven't shipped yet (forward-declared)

These are columns that will be reserved when the parent table lands — listed here so the migration author for the parent table doesn't have to rediscover them.

TableColumnTypeReason
appointmentsappointment_template_idUUIDLegacy field for a future migration from RestartiX's old system (P36 in patterns.md). Remove after Phase 4 if the migration window passes without use.

When Layer 6 introduces the appointments table, that migration includes appointment_template_id UUID NULL (no FK — the source table doesn't exist in the new schema) and a partial index WHERE appointment_template_id IS NOT NULL if a query ever needs it.

How to use this catalog

When writing a new migration that adds a foundational table (auth, audit, infra), check whether any feature spec in apps/docs/features/ describes future state that would need a column on that table. If yes, reserve the column now — nullable, no FK that doesn't exist yet, no writer required. Add it to the inventory above with Reserved. status.

When writing a feature that lights up a reserved column, change the status from "Reserved." to "Written today." in the inventory and link the writer (handler, middleware, or repo). Update the relevant pattern doc cross-link if the column completes the pattern.

Don't reserve speculatively. A column reserved without a written-down spec ages into "what was this for?" comments. Every entry above traces back to a feature spec or a documented pattern.