Files
story/migrations/000005_device_conversation.up.sql
T

215 lines
11 KiB
PL/PgSQL

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;