BEGIN; CREATE TABLE tenant ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), public_id uuid NOT NULL UNIQUE DEFAULT gen_random_uuid(), name varchar(200) NOT NULL, status varchar(32) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'closed')), version bigint NOT NULL DEFAULT 1 CHECK (version > 0), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), deleted_at timestamptz ); CREATE TABLE user_account ( 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), identity_subject varchar(255) NOT NULL, display_name varchar(120), status varchar(32) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled', 'deleted')), version bigint NOT NULL DEFAULT 1 CHECK (version > 0), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), deleted_at timestamptz, UNIQUE (tenant_id, identity_subject) ); CREATE TABLE audit_event ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id uuid REFERENCES tenant(id), actor_type varchar(32) NOT NULL, actor_public_id uuid, action varchar(120) NOT NULL, resource_type varchar(80) NOT NULL, resource_id uuid, request_id varchar(128), purpose varchar(255), result varchar(32) NOT NULL CHECK (result IN ('success', 'failure', 'denied')), detail jsonb NOT NULL DEFAULT '{}'::jsonb, occurred_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX audit_event_tenant_time_idx ON audit_event (tenant_id, occurred_at DESC); CREATE INDEX audit_event_request_idx ON audit_event (request_id) WHERE request_id IS NOT NULL; CREATE TABLE idempotency_record ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id uuid REFERENCES tenant(id), scope varchar(120) NOT NULL, idempotency_key varchar(255) NOT NULL, request_hash char(64) NOT NULL, state varchar(24) NOT NULL CHECK (state IN ('processing', 'completed', 'failed')), response_status integer, response_body jsonb, resource_id uuid, expires_at timestamptz NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (tenant_id, scope, idempotency_key) ); CREATE INDEX idempotency_record_expiry_idx ON idempotency_record (expires_at); CREATE TABLE outbox_event ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id uuid REFERENCES tenant(id), aggregate_type varchar(80) NOT NULL, aggregate_id uuid NOT NULL, event_type varchar(120) NOT NULL, payload jsonb NOT NULL, status varchar(24) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'dead')), attempt_count integer NOT NULL DEFAULT 0 CHECK (attempt_count >= 0), available_at timestamptz NOT NULL DEFAULT now(), locked_at timestamptz, locked_by varchar(120), last_error_code varchar(120), completed_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX outbox_event_claim_idx ON outbox_event (status, available_at, created_at) WHERE status IN ('pending', 'processing'); COMMIT;