Skip to content

Validating RDF graphs with the SHACL validator

The SHACL validator checks that an RDF graph (a Turtle, JSON-LD, or RDF/XML file) conforms to a set of SHACL shapes you provide. The most common use case is validating ASHRAE 223P semantic models — for example, when a controls contractor delivers a 223P file as part of a commissioning handoff and you need to confirm it follows the standard.

If you're new to RDF and SHACL, the Open223 project has a friendly introduction to ASHRAE 223P. The W3C SHACL primer covers the constraint language itself.

What you'll need

  • A Validibot account with permission to author workflows.
  • One or more SHACL shape files in Turtle format (typically .ttl).
  • The RDF document you want to validate (Turtle, JSON-LD, or RDF/XML).
  • A workflow to attach the step to (create one if you don't have it).

For ASHRAE 223P specifically, you'll typically have a copy of 223p.ttl from Open223. Validibot doesn't ship 223P shapes in the wheel because they are ASHRAE-copyrighted; you upload them yourself.

Setting up a SHACL step

  1. Open the workflow editor and click Add step.
  2. Pick SHACL Validator from the library.
  3. In the step config dialog, give the step a name like "223P conformance gate" and a one-line description.
  4. Under SHACL shapes (required), either:
  5. Click Choose files and upload your .ttl shape files (you can select multiple files at once), OR
  6. Paste shapes directly into the Or paste shapes inline textbox if they're short.
  7. Under Supplementary ontologies (optional), upload ontology files if your shapes need separate vocabulary declarations. For ASHRAE 223P, skip this — the 223P shapes file is also the ontology.
  8. Advanced options — leave the defaults unless you have a reason to change them:
  9. Inference mode: RDFS (works for 223P, Brick, Haystack).
  10. Enable advanced SHACL: on (required for 223P).
  11. Submission RDF format: Auto-detect from extension.
  12. Click Save step.
  13. Open the saved step and click Add assertion if you want to add project-specific SPARQL rules that go beyond what SHACL shapes can express. See "Writing project-specific SPARQL gates" below.

Validibot parses every uploaded file at save time, so if your Turtle has a syntax error you'll see it inline — no need to wait for a validation run to discover the problem.

For the workflow's allowed file types, choose Plain Text for Turtle, N-Triples, or N-Quads submissions; JSON for JSON-LD; and XML for RDF/XML.

Those file type choices are enforced before the SHACL validator runs. If a workflow allows only JSON, Validibot accepts JSON or JSON-LD uploads and rejects .ttl, .nt, .nq, .rdf, and .xml files. To use the 223P Turtle examples, make sure Plain Text is enabled on the workflow.

Writing acceptance assertions

You have two kinds of gate to choose from on a SHACL step.

Basic / CEL gates check engine-level facts about the run — how many triples were parsed, whether warnings appeared, which namespaces the submission uses. These work just like the Basic / CEL gates on every other Validibot validator.

SPARQL ASK gates check the actual contents of the graph — a specific shape's conformance, a project rule, a referential-integrity check. Any question that requires looking at triples is a SPARQL ASK gate.

You usually won't have to write a "did SHACL pass" gate explicitly: how SHACL violations affect the step is controlled by the result handling setting on the step config dialog. There are three modes:

  • Fail immediately on violations — the step fails as soon as SHACL reports a violation, before any author-written assertions run. Same shape as the JSON Schema validator's auto-fail behaviour.
  • Fail after assertions — author-written CEL/SPARQL ASK assertions still run, then the step fails if SHACL reported a violation or an assertion failed. This is the default.
  • Report only — SHACL violations become signals on the run (o.shacl_violation_count and friends) but never fail the step on their own. Useful for shape collections you want to track without blocking, or for staging a new ruleset before turning it on.

In the first two modes the gates you write are the ones the platform can't infer (project rules, referential integrity, SPARQL ASK questions). In Report only mode you can also use those same gates to explicitly fail on chosen violation counts.

Basic / CEL signals

The SHACL engine emits this fixed set of signals on every run, regardless of which shapes you uploaded:

Signal What it tells you
o.parse_ok Whether the RDF file parsed successfully.
o.parse_serialization The format Validibot detected (turtle, json-ld, ...).
o.triple_count How many RDF triples the submission contains.
o.namespaces_present The namespace URIs that appear in the graph.
o.has_s223_namespace Whether the graph uses the ASHRAE 223P namespace.
o.has_g36_namespace Whether the graph uses the Guideline 36 namespace.
o.has_brick_namespace Whether the graph uses the Brick namespace.
o.shacl_violation_count Number of SHACL violations. Violations fail the step automatically.
o.shacl_warning_count Number of SHACL warnings (don't fail the step by default).
o.shacl_info_count Number of SHACL informational findings.
o.shacl_total_count Number of SHACL results across all severities.

Useful gates built on these:

o.triple_count >= 100               # sanity: file isn't effectively empty
o.shacl_warning_count == 0          # strict mode: no warnings allowed
"http://data.ashrae.org/standard223#" in o.namespaces_present
                                    # this must be a 223P file

Writing project-specific SPARQL gates

For anything that requires looking at the graph itself, use a SPARQL ASK assertion. Open the SHACL step, click Add assertion, choose SHACL, then enter one SPARQL ASK query for that gate.

Example: "every Zone must have a CO2 sensor" for a LEED IEQ-1 rule:

PREFIX s223: <http://data.ashrae.org/standard223#>
PREFIX qudt: <http://qudt.org/schema/qudt/>
PREFIX quantitykind: <http://qudt.org/vocab/quantitykind/>

ASK {
  FILTER NOT EXISTS {
    ?zone a s223:Zone .
    FILTER NOT EXISTS {
      ?sensor s223:hasObservationLocation/^s223:hasDomainSpace ?zone ;
              s223:observes ?p .
      ?p qudt:hasQuantityKind quantitykind:MoleFraction .
    }
  }
}

Each SHACL assertion has:

  • SHACL graph"data" to query the submission, "results" to query the SHACL report, or "union" to query both joined together.
  • SPARQL ASK query — a SPARQL 1.1 ASK query. The step gate fails when the ASK returns false.
  • Severity"error", "warning", or "info". Only error blocks the step from passing.
  • Description — a label that appears in the findings list.
  • Failure message — what to show the submitter when the gate fails.

You can use SPARQL ASKs to express things that are awkward in SHACL shapes, like:

  • "Every typed resource has a human-readable label."
  • "There are no dangling IRI references in the graph."
  • "Every sensor declares an approved quantity kind."
  • "The DamperShape produced zero violations in this run." (run this against "results" rather than "data".)

Only ASK queries are supported in this release. SELECT, CONSTRUCT, DESCRIBE, and any Update operations (INSERT, DELETE, LOAD, ...) are rejected at save time. SERVICE federation and remote FROM references are also rejected — these are common SPARQL features that would let an assertion send your data to an external URL, which the validator deliberately refuses. The query field is also length-capped so one assertion stays small enough to review; split very large checks into separate ASK assertions.

Reviewing a failed run

When a submission fails, the run detail page shows:

  • A list of findings — one per SHACL constraint violation. Each finding includes the focus node (which RDF resource failed the check), the source shape (which SHACL shape fired), and the shape's message explaining what's wrong.
  • A downloadable SHACL validation report in Turtle format. This is the native pyshacl/SHACL output — analytics tools, BuildingMOTIF, and AI agents can ingest it directly.

Example finding for a 223P file missing a required damper actuator:

ERROR on mysystem:VAV-3-12-damper

Damper VAV-3-12-damper has no actuating Property. ASHRAE 223P requires every Damper to be actuated by a Property via s223:actuatedByProperty.

Pass that back to the contractor with the SHACL report attached and ask for a corrected delivery.

Multiple shape files

You can upload as many .ttl files as you need (up to 2 MB per file, 10 MB total). They're merged into a single shapes graph and evaluated against the submission together. This is useful for layered validation:

  • Upload 223p.ttl for base ASHRAE 223P conformance.
  • Upload g36-2021.ttl for Guideline 36 control-sequence requirements.
  • Upload your firm's project-specific shapes (e.g. "every zone needs a CO2 sensor for LEED IEQ-1") to layer on top.

If you find yourself uploading the same standard shapes for every project, create a library-level SHACL validator instead. From the Validator Library page, click New Validator → SHACL Validator (RDF graph rules). The create form is the same shape as the step config (shapes, ontologies, bundled standards, engine knobs) plus a name and version at the top. Once saved, the validator appears in the Custom tab of the library; any workflow can pick it as a step and inherit the bundled shapes without re-uploading. SPARQL ASK gates stay on individual workflow steps, where they are added with Add assertion.

This is the right path when:

  • One person in your org is the ASHRAE-licence holder and uploads 223p.ttl once for everyone else's workflows.
  • Your firm has a standard Cx-acceptance shape set used across many projects.
  • You want to version the shapes (v1, v2, ...) and have individual workflows pin to a specific version.

Workflows that reference a library validator can also layer project-specific extras on top via the step config form's "Additional shapes" area — the engine merges the library validator's shapes with the step-level extras at validation time.

Common issues

"Failed to parse submission as json-ld" — Validibot picked the wrong format for your file. Set the Submission RDF format in Advanced options to Turtle (.ttl) (or whichever serialisation matches your file) explicitly.

"No SHACL shapes were supplied" — the ruleset is empty. Re-open the step config and upload your shape files or paste shapes inline.

"SPARQL ASK rejected by security scrub" — your assertion uses a construct the validator refuses. Common causes: the query is a SELECT or CONSTRUCT (only ASK is supported); it has a SERVICE clause (used for SPARQL federation, which the validator refuses to prevent data exfiltration); or it uses FROM / FROM NAMED to reference an external graph. Rewrite the assertion as a plain ASK against the data, results, or union graph.

"JSON-LD content references remote @context" — your submission's JSON-LD points its @context at an external URL. Validibot refuses this because remote contexts can be used to exfiltrate data or change parsing semantics. Either inline the context object in the JSON-LD, or convert the file to Turtle.

"RDF/XML content contains '<!DOCTYPE'" — your submission contains an XML DOCTYPE or entity declaration. Validibot refuses these because they're the building blocks of XXE attacks. Remove the DTD / entity declarations, or convert the file to Turtle.

ASHRAE 223P specifically

Validibot does not ship ASHRAE 223P shapes in the wheel — those files are ASHRAE-copyrighted and we don't have permission to redistribute them. The practical workflow is:

  1. Download 223p.ttl from Open223 (this is the community-maintained copy).
  2. Upload it as a shapes file in your step config.
  3. Optionally upload G36 application files from the same repo for sequence-specific validation.
  4. Optionally check Include QUDT 2.1 so unit references in 223P resolve at validation time.

Validibot's licensing posture: ship what we have permission to ship (Brick, QUDT, the engine itself); let operators upload what they have permission to upload (ASHRAE materials, project-specific shapes).

Spotted a problem on this page? Report it or suggest an edit