feat(pipeline): F2+F3 action/object ontology — DB-backed normalization
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 31s

Migrates ACTION_TYPES (26+8 types), _NEGATIVE_PATTERNS (22), _ACTION_SYNONYMS
(65), and _OBJECT_SYNONYMS (75) from hardcoded dicts to DB tables.

- SQL migration: 003_action_object_ontology.sql (3 tables)
- Migration scripts: f2_migrate_actions.py (34 types, 145 synonyms), f3_migrate_objects.py (75 objects)
- OntologyRegistry cache: 5min TTL, raises RuntimeError if empty (safe fallback to dicts)
- control_ontology.classify_action/get_phase delegate to DB with dict fallback
- control_dedup.normalize_action/normalize_object delegate to DB with dict fallback
- 25 new tests, 446 total pass, 0 regressions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-03 23:47:53 +02:00
parent aab8eeb335
commit 652e3a65a3
7 changed files with 854 additions and 16 deletions
@@ -0,0 +1,58 @@
-- Migration 003: Action & Object Ontology (Block F2+F3)
-- Schema: compliance
-- Run: ssh macmini "docker exec -i bp-core-postgres psql -U breakpilot -d breakpilot_db" < control-pipeline/migrations/003_action_object_ontology.sql
SET search_path TO compliance, public;
-- ========================================
-- action_types — 34 canonical action verbs
-- ========================================
CREATE TABLE IF NOT EXISTS action_types (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canonical_name VARCHAR(50) UNIQUE NOT NULL,
phase VARCHAR(30) NOT NULL,
description_de TEXT,
description_en TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_action_types_phase ON action_types(phase);
-- ========================================
-- action_synonyms — German aliases + negative patterns
-- ========================================
CREATE TABLE IF NOT EXISTS action_synonyms (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canonical_action VARCHAR(50) NOT NULL REFERENCES action_types(canonical_name),
synonym VARCHAR(100) NOT NULL,
language VARCHAR(5) NOT NULL DEFAULT 'de',
source VARCHAR(20) NOT NULL DEFAULT 'manual'
CHECK (source IN ('manual', 'llm', 'migration')),
pattern_type VARCHAR(20) NOT NULL DEFAULT 'alias'
CHECK (pattern_type IN ('alias', 'negative_pattern')),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(synonym, language, pattern_type)
);
CREATE INDEX IF NOT EXISTS idx_action_synonyms_canonical ON action_synonyms(canonical_action);
CREATE INDEX IF NOT EXISTS idx_action_synonyms_pattern_type ON action_synonyms(pattern_type);
-- ========================================
-- object_synonyms — normalized object tokens
-- ========================================
CREATE TABLE IF NOT EXISTS object_synonyms (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canonical_token VARCHAR(100) NOT NULL,
synonym VARCHAR(200) NOT NULL,
language VARCHAR(5) NOT NULL DEFAULT 'de',
source VARCHAR(20) NOT NULL DEFAULT 'manual'
CHECK (source IN ('manual', 'llm', 'migration')),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(synonym, language)
);
CREATE INDEX IF NOT EXISTS idx_object_synonyms_canonical ON object_synonyms(canonical_token);