Skip to content

Test failures cannot distinguish advisory SHACL results (sh:Warning / sh:Info) from sh:Violation #88

Description

@smrgeoinfo

Test failures cannot distinguish advisory SHACL results from violations

Summary

bblocks-postprocess marks a building block's test as failed whenever pySHACL
reports conforms == False. pySHACL sets that for a result of any severity,
so a block that emits only sh:Warning or sh:Info is reported identically to
one that emits sh:Violation.

The effect on a register that uses advisory shapes deliberately is that it can
never be green, and "the build is red" stops carrying information.

Evidence from a real register

In the CDIF register (93 blocks), the postprocessor reports 22 failures. Across
every failing item:

severity results
sh:Warning 134
sh:Info 31
sh:Violation 0

Not one violation in the register. Blocks are failed for emitting advice they
were written to emit, e.g.

for cdi InstanceVariable a physical data type should be provided.

20 of the 22 failures are advisory-only. (Of the remaining two, one was a genuine
bug in our own SHACL, now fixed; the other is item 2 below.)

Where it happens

ogc/bblocks/validation/rdf.py:

def shacl_validate(g, s, ont_graph=None):
    validator = pyshacl.Validator(g, shacl_graph=s, ont_graph=ont_graph, options={
        'advanced': True
    })
    ...
    conforms, shacl_result, shacl_report = validator.run()

and then

if not shacl_conforms:
    shacl_errors_found = True
report.add_entry(ValidationReportEntry(
    ...
    is_error=not shacl_conforms,

There is no severity filter, and no CLI option or bblocks-config.yaml key
exposes one.

Proposed fix

pySHACL already supports this natively via allow_warnings, which excludes
sh:Warning and sh:Info from the conforms verdict while still reporting
them. Verified against one of the failing cases above:

options={'advanced': True}                           -> conforms=False  results={'Warning': 1}
options={'advanced': True, 'allow_warnings': True}   -> conforms=True   results={'Warning': 1}

So the change is to pass the option through and expose it, e.g.
--shacl-allow-warnings (and/or a shacl: allow-warnings: true key in
bblocks-config.yaml), defaulting to the current behaviour so nothing changes
for existing registers.

The warnings would still appear in the report and the generated docs - only the
pass/fail verdict changes.

Related: empty uplift output is also a hard failure

A second case with the same shape. An example that is a legitimate bare
{"@id": "..."} reference cannot produce any triples - that is what a node
object with no properties means in JSON-LD - but the uplift step reports

Empty output Turtle example_1_1.ttl created

as an error, failing the block. In our register this is the documented example
for "SKOS concept by URI reference", pointing at a published NERC vocabulary
term; it is correct, and deleting it to get a green build would remove the
documentation of the most common usage of that block.

Suggest this be a warning rather than an error, or suppressed when the source
document is a bare reference.

Why it matters

Both cases fail the same way: a check that cannot tell "broken" from "benign".
In our register the practical consequence was that a genuinely broken composite
profile - a rules.shacl referencing three shapes it did not define, which made
pySHACL reject the file and caused all five of that block's items to fail,
including its requireFail negative tests - sat unnoticed among 21 failures that
did not matter.

With allow_warnings plus that one real fix, the register would go from 22
failing blocks to 1.


🤖 Drafted with Claude Code, reviewed by SMR

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions