Export what is contested: fact conflicts and axiom violations in RDF - #922
Conversation
…eeplethe#564) The export carried the assertions but not the epistemic state Utopia has already established about them — a downstream consumer had to reimplement conflict and violation detection to know which statements are contested. Two distinct resource classes per ADR 0062: - FactConflict: the prior and incoming statements on a disputed slot, with status, resolution, and truthful timestamps (a withdrawn row's closedAt is the invalidation moment, not a page open). - AxiomViolation: the statements the finding is about, an ordered rdf:List for cycle evidence, status/resolution, and — for open rows only — the governing criterion and the relation it lives on. Precondition is the current-ontology invariant: status = open means the finding has been reconciled against the current ontology. Criterion-changing ontology edits (axiom flags, temporal, inverse_of/sub_property_of, domain/range, class parent hierarchy, deleting a referenced class or linked relation) now settle superseded open rows as criterion_changed and re-detect in the same transaction; criterion_changed is system-generated and stays outside the human review vocabulary. Label-only edits skip detection. Export stays read-only inside the existing REPEATABLE READ snapshot; no lazy cleanup or detection is triggered. Cross-KB or unresolved references, and open violations without a resolvable criterion, refuse the export. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Follow-up from review: ontology import now captures the pre-import detection baseline and reconciles after apply (same criterion_changed semantics as interactive edits; a failed reconciliation errors instead of reporting violations: 0). A contested-state preflight refuses the export before the first byte when referenced facts are foreign/dangling, storage values fall outside the contract vocabulary, or an open violation's criterion cannot be resolved — including a malformed derived_contradiction detail.predicate_id, which no longer falls back to the asserted fact's predicate. signature maps to rdfs:domain + rdfs:range per the ADR. Second review pass: reconciliation now partitions stale rows by row identity, not stored evidence — non-cycle kinds compare (kind, left, right) like their unique index, so a drifted evidence path can no longer demote a baseline finding from criterion_changed to deletion. A failed import still reconciles against the already-committed criteria (best-effort, original error preserved). Preflight additionally rejects status↔resolution-inconsistent rows; duplicate onStatement for self-pointing findings is deduplicated; two more store tests pin evidence-drift partition and sub_property_of unlinking.
The backup manifest compatibility constant is the count of migration files, guarded by schema_version_policy_compares_against_current. 0081 added a file without bumping it (78 != 79). Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ck's range), the record is accepted, and the schema version counts 81 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Wayland Yang <wayland0916@gmail.com>
|
Maintainer edits so this can land behind #938 and #932: merged current dev; the migration is renumbered 0081 → 0093 because 0081 to 0089 belong to the identity track's work in flight (the record and comments say 0093 now); CURRENT_SCHEMA_VERSION is 81; and 0062's status line reads accepted. Nothing in the code changed. |
WaylandYang
left a comment
There was a problem hiding this comment.
This is the stronger invariant as decided on #564, and it is carried where it belongs: every criterion mutation (axioms, temporal, inverse and sub-property links, domain and range, class parents, class and relation deletion, import) takes a baseline detection, writes, re-detects and settles superseded open rows as criterion_changed in the same transaction, with import's non-atomic apply handled honestly. The export stays read-only in the snapshot, refuses on cross-KB, dangling, off-vocabulary or unlinkable rows, and the two classes keep their different resolution vocabularies. The reconciliation suite and the format-parity test cover what matters. Thank you for holding the record until the invariant was real; merging.
Summary
Fixes #564 — the RDF export carried the assertions but not the epistemic
state Utopia has already established about them. Downstream consumers had
to reimplement conflict/violation detection to know which statements are
contested.
Two distinct resource classes per ADR 0062:
status/resolution, truthful timestamps (a withdrawn row's
closedAtisthe invalidation moment).
rdf:Listcycle evidence, status/resolution, and — for open rows only —the governing criterion and the relation it lives on.
Maintains the current-ontology invariant:
openmeans reconciled againstthe current ontology. Criterion-changing edits (axiom flags, temporal,
inverse_of/sub_property_of, domain/range, class parents, deleting a
referenced class or relation, relation-to-attribute demotion, import)
settle superseded open rows as
criterion_changedand re-detect in thesame transaction;
criterion_changedstays outside the human reviewvocabulary (422 on submission). Label-only edits skip detection.
Export stays read-only inside the existing REPEATABLE READ snapshot — no
lazy cleanup or detection. Cross-KB/dangling references, invalid
vocabulary, incoherent status/resolution, and open violations without a
resolvable criterion refuse the export before the first byte.
Migration 0081 adds
criterion_changedto the resolution CHECK andscrubs legacy violation paths containing non-fact members (temporary rule
derivation IDs could reach
pathsince #861).Explicitly out of scope (ADR 0062):
pending_facts, duplicate review,mappings, agent proposals,
decided_by, person identity,as_of,business-rule bodies, generic workflow export. Resolve-to-reopen history
is not reconstructable by design.
Verification
cargo test -p utopia-store— 271/271cargo test -p utopia-serverrdf + contested-export tests — 29/29refusal test (422), criterion_changed submission rejection test
cargo clippy,cargo fmt --checkcleanGenerated with Devin