shacl-runner: swap to shacl-engine so SHACL-SPARQL shapes actually run (#58) - #60
Merged
Conversation
#58) The second half of #58. rdf-validate-shacl cannot execute SPARQL-based constraints at any version in range — it throws: Cannot find validator for constraint component sh#SPARQLConstraintComponent Core SHACL cannot traverse a property path, so every cross-row invariant — referential agreement, acyclicity, "this status implies that relationship" — needs sh:sparql. The gate could therefore enforce per-node structure and nothing relational, and said so by failing in a way that reads like an infrastructure fault rather than an unsupported feature. shacl-engine is the same RDF/JS stack (same @zazuko/env-node the kit already depends on) and implements SHACL-SPARQL constraints and targets. Report format and exit codes are unchanged; printViolations normalises the two engines' differing result shapes (path as segments vs a term, constraintComponent vs sourceConstraintComponent) so consumers reading these logs see no difference. THE HAZARD, AND THE GUARDRAIL ----------------------------- shacl-engine ships CORE validations only. Without targetResolvers and validations from shacl-engine/sparql.js it does not warn and does not throw — it silently skips every sh:sparql shape. Measured on the new cyclic fixture: default config → conforms: TRUE (the cycle is invisible) with the sparql opt-in → conforms: false, cycle reported That is a worse failure mode than the validator it replaces, which at least threw loudly. So the swap lands with a required-to-fail fixture, and that guard was itself verified by removing the opt-in: the negative test goes red while the CONFORMING sparql test still passes — which is exactly why a positive test alone would not have caught it. DEPENDENCY FOOTPRINT — the reason this is a draft ------------------------------------------------- shacl-engine hard-depends on @comunica/query-sparql-rdfjs-lite, so the whole Comunica engine arrives with it: 297 → 758 packages (+468). It is not a peer or optional dependency, so there is no way to take shacl-engine's core validations without it. For a kit that consuming sites vendor hash-pinned, that is a real supply-chain cost and wants a decision rather than a merge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MqhgW5RCRn6wWfjKvDsxTq
…ting The kit ships its gates as hash-pinned Nix CLIs — npmDepsHash content-addresses the whole dependency tree, which is what makes a consuming site's vendored gate deterministic rather than re-resolved at install. Nothing built it, and the decay was already visible: flake.nix said 0.8.0 while package.json said 0.11.0, three minors of drift with nothing to report it. The engine swap in this PR makes it acute — changing package-lock.json invalidates npmDepsHash, and without a build there is no signal at all. Adds a `flake` job that runs `nix build`, which fails loudly on a stale hash and prints the correct one. It also asserts version agreement between flake.nix and package.json, and — because building is not evidence the artifact works — runs the BUILT ck-shacl-runner against the fixtures, including requiring it to reject the cyclic graph. A gate that only passes from the source tree says nothing about the binary a consumer actually vendors. npmDepsHash is a placeholder here; the job's failure carries the real value and it is pinned in the follow-up commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MqhgW5RCRn6wWfjKvDsxTq
The value the new `flake` job reported. Swapping rdf-validate-shacl for
shacl-engine changes package-lock.json, which changes the content hash of the
fetched npm dependencies:
specified: sha256-AAAA… (placeholder)
got: sha256-Y6daAa9ySvlQhl3pbmPtfnx4JUyZrfKdnihAoZV7tH4=
Worth noting the previous hash had been stale in a quieter way: nothing built
the derivation, so the value in tree was simply never checked against the
lockfile it claims to pin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqhgW5RCRn6wWfjKvDsxTq
bdelanghe
marked this pull request as ready for review
August 4, 2026 04:19
I had twice called this failure pre-existing and safe to ignore. That was wrong
about intent. deps.yml states the posture plainly:
Failure posture: HARD-FAIL. This repo's adoption grace was removed once its
findings cleared (conformance-kit#53) — a known vulnerability now reds the lane.
So a green deps lane is this repo's stated merge condition, not optional. It was
red on main too, which is why it read as background noise rather than as the gate
it was adopted to be.
All three packages are deep transitive deps with fixes published, so `overrides`
is the lever:
fast-uri 3.1.4 → 3.1.5 (via stylelint → table → ajv)
ip-address 10.2.0 → 10.4.0 (via sigstore → … → socks)
undici 6.27.0 → 6.28.0 (via jsonld → @digitalbazaar/http-client)
`npm audit` and `npm audit --omit=dev` both report 0 vulnerabilities; the suite
is unchanged at 32 passed.
Note this is deliberately NOT the escape hatch deps.yml offers. An
osv-scanner.toml [[IgnoredVulns]] entry is for an accepted or unfixable finding,
and all seven of these are fixable today — suppressing them would record a
judgement nobody made.
Landing it here rather than in a separate PR because the swap cannot honestly
merge while the lane the repo hard-gates on is red.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqhgW5RCRn6wWfjKvDsxTq
The overrides that cleared the OSV findings changed package-lock.json, so the
fetched npm dependencies hash differently:
specified: sha256-Y6daAa9y… (the shacl-engine tree)
got: sha256-MltNN6K9… (with fast-uri/ip-address/undici bumped)
Second time today the flake job has caught this, on a change made after it was
added — which is the argument for it. Before, a lockfile edit left the
derivation unbuildable with nothing anywhere reporting it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqhgW5RCRn6wWfjKvDsxTq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completes #58. One engine, and the reproducible artifact built and exercised in CI.
What it fixes
rdf-validate-shaclcannot execute SPARQL-based constraints at any version in range:Core SHACL cannot traverse a property path, so every cross-row invariant — referential agreement, acyclicity, "this status implies that relationship" — requires
sh:sparql. The gate could enforce per-node structure and nothing relational, and announced that by throwing in a way that reads like an infrastructure fault rather than an unsupported feature.shacl-engineis the same RDF/JS stack — same@zazuko/env-nodethe kit already depends on — and implements SHACL-SPARQL constraints and targets. Report format and exit codes are unchanged;printViolationsnormalises the two engines' differing result shapes (path as segments vs a single term,constraintComponentvssourceConstraintComponent) so consumers reading these logs see no difference.shacl-engineships core validations only. WithouttargetResolversandvalidationsfromshacl-engine/sparql.js, it does not warn and does not throw — it silently skips everysh:sparqlshape:That is a worse failure mode than the validator it replaces, which at least threw loudly. So the swap lands with a required-to-fail cyclic fixture — and I verified that guard actually guards, by removing the opt-in and watching what happened:
The conforming test still passed with SPARQL disabled. Only the negative test caught it — which is the entire argument for having one.
On the dependency footprint
shacl-enginehard-depends on@comunica/query-sparql-rdfjs-lite, so the Comunica query engine comes with it: 297 → 758 lockfile packages. It is not a peer or optional dependency, so there is no way to takeshacl-engine's core validations without it.I originally raised this as a blocking question and leaned toward keeping both engines with a dynamic import. That was the wrong call, for two measured reasons:
npmDepsHashcontent-addresses the entire tree, so a consuming site's vendored gate is hash-verified rather than re-resolved at install.fast-uri,ip-address,undici) asmain— all pre-existing, none from Comunica.And two engines would have meant two code paths, two result shapes, and two answers to "what does this gate enforce?" One engine is the point.
The reproducible path had stopped being built
Following from that, the flake had to actually work — and it didn't.
flake.nixdeclares all 29ck-*gates as reproducible CLIs, but:flake.nixsaid0.8.0whilepackage.jsonsaid0.11.0— three minors of driftnpmDepsHashhad never been checked against the lockfile it claims to pin, so this PR's lockfile change made it detectably wrong rather than newly wrongA reproducible-build guarantee nobody executes decays in exactly one direction. The new
flakejob runsnix build(which fails loudly on a stale hash and prints the correct one — that is how the pinned value here was obtained), asserts version agreement, and then runs the built binary:Building is not evidence the artifact works. That line is the Nix-built
ck-shacl-runnervalidating a conforming graph and refusing a cyclic one through a SHACL-SPARQL property path — so the binary a consumer vendors is verified, not just the source tree.Verification
32 passed.
buildgreen.version-checkgreen.Known-red, and deliberately not fixed here
osv / osv-scanfails on 7 advisories infast-uri,ip-addressandundici. All are pre-existing — present inmain's lockfile, and this branch's additions contribute none of them. They are real and fixable, but a three-package lockfile bump inside an engine swap would make one diff about two unrelated things. Worth its own PR.Also unexplained and worth someone's attention:
standard / osvpasses the same lockfile thatosv / osv-scanfails. Two scanners disagreeing about one input is worth understanding regardless of which is right.🤖 Generated with Claude Code
https://claude.ai/code/session_01MqhgW5RCRn6wWfjKvDsxTq