20260731_390_semantic_governance.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. """Add generic semantic governance, field mappings and publication audit."""
  2. from alembic import op
  3. revision = "20260731_390"
  4. down_revision = "20260730_380"
  5. branch_labels = None
  6. depends_on = None
  7. def upgrade() -> None:
  8. op.execute(
  9. """
  10. CREATE TABLE public.semantic_assets (
  11. uid UUID PRIMARY KEY,
  12. asset_kind VARCHAR(30) NOT NULL CHECK (
  13. asset_kind IN ('business_term','code_set','metric')
  14. ),
  15. code VARCHAR(120) NOT NULL,
  16. name VARCHAR(300) NOT NULL,
  17. owner_uid UUID NOT NULL REFERENCES public.users(id),
  18. business_domain_uid UUID NOT NULL,
  19. status VARCHAR(20) NOT NULL CHECK (
  20. status IN (
  21. 'draft','in_review','approved','published',
  22. 'superseded','retired'
  23. )
  24. ),
  25. current_version INTEGER NOT NULL CHECK (current_version > 0),
  26. created_by UUID NOT NULL REFERENCES public.users(id),
  27. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  28. updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  29. UNIQUE (asset_kind, code)
  30. );
  31. CREATE TABLE public.semantic_asset_versions (
  32. uid UUID PRIMARY KEY,
  33. asset_uid UUID NOT NULL REFERENCES public.semantic_assets(uid),
  34. version INTEGER NOT NULL CHECK (version > 0),
  35. status VARCHAR(20) NOT NULL CHECK (
  36. status IN (
  37. 'draft','in_review','approved','published',
  38. 'superseded','retired'
  39. )
  40. ),
  41. content_hash CHAR(64) NOT NULL,
  42. definition JSONB NOT NULL,
  43. change_reason VARCHAR(1000) NOT NULL,
  44. created_by UUID NOT NULL REFERENCES public.users(id),
  45. rollback_from_version INTEGER,
  46. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  47. published_at TIMESTAMPTZ,
  48. UNIQUE (asset_uid, version),
  49. CHECK (jsonb_typeof(definition) = 'object'),
  50. CHECK (
  51. rollback_from_version IS NULL
  52. OR rollback_from_version > 0
  53. )
  54. );
  55. CREATE TABLE public.semantic_asset_reviews (
  56. uid UUID PRIMARY KEY,
  57. asset_uid UUID NOT NULL REFERENCES public.semantic_assets(uid),
  58. version INTEGER NOT NULL CHECK (version > 0),
  59. decision VARCHAR(20) NOT NULL
  60. CHECK (decision IN ('approve','reject')),
  61. reason VARCHAR(1000) NOT NULL,
  62. reviewer_uid UUID NOT NULL REFERENCES public.users(id),
  63. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  64. UNIQUE (asset_uid, version),
  65. FOREIGN KEY (asset_uid, version)
  66. REFERENCES public.semantic_asset_versions(asset_uid, version)
  67. );
  68. CREATE TABLE public.semantic_asset_links (
  69. uid UUID PRIMARY KEY,
  70. asset_uid UUID NOT NULL REFERENCES public.semantic_assets(uid),
  71. version INTEGER NOT NULL CHECK (version > 0),
  72. link_type VARCHAR(30) NOT NULL CHECK (
  73. link_type IN ('asset','data_element','standard','code_value')
  74. ),
  75. target_type VARCHAR(60) NOT NULL,
  76. target_uid VARCHAR(200) NOT NULL,
  77. target_key VARCHAR(200),
  78. metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
  79. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  80. FOREIGN KEY (asset_uid, version)
  81. REFERENCES public.semantic_asset_versions(asset_uid, version),
  82. UNIQUE (
  83. asset_uid, version, link_type, target_type,
  84. target_uid, target_key
  85. ),
  86. CHECK (jsonb_typeof(metadata) = 'object')
  87. );
  88. CREATE TABLE public.data_element_field_mappings (
  89. uid UUID PRIMARY KEY,
  90. asset_uid UUID NOT NULL
  91. REFERENCES public.active_metadata_assets(uid),
  92. field_name VARCHAR(200) NOT NULL,
  93. data_element_uid UUID NOT NULL REFERENCES public.data_elements(uid),
  94. data_element_version INTEGER NOT NULL CHECK (
  95. data_element_version > 0
  96. ),
  97. owner_uid UUID NOT NULL REFERENCES public.users(id),
  98. status VARCHAR(20) NOT NULL
  99. CHECK (status IN ('published','retired')),
  100. evidence JSONB NOT NULL DEFAULT '{}'::jsonb,
  101. created_by UUID NOT NULL REFERENCES public.users(id),
  102. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  103. updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  104. UNIQUE (asset_uid, field_name, data_element_uid),
  105. CHECK (jsonb_typeof(evidence) = 'object')
  106. );
  107. CREATE TABLE public.semantic_publication_audits (
  108. uid UUID PRIMARY KEY,
  109. target_type VARCHAR(40) NOT NULL CHECK (
  110. target_type IN (
  111. 'semantic_asset','data_standard','field_mapping'
  112. )
  113. ),
  114. target_uid UUID NOT NULL,
  115. target_version INTEGER NOT NULL CHECK (target_version > 0),
  116. action VARCHAR(40) NOT NULL CHECK (
  117. action IN (
  118. 'created','revised','submitted','approved','rejected',
  119. 'published','rolled_back','mapped','retired'
  120. )
  121. ),
  122. actor_uid UUID NOT NULL REFERENCES public.users(id),
  123. detail JSONB NOT NULL DEFAULT '{}'::jsonb,
  124. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  125. CHECK (jsonb_typeof(detail) = 'object')
  126. );
  127. CREATE INDEX idx_semantic_assets_kind_status
  128. ON public.semantic_assets(asset_kind, status, updated_at DESC);
  129. CREATE INDEX idx_semantic_versions_asset
  130. ON public.semantic_asset_versions(asset_uid, version DESC);
  131. CREATE INDEX idx_semantic_links_target
  132. ON public.semantic_asset_links(target_type, target_uid, target_key);
  133. CREATE UNIQUE INDEX uq_published_physical_field_mapping
  134. ON public.data_element_field_mappings(asset_uid, field_name)
  135. WHERE status = 'published';
  136. CREATE INDEX idx_field_mappings_element
  137. ON public.data_element_field_mappings(
  138. data_element_uid, status, updated_at DESC
  139. );
  140. CREATE INDEX idx_semantic_audits_target
  141. ON public.semantic_publication_audits(
  142. target_type, target_uid, target_version
  143. );
  144. """
  145. )
  146. def downgrade() -> None:
  147. raise RuntimeError(
  148. "semantic governance history is append-only; "
  149. "downgrade requires an approved archival migration"
  150. )