Files
breakpilot-core/control-pipeline/migrations/001_dependency_engine.sql
Benjamin Admin 42ab5ead26 feat(pipeline): implement Control Dependency Engine (Block 9)
Core engine (dependency_engine.py):
- 5 dependency types: prerequisite, supersedes, compensating_control,
  conditional_requirement, scope_exclusion
- Generic condition evaluator (JSONB rules with AND/OR/NOT/field ops)
- Priority-based conflict resolution
- Cycle detection (DFS) + topological sort
- Full evaluation with MCP-compatible dependency_resolution trace
- 39 tests all passing (incl. GHV scenario from user requirements)

Automatic generator (dependency_generator.py):
- Ontology-based: same normalized_object + phase sequence -> prerequisite
- Pattern-based: define->implement, implement->monitor, etc.
- Domain packs: YAML rules for GDPR, AI Act, CRA, Security, Labor Contracts
- 14 tests all passing

API routes (dependency_routes.py):
- CRUD for dependencies
- POST /evaluate with dependency resolution
- POST /generate (auto-generation with dry_run)
- POST /validate (cycle detection)
- GET /graph (nodes + edges for visualization)

Prompt enhancement (decomposition_pass.py):
- Added dependency_hints + lifecycle_phase_order to Pass 0b prompt
- Stored in generation_metadata for post-processing

DB migration: control_dependencies + control_evaluation_results tables

126 tests total, all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-26 20:28:10 +02:00

80 lines
2.9 KiB
SQL

-- Migration 001: Control Dependency Engine (Block 9)
-- Schema: compliance (search_path already set)
-- Run: psql -U breakpilot -d breakpilot_db -f 001_dependency_engine.sql
SET search_path TO compliance, public;
-- ========================================
-- control_dependencies
-- ========================================
CREATE TABLE IF NOT EXISTS control_dependencies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source_control_id UUID NOT NULL REFERENCES canonical_controls(id) ON DELETE CASCADE,
target_control_id UUID NOT NULL REFERENCES canonical_controls(id) ON DELETE CASCADE,
dependency_type VARCHAR(30) NOT NULL
CHECK (dependency_type IN (
'prerequisite',
'conditional_requirement',
'supersedes',
'compensating_control',
'scope_exclusion'
)),
condition JSONB DEFAULT '{}',
effect JSONB NOT NULL DEFAULT '{}',
priority INTEGER NOT NULL DEFAULT 100,
generation_method VARCHAR(30) NOT NULL DEFAULT 'manual'
CHECK (generation_method IN (
'manual', 'ontology', 'pattern', 'domain_pack', 'llm_hint'
)),
generation_metadata JSONB DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT uq_dependency_edge UNIQUE (source_control_id, target_control_id, dependency_type),
CONSTRAINT no_self_dependency CHECK (source_control_id != target_control_id)
);
CREATE INDEX IF NOT EXISTS idx_dep_target ON control_dependencies(target_control_id) WHERE is_active = TRUE;
CREATE INDEX IF NOT EXISTS idx_dep_source ON control_dependencies(source_control_id) WHERE is_active = TRUE;
CREATE INDEX IF NOT EXISTS idx_dep_type ON control_dependencies(dependency_type);
-- ========================================
-- control_evaluation_results
-- ========================================
CREATE TABLE IF NOT EXISTS control_evaluation_results (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
control_id UUID NOT NULL REFERENCES canonical_controls(id) ON DELETE CASCADE,
evaluation_run_id UUID NOT NULL,
company_profile JSONB DEFAULT '{}',
raw_status VARCHAR(30) NOT NULL,
resolved_status VARCHAR(30) NOT NULL
CHECK (resolved_status IN (
'pass', 'fail', 'not_applicable',
'partially_satisfied', 'compensated_fail',
'review_required'
)),
dependency_resolution JSONB DEFAULT '[]',
confidence FLOAT DEFAULT 1.0,
reasoning TEXT DEFAULT '',
evaluated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT uq_eval_per_run UNIQUE (control_id, evaluation_run_id)
);
CREATE INDEX IF NOT EXISTS idx_eval_run ON control_evaluation_results(evaluation_run_id);
CREATE INDEX IF NOT EXISTS idx_eval_control ON control_evaluation_results(control_id);
CREATE INDEX IF NOT EXISTS idx_eval_status ON control_evaluation_results(resolved_status);