generated from coulomb/repo-seed
Some checks failed
Test / test (push) Has been cancelled
Closes the IHF improvement loop. Full antifragility chain now traversable: Widget → Annotation → Candidate → Requirement → Decision → Deployment → OutcomeSignal New artifacts: - DeploymentRecord (immutable, links DecisionRecord to a deployed version) - OutcomeSignal (append-only; DB trigger prevents UPDATE/DELETE) - ChangeEvaluation (one-per-deployment; UNIQUE constraint; 1–5 score) New capabilities: - DeploymentRecordsController (index, show, new, create) - RecordOutcomeSignalAction — capture improved/regressed/neutral/inconclusive signals - Pre/post comparison panel on deployment show (±30-day event/annotation counts) - Regression detection — improved signal followed by high/critical annotation - ChangeEvaluation — idempotent score+rationale per deployment - Recurrence tracking — cycle count per widget, leaderboard - AntifragilityDashboardAction (autoRefresh, 5 panels) per hub - Phase 4 integration tests (T01–T08 logic coverage) - docs/phase4-summary.md; SCOPE.md updated to Phase 4 complete State Hub: workstream 07e9c860 → completed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
266 lines
11 KiB
PL/PgSQL
266 lines
11 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);
|