Walk-through
One record's full journey through Squashage. Pathfinder Second Edition data from the Archives of Nethys.
Before — the input
{
"_type": "feat",
"url": "https://2e.aonprd.com/Feats.aspx?ID=750",
"name": "Power Attack",
"level": 1,
"rarity": "common",
"traits": ["flourish"],
"action_cost": "two-actions",
"_source": {
"target": "aonprd",
"url": "https://2e.aonprd.com/Feats.aspx?ID=750",
"plugin": "aonprd:parse"
}
}Any upstream tool can produce this — a scraper, an API client, a hand-written export. Squashage cares only that the record carries an optional _source block for provenance.
The config
A config file is one run. The root object carries input, output, and the run knobs (graphs, ontology, classification, enrichment, quarantine, concurrency, subjectIri) directly.
{
"input": { "basePath": "./input", "format": "json" },
"output": { "type": "file", "path": "./graphs/aonprd.trig", "format": "trig" },
"concurrency": 4,
"graphs": { "default": "https://example.org/graph/aonprd/default" },
"ontology": { "baseIri": "https://2e.aonprd.com/" },
"classification": {
"conflict": { "onConflict": "pickPriority", "evidence": true },
"source": true,
"urlPattern": {
"patterns": [
{ "className": "feat", "match": "/Feats\\.aspx", "priority": 35 },
{ "className": "spell", "match": "/Spells\\.aspx", "priority": 35 }
]
},
"structural": [
{ "className": "feat", "priority": 10,
"predicate": { "path": "/_type", "equals": "feat" },
"reasons": ["_type=feat"] }
],
"ontologyClassifier": {
"classes": {
"feat": "https://example.org/vocab/Feat",
"spell": "https://example.org/vocab/Spell"
}
}
}
}The CLI invokes:
squashage-dag build --config ./squashage.config.jsoncDuring — what the DAG does
walk-inputscans./inputand produces oneRecordLocatorper file. The nativescatter { dag }placement fans the array out across per-record DAG instances.index-entitiesruns once. It readsenrichment.entityLinkconfig; when absent, it routesskippedandservices.entityIndexstays null. (In this walk-through there is no enrichment config, so it skips.)scatter { dag }dispatches the per-record DAG once per locator, capped atconcurrency: 4workers. Each record runs:a.
json-readparses the file. On parse failure, routequarantined→record-quarantine→ end.b.
classify-allruns the classifier cascade concurrently:classify:sourcewrites a__source__sentinel because_sourceis present.classify:url-patternwrites{ className: 'feat', priority: 35 }because the URL matches/Feats\.aspx.classify:structuralwrites{ className: 'feat', priority: 10 }because_type === 'feat'.- The remaining classifiers have no config in this run, so their placeholders return
no-match. See Classifier cascade for the full set and what each reads.
c.
classify:ontologyreadsstate.proposalsand confirmsfeatis in the known class map; no__validation__sentinel needed.d.
classify:taxonomic-narrowinghas no config slot in this run, so it routesno-op.e.
record-health-gatesees two real proposals and zero errors; routeshas-proposals.f.
classify-conflictsees both proposals agree onfeat; writes:tsstate.classification = { type: 'feat', confidence: 1, engine: 'classify:url-pattern,classify:structural', reasons: ['engine=url-pattern,regex=/Feats\\.aspx → feat', 'url=https://...', '_type=feat'], };g.
squashprojects the record to RDF through lenient json-tology ABox projection: thefeatclass maps to<https://example.org/vocab/Feat>. The projection drops no record on shape mismatch — an unmapped class is emitted under a Generic fallback class. Here the quad<record-iri> rdf:type <https://example.org/vocab/Feat>lands instate.squashedQuadsandservices.dataset.h.
output-provenanceis a no-op (nooutput.provenanceconfig).enrich-entity-linkconfirms the enrichment pass. Noenrichment.entityLinkconfig → skipped. (When href-reconcile IS configured, enrichment happened inline during the scatter, not here.)The native fold gather (
squashage:record-fold) collects each per-record result. Withoutput.mode: 'stream'the ABox and PROV quads stream to disk as they arrive; otherwise the finalize step splits the dataset:- Quads whose graph is
urn:squashage:prov:*→./graphs/aonprd.prov.trig. - Everything else →
./graphs/aonprd.trig.
- Quads whose graph is
catalog-emitis a no-op withoutoutput.bucketing.
After — the success graph
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
<https://squashage.dev/instance/aonprd/record/a94a8fe5>
rdf:type <https://example.org/vocab/Feat> .The PROV graph (sibling file) carries one prov:Activity per node:
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix dag: <https://studnicky.dev/dagonizer/vocabulary#> .
<urn:squashage:prov:2026-05-18T00:00:00.000Z> {
<urn:squashage:activity:.../run:0>
a prov:Activity, dag:Run ;
dag:dagName "squashage:run" ;
prov:startedAtTime "2026-05-18T00:00:00.000Z"^^xsd:dateTime ;
prov:endedAtTime "2026-05-18T00:00:01.234Z"^^xsd:dateTime ;
dag:lifecycle "completed" .
<urn:squashage:activity:...:json-read:1716000000123>
a prov:Activity, dag:NodeExecution ;
dag:nodeName "json-read" ;
dag:output "loaded" ;
prov:wasInformedBy <urn:squashage:activity:.../run:0> .
# ... one activity per node execution ...
}See also
- DAG — every node + every route.
- Classifier cascade — what each classifier reads and proposes.
- Provenance — the PROV-O graph in detail.
- Architecture — module map + class lineage.
