CREATE TABLE IF NOT EXISTS task_input_attachments ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), organization_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, task_id uuid NOT NULL REFERENCES tasks(id) ON DELETE CASCADE, purpose text NOT NULL DEFAULT 'passenger_list' CHECK (purpose = 'passenger_list'), source text NOT NULL CHECK (source IN ('manual', 'agentbus')), file_name_ciphertext text NOT NULL, content_type text NOT NULL, byte_size integer NOT NULL CHECK (byte_size > 0), sha256 text NOT NULL CHECK (sha256 ~ '^[a-f0-9]{64}$'), status text NOT NULL CHECK (status IN ('normalized', 'rejected', 'superseded')), normalizer_version text, normalized_text_ciphertext text, row_count integer CHECK (row_count IS NULL OR row_count > 0), error_code text, error_details jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT task_input_attachments_normalized_check CHECK ( (status = 'normalized' AND normalizer_version IS NOT NULL AND normalized_text_ciphertext IS NOT NULL AND row_count IS NOT NULL AND error_code IS NULL) OR (status = 'rejected' AND normalized_text_ciphertext IS NULL AND row_count IS NULL AND error_code IS NOT NULL) OR status = 'superseded' ), UNIQUE (task_id, purpose, sha256) ); CREATE UNIQUE INDEX IF NOT EXISTS task_input_attachments_current_idx ON task_input_attachments (task_id, purpose) WHERE status = 'normalized'; CREATE INDEX IF NOT EXISTS task_input_attachments_task_idx ON task_input_attachments (organization_id, task_id, created_at DESC); CREATE INDEX IF NOT EXISTS tasks_awaiting_attachment_idx ON tasks (organization_id, updated_at) WHERE status = 'awaiting_attachment';