BEGIN; CREATE TABLE sharing_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), household_id uuid NOT NULL, subject_id uuid NOT NULL, resource_type varchar(32) NOT NULL CHECK (resource_type IN ('STORY','TRANSCRIPT','RECORDING')), resource_public_id uuid NOT NULL, grantee_user_id uuid NOT NULL, permission varchar(24) NOT NULL CHECK (permission IN ('VIEW_TEXT','LISTEN','DOWNLOAD','EDIT')), purpose varchar(500) NOT NULL CHECK (length(trim(purpose))>0), login_required boolean NOT NULL DEFAULT true, granted_by uuid NOT NULL, granted_at timestamptz NOT NULL DEFAULT now(), expires_at timestamptz NOT NULL, revoked_at timestamptz, revoked_by uuid, version bigint NOT NULL DEFAULT 1 CHECK (version>0), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT sharing_grant_household_tenant_fk FOREIGN KEY (household_id,tenant_id) REFERENCES household(id,tenant_id), CONSTRAINT sharing_grant_subject_tenant_fk FOREIGN KEY (subject_id,tenant_id) REFERENCES subject_profile(id,tenant_id) ON DELETE CASCADE, CONSTRAINT sharing_grant_grantee_tenant_fk FOREIGN KEY (grantee_user_id,tenant_id) REFERENCES user_account(id,tenant_id), CONSTRAINT sharing_grant_grantor_tenant_fk FOREIGN KEY (granted_by,tenant_id) REFERENCES user_account(id,tenant_id), CONSTRAINT sharing_grant_revoker_tenant_fk FOREIGN KEY (revoked_by,tenant_id) REFERENCES user_account(id,tenant_id), CONSTRAINT sharing_grant_permission_resource_check CHECK ( (resource_type='STORY' AND permission IN ('VIEW_TEXT','EDIT')) OR (resource_type='TRANSCRIPT' AND permission='VIEW_TEXT') OR (resource_type='RECORDING' AND permission IN ('LISTEN','DOWNLOAD')) ), CONSTRAINT sharing_grant_expiry_check CHECK (expires_at>granted_at) ); CREATE INDEX sharing_grant_subject_idx ON sharing_grant(tenant_id,subject_id,granted_at DESC); CREATE INDEX sharing_grant_recipient_idx ON sharing_grant(tenant_id,grantee_user_id,expires_at DESC) WHERE revoked_at IS NULL; CREATE INDEX sharing_grant_resource_idx ON sharing_grant(tenant_id,resource_type,resource_public_id); CREATE TABLE sharing_access_log ( id bigserial PRIMARY KEY, tenant_id uuid NOT NULL REFERENCES tenant(id), sharing_grant_id uuid NOT NULL REFERENCES sharing_grant(id) ON DELETE CASCADE, actor_user_id uuid, actor_identifier_digest char(64) NOT NULL, resource_type varchar(32) NOT NULL, resource_public_id uuid NOT NULL, permission varchar(24) NOT NULL, result varchar(16) NOT NULL CHECK (result IN ('SUCCESS','DENIED')), reason_code varchar(80), request_id varchar(128), accessed_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT sharing_access_actor_tenant_fk FOREIGN KEY (actor_user_id,tenant_id) REFERENCES user_account(id,tenant_id) ); CREATE INDEX sharing_access_grant_time_idx ON sharing_access_log(sharing_grant_id,accessed_at DESC); CREATE INDEX sharing_access_tenant_time_idx ON sharing_access_log(tenant_id,accessed_at DESC); COMMIT;