BEGIN; CREATE TABLE subject_profile ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), public_id uuid NOT NULL UNIQUE DEFAULT gen_random_uuid(), tenant_id uuid NOT NULL REFERENCES tenant(id), household_id uuid NOT NULL, preferred_name varchar(120) NOT NULL, status varchar(24) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'deleted')), profile_version bigint NOT NULL DEFAULT 1 CHECK (profile_version > 0), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), deleted_at timestamptz, CONSTRAINT subject_household_tenant_fk FOREIGN KEY (household_id, tenant_id) REFERENCES household (id, tenant_id), CONSTRAINT subject_id_tenant_key UNIQUE (id, tenant_id) ); CREATE INDEX subject_tenant_household_status_idx ON subject_profile (tenant_id, household_id, status); CREATE TABLE device ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), public_id uuid NOT NULL UNIQUE DEFAULT gen_random_uuid(), tenant_id uuid REFERENCES tenant(id), serial_digest char(64) NOT NULL UNIQUE, model varchar(120) NOT NULL, hardware_revision varchar(80), status varchar(24) NOT NULL DEFAULT 'registered' CHECK (status IN ('registered', 'quarantined', 'revoked')), credential_ref varchar(500) NOT NULL, minimum_security_version varchar(80), registered_at timestamptz NOT NULL DEFAULT now(), revoked_at timestamptz, version bigint NOT NULL DEFAULT 1 CHECK (version > 0), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT device_id_tenant_key UNIQUE (id, tenant_id) ); CREATE INDEX device_tenant_status_idx ON device (tenant_id, status); CREATE TABLE device_binding ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), public_id uuid NOT NULL UNIQUE DEFAULT gen_random_uuid(), tenant_id uuid NOT NULL REFERENCES tenant(id), device_id uuid NOT NULL, household_id uuid NOT NULL, subject_id uuid NOT NULL, bound_at timestamptz NOT NULL DEFAULT now(), unbound_at timestamptz, bound_by uuid, unbound_by uuid, reason varchar(255), created_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT device_binding_device_tenant_fk FOREIGN KEY (device_id, tenant_id) REFERENCES device (id, tenant_id), CONSTRAINT device_binding_household_tenant_fk FOREIGN KEY (household_id, tenant_id) REFERENCES household (id, tenant_id), CONSTRAINT device_binding_subject_tenant_fk FOREIGN KEY (subject_id, tenant_id) REFERENCES subject_profile (id, tenant_id), CONSTRAINT device_binding_bound_by_tenant_fk FOREIGN KEY (bound_by, tenant_id) REFERENCES user_account (id, tenant_id), CONSTRAINT device_binding_unbound_by_tenant_fk FOREIGN KEY (unbound_by, tenant_id) REFERENCES user_account (id, tenant_id) ); CREATE UNIQUE INDEX device_binding_current_device_key ON device_binding (device_id) WHERE unbound_at IS NULL; CREATE INDEX device_binding_tenant_subject_idx ON device_binding (tenant_id, subject_id) WHERE unbound_at IS NULL; CREATE TABLE device_twin ( device_id uuid PRIMARY KEY, tenant_id uuid NOT NULL REFERENCES tenant(id), desired_revision bigint NOT NULL DEFAULT 1 CHECK (desired_revision > 0), desired_config jsonb NOT NULL DEFAULT '{}'::jsonb, reported_revision bigint, reported_config jsonb NOT NULL DEFAULT '{}'::jsonb, firmware_version varchar(80), board_revision varchar(80), rtc_sdk_version varchar(80), capabilities jsonb NOT NULL DEFAULT '{}'::jsonb, last_reported_at timestamptz, updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT device_twin_device_tenant_fk FOREIGN KEY (device_id, tenant_id) REFERENCES device (id, tenant_id) ); CREATE TABLE consent_snapshot ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), public_id uuid NOT NULL UNIQUE DEFAULT gen_random_uuid(), tenant_id uuid NOT NULL REFERENCES tenant(id), subject_id uuid NOT NULL, snapshot_version bigint NOT NULL CHECK (snapshot_version > 0), effective_consents jsonb NOT NULL, source_consent_ids jsonb NOT NULL DEFAULT '[]'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT consent_snapshot_subject_tenant_fk FOREIGN KEY (subject_id, tenant_id) REFERENCES subject_profile (id, tenant_id), CONSTRAINT consent_snapshot_id_tenant_key UNIQUE (id, tenant_id) ); CREATE TABLE context_snapshot ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), public_id uuid NOT NULL UNIQUE DEFAULT gen_random_uuid(), tenant_id uuid NOT NULL REFERENCES tenant(id), subject_id uuid NOT NULL, profile_version bigint NOT NULL CHECK (profile_version > 0), consent_snapshot_id uuid NOT NULL, memory_versions jsonb NOT NULL DEFAULT '[]'::jsonb, prompt_bundle_version varchar(80) NOT NULL, engine_mode varchar(32) NOT NULL CHECK (engine_mode IN ('s2s_pure', 's2s_hybrid', 'cascade')), content_digest char(64) NOT NULL, sanitized_summary varchar(1000), created_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT context_snapshot_subject_tenant_fk FOREIGN KEY (subject_id, tenant_id) REFERENCES subject_profile (id, tenant_id), CONSTRAINT context_snapshot_consent_tenant_fk FOREIGN KEY (consent_snapshot_id, tenant_id) REFERENCES consent_snapshot (id, tenant_id), CONSTRAINT context_snapshot_id_tenant_key UNIQUE (id, tenant_id) ); CREATE TABLE conversation ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), public_id uuid NOT NULL UNIQUE DEFAULT gen_random_uuid(), tenant_id uuid NOT NULL REFERENCES tenant(id), household_id uuid NOT NULL, subject_id uuid NOT NULL, device_id uuid NOT NULL, consent_snapshot_id uuid NOT NULL, context_snapshot_id uuid NOT NULL, provider_task_ref varchar(200) NOT NULL UNIQUE, engine_mode varchar(32) NOT NULL CHECK (engine_mode IN ('s2s_pure', 's2s_hybrid', 'cascade')), retention_mode varchar(32) NOT NULL CHECK (retention_mode IN ('none', 'transcript_only', 'transcript_and_audio')), status varchar(32) NOT NULL CHECK (status IN ('PREPARING', 'ROOM_ISSUED', 'STARTING_AGENT', 'ACTIVE', 'STOPPING', 'COMPLETED', 'FAILED', 'EXPIRED')), allocation_expires_at timestamptz NOT NULL, lease_expires_at timestamptz, last_heartbeat_seq bigint NOT NULL DEFAULT 0 CHECK (last_heartbeat_seq >= 0), started_at timestamptz, ended_at timestamptz, stop_reason varchar(64), failure_code varchar(64), version bigint NOT NULL DEFAULT 1 CHECK (version > 0), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT conversation_household_tenant_fk FOREIGN KEY (household_id, tenant_id) REFERENCES household (id, tenant_id), CONSTRAINT conversation_subject_tenant_fk FOREIGN KEY (subject_id, tenant_id) REFERENCES subject_profile (id, tenant_id), CONSTRAINT conversation_device_tenant_fk FOREIGN KEY (device_id, tenant_id) REFERENCES device (id, tenant_id), CONSTRAINT conversation_consent_tenant_fk FOREIGN KEY (consent_snapshot_id, tenant_id) REFERENCES consent_snapshot (id, tenant_id), CONSTRAINT conversation_context_tenant_fk FOREIGN KEY (context_snapshot_id, tenant_id) REFERENCES context_snapshot (id, tenant_id), CONSTRAINT conversation_id_tenant_key UNIQUE (id, tenant_id) ); CREATE INDEX conversation_tenant_subject_time_idx ON conversation (tenant_id, subject_id, created_at DESC); CREATE INDEX conversation_device_status_idx ON conversation (device_id, status); CREATE UNIQUE INDEX conversation_one_active_per_device ON conversation (device_id) WHERE status NOT IN ('COMPLETED', 'FAILED', 'EXPIRED'); CREATE TABLE conversation_state_history ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id uuid NOT NULL REFERENCES conversation(id), from_status varchar(32), to_status varchar(32) NOT NULL, reason varchar(255), source varchar(40) NOT NULL CHECK (source IN ('device', 'platform', 'provider', 'worker', 'operator')), provider_event_id uuid, request_id varchar(128), occurred_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX conversation_history_time_idx ON conversation_state_history (conversation_id, occurred_at, id); CREATE TABLE rtc_task ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id uuid NOT NULL UNIQUE REFERENCES conversation(id), provider varchar(40) NOT NULL, provider_task_ref varchar(200) NOT NULL UNIQUE, room_id varchar(200) NOT NULL UNIQUE, device_rtc_user_id varchar(200) NOT NULL, agent_rtc_user_id varchar(200) NOT NULL, status varchar(32) NOT NULL CHECK (status IN ('ALLOCATED', 'STARTING', 'ACTIVE', 'STOPPING', 'COMPLETED', 'FAILED', 'UNKNOWN')), lease_expires_at timestamptz, start_attempts integer NOT NULL DEFAULT 0 CHECK (start_attempts >= 0), stop_attempts integer NOT NULL DEFAULT 0 CHECK (stop_attempts >= 0), last_checked_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT rtc_distinct_users CHECK (device_rtc_user_id <> agent_rtc_user_id) ); CREATE TABLE provider_event ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id uuid NOT NULL REFERENCES tenant(id), conversation_id uuid NOT NULL, provider varchar(40) NOT NULL, provider_event_key varchar(255) NOT NULL, provider_task_ref varchar(200) NOT NULL, event_type varchar(100) NOT NULL, occurred_at timestamptz NOT NULL, received_at timestamptz NOT NULL DEFAULT now(), payload_digest char(64) NOT NULL, sanitized_payload jsonb NOT NULL DEFAULT '{}'::jsonb, processing_status varchar(24) NOT NULL DEFAULT 'pending' CHECK (processing_status IN ('pending', 'processed', 'ignored', 'failed')), processed_at timestamptz, last_error varchar(500), CONSTRAINT provider_event_conversation_tenant_fk FOREIGN KEY (conversation_id, tenant_id) REFERENCES conversation (id, tenant_id), CONSTRAINT provider_event_key UNIQUE (provider, provider_event_key) ); CREATE INDEX provider_event_conversation_time_idx ON provider_event (conversation_id, received_at); CREATE INDEX provider_event_pending_idx ON provider_event (processing_status, received_at) WHERE processing_status IN ('pending', 'failed'); COMMIT;