Ontology Layer Separation from Project Layer — Content-Addressed Decentralized Ontology with Classification

Accepted

ontoref
Through this session's evolution, the project's ontology lived inside the

Context

Through this session's evolution, the project's ontology lived inside the project's filesystem (.ontology/, adrs/, reflection/), git-versioned alongside the project's code. This pattern made sense when the ontology was specific to a single project; it became structurally problematic as the ecosystem grew to 19 onboarded projects sharing the same ontoref protocol, the same axioms, the same Practice nodes for common patterns, and cross-project federation became a recurring need.

The session's load-bearing insight (your formulation): when the ontology is coupled to the project's filesystem, cross-project operations or verifications require dragging the entire other project into local scope. A project A trying to validate against a constraint declared in project B has no choice but to clone B, parse B's NCL, materialize B's state — and then perform a single check that touches one node of B. This is the H7 saturation pattern at the storage layer: as agents saturate when loaded with full project context, verifiers saturate when loaded with full foreign-project context.

The substrate G1-G10 (G5 in particular: Merkle commitments + witness API) was designed to escape this — a verifier holding only state_root + witness can validate a claim without holding the source. But the witness mechanism is useless when the verifier still must clone the source to know what state_root applies, what schema is in force, what validators bind to which ops. The asymmetry between (small witness) and (huge cloned context) breaks the local-verifiability promise.

This ADR closes the asymmetry by separating two layers that have been conflated:

Project layer — the consumer codebase. Rust crates, Nushell scripts, config files, project-specific NCL extensions. Lives in Radicle / jj repositories. Managed by the existing VCS abstraction (ADR-013).

Ontology layer — protocol-level and shared-domain ontologies. Lives as content-addressed decentralized streams: each ontology is an append-only oplog (G3 primitive), with Merkle commitments (G5) over its current state. Reachable by content-address, not by filesystem location. Multiple projects reference the same ontology by its content-address; none of them owns it.

Cross-project verification under this separation: project A's operation that requires validating against an ontological entity declared in shared ontology X resolves the reference by content-address, fetches only the witness for the specific cell, verifies against the ontology's state_root. No project is dragged; only the witness travels.

Decision

Separate the operational world into two layers with distinct sovereignty, sync mechanisms, and discovery patterns:

Ontology layer (decentralized, content-addressed):

An ontology is an append-only oplog (G3 primitive from substrate G1-G10) containing operations that build the ontological knowledge: axioms, tensions, practices, ADR entries, validators, schemas. Each ontology has: - An identity (Ed25519 keypair for its maintainers; multi-maintainer via CRDT per-domain from ADR-027) - A content-address: blake3 hash of the head OpId chain root - A current state_root (G5 Merkle commitment) - A discovery surface: pubkey + recent head OpId, broadcast via SyncBackend (ADR-027) — peer projects subscribe to follow updates

Ontologies are independent of any project. They exist as their own data structures with their own sync, their own maintainers, their own evolution. A project consumes ontologies by reference; cannot mutate them locally; cannot fork them without explicit forking semantics (which is itself an ontology operation — fork is a recorded act).

Project layer (Radicle + jj, existing infrastructure):

Project codebases continue to live in Radicle repos managed via jj (ADR-013 VCS abstraction + the existing agent-workspace-orchestration pattern with jjw). What changes is the content: - Source code (Rust, Nushell, etc.) — unchanged - Project-specific NCL extensions — minimized; most ontology content moves to the ontology layer - Local oplog of the project — records ops the project emits (proposed ADRs, FSM state moves, backlog ops); references the ontology layer for the schema and validators that apply - Witnesses + state_root snapshots — for cross-project verification

Ontology classification with granularity:

