From c7e0a6cf4223fe2be5b3d139cfa1adced0e603c9 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 14 May 2026 15:33:06 +0100 Subject: [PATCH] fix(codegen): add self-FK on simulation_branches + four enum CHECKs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #43. The overlay DDL had no structural enforcement of: - `simulation_branches.parent_branch` referencing `branch_id` (self-referencing tree of branches) - the four enum-shaped TEXT columns: * `simulation_branches.status` ∈ {active, merged, abandoned} * `provenance_log.operation` ∈ {insert, update, delete, transform} * `access_policies.access_level` ∈ {read, write, admin, deny} * `lineage_graph.derivation_type` ∈ {copy, transform, aggregate, join, filter} Anything could be written to those columns; downstream consumers had to re-validate every row. Add the FK (NULL still allowed for root branches) and the four CHECK constraints. Test `test_overlay_has_enum_checks_and_fk` asserts each by substring against the generated DDL. `cargo clippy --all-targets -- -D warnings` clean; 36 unit tests pass. Co-Authored-By: Claude Opus 4.7 --- src/codegen/overlay.rs | 56 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/src/codegen/overlay.rs b/src/codegen/overlay.rs index 5af04e6..20e6612 100644 --- a/src/codegen/overlay.rs +++ b/src/codegen/overlay.rs @@ -126,7 +126,8 @@ fn generate_provenance_table() -> String { \x20 actor TEXT NOT NULL,\n\ \x20 timestamp TEXT NOT NULL, -- ISO 8601\n\ \x20 before_snapshot TEXT, -- JSON of entity state before operation\n\ - \x20 transformation TEXT -- description of transformation applied\n\ + \x20 transformation TEXT, -- description of transformation applied\n\ + \x20 CHECK (operation IN ('insert','update','delete','transform'))\n\ );\n\ CREATE INDEX IF NOT EXISTS idx_provenance_entity ON verisimdb_provenance_log(entity_id);\n\ CREATE INDEX IF NOT EXISTS idx_provenance_table ON verisimdb_provenance_log(table_name);\n\n" @@ -153,7 +154,8 @@ fn generate_lineage_table() -> String { \x20 derivation_type TEXT NOT NULL, -- copy, transform, aggregate, join, filter\n\ \x20 description TEXT,\n\ \x20 created_at TEXT NOT NULL, -- ISO 8601\n\ - \x20 CHECK (source_entity <> target_entity OR source_table <> target_table)\n\ + \x20 CHECK (source_entity <> target_entity OR source_table <> target_table),\n\ + \x20 CHECK (derivation_type IN ('copy','transform','aggregate','join','filter'))\n\ );\n\ CREATE INDEX IF NOT EXISTS idx_lineage_source ON verisimdb_lineage_graph(source_entity);\n\ CREATE INDEX IF NOT EXISTS idx_lineage_target ON verisimdb_lineage_graph(target_entity);\n\n" @@ -196,7 +198,8 @@ fn generate_access_policy_table() -> String { \x20 access_level TEXT NOT NULL, -- read, write, admin, deny\n\ \x20 condition TEXT, -- SQL-like filter condition\n\ \x20 created_at TEXT NOT NULL, -- ISO 8601\n\ - \x20 active INTEGER NOT NULL DEFAULT 1\n\ + \x20 active INTEGER NOT NULL DEFAULT 1,\n\ + \x20 CHECK (access_level IN ('read','write','admin','deny'))\n\ );\n\ CREATE INDEX IF NOT EXISTS idx_access_table ON verisimdb_access_policies(target_table);\n\ CREATE INDEX IF NOT EXISTS idx_access_principal ON verisimdb_access_policies(principal);\n\n" @@ -211,12 +214,13 @@ fn generate_simulation_table() -> String { "-- Simulation: what-if branching and sandbox queries\n\ CREATE TABLE IF NOT EXISTS verisimdb_simulation_branches (\n\ \x20 branch_id TEXT PRIMARY KEY,\n\ - \x20 parent_branch TEXT, -- NULL for root branch\n\ + \x20 parent_branch TEXT REFERENCES verisimdb_simulation_branches(branch_id), -- NULL for root\n\ \x20 name TEXT NOT NULL,\n\ \x20 description TEXT,\n\ \x20 created_at TEXT NOT NULL, -- ISO 8601\n\ \x20 merged_at TEXT, -- ISO 8601, NULL if not merged\n\ - \x20 status TEXT NOT NULL DEFAULT 'active' -- active, merged, abandoned\n\ + \x20 status TEXT NOT NULL DEFAULT 'active', -- active, merged, abandoned\n\ + \x20 CHECK (status IN ('active','merged','abandoned'))\n\ );\n\n\ CREATE TABLE IF NOT EXISTS verisimdb_simulation_deltas (\n\ \x20 delta_id TEXT PRIMARY KEY,\n\ @@ -285,6 +289,48 @@ mod tests { assert!(ddl.contains("verisimdb_simulation_branches")); } + /// All four enum-shape columns must be CHECK-constrained, and + /// simulation_branches.parent_branch must be a self-referencing FK + /// (closes #43). + #[test] + fn test_overlay_has_enum_checks_and_fk() { + let schema = test_schema(); + let octad = OctadConfig { + enable_provenance: true, + enable_lineage: true, + enable_temporal: true, + enable_access_control: true, + enable_constraints: true, + enable_simulation: true, + }; + let ddl = generate_sidecar_schema(&schema, &octad); + + // Self-referencing FK on parent_branch. + assert!( + ddl.contains("parent_branch TEXT REFERENCES verisimdb_simulation_branches(branch_id)"), + "simulation_branches.parent_branch is missing the self-referencing FK" + ); + // Enum CHECKs. + assert!( + ddl.contains("CHECK (status IN ('active','merged','abandoned'))"), + "simulation_branches.status enum CHECK missing" + ); + assert!( + ddl.contains("CHECK (operation IN ('insert','update','delete','transform'))"), + "provenance_log.operation enum CHECK missing" + ); + assert!( + ddl.contains("CHECK (access_level IN ('read','write','admin','deny'))"), + "access_policies.access_level enum CHECK missing" + ); + assert!( + ddl.contains( + "CHECK (derivation_type IN ('copy','transform','aggregate','join','filter'))" + ), + "lineage_graph.derivation_type enum CHECK missing" + ); + } + /// Lineage edges must refuse self-loops at the storage layer /// (closes #42). The DAG claim in the README would be unenforced /// without this check.