Repository navigation
feat(shacl): explicit graph scopes + dataset-preserving SHACL-AF expansion - #6653
Conversation
…nsion (#6614) Add an opt-in, additive SHACL-AF API (feature `shacl-af`) for datasets: `GraphScope` (Default / Named / explicit Union) selects which asserted graphs the rule engine sees; the selected named graphs are also the only ones a sh:SPARQLRule body reaches via GRAPH (FROM NAMED cannot widen it). `expand_dataset` materializes derived triples into a `Destination` (default or a named graph, deduplicated there) while preserving every asserted named graph, and returns the Inference diagnostics (iterations / capped). `apply_rules` / `expand` are unchanged (GraphScope::Default delegates to them). Capability addition, not a conformance fix. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n
…copes Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n
🔎 Codex reviewer —
|
A literal or triple-term destination produced a dataset whose graph name cannot be persisted and reopened. The type now rules it out. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n
🔎 Codex reviewer —
|
|
Local ci-fast gate (GitHub Actions outage; Jesse approved local-gate merges). PR head
Squash-merging under the local-gate rule. Generated by Claude Code |
Requested by Jesse · project thread
Summary
This is a capability addition, not a conformance fix. SHACL-AF over a single selected data graph is valid; this adds an opt-in seam for datasets (feature
shacl-af), as requested in #6614.New public API in
sparq_shacl::rules(re-exported at the crate root):GraphScope::{Default, Named(Term), Union { default: bool, named: Vec<Term> }}: which asserted graphs the rule engine sees. The selected graphs are merged into the working default graph used for targets, paths,sh:condition, node expressions and SPARQL rules, so cross-graph joins work. The selected named graphs are also the only named graphs ash:SPARQLRulebody can reach (GRAPH <g>/GRAPH ?g). An unselected graph behaves like an absent one, andFROM/FROM NAMEDcannot widen that set. Nothing is unioned implicitly: the default graph is added to a union only throughdefault: true.apply_rules_in_scope(data, shapes, &scope)/apply_rules_in_scope_with_model(..)->Inference.Destination::{Default, Named(Term)}+expand_dataset(data, shapes, &scope, &dest) -> DatasetExpansion { dataset, inference }: returns a copy of the whole dataset with every asserted named graph kept, including unrelated ones. Derived triples are written to the destination and deduplicated against what that graph already holds. An identical triple asserted in another graph is left where it is. A named destination that does not exist yet is appended. Theiterations/cappeddiagnostics are returned, not hidden. Inferred triples feed later passes through the working scope whatever the destination is. The input is never mutated.apply_rules/apply_rules_with_model/expandbehave exactly as before: they now call a shared internalrun_ruleswith no named graphs, andGraphScope::Defaulthands off to them. A test pins the issue's legacy example.Out of scope: an inferred-triple budget beyond the existing
MAX_ITERATIONScap, and dataset-scoped validation (validate_with_domain, related #4293).Closes #6614
Base gate (always required)
cargo build --workspacesucceeds. (left to CI; ran crate-scoped)cargo clippy --workspace --exclude sparq-py --all-targets -- -D warningsis clean. (left to CI; rancargo clippy -p sparq-shacl --all-targets --all-features -- -D warnings, clean)cargo testpasses for every crate this PR touches. (cargo test -p sparq-shacl --all-features: all pass exceptdiff_fuzz::node_adapter_excludes_nested_sh_detail, which fails locally only because the npm package@zazuko/env-nodeis not installed. That is environmental and unrelated to this change.)Targeted re-evaluation (check the rows that apply to your change)
skills/shacl-validation/SKILL.md(new section, API list, feature-gate symbol list, description) and the crate README in this change, and re-ran the sparq-shacl tests.sparq-shacl): fetched the pinned W3C suite (fetch-shacl-tests.sh, b6e73695) and re-ran allw3c_*ratchets with default features and withshacl-af. Both are green.New tests are in
crates/sparq-shacl/tests/shacl_af_graph_scopes.rs(12 tests). They cover: the issue's example (legacy behaviour unchanged); default, named and explicit-union scopes, including an absent graph and an empty union; a cross-graph join over targets, path andsh:condition; allowed and disallowedGRAPHaccess in SPARQLRule bodies; aFROM NAMEDbypass attempt; preservation of unrelated graphs and the input; default and named destinations, including merging into an existing graph; per-destination dedup; fixpoint chaining with a named destination; and cap-diagnostic propagation. The two access-boundary tests were mutation-checked: they fail if every named graph is exposed.Ratchets and conventions
TODO/FIXMEmarkers. (none added)Security
Performance check (local, before vs after main)
Non-canonical, shared-box measurements: 4 cores, other jobs running, release profile. Each binary was built from
origin/mainand from this branch in separate target dirs. The runs alternate main/PR to cancel drift. Noise band is the p10–p90 spread of the per-run times, relative to the median.apply_rules+expand, default graph, 3,000 focus nodes, TripleRule + SPARQLRule + a chained TripleRule (9,000 inferred triples); 15 runs × median of 3Verdict: no regression beyond noise. On the default-graph path,
run_rulesreceives an empty named-graph slice, andexpand_graphis the same code factored intograph_triples.🤖 Generated with Claude Code
https://claude.ai/code/session_01ScyGGohDhirnbLbrUSnA5n
Generated by Claude Code