Type 1 — Protocol ontology (mandatorily decentralized): The ontoref-core itself — axioms (voluntary-adoption, internal-coherence-enforced, protocol-not-runtime, self-describing, dag-formalized), named tensions, base practices, base validators. Every adopting project references this by content-address; no project embeds it. The protocol ontology's maintainers are the ontoref project's contributors (this repo's current authors); its content-address is the protocol's published identity.

Type 2 — Shared domain ontology (granular: federable to mandatorily decentralized): Domain ontologies used by multiple projects: provisioning (cluster ops, component catalog), persona (career, opportunities), lian-build (image layers), forge-fleet (fleet management). The granularity: - Type 2a — Federable: shared between this author's projects; decentralization recommended but not mandatory; can live as a single oplog accessible to a known peer set - Type 2b — Mandatorily decentralized: shared with external consumers (any third party may consume); MUST be content- addressed and discoverable via SyncBackend; maintainers may be broader than the originating author

Type 3 — Project-specific ontology (optional decentralization): Extensions unique to a single project that have not (yet) graduated to shared-domain status. Lives as project-local oplog initially; promotable to Type 2 when consumption emerges. The current .ontology/ content of the 19 onboarded projects predominantly falls here.

Type 4 — Private ontology (local, tier-0): Projects that have NCL files but no substrate, no oplog, no operations layer. The pre-ADR-024 baseline. Untouched by this ADR.

The classification is metadata on every ontology declaration. The ontoref-ontology-content crate (new, see below) reads the classification and applies the corresponding sync / sovereignty rules.

New crate: ontoref-ontology-content

Hosts the per-ontology oplog primitives, content-addressing, and fetch-by-witness logic. Distinct from ontoref-ontology (which becomes the bridge between state and NCL manifestation, per ADR-025). Roles: - OntologyId(blake3) — content-address - fetch_ontology(id, sync_backend) — pull ops from peers - fetch_cell_witness(id, cell_query) — fetch only the witness needed to verify a specific cell, not the whole state - verify_cell(ontology_id, cell, witness, state_root) — local check - publish_op(ontology_id, op) — append to the ontology's oplog if authorized; broadcast via SyncBackend

Cross-project verification via witness (the asymmetry-closing property):

Project A invokes an operation whose precondition includes "this entity is in the pool declared by shared ontology X". The runtime: 1. Resolves X by content-address (ontology_id) 2. Determines the cell-of-X the precondition reads (e.g. the pool entity) 3. Calls fetch_cell_witness(X, pool_query) — receives only the witness for that cell + the current state_root of X 4. Verifies the cell content + Merkle proof against state_root 5. If valid, the precondition is satisfied; runtime proceeds

No clone of X. No materialization of X's state. Only the witness travels. The asymmetry is closed.

Migration path:

The existing 19 onboarded projects do not migrate immediately. ADR-028 is a structural commitment with progressive realization: - The ontoref-core ontology (Type 1) is extracted first into its own content-addressed oplog; the existing .ontology/core.ncl content becomes the bootstrap content of that oplog - Each shared-domain ontology (Type 2) is extracted when its multi-project usage is operationally evident (provisioning is the most concrete candidate — used by provisioning + forge-fleet + libre-* projects) - Project-specific (Type 3) extensions stay in their projects until they need cross-project consumption - Private (Type 4) projects remain unaffected

Constraints

  • Hard A new crate ontoref-ontology-content MUST exist with the following surface: OntologyId, fetch_ontology, fetch_cell_witness, verify_cell, publish_op. The crate depends on ontoref-types (for OpBody), ontoref-oplog (for log primitives), ontoref-commit (for state_root and witnesses), and optionally an SyncBackend impl.
  • Hard Every ontology (in this project: ontoref-core, the existing .ontology/) MUST declare its classification (Type 1 | Type 2a | Type 2b | Type 3 | Type 4) in its metadata. The classification field is part of the OntologyMetadata struct surfaced via ontoref-ontology-content::metadata(ontology_id).
  • Hard When ADR-028 transitions to Accepted (post-implementation), the ontoref-core ontology (current .ontology/core.ncl content) MUST be addressable via its content-address (blake3 of its head OpId chain root) and discoverable via at least one SyncBackend (FilesystemSync acceptable initially). The transition records the content-address as the canonical reference in CHANGELOG.md.
  • Hard An integration test in crates/ontoref-ontology-content MUST demonstrate: project A invokes an operation whose precondition references a cell in ontology X, the runtime fetches only the cell witness (not the full ontology), verifies via state_root, and the operation proceeds without materializing X locally. The test fails if X's full state is loaded.
  • Hard This ADR engages and synthesizes the protocol-not-runtime + self-describing axioms and the formalization-vs-adoption tension. The synthesis preserves all three: the ontology layer is reference content addressable by content-hash (not a runtime); self-description moves to its own layer (preserved structurally); classification with granularity absorbs the adoption shock per ontology.

Alternatives considered

  • Keep ontology inside each project; build cross-project federation that smart-loads partial ontology datarejected: This was the current federation.rs design (BFS depth 5 over HTTP). It has been unused for 419 lines of code precisely because the cost of operating it (every project must expose endpoints, every cross-project query is a network call to a project's filesystem) exceeds the benefit. ADR-028's content-addressing inverts the model: the ontology IS the addressable unit, not 'a slice of a project'. The cost of operation drops because the sync target is purpose-built.
  • Single global ontology repo (one Radicle repo for all shared-domain content)rejected: Reproduces the centralization anti-pattern at the content-management layer. A single repo for all shared ontologies creates a coordination point (who can merge to it?) and a single failure mode. The per-ontology oplog model gives each ontology its own sovereignty — provisioning ontology and persona ontology have different maintainers, different update cadences, different consumers; collapsing them into one repo would force false coordination.
  • Move all ontology to a decentralized layer immediately; no Type 3 (project-specific) or Type 4 (private) tierrejected: Crushes adoption. A new project trying ontoref for the first time would be forced to publish its ontology to a decentralized channel as a precondition of using the protocol at all. The classification with optional decentralization for Types 3 and 4 lets projects start where they are; the decentralization happens when consumption justifies it, not as an entry tax.
  • Keep ontology in project but use IPFS-style content addressing for cross-project refsrejected: Hybrid that does not resolve the core asymmetry. The ontology still lives in project filesystem; the content-address is a thin convenience layer. Cross-project queries still require the responding project to materialize its full ontology (because the content-address points to a file the consumer might not have, hence needs full materialization to serve the content). The asymmetry is unresolved.
  • Use Radicle for ontology layer too (one Radicle repo per ontology)rejected: Radicle is a git-protocol-based system; its strengths are code review, branching, social collaboration. Ontology evolution is operationally distinct — append-only oplog with CRDT merge, not git-style branching. Forcing ontology into Radicle would impose the wrong semantics. The ontology layer needs its own primitive (oplog from G3); Radicle stays for project code.

Anti-patterns

  • Drag Whole Project For Cross-Project Verification — Verifying a single ontological assertion declared in another project by cloning that project and materializing its entire NCL state. Inverts the substrate's G5 witness asymmetry: a Merkle witness is hundreds of bytes, a project clone is megabytes-to-gigabytes plus parse cost. Saturates verifiers the way unbounded context saturates agents (H7 pattern).
  • Centralized Shared-Ontology Repo — Collapsing every shared-domain ontology (provisioning, persona, lian-build, forge-fleet) into a single global repository. Creates one merge gatekeeper, one failure mode, and forces false coordination between ontologies whose maintainers, update cadences, and consumer sets are independent.

Related ADRs

ADR-001 · ADR-005 · ADR-009 · ADR-013 · ADR-018 · ADR-020 · ADR-023 · ADR-024 · ADR-025 · ADR-026 · ADR-027

Was this useful? Rate it
Got something to add? Tell me what you think, what you'd suggest, or whether we should keep exploring this topic.
· reads

We use cookies to help this site function, understand service usage, and support marketing efforts. Cookie Policy for more info.