-- IHF Phase 1 + Phase 2 Schema -- Hub, Widget, WidgetVersion, InteractionEvent, Annotation -- Phase 2: AnnotationThread, RequirementCandidate, TriageState, ReviewerAssignment -- See workplans/IHUB-WP-0001-ihf-phase1-minimal-interaction-core.md -- See workplans/IHUB-WP-0002-ihf-phase2-structured-feedback-and-triage.md CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- Users (T10 — authentication) CREATE TABLE users ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, name TEXT NOT NULL, locked_at TIMESTAMP WITH TIME ZONE DEFAULT NULL, failed_login_attempts INT NOT NULL DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); -- Hubs — bounded domains of responsibility CREATE TABLE hubs ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, slug TEXT NOT NULL UNIQUE, name TEXT NOT NULL, domain TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); -- Widgets — smallest semantically governable interaction units CREATE TABLE widgets ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, hub_id UUID NOT NULL REFERENCES hubs(id) ON DELETE RESTRICT, name TEXT NOT NULL, widget_type TEXT NOT NULL, capability_ref TEXT, view_context TEXT, policy_scope TEXT NOT NULL DEFAULT 'internal', status TEXT NOT NULL DEFAULT 'active', version INT NOT NULL DEFAULT 1, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); -- Widget version history CREATE TABLE widget_versions ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE CASCADE, version INT NOT NULL, schema_snapshot JSONB NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, UNIQUE (widget_id, version) ); -- Interaction events — append-only capture CREATE TABLE interaction_events ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE CASCADE, event_type TEXT NOT NULL, actor_id UUID, actor_type TEXT NOT NULL DEFAULT 'user', view_context_ref TEXT, metadata JSONB DEFAULT '{}' NOT NULL, occurred_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX interaction_events_widget_id_idx ON interaction_events (widget_id); CREATE INDEX interaction_events_occurred_at_idx ON interaction_events (occurred_at DESC); -- Enforce append-only on interaction_events CREATE OR REPLACE FUNCTION prevent_interaction_event_mutation() RETURNS TRIGGER AS $$ BEGIN RAISE EXCEPTION 'interaction_events is append-only: UPDATE and DELETE are not permitted'; END; $$ LANGUAGE plpgsql; CREATE TRIGGER interaction_events_no_update BEFORE UPDATE ON interaction_events FOR EACH ROW EXECUTE FUNCTION prevent_interaction_event_mutation(); CREATE TRIGGER interaction_events_no_delete BEFORE DELETE ON interaction_events FOR EACH ROW EXECUTE FUNCTION prevent_interaction_event_mutation(); -- Annotation threads — groups related annotations for triage (Phase 2) CREATE TABLE annotation_threads ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE CASCADE, title TEXT NOT NULL, description TEXT, created_by UUID REFERENCES users(id), created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); -- Annotations — structured commentary, also append-only by convention -- Phase 2 additions: severity, thread_id CREATE TABLE annotations ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE CASCADE, parent_id UUID REFERENCES annotations(id) ON DELETE CASCADE, body TEXT NOT NULL, category TEXT NOT NULL DEFAULT 'friction', severity TEXT NOT NULL DEFAULT 'medium', thread_id UUID REFERENCES annotation_threads(id) ON DELETE SET NULL, actor_id UUID, actor_type TEXT NOT NULL DEFAULT 'user', widget_state_ref TEXT, retracted_at TIMESTAMP WITH TIME ZONE DEFAULT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX annotations_widget_id_idx ON annotations (widget_id); -- Requirement candidates — escalated from annotations/threads (Phase 2) CREATE TABLE requirement_candidates ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, title TEXT NOT NULL, description TEXT NOT NULL, source_widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE RESTRICT, source_thread_id UUID REFERENCES annotation_threads(id) ON DELETE SET NULL, source_annotation_id UUID REFERENCES annotations(id) ON DELETE SET NULL, category TEXT NOT NULL DEFAULT 'friction', status TEXT NOT NULL DEFAULT 'open', created_by UUID REFERENCES users(id), created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX requirement_candidates_widget_id_idx ON requirement_candidates (source_widget_id); CREATE INDEX requirement_candidates_status_idx ON requirement_candidates (status); -- Triage state history — append-only audit trail of status transitions (Phase 2) CREATE TABLE triage_states ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, candidate_id UUID NOT NULL REFERENCES requirement_candidates(id) ON DELETE CASCADE, status TEXT NOT NULL, notes TEXT, changed_by UUID REFERENCES users(id), changed_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX triage_states_candidate_id_idx ON triage_states (candidate_id); -- Reviewer assignments — one reviewer per candidate (Phase 2) CREATE TABLE reviewer_assignments ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, candidate_id UUID NOT NULL REFERENCES requirement_candidates(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, assigned_by UUID REFERENCES users(id), assigned_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, UNIQUE (candidate_id) ); -- Requirements — promoted from accepted RequirementCandidates (Phase 3) CREATE TABLE requirements ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, title TEXT NOT NULL, description TEXT NOT NULL, source_candidate_id UUID NOT NULL REFERENCES requirement_candidates(id) ON DELETE RESTRICT, status TEXT NOT NULL DEFAULT 'active', created_by UUID REFERENCES users(id), created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX requirements_source_candidate_id_idx ON requirements (source_candidate_id); -- Decision records — governance decisions acting on requirements/candidates (Phase 3) CREATE TABLE decision_records ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, title TEXT NOT NULL, rationale TEXT NOT NULL, outcome TEXT NOT NULL, requirement_id UUID REFERENCES requirements(id) ON DELETE SET NULL, candidate_id UUID REFERENCES requirement_candidates(id) ON DELETE SET NULL, decided_by UUID REFERENCES users(id), decided_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX decision_records_outcome_idx ON decision_records (outcome); CREATE INDEX decision_records_requirement_id_idx ON decision_records (requirement_id); -- Policy references — editorial links from decisions to policy scope (Phase 3) CREATE TABLE policy_references ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, decision_id UUID NOT NULL REFERENCES decision_records(id) ON DELETE CASCADE, policy_scope TEXT NOT NULL, constraint_note TEXT, created_by UUID REFERENCES users(id), created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX policy_references_decision_id_idx ON policy_references (decision_id); -- Implementation change references — editorial links to work items (Phase 3) CREATE TABLE implementation_change_references ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, decision_id UUID NOT NULL REFERENCES decision_records(id) ON DELETE CASCADE, work_item_ref TEXT NOT NULL, system TEXT NOT NULL DEFAULT 'github', linked_by UUID REFERENCES users(id), linked_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX impl_change_refs_decision_id_idx ON implementation_change_references (decision_id); -- Back-reference: which candidate was promoted to a requirement (Phase 3) ALTER TABLE requirement_candidates ADD COLUMN requirement_id UUID REFERENCES requirements(id) ON DELETE SET NULL; -- Deployment records — connect decisions to deployed versions (Phase 4) CREATE TABLE deployment_records ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, impl_ref_id UUID REFERENCES implementation_change_references(id) ON DELETE SET NULL, decision_id UUID NOT NULL REFERENCES decision_records(id) ON DELETE RESTRICT, version_ref TEXT NOT NULL, deployed_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, deployed_by UUID REFERENCES users(id), notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX deployment_records_decision_id_idx ON deployment_records (decision_id); CREATE INDEX deployment_records_deployed_at_idx ON deployment_records (deployed_at DESC); -- Outcome signals — append-only observation of widget behaviour post-deployment (Phase 4) CREATE TABLE outcome_signals ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE CASCADE, deployment_id UUID NOT NULL REFERENCES deployment_records(id) ON DELETE CASCADE, signal_type TEXT NOT NULL, value NUMERIC, observed_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX outcome_signals_widget_id_idx ON outcome_signals (widget_id); CREATE INDEX outcome_signals_deployment_id_idx ON outcome_signals (deployment_id); CREATE INDEX outcome_signals_observed_at_idx ON outcome_signals (observed_at DESC); CREATE OR REPLACE FUNCTION prevent_outcome_signal_mutation() RETURNS TRIGGER AS $$ BEGIN RAISE EXCEPTION 'outcome_signals is append-only: UPDATE and DELETE are not permitted'; END; $$ LANGUAGE plpgsql; CREATE TRIGGER outcome_signals_no_update BEFORE UPDATE ON outcome_signals FOR EACH ROW EXECUTE FUNCTION prevent_outcome_signal_mutation(); CREATE TRIGGER outcome_signals_no_delete BEFORE DELETE ON outcome_signals FOR EACH ROW EXECUTE FUNCTION prevent_outcome_signal_mutation(); -- Change evaluations — one score per deployment (Phase 4) CREATE TABLE change_evaluations ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, deployment_id UUID NOT NULL REFERENCES deployment_records(id) ON DELETE CASCADE, decision_id UUID REFERENCES decision_records(id) ON DELETE SET NULL, score SMALLINT NOT NULL CHECK (score BETWEEN 1 AND 5), rationale TEXT NOT NULL, evaluated_by UUID REFERENCES users(id), evaluated_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, UNIQUE (deployment_id) ); CREATE INDEX change_evaluations_deployment_id_idx ON change_evaluations (deployment_id); -- Agent proposals — AI-generated outputs awaiting human review (Phase 5) CREATE TABLE agent_proposals ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, proposal_type TEXT NOT NULL, -- proposal_type values: summary | requirement_draft | duplicate_flag | -- policy_flag | impl_proposal source_widget_id UUID REFERENCES widgets(id) ON DELETE SET NULL, source_candidate_id UUID REFERENCES requirement_candidates(id) ON DELETE SET NULL, source_thread_id UUID REFERENCES annotation_threads(id) ON DELETE SET NULL, source_decision_id UUID REFERENCES decision_records(id) ON DELETE SET NULL, content TEXT NOT NULL, model_ref TEXT NOT NULL, confidence NUMERIC CHECK (confidence BETWEEN 0 AND 1), status TEXT NOT NULL DEFAULT 'pending', -- status values: pending | accepted | rejected | superseded created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX agent_proposals_proposal_type_idx ON agent_proposals (proposal_type); CREATE INDEX agent_proposals_status_idx ON agent_proposals (status); CREATE INDEX agent_proposals_source_widget_id_idx ON agent_proposals (source_widget_id); CREATE INDEX agent_proposals_created_at_idx ON agent_proposals (created_at DESC); -- One review record per proposal (human decision on AI output) (Phase 5) CREATE TABLE agent_review_records ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, proposal_id UUID NOT NULL REFERENCES agent_proposals(id) ON DELETE CASCADE, reviewer_id UUID REFERENCES users(id), decision TEXT NOT NULL, -- accepted | rejected | modified notes TEXT, reviewed_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, UNIQUE (proposal_id) ); CREATE INDEX agent_review_records_proposal_id_idx ON agent_review_records (proposal_id); -- Confidence annotations — per-dimension breakdown of AI confidence (Phase 5) CREATE TABLE confidence_annotations ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, proposal_id UUID NOT NULL REFERENCES agent_proposals(id) ON DELETE CASCADE, dimension TEXT NOT NULL, -- dimension values: accuracy | relevance | completeness | policy_alignment score NUMERIC NOT NULL CHECK (score BETWEEN 0 AND 1), explanation TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX confidence_annotations_proposal_id_idx ON confidence_annotations (proposal_id); -- ============================================================ -- Phase 6 — Cross-Framework UI Adaptation Layer -- ============================================================ -- Formalises the rules for widget envelope emission: which data-* attributes -- are required, their format, and the contract version. CREATE TABLE envelope_emission_contracts ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, contract_version TEXT NOT NULL UNIQUE, -- e.g. "1.0", "1.1" required_attributes JSONB NOT NULL, -- e.g. ["data-widget-id", "data-view-context", "data-hub-id"] optional_attributes JSONB NOT NULL DEFAULT '[]', validation_rules JSONB NOT NULL DEFAULT '{}', -- machine-readable rules: format checks, presence guards description TEXT, status TEXT NOT NULL DEFAULT 'active', -- status values: draft | active | superseded created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX envelope_emission_contracts_status_idx ON envelope_emission_contracts (status); -- Standardised REST interface contract for external event and annotation -- submission — used by non-IHP adapters. CREATE TABLE interaction_reporting_contracts ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, contract_version TEXT NOT NULL UNIQUE, -- e.g. "1.0" endpoint_path TEXT NOT NULL, -- e.g. "/api/v1/interaction-events" accepted_event_types JSONB NOT NULL, -- e.g. ["clicked","viewed","submitted"] required_fields JSONB NOT NULL, -- minimum payload: widget_id, hub_id, event_type, occurred_at auth_scheme TEXT NOT NULL DEFAULT 'bearer', description TEXT, status TEXT NOT NULL DEFAULT 'active', created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX interaction_reporting_contracts_status_idx ON interaction_reporting_contracts (status); -- Describes how a specific UI technology maps to IHF widget protocol obligations. CREATE TABLE widget_adapter_specs ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, name TEXT NOT NULL UNIQUE, -- e.g. "react-18", "vue-3", "web-component" framework TEXT NOT NULL, -- e.g. "react", "vue", "vanilla" version TEXT NOT NULL, -- adapter spec version, e.g. "1.0" envelope_contract_id UUID REFERENCES envelope_emission_contracts(id), reporting_contract_id UUID REFERENCES interaction_reporting_contracts(id), status TEXT NOT NULL DEFAULT 'draft', -- status values: draft | active | deprecated notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX widget_adapter_specs_framework_idx ON widget_adapter_specs (framework); CREATE INDEX widget_adapter_specs_status_idx ON widget_adapter_specs (status); -- Link widgets to their adapter spec (null = native IHP widget). ALTER TABLE widgets ADD COLUMN adapter_spec_id UUID REFERENCES widget_adapter_specs(id); CREATE INDEX widgets_adapter_spec_id_idx ON widgets (adapter_spec_id); -- Per-hub API key for bearer-token auth on the interaction reporting endpoint. ALTER TABLE hubs ADD COLUMN api_key TEXT; -- Phase 7: Advanced Observability and Operational Integration -- Aggregated pain score per widget, recomputed on demand or scheduled. CREATE TABLE friction_scores ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, widget_id UUID NOT NULL REFERENCES widgets(id), score INTEGER NOT NULL DEFAULT 0, annotation_count INTEGER NOT NULL DEFAULT 0, error_event_count INTEGER NOT NULL DEFAULT 0, regression_flag BOOLEAN NOT NULL DEFAULT FALSE, stale_candidate_count INTEGER NOT NULL DEFAULT 0, last_computed_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, UNIQUE (widget_id) ); CREATE INDEX friction_scores_widget_id_idx ON friction_scores (widget_id); CREATE INDEX friction_scores_score_idx ON friction_scores (score DESC); -- Detected stalls at specific pipeline stages. CREATE TABLE bottleneck_records ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, hub_id UUID NOT NULL REFERENCES hubs(id), stage TEXT NOT NULL, subject_type TEXT NOT NULL, subject_id UUID NOT NULL, stalled_since TIMESTAMP WITH TIME ZONE NOT NULL, severity TEXT NOT NULL DEFAULT 'medium', resolved_at TIMESTAMP WITH TIME ZONE, notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX bottleneck_records_hub_id_idx ON bottleneck_records (hub_id); CREATE INDEX bottleneck_records_stage_idx ON bottleneck_records (stage); CREATE INDEX bottleneck_records_resolved_idx ON bottleneck_records (resolved_at) WHERE resolved_at IS NULL; -- Periodic health snapshots for trend tracking. CREATE TABLE hub_health_snapshots ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, hub_id UUID NOT NULL REFERENCES hubs(id), health_score INTEGER NOT NULL, open_candidates INTEGER NOT NULL DEFAULT 0, regressed_widgets INTEGER NOT NULL DEFAULT 0, stale_decisions INTEGER NOT NULL DEFAULT 0, active_bottlenecks INTEGER NOT NULL DEFAULT 0, computed_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX hub_health_snapshots_hub_id_idx ON hub_health_snapshots (hub_id); CREATE INDEX hub_health_snapshots_computed_at_idx ON hub_health_snapshots (hub_id, computed_at DESC); -- Patterns detected across multiple hubs. CREATE TABLE cross_hub_propagations ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, pattern_type TEXT NOT NULL, source_hub_id UUID REFERENCES hubs(id), affected_hub_ids JSONB NOT NULL DEFAULT '[]', summary TEXT NOT NULL, detected_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, status TEXT NOT NULL DEFAULT 'open', notes TEXT ); CREATE INDEX cross_hub_propagations_status_idx ON cross_hub_propagations (status); CREATE INDEX cross_hub_propagations_pattern_idx ON cross_hub_propagations (pattern_type); -- Phase 8: Federated Hub Maturity -- Explicit ownership record for a widget. CREATE TABLE widget_ownerships ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, widget_id UUID NOT NULL REFERENCES widgets(id), owner_hub_id UUID NOT NULL REFERENCES hubs(id), steward_hub_id UUID REFERENCES hubs(id), ownership_type TEXT NOT NULL DEFAULT 'local', -- 'local' | 'delegated' | 'global' effective_from TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), effective_until TIMESTAMP WITH TIME ZONE, notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX widget_ownerships_widget_id_idx ON widget_ownerships (widget_id); CREATE INDEX widget_ownerships_owner_hub_idx ON widget_ownerships (owner_hub_id); CREATE INDEX widget_ownerships_steward_hub_idx ON widget_ownerships (steward_hub_id); -- Routing rule: automatically routes a RequirementCandidate to another hub. CREATE TABLE hub_routing_rules ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, source_hub_id UUID NOT NULL REFERENCES hubs(id), target_hub_id UUID NOT NULL REFERENCES hubs(id), match_category TEXT, match_widget_type TEXT, priority INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'inactive', -- 'active' | 'inactive' notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX hub_routing_rules_source_idx ON hub_routing_rules (source_hub_id); CREATE INDEX hub_routing_rules_status_idx ON hub_routing_rules (status); -- Routing destination on requirement candidates. ALTER TABLE requirement_candidates ADD COLUMN routed_to_hub_id UUID REFERENCES hubs(id); CREATE INDEX requirement_candidates_routed_hub_idx ON requirement_candidates (routed_to_hub_id) WHERE routed_to_hub_id IS NOT NULL; -- Org-wide policy overlay applied across selected hubs. CREATE TABLE federated_policy_overlays ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, title TEXT NOT NULL, policy_text TEXT NOT NULL, applies_to_hubs JSONB NOT NULL DEFAULT '[]', enforced_from TIMESTAMP WITH TIME ZONE, status TEXT NOT NULL DEFAULT 'draft', -- 'draft' | 'active' | 'retired' notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL ); CREATE INDEX federated_policy_overlays_status_idx ON federated_policy_overlays (status); -- Named governance role assigned to a hub. CREATE TABLE stewardship_roles ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, hub_id UUID NOT NULL REFERENCES hubs(id), role_name TEXT NOT NULL, assigned_to TEXT NOT NULL, granted_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, revoked_at TIMESTAMP WITH TIME ZONE, notes TEXT ); CREATE INDEX stewardship_roles_hub_id_idx ON stewardship_roles (hub_id); CREATE INDEX stewardship_roles_active_idx ON stewardship_roles (revoked_at) WHERE revoked_at IS NULL; -- Long-term archival entry for any IHF artifact. CREATE TABLE archive_records ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, subject_type TEXT NOT NULL, subject_id UUID NOT NULL, archived_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, reason TEXT NOT NULL, archived_by TEXT NOT NULL, lineage_ref TEXT ); CREATE INDEX archive_records_subject_type_idx ON archive_records (subject_type); CREATE INDEX archive_records_subject_id_idx ON archive_records (subject_id); -- Soft-archive flag on widgets. ALTER TABLE widgets ADD COLUMN is_archived BOOLEAN NOT NULL DEFAULT FALSE; CREATE INDEX widgets_is_archived_idx ON widgets (is_archived) WHERE is_archived = TRUE;