Skip to content

Taxonomy — upper ontology and parents DSL

Squashage ships a small, reusable upper ontology called squashage-core. Every target builds its domain-specific leaf classes by extending these 10 generic classes. Inheritance is expressed with native JSON Schema allOf + $ref, not a custom keyword. The framework materializes transitive ancestor rdf:type triples at build time so downstream consumers do not need OWL reasoning.

The squashage-core upper ontology

All 10 classes live in src/schemas/core/. Their $id base is https://noocodec.dev/squashage/core/.

Class inventory

ClassParentKey propertySemantic bindingDescription
ThingRoot; every entity is at minimum a Thing
NamedThingThingname: stringname → rdfs:labelA Thing with a human-readable name
IdentifiedThingid: stringCarries a stable identifier independent of the subject IRI
ProvenanceThingsource: IriStringsource → dct:sourceMixin for dct:source tracking
DocumentSegmentNamedThingtext: stringStructured prose (chapter pages, rules text)
ContentEntryNamedThingdescription, rarityCanonical superclass for catalog-style records
VocabularyNamedThingcategory: string|nullClosed-vocabulary term (traits, rarities, action costs)
ReferenceThinghref: IriStringIRI-promoted cross-reference between entities
MechanicThingeffect: stringRules-text-bearing structure
ContainerThingmembers: Reference[]Collection holding References to member entities

Inheritance diagram

Schema locations

Each class is a standalone JSON Schema 2020-12 document:

ClassSchema file
Thingsrc/schemas/core/Thing.schema.json
NamedThingsrc/schemas/core/NamedThing.schema.json
Identifiedsrc/schemas/core/Identified.schema.json
Provenancesrc/schemas/core/Provenance.schema.json
DocumentSegmentsrc/schemas/core/DocumentSegment.schema.json
ContentEntrysrc/schemas/core/ContentEntry.schema.json
Vocabularysrc/schemas/core/Vocabulary.schema.json
Referencesrc/schemas/core/Reference.schema.json
Mechanicsrc/schemas/core/Mechanic.schema.json
Containersrc/schemas/core/Container.schema.json

The parents refinement DSL

The refinement DSL (schema: src/schemas/refinement.schema.json) is applied to draft schemas during the refine phase. The parents field is operation #15 — the final operation applied by RefinementApplier.apply().

Field reference

json
{
  "$schema": "https://squashage.dev/schemas/refinement.schema.json",
  "appliesTo": "Feat",
  "parents": ["ContentEntry", "Mechanic"],
  "parentsBase": "https://noocodec.dev/squashage/core"
}
FieldTypeRequiredDescription
parentsstring[]noClass names to extend. Each becomes one allOf: [{ $ref }] entry.
parentsBasestringnoBase URL for $ref construction. When absent, derived from the draft's $id by stripping the /inferred/<leaf> suffix.

How $ref paths are constructed

For each entry in parents, the applier builds:

<parentsBase>/<ClassName>.schema.json

Example: parents: ["ContentEntry"] with parentsBase: "https://noocodec.dev/squashage/core" emits:

json
{ "$ref": "https://noocodec.dev/squashage/core/ContentEntry.schema.json" }

Additive merge with existing allOf

parents entries are appended to any allOf array already present in the draft schema — they do not overwrite it. Drafts produced by SchemaInducer do not carry allOf, so the merge is a no-op in practice. If a draft does carry existing allOf entries (e.g. from a hand-authored seed), they are preserved and the parent refs are appended after them.

Multiple inheritance

A class may extend multiple parents. List them all; order is preserved in the output allOf array and in the ancestor traversal order used by TaxonomicInheritanceEnricher:

json
{
  "parents": ["ContentEntry", "Mechanic"],
  "parentsBase": "https://noocodec.dev/squashage/core"
}

This produces:

json
{
  "allOf": [
    { "$ref": "https://noocodec.dev/squashage/core/ContentEntry.schema.json" },
    { "$ref": "https://noocodec.dev/squashage/core/Mechanic.schema.json" }
  ]
}

parentsBase derivation

When parentsBase is absent the applier derives it from the draft's $id:

  1. Parse the $id URL.
  2. Take the pathname directory (everything before the last /).
  3. Strip a trailing /inferred segment if present — drafts live in <origin>/schemas/inferred/, finals live in <origin>/schemas/.
  4. Reconstruct origin + derived path (no trailing slash).

Example derivation:

$id  = "https://2e.aonprd.com/schemas/inferred/Feat.draft.json"
dir  = /schemas/inferred   →  strip /inferred  →  /schemas
base = "https://2e.aonprd.com/schemas"
ref  = "https://2e.aonprd.com/schemas/ContentEntry.schema.json"

This is only useful when the parent schemas live beside the leaf schemas (same origin, same schema directory). When extending squashage-core, always specify parentsBase explicitly.

Name validation

Each parent name must match [A-Za-z][A-Za-z0-9_]*. Invalid names (empty string, names with hyphens, etc.) emit a PARENTS_INVALID_NAME warning and are skipped. The rest of the parents list continues to apply.


TaxonomicInheritanceEnricher

Source: src/induction/TaxonomicInheritanceEnricher.ts

The enricher is a pure, stateless class called from ontologyProjection after the VocabEnricher pass. It adds ancestor rdf:type quads for every subject that has already received its direct class type quad.

Why it exists

OWL semantics entail that if aonprd:Feat rdfs:subClassOf core:ContentEntry, every Feat instance is also a ContentEntry. Most consumers — SPARQL endpoints, graph stores, the visualization layer — do not run OWL reasoning. The enricher materializes those entailed triples explicitly so consumers get the full type chain without a reasoner.

What it emits

For a Feat record with subject https://2e.aonprd.com/PowerAttack, given ancestors [core:ContentEntry, core:NamedThing, core:Thing], it appends:

turtle
<https://2e.aonprd.com/PowerAttack>  rdf:type  <https://noocodec.dev/squashage/core/ContentEntry>  <graph> .
<https://2e.aonprd.com/PowerAttack>  rdf:type  <https://noocodec.dev/squashage/core/NamedThing>    <graph> .
<https://2e.aonprd.com/PowerAttack>  rdf:type  <https://noocodec.dev/squashage/core/Thing>         <graph> .

Deduplication is enforced: quads already present in baseQuads are not emitted again. Iteration order mirrors ancestorIris exactly — the caller (JsonTologyOntology.ancestorIris) supplies BFS order (immediate parent first, root last).

Signature

ts
static enrich(
  baseQuads:    ReadonlyArray<Quad>,
  className:    string,
  ancestorIris: ReadonlyArray<string>,
  subjectIri:   string,
  factory:      DataFactory,
  targetGraph:  NamedNode | DefaultGraph,
): ReadonlyArray<Quad>

Returns a new array — never mutates the input.


See also

Released under the MIT License.