Skip to content

Export what is contested: fact conflicts and axiom violations in RDF - #922

Merged
WaylandYang merged 4 commits into
deeplethe:devfrom
ansonnmm:issue-564-export-contest-state
Sep 26, 2026
Merged

WaylandYang merged 4 commits into
deeplethe:devfrom
ansonnmm:issue-564-export-contest-state

Conversation

@ansonnmm

Copy link
Copy Markdown
Contributor

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:

  • utopia:FactConflict — prior/incoming statements on a disputed slot,
    status/resolution, truthful timestamps (a withdrawn row's closedAt is
    the invalidation moment).
  • utopia:AxiomViolation — the statements the finding is about, ordered
    rdf:List cycle evidence, status/resolution, and — for open rows only —
    the governing criterion and the relation it lives on.

Maintains the current-ontology invariant: open means reconciled against
the 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_changed and re-detect in the
same transaction; criterion_changed stays outside the human review
vocabulary (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_changed to the resolution CHECK and
scrubs legacy violation paths containing non-fact members (temporary rule
derivation IDs could reach path since #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/271
  • cargo test -p utopia-server rdf + contested-export tests — 29/29
  • New: 9-test reconciliation suite, read-only export test, contest-state
    refusal test (422), criterion_changed submission rejection test
  • cargo clippy, cargo fmt --check clean
  • Three independent fresh reviews; final narrow review ACCEPT

Generated with Devin

ansonnmm and others added 4 commits September 26, 2026 01:21
…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>
…thub.com>

I, Anson <310461893+ansonnmm@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 67d36a9
I, Anson <310461893+ansonnmm@users.noreply.github.com>, hereby add my Signed-off-by to this commit: c876303

Signed-off-by: Anson <310461893+ansonnmm@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>
@WaylandYang

Copy link
Copy Markdown
Contributor

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 WaylandYang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@WaylandYang
WaylandYang merged commit 06768aa into deeplethe:dev Sep 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose conflict and review state in the supported RDF read contract

2 participants