Files
inter-hub/Application/Schema.sql
Bernd Worsch 840b0e5c7b feat(P2+P3): IHF Phase 2 complete; register Phase 3 workplan
Phase 2 — Structured Feedback and Triage (IHUB-WP-0002):
- Schema: annotation_threads, requirement_candidates, triage_states,
  reviewer_assignments; annotations extended with severity + thread_id
- AnnotationThreadsController: create threads, assign annotations
- RequirementCandidatesController: CRUD, escalation, triage lifecycle,
  reviewer assignment, my-queue
- Annotation severity (low/medium/high/critical) with Tailwind color cues
- TriageDashboardAction on HubsController with autoRefresh
- Integration tests (T01–T09), SCOPE.md updated, docs/phase2-summary.md

Phase 3 — Governance and Decision Linkage (IHUB-WP-0003):
- Workplan registered: 9 tasks, State Hub workstream 5f201ee3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 10:42:56 +00:00

151 lines
5.9 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)
);