BEGIN; CREATE TABLE upload_batch ( 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), conversation_id uuid NOT NULL, device_id uuid NOT NULL, idempotency_key varchar(128) NOT NULL, manifest_digest char(64) NOT NULL, status varchar(24) NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING', 'UPLOADING', 'VERIFYING', 'COMPLETED', 'FAILED', 'EXPIRED')), segment_count integer NOT NULL CHECK (segment_count > 0), expected_total_size bigint NOT NULL CHECK (expected_total_size > 0), verified_count integer NOT NULL DEFAULT 0 CHECK (verified_count >= 0), rejected_count integer NOT NULL DEFAULT 0 CHECK (rejected_count >= 0), verification_attempts integer NOT NULL DEFAULT 0 CHECK (verification_attempts >= 0), last_error_code varchar(80), expires_at timestamptz NOT NULL, completed_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT upload_batch_conversation_tenant_fk FOREIGN KEY (conversation_id, tenant_id) REFERENCES conversation (id, tenant_id), CONSTRAINT upload_batch_device_tenant_fk FOREIGN KEY (device_id, tenant_id) REFERENCES device (id, tenant_id), CONSTRAINT upload_batch_id_tenant_key UNIQUE (id, tenant_id), CONSTRAINT upload_batch_idempotency_key UNIQUE (device_id, conversation_id, idempotency_key) ); CREATE INDEX upload_batch_device_status_idx ON upload_batch (device_id, status, updated_at DESC); CREATE INDEX upload_batch_conversation_time_idx ON upload_batch (tenant_id, conversation_id, created_at DESC); CREATE TABLE media_segment ( 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), conversation_id uuid NOT NULL, device_id uuid NOT NULL, track varchar(8) NOT NULL CHECK (track IN ('near', 'far')), sequence_no integer NOT NULL CHECK (sequence_no >= 0), codec varchar(32) NOT NULL, content_type varchar(100) NOT NULL, duration_ms integer NOT NULL CHECK (duration_ms > 0), expected_size bigint NOT NULL CHECK (expected_size > 0), expected_digest char(64) NOT NULL, object_ref varchar(700) UNIQUE, encryption_key_ref varchar(300) NOT NULL DEFAULT 'object-store-managed', status varchar(24) NOT NULL DEFAULT 'DECLARED' CHECK (status IN ('DECLARED', 'AUTHORIZED', 'UPLOADED', 'VERIFYING', 'VERIFIED', 'REJECTED', 'DELETED')), verification_attempts integer NOT NULL DEFAULT 0 CHECK (verification_attempts >= 0), last_error_code varchar(80), uploaded_at timestamptz, verified_at timestamptz, deleted_at timestamptz, local_delete_after timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT media_segment_conversation_tenant_fk FOREIGN KEY (conversation_id, tenant_id) REFERENCES conversation (id, tenant_id), CONSTRAINT media_segment_device_tenant_fk FOREIGN KEY (device_id, tenant_id) REFERENCES device (id, tenant_id), CONSTRAINT media_segment_id_tenant_key UNIQUE (id, tenant_id), CONSTRAINT media_segment_track_sequence_key UNIQUE (conversation_id, track, sequence_no) ); CREATE INDEX media_segment_conversation_track_idx ON media_segment (tenant_id, conversation_id, track, sequence_no); CREATE TABLE upload_batch_segment ( upload_batch_id uuid NOT NULL, media_segment_id uuid NOT NULL, tenant_id uuid NOT NULL REFERENCES tenant(id), created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (upload_batch_id, media_segment_id), CONSTRAINT upload_batch_segment_batch_tenant_fk FOREIGN KEY (upload_batch_id, tenant_id) REFERENCES upload_batch (id, tenant_id), CONSTRAINT upload_batch_segment_media_tenant_fk FOREIGN KEY (media_segment_id, tenant_id) REFERENCES media_segment (id, tenant_id) ); CREATE INDEX upload_batch_segment_media_idx ON upload_batch_segment (media_segment_id, upload_batch_id); CREATE TABLE recording_access_grant ( 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, grantee_user_id uuid NOT NULL, permission varchar(16) NOT NULL CHECK (permission IN ('PLAY', 'DOWNLOAD')), purpose varchar(120) NOT NULL, granted_by uuid NOT NULL, granted_at timestamptz NOT NULL DEFAULT now(), expires_at timestamptz NOT NULL, revoked_at timestamptz, revoked_by uuid, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT recording_access_subject_tenant_fk FOREIGN KEY (subject_id, tenant_id) REFERENCES subject_profile (id, tenant_id), CONSTRAINT recording_access_grantee_tenant_fk FOREIGN KEY (grantee_user_id, tenant_id) REFERENCES user_account (id, tenant_id), CONSTRAINT recording_access_grantor_tenant_fk FOREIGN KEY (granted_by, tenant_id) REFERENCES user_account (id, tenant_id), CONSTRAINT recording_access_revoker_tenant_fk FOREIGN KEY (revoked_by, tenant_id) REFERENCES user_account (id, tenant_id), CONSTRAINT recording_access_expiry_check CHECK (expires_at > granted_at), CONSTRAINT recording_access_revoke_check CHECK (revoked_at IS NULL OR revoked_at >= granted_at), CONSTRAINT recording_access_id_tenant_key UNIQUE (id, tenant_id) ); CREATE INDEX recording_access_lookup_idx ON recording_access_grant (tenant_id, subject_id, grantee_user_id, permission, expires_at DESC) WHERE revoked_at IS NULL; CREATE TABLE media_access_log ( 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), actor_user_id uuid NOT NULL, conversation_id uuid NOT NULL, media_segment_id uuid NOT NULL, action varchar(16) NOT NULL CHECK (action IN ('PLAY', 'DOWNLOAD')), purpose varchar(120) NOT NULL, result varchar(16) NOT NULL CHECK (result IN ('SUCCESS', 'DENIED', 'FAILED')), reason_code varchar(80), request_id varchar(128), occurred_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT media_access_actor_tenant_fk FOREIGN KEY (actor_user_id, tenant_id) REFERENCES user_account (id, tenant_id), CONSTRAINT media_access_conversation_tenant_fk FOREIGN KEY (conversation_id, tenant_id) REFERENCES conversation (id, tenant_id), CONSTRAINT media_access_segment_tenant_fk FOREIGN KEY (media_segment_id, tenant_id) REFERENCES media_segment (id, tenant_id) ); CREATE INDEX media_access_resource_time_idx ON media_access_log (tenant_id, media_segment_id, occurred_at DESC); CREATE INDEX media_access_actor_time_idx ON media_access_log (tenant_id, actor_user_id, occurred_at DESC); COMMIT;