feat(pipeline): G1 Decision Trace — compliance decision tracking
New table: decision_traces (status, reason, evidence, fix plan per control)
New API:
POST/GET/PUT /v1/decision-traces (CRUD for decisions)
GET /v1/decision-traces/stats (compliance dashboard)
GET /v1/controls/{id}/full-trace (Regulation→Obligation→Control→Decision→Evidence)
454 tests pass, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
-- Migration 006: Decision Traces (G1)
|
||||
-- Schema: compliance
|
||||
-- Run: ssh macmini "docker exec -i bp-core-postgres psql -U breakpilot -d breakpilot_db" < control-pipeline/migrations/006_decision_traces.sql
|
||||
|
||||
SET search_path TO compliance, public;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS decision_traces (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
control_uuid UUID NOT NULL,
|
||||
regulation_id VARCHAR(100),
|
||||
obligation_id VARCHAR(100),
|
||||
|
||||
-- Decision
|
||||
status VARCHAR(30) NOT NULL DEFAULT 'not_assessed'
|
||||
CHECK (status IN ('not_assessed', 'compliant', 'partially_compliant',
|
||||
'not_compliant', 'not_applicable', 'under_remediation')),
|
||||
decision_reason TEXT,
|
||||
decided_by VARCHAR(100),
|
||||
decided_at TIMESTAMPTZ,
|
||||
|
||||
-- Fix/Remediation
|
||||
fix_strategy TEXT,
|
||||
fix_owner VARCHAR(100),
|
||||
fix_target_date DATE,
|
||||
fix_completed_date DATE,
|
||||
|
||||
-- Evidence
|
||||
evidence_ids JSONB DEFAULT '[]',
|
||||
confidence NUMERIC(3,2) DEFAULT 0.0,
|
||||
|
||||
-- Multi-tenant
|
||||
tenant_id UUID,
|
||||
project_id UUID,
|
||||
metadata JSONB DEFAULT '{}',
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_dt_control ON decision_traces(control_uuid);
|
||||
CREATE INDEX IF NOT EXISTS idx_dt_status ON decision_traces(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_dt_tenant ON decision_traces(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_dt_decided_at ON decision_traces(decided_at);
|
||||
|
||||
-- Updated-at trigger
|
||||
CREATE OR REPLACE FUNCTION update_decision_traces_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_decision_traces_updated_at ON decision_traces;
|
||||
CREATE TRIGGER trg_decision_traces_updated_at
|
||||
BEFORE UPDATE ON decision_traces
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_decision_traces_updated_at();
|
||||
Reference in New Issue
Block a user