Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SHACL Validation

SHACL (Shapes Constraint Language) lets you define what valid data looks like and enforce it at write time. When an agent or user tries to add facts that violate a shape, Quipu rejects the write and returns structured feedback explaining exactly what’s wrong.

Why Validate?

Without validation, agents can write anything:

hw:koror hw:cpuCores "lots" .   # Should be an integer
hw:koror a hw:Host .            # Missing required hostname

With SHACL shapes loaded, Quipu catches these problems before they enter the fact log.

Defining a Shape

A shape declares constraints for a class of entities. Here’s a shape that says “every Host must have exactly one hostname (a string) and at least one cpuCores (an integer)”:

@prefix sh:  <http://www.w3.org/ns/shacl#> .
@prefix hw:  <http://example.org/homelab/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

hw:HostShape
    a sh:NodeShape ;
    sh:targetClass hw:Host ;
    sh:property [
        sh:path hw:hostname ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path hw:cpuCores ;
        sh:datatype xsd:integer ;
        sh:minCount 1 ;
    ] .

Loading Shapes

CLI

quipu shapes load --name homelab --file shapes/homelab.shapes.ttl --db homelab.db

REST API

curl -s localhost:3030/shapes -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "action": "load",
    "name": "homelab",
    "turtle": "@prefix sh: <http://www.w3.org/ns/shacl#> .\n@prefix hw: <http://example.org/homelab/> .\nhw:HostShape a sh:NodeShape ; sh:targetClass hw:Host ; sh:property [ sh:path hw:hostname ; sh:minCount 1 ] ."
  }'

Listing loaded shapes

quipu shapes list --db homelab.db

Declaring ontology classes and properties

The bundled governance shape set sanctions named ontology resources typed rdfs:Class and rdf:Property. Load that set before an adapter imports its ontology declarations through /knot. These declarations require IRI subjects.

Declaring <urn:example:Task> a rdfs:Class in the data graph does not authorize instances of urn:example:Task. The write vocabulary still comes from loaded shape sets; add and load a shape targeting the intended class before writing its instances. Unknown types continue to be refused.

Validation in Action

Try to add a Host without a hostname:

quipu knot - --db homelab.db --shapes shapes/homelab.shapes.ttl <<'EOF'
@prefix hw: <http://example.org/homelab/> .
hw:badhost a hw:Host .
EOF

Quipu rejects it with structured feedback:

{
  "conforms": false,
  "violations": 1,
  "issues": [
    {
      "severity": "Violation",
      "focus_node": "http://example.org/homelab/badhost",
      "path": "http://example.org/homelab/hostname",
      "component": "MinCountConstraintComponent",
      "message": "Less than 1 values for hw:hostname",
      "source_shape": "http://example.org/homelab/HostShape"
    }
  ]
}

This feedback is designed for agents — structured JSON with enough detail to fix the problem automatically.

Supported Constraints

ConstraintWhat it checks
sh:minCount / sh:maxCountCardinality (how many values)
sh:datatypeValue type (xsd:string, xsd:integer, etc.)
sh:minInclusive / sh:maxInclusiveNumeric ranges
sh:minLength / sh:maxLengthString length
sh:patternRegex match
sh:inAllowed values (enumeration)
sh:classReferenced entity must have rdf:type
sh:nodeNested shape reference
sh:or / sh:and / sh:notLogical constraints
sh:equals / sh:disjointProperty pair constraints

Quipu supports the full SHACL Core specification via the rudof library.

Dry-Run Validation

Validate data without writing it to the store:

quipu validate --shapes shapes/homelab.shapes.ttl --data data.ttl
curl -s localhost:3030/validate -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "shapes": "@prefix sh: ... shapes turtle ...",
    "data": "@prefix hw: ... data turtle ..."
  }'

Pre-Built Shapes

Quipu ships with shapes for the Aegis infrastructure ontology in the shapes/ directory. These cover:

  • LXCContainer, ProxmoxNode, BareMetalHost — compute resources
  • SystemdService, WebApplication, Database — services
  • CommandDiskImpactObservation — one privacy-preserving command/filesystem measurement. diskDeltaBytes is signed: positive values mean space consumed and negative values mean space freed. Command and filesystem identity use canonical classes rather than raw arguments or paths; aggregate counts and quantiles belong in a separate summary entity.
  • ConfigFile — optional migration-safe configPath and contentSha256 facts support exact drift lookups. When present, each is single-valued and the digest must be a full lowercase SHA-256 hexadecimal string.
  • CiJob and LocalCommand — map a CI job to at most one typed local equivalent, the repository paths it gates, and the command text used to run that equivalent before push.
  • Common properties: hostname, ipAddress, memoryMB, cpuCores, dependsOn

Load them with:

quipu shapes load --name aegis --file shapes/aegis-ontology.shapes.ttl --db homelab.db

Best Practices

  • Load shapes before data — shapes must be present to validate incoming writes
  • One shape set per domain — group related constraints (e.g., “homelab”, “code”)
  • Start permissive, tighten later — begin with minCount constraints, add datatype checks as your ontology stabilizes
  • Use validation feedback — the structured JSON is designed for automated remediation by agents