generated from coulomb/repo-seed
Adds Phase 6 tables: envelope_emission_contracts, interaction_reporting_contracts, widget_adapter_specs. Adds adapter_spec_id FK to widgets and api_key to hubs. Seeds v1.0 contracts in migration. Registers Phase 6 controller types and routes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
382 lines
16 KiB
PL/PgSQL
382 lines
16 KiB
PL/PgSQL
-- 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;
|