Repository navigation
Land #317, #337, #338 and fixes for #333, #334, #336, #339, #340, #341 - #342
Merged
Merged
Conversation
…e like the CLI does The MCP handlers (and CLI diff) passed the raw file to the Turtle parser, so an RDF/XML .owl file and Turtle with relative IRIs failed, while CLI lint, onto_load and onto_validate accepted them. Read through GraphStore::read_as_turtle, as CLI lint does.
read_as_turtle gave each file its own @base, so <#Dog> in two relative-IRI Turtle files named two IRIs and diff reported every triple as added and removed. Both sides now resolve against the old file (read_as_turtle_against), in onto_diff and CLI diff. CLI diff's read error names the file again (Cannot read <path>: ...).
The corpus URL is not in the repository and the old competition pages are 404, so the EL/DL comparison against HermiT could not be reproduced by anyone outside the project. It is on Zenodo (10.5281/zenodo.18578), 725 MB, CC-BY-NC-ND-4.0. Also documents the jar gap that makes the first Convert.java run fail with NoClassDefFoundError, with the exact classes observed, and why the driver correctly reports it as conversion_failed.
Resolve existing mutable action tags to their current release commits and document exact patch versions inline. Keep rust-toolchain on stable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d-any-format # Conflicts: # src/graph.rs
#338 pinned actions/setup-java to 7f4fc3e2 ("v4.9.1") and actions/setup-python to b6effb05 ("v5.7.0"). Neither commit exists in its repository (the commits API answers 422 "No commit found"), and setup-python has no v5.7.0: its newest v5 release is v5.6.0. Every job using either action would fail to resolve it. They now pin the commits the tags point at: setup-java v4.9.1 = cf277c60, setup-python v5.6.0 = a26af69b (also what @v5 resolves to today, so behaviour is unchanged). All 17 pinned actions in .github/workflows were re-resolved against their tags with the GitHub API: 17/17 match. actionlint clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read the Zenodo archive's central directory (HTTP range requests, no full download): 1,952 zip entries, 1,938 files, 1,920 ontologies under pool_sample/files/, 21.9 GB extracted, largest ore_ont_12898.owl at 590,824,914 bytes. The note said 1,952 files and 576 MB. It also pointed at tests/ore_pool_round_trip, which does not exist: the test is the #[ignore]d ore_pool_round_trip in tests/ofn_writer_test.rs, and it needs OO_ORE_POOL, so the note now gives the command. DOI, title, licence (cc-by-nc-nd-4.0), 725,398,299-byte download, the pool_sample/ root, both dead URLs and the 17 selftest checks were confirmed as written. Em dashes replaced, as in the rest of the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merging main into #317 left its entry inside "## [2.1.0] - 2026-10-23 ### Fixed", so the notes of an already-tagged release would have gained a fix that is not in it. Same text, moved to [Unreleased] -> Fixed with the PR number; the 2.1.0 section is again byte-identical to main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hen asked defect_count counted query rows. For inherited_disjoint a row is one (class, disjoint pair), so a class under two disjoint pairs counted twice and the headline ran higher than the reasoner's unsatisfiable classes with nothing in the output to say why. The listing was capped at MAX_PER_KIND = 50 with no flag to lift it, so rows 51 onward were counted under `truncated` and could not be obtained (#341). Defects::findings now returns every row, uncapped, with its subject (the class or property it is about); check_with_limit builds the report from it. The report adds affected_classes and affected_properties (distinct subjects), kind_counts (findings plus the affected count for each of the eight kinds) and limit_per_kind, and defect_count is documented as a count of findings. `defects --limit N` on the CLI and `limit` on onto_defects set the per-kind bound (default 50, 0 lists all), and the proxied form forwards it. `truncated` is keyed on the bound in force. Default output is unchanged apart from the added fields and a stable row order. Measured on the issue's reproductions with the debug binary: - double.ttl: before defect_count 2, no affected count; after defect_count 2, affected_classes 1. - 80 classes under one disjoint pair: before reported 50, truncated 80, `--limit` rejected by clap; after `--limit 0` reports 80 with truncated {}, `--limit 10` reports 10 with truncated {"inherited_disjoint": 80}. New tests fail on origin/main: the two CLI --limit tests (unexpected argument) and the three affected-count tests (field absent); the library limit and onto_defects tests do not compile there (new API). After: defects_test 24/24, defects_cli_test 6/6, tool_reference_test 5/5, rdf_document_base_test 8/8, bin unit tests 9/9; cargo clippy --all-features --all-targets -D warnings clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n override them for one run Issue #339. Cause: setup(), which every one-shot CLI command and batch go through, loaded <data-dir>/config.toml and kept only [storage]. runtime::init_from_config ran in serve, serve-http and serve-unix only. So `reason --profile owl-dl` always ran with a 10 000 ms phase budget and a 180 000 ms ceiling, and its budget block reported those defaults as the configuration. Fix: setup() calls init_from_config with the Config it already loads. init_from_config only stores atomics (reasoner caps and clocks, cache prefix, feedback thresholds, repo list limit, imports policy, webhook timeout, preferred languages). It loads no model and opens nothing, and it overwrites rather than latches, so it is cheap and safe to call on every one-shot command. No serve path calls setup(), so nothing runs it twice. `reason` gains --phase-timeout-ms and --classify-timeout-ms. Precedence is CLI > [reasoner] > default. No environment variable exists for either clock, and none is added. The budget block reads the same globals, so it reports the values in force. Daemon path: `daemon start` passes <data-dir>/config.toml to serve-http, which already applied [reasoner] at startup, so proxied runs were never affected. The clocks are process-wide in the daemon, so a per-request budget cannot be honoured there. A reason that carries either flag is not serialised for the proxy. While a live daemon is up, it is refused with exit 1 and a pointer to --no-connect, rather than run locally against a store that does not hold the caller's graph. Verification: - tests/cli_reasoner_config_test.rs on origin/main 9e73d95 fails 2 of 3. The config test reports phase_timeout_ms 10000 against a configured 200, and the flag test fails because --phase-timeout-ms is not a known argument. On this branch all 3 pass, with flags beating config, one flag leaving the other at its configured value, and 0 meaning no limit. - A unit test checks that either flag stops proxy serialisation and makes a live daemon refuse. - A real daemon was run by hand with tableaux_test_timeout_ms = 200 and classify_timeout_ms = 60000. The proxied reason reported 200/60000. A flagged reason exited 1 with the refusal. With --no-connect the run was local and checked 0 classes, because the in-memory store was empty. - cargo clippy --all-features --all-targets -- -D warnings: clean. - cargo test: 235 result lines, 2021 passed, 0 failed, 2 ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…datatype D anywhere in the SHACL engine (#333) Cause. "Value V has datatype D" was spliced into SPARQL as `DATATYPE(?v) = <D>` (or its negation) at eight sites. For an IRI or blank node DATATYPE() is a type error, the error survives `!(a || b)`, and FILTER drops a row whose test errors. So under `sh:or` over datatypes an IRI value matched no alternative and was never reported, and the same drop let an IRI pass a property shape's `sh:datatype`, a datatype under a node-level `sh:or` member, and a datatype nested under `sh:node` (on the referenced shape and on a property inside it). Separately, only the two direct `sh:datatype` checks carried the lexical-space test, so the `sh:or`, `sh:not`, `sh:qualifiedValueShape` and `sh:node` member tests took the ill-typed "none"^^xsd:boolean as an xsd:boolean. Fix. One helper, `datatype_conforms(D, var)`, is TRUE exactly when the value conforms to sh:datatype D (SHACL 4.1.2): `isLiteral` first, then the datatype, then the lexical-space regex where one is written down. `false && error` is false, so it cannot be a type error. All eight sites splice it, positively for members and negated for the constraint itself. `ill_typed_test` becomes `lexical_space_regex`, which only returns the escaped pattern. Verification. - tests/shacl_or_test.rs, seven new tests; six FAILED on origin/main and pass now: or_over_datatypes_reports_an_iri_and_an_ill_typed_literal (1 of 3 values reported before, 3 of 3 now), an_iri_value_violates_sh_datatype (0 -> 1), node_level_or_member_datatype_is_not_satisfied_by_an_iri_or_ill_typed_value (0 -> 2 foci), an_ill_typed_literal_satisfies_sh_not_datatype (2 -> 1: the ill-typed boolean no longer counts as conforming to the inner shape), an_ill_typed_literal_does_not_count_towards_a_qualified_datatype_shape (0 -> 1), sh_node_datatype_rejects_an_iri_and_an_ill_typed_value (0 -> 4 foci). or_over_datatypes_still_admits_every_alternative passes before and after: every well-typed alternative still conforms. - W3C suite, suite commit 94d8bc2: gated column PASS 61 / FAIL 15 / UNDET 44 / ERROR 0 before and after; full-tuple column PASS 51 -> 52, FAIL 25 -> 24. The baseline regenerated with exactly one outcome changed, core/property/or-datatypes-001 FAIL -> PASS; no named PASS regressed. - tests/shacl_validation_report_w3c_test.rs: report match 48 -> 49 of 56 core/ PASS cases; or-datatypes-001 leaves KNOWN_DIFFERENCES and MATCHED_FLOOR rises to 49. - cargo test (full, this src with the next commits' test edits present in the tree): 2023 passed, 3 failed, all three in tests/shacl_validation_report_test.rs and all belonging to #334/#335, not to this commit. cargo clippy --all-features --all-targets -D warnings: clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/assets/hqdm/owl-dl.json's source says the run used [reasoner] tableaux_test_timeout_ms = 120000 and classify_timeout_ms = 900000, but the one-shot batch that produced it ignored config.toml (#339), so its budget block reported the defaults, 10000 and 180000. Rerun with the fix, the same command reports 120000 / 900000 / worst case 600000 and every verdict field is identical (unsatisfiable_classes, undetermined_classes, consistent, complete, search_finished, named_classes, budget_exhausted_in, constructs_not_modelled). Only the budget block is replaced; timings are left as committed. PROVENANCE.md and the #339 CHANGELOG entry say so. hqdm_audit_asset_test 7/7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
Cause. Every path that reads sh:severity (a node shape's own constraints, a property shape, a qualified shape, sh:sparql) set the JSON `severity` to the text after the last `#` of the declared IRI. Both readers of that field, tests/w3c_shacl_conformance_test.rs and tools/shacl_differential.py, take a bare name as `sh:`, so W3C core/misc/severity-002's ex:MySeverity (a hash namespace) read back as sh:MySeverity, a different severity. A custom severity in a slash namespace survived whole only because it has no `#`. The RDF report writer (#318) already carried the declared term. Fix. One helper, `severity_name`, used on all four paths: the local name for an IRI in the SHACL namespace, so sh:Violation, sh:Warning and sh:Info stay "Violation", "Warning" and "Info" for existing readers, and the full IRI for anything else. Both readers already treat a name containing `:` as a full IRI, so they need no change; the Python docstring now says so. Verification. - tests/shacl_validation_report_test.rs: a_custom_severity_keeps_its_namespace_in_the_json_on_every_path FAILED on origin/main ("MySeverity" on the datatype, nodeKind, qualifiedMinCount and sparql results) and passes now; sh:Warning stays "Warning" and the default stays "Violation". a_custom_severity_iri_and_a_language_tag_survive_into_the_graph pinned the lossy "MySeverity" and now asserts the full IRI. - W3C suite, suite commit 94d8bc2: gated column PASS 61 / FAIL 15 / UNDET 44 / ERROR 0, unchanged; full-tuple column PASS 52 -> 53, FAIL 24 -> 23, and "tests whose full tuple differs in severity" 1 -> 0. The baseline regenerated with exactly one outcome changed, core/misc/severity-002 FAIL -> PASS; no named PASS regressed. Report match unchanged at 49 of 56 (the RDF writer was already right). - cargo clippy --all-features --all-targets -D warnings: clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the clock that actually stopped the run Cause (#340). The subsumption sweep checked only the run's global ceiling before building each pair's tableau. The phase deadline was handed to the tableau and checked nowhere else, so after it passed every remaining pair still built a tableau that returned Unknown from its first step. A class whose satisfiability test ran out has no model, so it stood for a pair against every satisfiable class, and all k x n pairs were materialised along with a verdict for each. Sweep time therefore grew with the pair count, not the budget. Separately, budget.binding_bound repeated what the settings predicted. The note said the ceiling "cannot" stop the run, even when the run was heading for it. Fix. The satisfiability phase and the sweep check the deadline they hand their tableaux (the phase budget folded under the ceiling) before building one. A tableau opened after that deadline can only return Unknown, so skipping it changes no verdict. The sweep runs over rows: a class with a nondeterministic model lists its candidates, and a class with no model carries only a count, computed from its told subsumers. Pairs are generated while the budget lasts. Once a latch sees the deadline pass, no further tableau is built, and the remaining pairs and rows are counted as not reached, in bulk. Every pair not proved is undetermined, and subsumption_cut_short (so complete: false) is set whenever any pair was tested Unknown or not reached. Inferred pairs come only from a decided Unsatisfiable. subsumption_agent gains pairs_scheduled and pairs_not_reached, and pairs_tested now counts tableaux built. binding_bound is read off the cut-short phases ("global" when the ceiling's deadline had passed, "phase" when only a phase budget had, otherwise "none"). The prediction moves to the new configured_bound. tests/reasoner_global_budget_test.rs is updated deliberately: trivial runs now report binding_bound "none" with the prediction in configured_bound, and the note text changed from "cannot" to "not expected to be the bound". Verified. The issue's 6,000-class ontology, 40 unions per class, release build, ceiling 180000 ms, two alternating runs per build on a loaded machine. At a 200 ms phase budget, main took 5597/5661 ms in the subsumption phase over 28.5M/28.4M pairs, all built. The fix took 213/203 ms, built 855/1038 tableaux and left 29.2M/29.9M pairs not reached. All of those are counted in undetermined, with 0 subsumptions found and complete false. An earlier single run of each build gave 5042 ms over 27.45M pairs on main and 203 ms over 806 tableaux with the fix. At 10000 ms, main decided 50313/42626 pairs and the fix 47768/48783. New tests/subsumption_sweep_phase_budget_test.rs (1,500 classes, debug) fails on main: 2.47M pairs swept in 1275-1860 ms under a 30 ms budget, with no not-reached count. It passes with the fix (41 ms, 22 tableaux) and checks that binding_bound is "phase" where the settings predict "global". Six unit tests beside sweep_pairs drive the clock deterministically. cargo test: 2022 passed, 0 failed. cargo clippy --all-features --all-targets -D warnings: clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md # src/main.rs
#340 makes budget.binding_bound report the clock that actually stopped a run and moves the settings' prediction to budget.configured_bound. The two #339 tests asserted the prediction through binding_bound ("phase", "global") and failed on the combined branch, since their tiny runs hit no clock and now report "none"; they assert configured_bound for the prediction and binding_bound "none" for the outcome. The HQDM run's budget block is regenerated on the combined build with the same command: it gains configured_bound "phase" and binding_bound "none", and every verdict field is identical. cli_reasoner_config_test 3/3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ore keeps it by value, and a test now pins that cause (#335) Not a fix. #335 asked whether "4.0"^^xsd:decimal coming back as "4" in sh:value (W3C core/node/maxExclusive-001, minExclusive-001) is oxigraph's decimal storage. Measured: it is, and it cannot be repaired in this repository. Cause. oxigraph 0.5.9 (the pinned fork at abf36ef adds only Store::snapshot) encodes a literal of a datatype it knows by value at insert (lib/oxigraph/src/storage/numeric_encoder.rs: "xsd:decimal" -> parse_decimal_str -> EncodedTerm::DecimalLiteral; integer, boolean, double, float, dateTime and the rest likewise) and decodes it canonically (Literal::from(Decimal)). ShaclValidator loads the shapes text into a store of its own, so `sh:targetNode 4.0` already names "4"^^xsd:decimal before the first query, and the data graph is a GraphStore over the same Store. Why nothing here can recover it. Measured on load: "4.0"^^xsd:decimal and "4"^^xsd:decimal on one subject and predicate load as ONE triple (load_turtle returns 1); "04"^^xsd:integer -> "4", "1"^^xsd:boolean -> "true", "1.0E0"^^xsd:double -> "1", "...00.000Z"^^xsd:dateTime -> "...00Z"; the ill-typed "none"^^xsd:boolean survives because it has no native encoding. Two distinct RDF terms become one stored term, so no side table keyed by that term can say which one a result was about. The repair belongs in the store's literal encoding (keep the lexical form when it is not canonical); oxigraph's SPARQL layer re-parses a decoded typed literal by datatype (spareval dataset.rs, parse_typed_literal), which suggests value comparisons would survive that change, but that was read, not built or measured. The loss reaches verdicts, not only the report: a lexical constraint judges the canonical string, so sh:pattern "\\.0$" reports a lone "4.0"^^xsd:decimal whose lexical form matches. Same for sh:minLength, sh:maxLength and sh:in/sh:hasValue over such literals (by the same mechanism; only sh:pattern is measured here). What this commit does. - tests/shacl_validation_report_test.rs: a_non_canonical_literal_is_canonicalised_by_the_store_before_validation_sees_it pins each measurement above (one triple, sh:value "4", focus node "4" from sh:targetNode 4.0, the sh:pattern false positive) and fails, with a message naming the follow-up, on the day the store keeps a lexical form. Before it, a probe asserting sh:value "4.0" failed on origin/main with left: "4"^^xsd:decimal, right: "4.0"^^xsd:decimal. - tests/shacl_validation_report_w3c_test.rs: the KNOWN_DIFFERENCES reason for maxExclusive-001 now states the measured cause instead of a guess. - tests/w3c_shacl_conformance_test.rs: documents that the full-tuple column cannot see #335 by construction. It reads the expected report through an oxigraph Store too, which canonicalises the expected "4.0" the same way, so maxExclusive-001 scores PASS there. W3C numbers unchanged by this commit: gated 61, full tuple 53, report match 49 of 56. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
…e ABox inconsistent, and batch runs defects risk_score came from removal counts and triples_affected alone, and apply gated only on the monitor, so a change that left a named class with no possible instance (R1: Manager subClassOf Person subClassOf Agent, Manager disjointWith Agent) or made the ABox inconsistent (R2: Machine disjointWith Person, alice a Machine where alice a Person) removed nothing and planned `low` on every entry point. `defects` had no batch arm, so batch, and the CLI whenever a daemon was up (it proxies there), answered "unknown batch command: 'defects'" (#336). Planner::plan_with is now the one function behind the plan subcommand, batch `plan` and onto_plan, and adds a `logic` block: new_defects (the eight defects kinds on the proposed graph, minus findings the store already had), abox_consistent (owl-dl tableaux, TBox and ABox, under the existing [reasoner] budget) and abox_consistent_before (the store, checked only when the proposed graph is proven inconsistent). A new error- or warning-severity defect, or an inconsistency the store was not proven to have, sets risk_score to high; risk_reasons says why. A check that ran out of budget, or a graph with a construct the tableaux does not model, reports null with the reason, never true. On by default; --no-logic-check (CLI, batch) and check_logic=false (onto_plan) skip it and the block says not_checked, mirroring --no-conservativity. apply is deliberately unchanged: it has always applied a `high` plan from removals, so refusing on a new field would silently break pipelines; plan's exit code also stays 1-on-error-only, consistent with #329's treatment of plan. Both are documented in docs/lifecycle.md and the apply doc comment. batch gains `defects [path] [--limit N]` (the file alone, or the batch store with no path). The local CLI `defects` loaded its file into the main store, so under persistent storage it checked file plus store and left the file's triples behind (measured on origin/main: stats 0 -> 11 triples after `defects double.ttl`); it now uses its own store. dlp-boundary and dl are MCP-only (no CLI command, nothing proxied), so no batch arm was added for them. README.md:121 and its zh-CN line now say the plan checks what a change means and what it breaks. Measured, batch `load base; plan rN`, debug binary: - R1 before: risk_score low, no logic block; defects -> unknown batch command. After: risk_score high, new_defects 1 (disjoint_with_ancestor on ex:Manager), abox_consistent true. - R2 before: risk_score low. After: risk_score high, new_defects 0, abox_consistent false, abox_consistent_before true. tests/plan_logic_test.rs (15 tests): the 10 that need no new API fail on the pre-fix plan code (no logic block, risk low, batch defects unknown); the rest use the new opt-out API. The bin unit test every_proxy_able_command_has_an_arm_in_the_batch_runner, now listing defects, fails without the arm ("`defects` is proxied but the batch runner has no arm for it") and passes with it. Full cargo test --no-fail-fast: 2043 passed, 0 failed, 2 ignored across 235 test binaries; cargo clippy --all-features --all-targets -D warnings clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
…t and sh:qualifiedValueShape, as it already was on its own Cause. The store widens the twelve integer-derived XSD types (xsd:int, xsd:nonNegativeInteger, ...) and xsd:dateTimeStamp to the type they derive from, so `datatype_is_indistinguishable_in_store` names datatypes a value cannot be tested against. The direct sh:datatype constraint, on a property shape and on a node shape, consults it and refuses: the constraint goes to `skipped_constraints` and the verdict is null. The paths that test a datatype as a MEMBER of something else did not consult it and answered the question the direct path refuses, wrongly: - property-shape sh:or with a member [ sh:datatype xsd:nonNegativeInteger ] reported every conforming value as matching no alternative; - sh:not [ sh:datatype xsd:nonNegativeInteger ] returned conforms: true over values of exactly that type, a false clean; - sh:qualifiedValueShape [ sh:datatype xsd:nonNegativeInteger ] counted no value as conforming and reported the minimum breached. The sh:not and qualified paths are the same gap as the sh:or one that was asked for, so they are closed in the same commit. Fix. Each member path checks `datatype_is_indistinguishable_in_store` where it reads the member's datatype, removes the constraint from evaluation, and records it through `unpreserved_datatype_skip`, which is the direct property path's skip record (shape, constraint, path, datatype, the same reason text) factored into one function the direct path now also uses. No new rule: a widened value is never judged against the narrower datatype on any path; the run is undetermined, as on the direct path. Verification. - tests/shacl_datatype_indistinguishable_test.rs, run against origin/main's src/shacl.rs and against this commit: an_or_member_naming_an_unpreserved_datatype_is_not_answered (main: 2 violations, conforms false; now 0, conforms null, sh:or skipped), a_not_member_naming_an_unpreserved_datatype_is_not_answered (main: conforms true; now null, sh:not skipped), a_qualified_shape_naming_an_unpreserved_datatype_is_not_answered (main: 2 violations; now 0, null, sh:qualifiedValueShape skipped). Control: members_naming_a_preserved_datatype_are_still_evaluated passes on both, each path still reports 2 violations over xsd:integer/xsd:string members. - W3C suite 94d8bc2: gated 61/15/44/0 and full tuple 53/23/44/0, unchanged; regenerating the baseline produced a byte-identical file. Report match 49 of 56, unchanged. - cargo test (full, with the two following commits' changes also applied): 2032 passed, 0 failed, 2 ignored. cargo clippy --all-features --all-targets -D warnings: clean on the same tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is refused rather than answered Cause. `compile_node_shape` turns the shape an sh:node points at into one boolean expression. It already returned None (the sh:node goes to `skipped_constraints`, verdict null) for a property INSIDE that shape whose sh:datatype `datatype_is_indistinguishable_in_store` names, with the comment "a nested shape resting on one is not decidable either". It did not check the referenced shape's OWN sh:datatype, so `sh:node ex:Count` with `ex:Count sh:datatype xsd:nonNegativeInteger` judged every value the store had widened to xsd:integer as not conforming and reported it. Fix. The node-level "datatype" arm consults the same function and returns None, exactly as the property arm below it does, so the whole sh:node is recorded as not evaluated with the existing reason. Verification. - tests/shacl_datatype_indistinguishable_test.rs: a_node_shape_reference_naming_an_unpreserved_datatype_is_not_answered, run against origin/main's src/shacl.rs: 2 violations, conforms false; now 0 violations, conforms null, sh:node in skipped_constraints. Control: the sh:node case in members_naming_a_preserved_datatype_are_still_evaluated (a referenced shape with sh:datatype xsd:integer) still reports 2. - W3C suite 94d8bc2: gated 61/15/44/0 and full tuple 53/23/44/0, unchanged; regenerated baseline byte-identical. Report match 49 of 56. - cargo test (full, tree including the next commit): 2032 passed, 0 failed, 2 ignored. cargo clippy --all-features --all-targets -D warnings: clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d of always Violation Cause. sh:or asserted on a node shape over member shapes built its result with `"severity": "Violation"` and `RdfTerms::new(None, ...)`, so the JSON said "Violation" and the RDF report wrote sh:resultSeverity sh:Violation whatever sh:severity the shape declared. SHACL 3.6.2 makes the result's severity the shape's sh:severity, and every other path already reads it. Fix. The query that already fetched the node shape's sh:message now also fetches its sh:severity (both OPTIONAL). The JSON field goes through `severity_name` (#334: local name for sh: severities, full IRI otherwise) and the declared term goes to `RdfTerms` for the RDF writer; with none declared both stay sh:Violation. Verification. - tests/shacl_validation_report_test.rs: a_node_level_or_result_carries_the_shapes_declared_severity checks sh:Warning, a custom hash-namespace IRI and no declaration, in the JSON and in the Turtle report. Run against origin/main's src/shacl.rs it fails at the JSON assertion for sh:Warning (left "Violation", right "Warning"); it passes now. - W3C suite 94d8bc2: gated 61/15/44/0 and full tuple 53/23/44/0, unchanged; regenerated baseline byte-identical. Report match 49 of 56. - cargo test (full, this tree): 2032 passed, 0 failed, 2 ignored. cargo clippy --all-features --all-targets -D warnings: clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
This was referenced Oct 28, 2026
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.
Lands three contributor PRs and fixes for seven issues. Each fix has a test that fails on main and passes here; the full suite passed locally before the last merge (2067 passed, 0 failed), and clippy is clean.
PRs, merged with their authors' commits plus one correction each:
Issues:
reason --phase-timeout-ms / --classify-timeout-msoverride it (refused while a daemon is up).defectshas no batch arm #336: plan carries a logic verdict (new TBox defects, ABox consistency) and scores high when it flips; batch gains a defects arm; --no-logic-check opts out. apply and plan's exit code are unchanged.#335 (literal lexical forms) is fixed in the store, oxigraph/oxigraph#2010, and lands separately once the pin moves.
Fixes #333
Fixes #334
Fixes #336
Fixes #339
Fixes #340
Fixes #341
Closes #317
Closes #337
Closes #338
🤖 Generated with Claude Code