The gap
Migration 0070 (0048) protects 39 reference edges — 26 by composite foreign key, 13 by trigger. The list was enumerated by hand, and nothing forces a reference column added later to join it.
The tests that ship with the migration assert that what was installed is installed:
SELECT COUNT(*) FROM pg_constraint WHERE contype = 'f' AND conname IN (...)
SELECT COUNT(*) FROM pg_trigger WHERE NOT tgisinternal AND tgname IN (...)
They never ask the reverse question: is every reference column in the schema covered by something? So a new table with a predicate_id, or a new entity_id on an existing table, gets no coverage and no failing test.
This applies to both mechanisms equally. A new column does not automatically receive a composite key any more than it receives a trigger.
Why it matters more than an ordinary missing test
An invariant that silently stops covering new edges is worse than no invariant, because it is load-bearing for other decisions. The exporter mints local IRIs on the strength of it, and 0048 records that as the reason the invariant exists. Once people believe the schema guarantees same-base references, nobody re-checks — and the failure mode stays exactly what it was before 0070: a complete-looking export naming a row in another base, or a vocabulary reference resolving to nothing, with no error either way.
Shape of the fix
A catalog-driven test that enumerates every foreign-key column between kb-owned tables from pg_constraint, and asserts each is either
- part of a composite
(kb_id, ref) → (kb_id, id) key, or
- named by one of the same-KB trigger functions, or
- on an explicit, commented exemption list (the JSONB predicate references in
attribute_rules.conclude_expr are the known one — 0048 records that they are enforced export-side instead).
Failing loudly on an uncovered column is the point; the exemption list is what keeps it honest rather than making people disable it.
Background
Found while reviewing #832. The read-only §0 scan was run across 22 local databases at that time: 21 clean, and one distinct cross-KB row (relation_types.inverse_of pointing at another base's employs, present in three copies of the same database) which has been repaired. That row landed on an edge the composite keys cover, but the motivating failure modes in 0048 — evidence and derivation provenance — are on trigger-covered edges, which is why this needs to cover both halves.
Related: #842 (hybrid split versus uniform triggers).
The gap
Migration 0070 (0048) protects 39 reference edges — 26 by composite foreign key, 13 by trigger. The list was enumerated by hand, and nothing forces a reference column added later to join it.
The tests that ship with the migration assert that what was installed is installed:
They never ask the reverse question: is every reference column in the schema covered by something? So a new table with a
predicate_id, or a newentity_idon an existing table, gets no coverage and no failing test.This applies to both mechanisms equally. A new column does not automatically receive a composite key any more than it receives a trigger.
Why it matters more than an ordinary missing test
An invariant that silently stops covering new edges is worse than no invariant, because it is load-bearing for other decisions. The exporter mints local IRIs on the strength of it, and 0048 records that as the reason the invariant exists. Once people believe the schema guarantees same-base references, nobody re-checks — and the failure mode stays exactly what it was before 0070: a complete-looking export naming a row in another base, or a vocabulary reference resolving to nothing, with no error either way.
Shape of the fix
A catalog-driven test that enumerates every foreign-key column between kb-owned tables from
pg_constraint, and asserts each is either(kb_id, ref) → (kb_id, id)key, orattribute_rules.conclude_exprare the known one — 0048 records that they are enforced export-side instead).Failing loudly on an uncovered column is the point; the exemption list is what keeps it honest rather than making people disable it.
Background
Found while reviewing #832. The read-only §0 scan was run across 22 local databases at that time: 21 clean, and one distinct cross-KB row (
relation_types.inverse_ofpointing at another base'semploys, present in three copies of the same database) which has been repaired. That row landed on an edge the composite keys cover, but the motivating failure modes in 0048 — evidence and derivation provenance — are on trigger-covered edges, which is why this needs to cover both halves.Related: #842 (hybrid split versus uniform triggers).