BEGIN; SET LOCAL lock_timeout = '10s'; SET LOCAL search_path = pg_catalog, public; SELECT pg_advisory_xact_lock(290926,170004); -- A pre-existing bridge must match the schema fingerprint recorded on its first application. DO $existing$ DECLARE name text; relation regclass; signature text; BEGIN FOREACH name IN ARRAY ARRAY['identity_links','sector_links','business_links','compatibility_checkpoints','compatibility_rejections','compatibility_controls'] LOOP relation := to_regclass('mne.'||name); IF relation IS NOT NULL THEN SELECT md5(COALESCE((SELECT string_agg(a.attname||':'||format_type(a.atttypid,a.atttypmod)||':'||a.attnotnull::text||':'||COALESCE(pg_get_expr(d.adbin,d.adrelid),''),'|' ORDER BY a.attnum) FROM pg_attribute a LEFT JOIN pg_attrdef d ON d.adrelid=a.attrelid AND d.adnum=a.attnum WHERE a.attrelid=relation AND a.attnum>0 AND NOT a.attisdropped),'')||COALESCE((SELECT string_agg(conname||':'||pg_get_constraintdef(oid),'|' ORDER BY conname) FROM pg_constraint WHERE conrelid=relation),'')) INTO signature; IF obj_description(relation,'pg_class') IS DISTINCT FROM '004_mne_compatibility:'||signature THEN RAISE EXCEPTION 'Unexpected compatibility table definition: %',name; END IF; END IF; END LOOP; END $existing$; DO $preflight$ DECLARE item record; BEGIN FOR item IN SELECT * FROM (VALUES ('public.users','id','bigint'),('public.sectors','id','bigint'), ('public.existing_businesses','id','uuid'),('mne.parties','id','uuid'), ('mne.parties','user_id','uuid'),('mne.existing_businesses','id','uuid') ) AS expected(table_name,column_name,type_name) LOOP IF NOT EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid=to_regclass(item.table_name) AND attname=item.column_name AND NOT attisdropped AND format_type(atttypid,atttypmod)=item.type_name) THEN RAISE EXCEPTION 'Unexpected compatibility prerequisite: %.%',item.table_name,item.column_name; END IF; END LOOP; IF EXISTS(SELECT 1 FROM mne.parties WHERE user_id IS NOT NULL) THEN RAISE EXCEPTION 'Legacy UUID party identities require explicit reconciliation first'; END IF; IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid='mne.audit_events'::regclass AND tgname='mne_evidence_immutable') THEN RAISE EXCEPTION '003 integrity prerequisite absent'; END IF; END $preflight$; -- Retain the unused UUID column for reversibility, but prohibit account bindings through it. DO $party$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid='mne.parties'::regclass AND conname='ck_parties_identity_links') THEN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid='mne.parties'::regclass AND conname='parties_check' AND contype='c') THEN RAISE EXCEPTION 'Unexpected parties shape constraint'; END IF; ALTER TABLE mne.parties DROP CONSTRAINT parties_check; ALTER TABLE mne.parties ADD CONSTRAINT ck_parties_identity_links CHECK (user_id IS NULL AND ((party_type='person' AND organization_id IS NULL) OR (party_type='organization' AND organization_id IS NOT NULL))); END IF; END $party$; CREATE TABLE IF NOT EXISTS mne.identity_links ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id bigint NOT NULL UNIQUE REFERENCES public.users(id) ON UPDATE RESTRICT ON DELETE RESTRICT, party_id uuid UNIQUE REFERENCES mne.parties(id) ON UPDATE RESTRICT ON DELETE RESTRICT, source_system text NOT NULL CHECK(source_system='public.users'), status text NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','active','revoked')), valid_from timestamptz NOT NULL DEFAULT now(), valid_until timestamptz, source_checksum char(64) NOT NULL, CHECK(valid_until IS NULL OR valid_until>valid_from), CHECK(status<>'active' OR party_id IS NOT NULL) ); -- This is an identifier mapping, not a second sector taxonomy. CREATE TABLE IF NOT EXISTS mne.sector_links ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), sector_id bigint NOT NULL UNIQUE REFERENCES public.sectors(id) ON UPDATE RESTRICT ON DELETE RESTRICT, canonical_sector_id uuid NOT NULL UNIQUE, source_system text NOT NULL CHECK(source_system='public.sectors'), source_code text NOT NULL UNIQUE, resolver_version integer NOT NULL DEFAULT 1 CHECK(resolver_version=1), status text NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','active','revoked')), valid_from timestamptz NOT NULL DEFAULT now(), valid_until timestamptz, source_checksum char(64) NOT NULL, CHECK(valid_until IS NULL OR valid_until>valid_from) ); CREATE TABLE IF NOT EXISTS mne.business_links ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), legacy_business_id uuid NOT NULL UNIQUE REFERENCES public.existing_businesses(id) ON UPDATE RESTRICT ON DELETE RESTRICT, canonical_business_id uuid UNIQUE REFERENCES mne.existing_businesses(id) ON UPDATE RESTRICT ON DELETE RESTRICT, source_system text NOT NULL CHECK(source_system='public.existing_businesses'), status text NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','active','revoked')), write_authority text NOT NULL DEFAULT 'legacy' CHECK(write_authority='legacy'), valid_from timestamptz NOT NULL DEFAULT now(), valid_until timestamptz, source_checksum char(64) NOT NULL, reconciled_version integer, CHECK(valid_until IS NULL OR valid_until>valid_from), CHECK(status<>'active' OR (canonical_business_id IS NOT NULL AND reconciled_version IS NOT NULL)) ); CREATE TABLE IF NOT EXISTS mne.compatibility_checkpoints ( domain text PRIMARY KEY CHECK(domain IN ('identity','sector','business')), last_key text, processed bigint NOT NULL DEFAULT 0, checksum char(64), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS mne.compatibility_rejections ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), domain text NOT NULL, source_key text NOT NULL, code text NOT NULL, cause text NOT NULL, responsible text NOT NULL DEFAULT 'data-steward', resolution text, resolved_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), UNIQUE(domain,source_key,code), CHECK((resolved_at IS NULL)=(resolution IS NULL)) ); CREATE TABLE IF NOT EXISTS mne.compatibility_controls ( capability text PRIMARY KEY CHECK(capability IN ('shadow','canonical_read')), enabled boolean NOT NULL DEFAULT false, percentage integer NOT NULL DEFAULT 0 CHECK(percentage BETWEEN 0 AND 100), -- Activation also requires a compatible adapter and per-record reconciliation. evidence_reference text, updated_at timestamptz NOT NULL DEFAULT now(), CHECK(NOT enabled OR length(btrim(evidence_reference))>=10) ); INSERT INTO mne.compatibility_controls(capability) VALUES ('shadow'),('canonical_read') ON CONFLICT DO NOTHING; DO $record$ DECLARE name text; relation regclass; signature text; BEGIN FOREACH name IN ARRAY ARRAY['identity_links','sector_links','business_links','compatibility_checkpoints','compatibility_rejections','compatibility_controls'] LOOP relation := to_regclass('mne.'||name); SELECT md5(COALESCE((SELECT string_agg(a.attname||':'||format_type(a.atttypid,a.atttypmod)||':'||a.attnotnull::text||':'||COALESCE(pg_get_expr(d.adbin,d.adrelid),''),'|' ORDER BY a.attnum) FROM pg_attribute a LEFT JOIN pg_attrdef d ON d.adrelid=a.attrelid AND d.adnum=a.attnum WHERE a.attrelid=relation AND a.attnum>0 AND NOT a.attisdropped),'')||COALESCE((SELECT string_agg(conname||':'||pg_get_constraintdef(oid),'|' ORDER BY conname) FROM pg_constraint WHERE conrelid=relation),'')) INTO signature; EXECUTE format('COMMENT ON TABLE mne.%I IS %L',name,'004_mne_compatibility:'||signature); END LOOP; END $record$; COMMIT;