pgrdf 0.3.0

Rust-native PostgreSQL extension for RDF, SPARQL, SHACL and OWL reasoning
# 05 — Validation

> **Status: stub (Phase 5 v0.3).** The SQL surface
> `pgrdf.validate(data BIGINT, shapes BIGINT) → JSONB` exists; the
> body returns `{"status": "stub", …}` because the upstream SHACL
> processor cannot currently coexist with our OWL 2 RL reasoner in
> one workspace. See [`specs/ERRATA.v0.2.md`]../specs/ERRATA.v0.2.md
> **E-009** for the full dep-resolution analysis.

## Surface

```sql
SELECT pgrdf.validate(data_graph_id, shapes_graph_id);
--   data_graph_id   — graph containing the assertions to validate
--   shapes_graph_id — graph containing the SHACL shapes (sh:NodeShape /
--                     sh:PropertyShape) those assertions must satisfy
-- Returns JSONB:
--   { "status": "stub",
--     "reason": "ERRATA E-009 — …",
--     "data_graph_id":    <i64>,
--     "shapes_graph_id":  <i64>,
--     "data_triples":     <i64>,
--     "shapes_triples":   <i64>,
--     "conforms":         null,        -- placeholder; never set
--     "results":          []           -- placeholder; never populated
--   }
```

`conforms` is `null` (rather than the W3C `sh:conforms` boolean) so
calling code can distinguish "stub" from "validated and clean".

## Why it's a stub today

We need a SHACL processor and an OWL 2 RL reasoner in the same
binary. The two upstream crates have an incompatible dependency
shape:

1. `shacl_validation 0.2.x` (latest 0.2.12, 2026-04-22) ships an
   unfinished `iri_s``rudof_iri` migration. `shacl_ast 0.2.9`
   contains references to both crates and fails to compile:
   `expected rudof_iri::IriS, found iri_s::IriS`.
2. `shacl_validation 0.1.149` (last 0.1.x) compiles in isolation,
   but its transitives enable `oxrdf`'s `rdf-12` feature. That
   feature adds the `TermRef::Triple(_)` variant to `oxrdf::TermRef`.
3. `reasonable 0.4.1` (our OWL 2 RL reasoner, see
   [`docs/04-inference.md`]04-inference.md) has a non-exhaustive
   pattern match in `common.rs::oxrdf_to_rio` that does not cover
   the triple-term variant.

So we can have `shacl_validation OR reasonable` in the workspace
today, but not both. Phase 4 (inference) shipped first and is the
load-bearing user-facing surface; Phase 5 ships as a stub until
upstream catches up.

## What unblocks the real integration

Either of:

- **`shacl_validation` 0.2.x release** that compiles cleanly against
  a single `iri_s` major (i.e. completes the `rudof_iri` migration
  in the AST + IR crates).
- **`reasonable`** publishes a version whose pattern matches handle
  RDF 1.2 triple-term operands (or otherwise tolerates the
  `rdf-12` feature being on).

When that happens the unblock ticket is straightforward (per
[`specs/SPEC.pgRDF.LLD.v0.4-FUTURE.md`](../specs/SPEC.pgRDF.LLD.v0.4-FUTURE.md) §9,
the real integration is gated to v0.5 — v0.4 keeps the stub):
1. Uncomment the `shacl_validation` line in `Cargo.toml`.
2. Replace the stub body of `pgrdf.validate` with serialization of
   both graphs to N-Triples + a `GraphValidation::from_graph(…)
   .validate(&schema_ir)` call.
3. Translate `ValidationReport.results()` into the JSONB
   `sh:ValidationReport` shape.

## Scope when the real validator lands

Per LLD v0.3 §5.3:

- ✅ SHACL Core node + property shapes
- ✅ Cardinality, value-type, value-range constraints
- ⚠️ SHACL-SPARQL constraints — Phase 5 stretch
- ❌ SHACL inheritance via `sh:and` / `sh:or` / `sh:xone`      depends on processor support
- ❌ Custom validators via `sh:js` — out of scope

## See also

- Implementation: [`src/validation/shacl.rs`]../src/validation/shacl.rs
- Regression: [`tests/regression/sql/70-validate-stub.sql`]../tests/regression/sql/70-validate-stub.sql
- ERRATA: [`E-001`]../specs/ERRATA.v0.2.md — original
  `shacl-rust``shacl_validation` supersession.
- ERRATA: [`E-009`]../specs/ERRATA.v0.2.md — current dep-block.
- Forward-looking: [`specs/SPEC.pgRDF.LLD.v0.4-FUTURE.md`]../specs/SPEC.pgRDF.LLD.v0.4-FUTURE.md §9 —
  real SHACL integration (v0.5, gated on E-009 resolution).