Skip to content

Using SAL Modules

A SAL Module is a git repository with a Dockerfile in its root whose container implements the SAL Module command line interface. Referencing one from your project’s RDF lets sal build run it and fold the RDF it produces into your data product.

A module is referenced through the salmodule:// protocol scheme. Declare it as a prefix and type an instance with one of the classes the module’s ontology defines:

@prefix salmodule: <https://w3id.org/sal/cgs-earth/sal-module-spec/salmodule#> .
@prefix states: <salmodule://github.com/cgs-earth/python-geoconnex/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<GeoconnexStates> a states:GeoconnexReferenceFeatureStates ;
a salmodule:NodeProcessor ;
states:maxRetries "5"^^xsd:integer .

The prefix takes the form salmodule://[HOST/]OWNER/REPO/. HOST is optional and defaults to github.com.

A module’s ontology declares its classes as relative IRIs, such as "@id": "GeoconnexReferenceFeatureStates". SAL resolves them against the prefix you declared, and only a slash-terminated prefix places them underneath the module:

# correct
@prefix states: <salmodule://github.com/cgs-earth/python-geoconnex/> .
# states:GeoconnexReferenceFeatureStates and the ontology's own term both become
# salmodule://github.com/cgs-earth/python-geoconnex/GeoconnexReferenceFeatureStates
# wrong
@prefix states: <salmodule://github.com/cgs-earth/python-geoconnex#> .
# relative IRI resolution drops the last path segment, so the ontology's term becomes
# salmodule://github.com/cgs-earth/GeoconnexReferenceFeatureStates
# which no longer matches the term the prefix expands to

A prefix that does not end in a slash reports every module term as undefined even though the module and its ontology are fine, so reach for the slash-terminated form whenever a module’s terms unexpectedly fail to validate.

A task is configured in RDF, with the properties the module’s own vocabulary defines. states:maxRetries above is one of them, so the module decides what a valid task instance looks like and sal validate reports a typo in a configuration property the same way it reports a typo in a class.

SAL serializes those properties into the JSON-LD node object that salmodule:runCommand is invoked with. The instance above is passed as:

{"@id":"https://example.org/GeoconnexStates","@type":"GeoconnexReferenceFeatureStates","maxRetries":{"@value":"5","@type":"xsd:integer"}}

The rules SAL follows when writing it:

  • Only properties in the module’s own namespace become keys. Everything else the project says about the instance, such as an rdfs:label or a schema:name, is left out, since only the module knows what configures it.
  • Keys and IRIs are written the way the module’s ontology writes them: a term of the module’s vocabulary becomes its relative name, and any other IRI is shortened with whichever prefix the ontology’s @context binds it to.
  • A typed literal becomes a {"@value": ..., "@type": ...} object and a language-tagged literal becomes a {"@value": ..., "@language": ...} object. A plain string is written as a string.
  • A property used more than once becomes an array.
  • A blank node is inlined as a nested object, so structured configuration can be expressed with the usual turtle [ ... ] syntax. A named node stays a {"@id": ...} reference.

Two properties of the SAL Module ontology are involved, and it is worth keeping them apart:

  • salmodule:runCommand describes the invocation the task instance is an input to, and so is the property that documents the JSON-LD node object above. Its skos:note is what says the value must be a JSON-LD representation of the salmodule:Task subclass instance being invoked.
  • salmodule:taskInstanceEnvVar only defines a constant: the name of the environment variable that value is carried in. A module declares it on its own ontology, and it defaults to SALMODULE_TASK_INSTANCE.

Validation dereferences the salmodule:// prefix so that the module’s terms are checked like any other vocabulary. SAL clones the repository, builds the Dockerfile in its root, and runs docker run IMAGE salmodule ontology to read the module’s vocabulary. A typo in a module term is reported the same way a typo in a schema: term is:

data/module.ttl:5: undefined term states:GeoconnexReferenceFeatureStatess

sal build then runs each task instance with docker run -e SALMODULE_TASK_INSTANCE=... IMAGE salmodule run. An instance is only run when it is typed as a SAL Module task, either because the project’s RDF types it as a salmodule:NodeProcessor, salmodule:NodeProducer, salmodule:NodeConsumer, or salmodule:Task, or because the module’s own ontology declares its class as a subclass of one of those.

A task writes newline delimited JSON to stdout. SAL injects the module ontology’s @context into each node so that its keys resolve to the module’s vocabulary, converts the result to RDF, and merges it into the graph being built. Those triples land in the Iceberg table alongside the triples from your source files.

If a task emits salmodule:Error nodes instead of data, the build fails with the messages the module reported.

Every module a build downloaded, whether it was dereferenced for its vocabulary or run as a task, is written to the Iceberg table metadata as a JSON list under the sal.salmodules property:

["salmodule://github.com/cgs-earth/python-geoconnex"]

The list is rewritten on every build, so dropping a module from your RDF drops it from the table too. sal query --info properties prints it, and the Modules tab of sal serve --with-ui offers each entry as a chip and as an autocompletion so a module the table was built from can be inspected without typing its URI.

  • A reachable docker daemon. SAL talks to whatever DOCKER_HOST points at, defaulting to the local socket.
  • git on PATH, used to clone the module repository.

Each module is cloned and built once per sal build invocation. Dereferenced module ontologies are cached alongside SAL’s other vocabularies; --no-cache clears that cache.