Ontology generator — implementation infrastructure
Context and Problem Statement
ADR-0007 ratifies the specification of the OPDA ontology generator: a bespoke Python tool with rdflib-backed primitives plus a custom canonical N-Triples → Turtle serialiser, satisfying ODR-0004 §6a byte-identity CI contract.
This ADR ratifies the implementation infrastructure that realises ADR-0007:
- Repository structure for the generator package (
tools/opda-gen/). - CLI design (
opda-gen emit ...). - Test framework and per-module test fixtures.
- CI workflow integration with GitHub Actions.
- Dependency pinning and reproducibility guarantees.
- Local-development workflow (one-command regeneration).
Without this ADR, ADR-0007 stays as documentation without executable code. With it, ADR-0009 (foundation emission) and onwards can land.
Decision Drivers
- One-command developer experience.
opda-gen emitmust produce byte-identical TTL with no further configuration. Onboarding cost for a new contributor: read README, install, run, see green CI. - Reproducibility across machines. Two engineers running the generator on different platforms (macOS, Linux, Windows-via-WSL) MUST produce byte-identical output. Mitigation: lock all dependencies; pin Python version; use deterministic stdlib hash + canonical serialiser.
- Fast iteration. Generator runs in under 5 seconds on a typical laptop for the full corpus. Test suite runs in under 30 seconds.
- CI feedback loop. Every PR sees byte-identity test result within 2 minutes of push.
- Inspectable. No opaque framework behaviour. Every emission rule traceable to a line of OPDA-owned Python.
Considered Options
- A — Standalone repository for the generator (separate from main OPDA repo). Pro: focused scope; cross-project reuse potential. Con: cross-repo coordination overhead; ODR + ADR + generator stay in lockstep regardless, so single-repo is operational kindness.
- B — Inline Python module at root of OPDA repo (
src/ontology_generator/). Pro: zero cross-repo coordination. Con: pollutes the Astro site repo namespace; root-level Python invites accidental coupling. - C —
tools/opda-gen/subdirectory within main repo (chosen). Pro: clearly scoped utility; idiomatic Python package; doesn’t pollute Astro site src/; one PR per ODR-driven change touches both ODR + generator together.
Decision Outcome
Chosen option: C — tools/opda-gen/ subdirectory within the main OPDA repository, because it satisfies all five decision drivers while keeping ODR + ADR + generator in one commit-stream for traceability.
Repository structure
opda/ # Repository root
├── docs/
│ ├── adr/ # This ADR + siblings
│ └── ontology/odr/ # ODR corpus (ratified inputs)
├── source/
│ ├── 00-deliverables/semantic-models/ # Glossary + data dictionary (inputs)
│ └── 03-standards/
│ ├── schemas/ # JSON schemas (inputs; nested git repo)
│ └── ontology/ # Generator outputs (this programme)
│ ├── foundation.ttl # ADR-0009 output
│ ├── opda-classes.ttl
│ ├── opda-shapes.ttl
│ ├── opda-annotations.ttl
│ ├── opda-vocabularies.ttl # ADR-0010 output
│ ├── opda-property.ttl # ADR-0011 outputs
│ ├── opda-agent.ttl
│ ├── opda-transaction.ttl
│ ├── opda-claim.ttl
│ ├── opda-governance.ttl
│ ├── opda-descriptive.ttl
│ ├── profiles/ # ADR-0013 outputs
│ │ ├── baspi5.ttl
│ │ ├── ta6.ttl
│ │ └── ...
│ ├── derived/ # Build-step composition
│ │ ├── opda-validation.ttl
│ │ ├── opda-ui.ttl
│ │ └── opda-inference.ttl
│ └── exemplars/ # ADR-0014 + existing
│ ├── *.ttl # 15 diagnostic exemplars
│ └── *-expected-report.ttl # ADR-0014 pairings
└── tools/
└── opda-gen/ # This ADR's deliverable
├── pyproject.toml # Python project metadata + deps
├── README.md # Developer-facing docs
├── src/opda_gen/
│ ├── __init__.py
│ ├── __main__.py # `python -m opda_gen` entry
│ ├── cli.py # Click-based CLI
│ ├── inputs/ # Input parsers
│ │ ├── glossary.py
│ │ ├── data_dictionary.py
│ │ └── odr_corpus.py # Parse `kind: pattern` UFO commitments
│ ├── term_sourcing.py # 5-line precedence resolver (ODR-0004 §7a)
│ ├── emitters/
│ │ ├── foundation.py # ADR-0009 implementation
│ │ ├── vocabularies.py # ADR-0010 implementation
│ │ ├── classes.py # ADR-0011 implementation
│ │ ├── shapes.py # ADR-0012 implementation
│ │ ├── annotations.py # ADR-0012 implementation
│ │ └── profiles.py # ADR-0013 implementation
│ ├── serialiser/
│ │ ├── canonical.py # Canonical N-Triples → Turtle
│ │ ├── blank_nodes.py # SHA-256 skolemisation
│ │ └── ordering.py # Deterministic emission ordering
│ ├── composer.py # Derived-profile build-step
│ └── ci/
│ ├── three_graph_test.py # ODR-0004 §3a five-part CI test
│ └── byte_identity.py # Regenerate-and-diff check
├── tests/
│ ├── fixtures/
│ │ ├── minimal_glossary.json
│ │ └── minimal_dictionary.json
│ ├── test_serialiser.py # Canonical ordering invariants
│ ├── test_term_sourcing.py # 5-line precedence cases
│ ├── test_blank_nodes.py # SHA-256 skolemisation determinism
│ ├── test_three_graph.py # CI test correctness
│ └── test_byte_identity.py # End-to-end regeneration determinism
└── .python-version # 3.11 lock
CLI design
$ opda-gen --help
Usage: opda-gen [OPTIONS] COMMAND [ARGS]...
OPDA ontology generator.
Options:
--version Show generator version + git SHA
--help Show this message
Commands:
emit Emit the full ontology corpus
emit-foundation Emit foundation.ttl only (Phase 1)
emit-vocabularies Emit SKOS schemes only (Phase 2)
emit-module <name> Emit one module .ttl (Phase 3)
emit-shapes Emit shapes graph (Phase 4)
emit-profile <overlay> Emit one overlay profile (Phase 5)
compose Build-step compose derived consumer profiles
ci-byte-identity Run byte-identity check (regenerate + diff)
ci-three-graph Run ODR-0004 §3a five-part CI test
validate-exemplar <path> Run pyshacl against an exemplar; compare to expected-report
The emit command is the default umbrella: regenerates the full corpus. Sub-commands enable incremental work + targeted testing.
Dependencies (locked)
pyproject.toml:
[project]
name = "opda-gen"
version = "0.1.0"
requires-python = ">=3.11,<3.12"
dependencies = [
"rdflib==7.0.0", # RDF primitives; canonical serialiser is OPDA-owned
"pyshacl==0.25.0", # SHACL validation (CI + exemplar tests)
"click==8.1.7", # CLI framework
"pydantic==2.5.0", # Input schema validation
"tomli==2.0.1", # pyproject reading
]
[project.scripts]
opda-gen = "opda_gen.cli:main"
[tool.pytest.ini_options]
testpaths = ["tests"]
Lock file (uv.lock or requirements.txt) committed to repo. CI installs with pip install -e . from the locked manifest.
CI workflow
.github/workflows/ontology-byte-identity.yml:
name: Ontology byte-identity
on:
push: { branches: [main] }
pull_request: { branches: [main] }
jobs:
byte-identity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.11' }
- working-directory: tools/opda-gen
run: pip install -e .
- working-directory: tools/opda-gen
run: opda-gen emit --output /tmp/ontology
- run: diff -rq /tmp/ontology source/03-standards/ontology --exclude=exemplars --exclude=derived
- working-directory: tools/opda-gen
run: opda-gen ci-three-graph
- working-directory: tools/opda-gen
run: opda-gen validate-exemplar source/03-standards/ontology/exemplars/registered-freehold-house.ttl
# (Plus per-exemplar iteration in matrix strategy.)
Sub-2-minute target (Python startup + rdflib parse + emit + diff is fast).
Consequences
- Good, because the generator lives where the ODRs and ADRs live — one commit can touch all three (rule + spec + implementation).
- Good, because Python 3.11 + rdflib 7.0 is a mature, well-supported stack with predictable behaviour.
- Good, because
tools/opda-gen/clearly scopes the utility — not part of the Astro site build (ADR-0003) and not part of the schemas nested git repo. - Good, because CLI sub-commands enable incremental Phase-by-Phase development (Phase 1 emit-foundation; Phase 2 emit-vocabularies; etc.).
- Good, because dependency locking + Python version pinning guarantee cross-machine reproducibility.
- Good, because the test suite catches serialiser regressions before they hit byte-identity CI on the full corpus.
- Bad, because Python adds a runtime dependency to OPDA’s build chain (alongside Node for Astro). Mitigation: Python is only required for ontology regeneration, not for site rendering or general contribution.
- Bad, because rdflib version-lock means OPDA can’t auto-upgrade with security patches without a generator regression test. Mitigation: weekly Dependabot PR; manual review; small surface area (canonical serialiser bypasses most rdflib code paths).
- Neutral, because the generator is small enough (~800-1200 LOC target per ADR-0007) to be maintained by one engineer.
Confirmation
The ADR is honoured when all six hold:
- Package installs.
cd tools/opda-gen && pip install -e .succeeds on a fresh Python 3.11 environment. - CLI runs.
opda-gen --versionreturns a sensible version + git SHA. - Test suite green.
pytest tools/opda-gen/tests/passes (initial scope: serialiser invariants + term-sourcing + blank-node determinism + three-graph CI test). - CI workflow exists.
.github/workflows/ontology-byte-identity.ymlexists and runs on push + PR. - README ships.
tools/opda-gen/README.mdincludes: installation, usage, contribution guide, link back to ADR-0007 specification + ADR-0008 implementation. - Reproducibility verified. Two contributors regenerate from the same input commit; outputs are byte-identical.
Manual test: clone fresh, install, run opda-gen emit --output /tmp/test, diff -rq /tmp/test source/03-standards/ontology/... → empty diff.
Programme-wide validation gate (per ADR programme plan §9 — Validation discipline). In addition to the ADR-specific criteria above, this ADR moves proposed → accepted only when all four of the following hold (independent of the worker that implemented this ADR):
- (a) Soundness check PASS — every emitted artefact traces to a cited ODR/ADR
## Rulesor## Operational specificationsclause viadct:source(for Turtle) or code-comment provenance header (for Python). The validation agent extracts emitted-artefact provenance and verifies each resolves to a ratified section. - (b) Completeness check PASS — every cited ODR’s
## Rulesand## Operational specificationssubsection is realised by an emitted artefact OR explicitly deferred with a named follow-up trigger. The validation agent enumerates cited subsections and checks coverage. - (c) Cross-ADR consistency check PASS — every downstream ADR’s confirmation criteria can be met given this ADR’s emission (e.g. classes emitted here are referenceable by downstream shapes; shapes here are composable by downstream profiles). The validation agent simulates the downstream contract against this ADR’s output.
- (d) Validation report committed at
docs/adr/validation/ADR-0008-validation-report.md, produced by an independent validation-agent spawn (NOT the implementing worker; mirrors the Council Devil’s Advocate independence per ODR-0001 §Roles for every session; see ADR programme plan §8 swarm orchestration topology).
A FAIL on any of (a)–(d) blocks accepted status; the implementing worker amends and validation re-runs. Two consecutive validation failures on the same ADR escalate to a Council mini-session per ODR-0001 §Self-amendment process — engineering does not re-deliberate; surfaced ## Rules ambiguity routes to Council ratification.
More Information
- Specification (predecessor ADR): ADR-0007 — Ontology generator specification. This ADR implements ADR-0007’s architectural specification.
- First emission ADR (successor): ADR-0009 — Foundation TTL emission. Validates that ADR-0008’s infrastructure produces byte-identical foundation output.
- Sibling Astro build: ADR-0003 — Idiomatic Astro refactor. The Node-based site build is parallel infrastructure; ontology generation lives in
tools/opda-gen/, not insrc/. - Council-ratified contract: ODR-0004 §6a generator-first + byte-identity CI. The MUST-level rules the generator implements.
- Reference repositories to study:
- DPV generator pipeline — Python + rdflib-based TBox generation for W3C DPV. Closest published precedent.
- LinkML
gen-shacl— SHACL emission from a structured schema source. Pattern reference for shapes-emission. - rdflib serialisers — study Turtle serialiser internals; bypass for canonical output.
- Out of scope:
- SHACL profile composition mechanics (ADR-0013).
- JSON-LD context emission (deferred per ODR-0011 Q3).
- Ontology editor UI integration (third-party tools consume OPDA TTL).
Amendments
- 2026-05-27 — Implementation landed (commit
2ac4ce2). 41 files / 2,713 lines added undertools/opda-gen/; 28/28 pytest green; CLI surface complete (all 10 subcommands per §“CLI design”); CI workflow at.github/workflows/ontology-byte-identity.yml. Implementation report atdocs/adr/implementation-reports/ADR-0008-implementation.md. - 2026-05-27 — Independent validation PASS-WITH-FOLLOW-UPS (commit
6439b57). Soundness 28/28, Completeness 23 PASS + 8 explicit deferrals + 1 N/A, Cross-ADR 6/6 + 4/4 probes. Status movesproposed → acceptedwith five named follow-ups queued at ADR-0005 §G (G1–G5). Validation report atdocs/adr/validation/ADR-0008-validation-report.md.
Comments
Loading comments…
Sign in to post a comment