# Oikos — Ontology Views > Companion to [the Framework](framework.md), [the Model](README.md), and > [the Component Views](components.md). Holt calls Ontology "arguably the > single most important part of MBSE, as all of the other elements that > make up MBSE are ultimately traceable back to [it]" (*Systems Engineering > Demystified*, 2nd ed., Ch. 2, p. 40). [framework.md §3](framework.md#3-the-ontology--oikoss-domain-specific-language) > sketched this in condensed form (13 classes) to make one point: Oikos's > domain ontology already exists as running code, not documentation. This > document is the fuller treatment that sketch promised — a repeating > Viewpoint (registered as Viewpoint 11 in > [framework.md §4](framework.md#4-the-viewpoint-catalog--the-frameworks-template-for-views)), > instantiated as four Views below. **Every fact in this document was read directly from `seeds/ontology.yaml` during this pass** (not carried over from ADR-0014's summary, though it is cross-checked against it) — where the two disagree, that disagreement is itself reported as a finding, not silently reconciled. **Stakeholders for all four Views below:** engineers adding a new entity or relationship type, anyone reasoning about whether a lifecycle transition is actually gated or just documented, and anyone deciding whether a term used elsewhere in this documentation set means what they think it means. **Why they earn their place:** every Viewpoint in [framework.md §4](framework.md#4-the-viewpoint-catalog--the-frameworks-template-for-views) "traces terminology to the Ontology" (§3.1 of that document) — these four Views are where that tracing actually terminates. **Notation:** tables (the source data), Mermaid `graph`/`stateDiagram-v2` (the structure). ## Contents | View | Answers | |---|---| | [11a. Entity Type Hierarchy](#11a-entity-type-hierarchy) | What can exist, and how is it classified? | | [11b. Relationship Catalog](#11b-relationship-catalog) | How can two entities be connected, and with what multiplicity? | | [11c. Lifecycle State Machines](#11c-lifecycle-state-machines) | What states can a governed entity be in, and what gates each transition? | | [11d. Concrete Population](#11d-concrete-population) | What's actually instantiated, versus merely possible? | --- ## 11a. Entity Type Hierarchy 60 entity types, 5 abstract (cannot be instantiated directly — they exist only as polymorphic relationship endpoints and `is-a` parents), organized by `domain:` (8 values) and `layer:` (4 values: meta, infrastructure, governance, cognition). | Layer | Domains it contains | Entity type count | |---|---|---| | `meta` | meta | 1 (`entity`, the abstract root) | | `infrastructure` | physical, compute, network, storage, software, external | 40 | | `governance` | identity | 7 | | `cognition` | cognition | 12 | | Domain | Count | Abstract types in this domain | |---|---|---| | compute | 11 | `compute-entity`, `machine`, `container` | | network | 10 | `network` | | cognition | 12 | *(none)* | | identity | 7 | *(none)* | | software | 7 | *(none)* | | external | 4 | *(none)* | | storage | 4 | *(none)* | | physical | 4 | *(none)* | | meta | 1 | `entity` | One 60-node diagram doesn't fit on a screen and, worse, tempts you to fall back on subgraph grouping instead of explicit edges for the flatter domains — which is what an earlier version of this section did: most leaf types were boxed together visually but had no drawn `-->` from `entity` at all. Split by domain instead, every type below has an explicit parent edge — nothing is implied by proximity alone. ### Layer overview ```mermaid graph TD entity["entity — abstract root\nlayer: meta"] --> INFRA["infrastructure layer\n40 types — physical, compute, network,\nstorage, software, external"] entity --> GOV["governance layer\n7 types — identity domain"] entity --> COG["cognition layer\n12 types"] ``` ### Domain: physical (4 types, all flat) ```mermaid graph TD entity["entity"] --> site entity --> ups entity --> sensor entity --> peripheral ``` ### Domain: compute (11 types — the deepest nesting in the Ontology) ```mermaid graph TD entity["entity"] --> ce["compute-entity — abstract"] entity --> hypervisor ce --> machine["machine — abstract"] ce --> vm ce --> container["container — abstract"] machine --> proxmoxhost["proxmox-host"] machine --> standalone["standalone-server"] machine --> workstation machine --> appliance container --> lxc container --> dockercontainer["docker-container"] ``` ### Domain: network (10 types) ```mermaid graph TD entity["entity"] --> net["network — abstract"] entity --> netiface["network-interface"] entity --> dnszone["dns-zone"] entity --> dnsrecord["dns-record"] entity --> ingress["ingress-route"] entity --> certificate entity --> firewallrule["firewall-rule"] net --> lan net --> mesh net --> vlan ``` ### Domain: storage (4 types, all flat) ```mermaid graph TD entity["entity"] --> storagepool["storage-pool"] entity --> volume entity --> backuptarget["backup-target"] entity --> dataset ``` `storage-pool`/`volume`/`dataset` look like they should nest (a pool *contains* volumes, a volume *holds* datasets) — they don't, in the type hierarchy. That containment is a **relationship** (`contains`, `holds-dataset`, [§11b](#11b-relationship-catalog)), not an `is-a` parent. Worth stating plainly since the two are easy to conflate: `parent:` says "this is a kind of that"; a relationship says "this instance is connected to that instance." Storage is the domain where the difference is most visible. ### Domain: software (7 types, all flat) ```mermaid graph TD entity["entity"] --> service entity --> application entity --> configrepo["config-repo"] entity --> deploypipeline["deploy-pipeline"] entity --> packageset["package-set"] entity --> cluster entity --> composestack["compose-stack"] ``` ### Domain: external (4 types, all flat) ```mermaid graph TD entity["entity"] --> domainreg["domain-registration"] entity --> cloudservice["cloud-service"] entity --> isplink["isp-link"] entity --> vendordep["vendor-dependency"] ``` ### Domain: identity (governance layer, 7 types, all flat) ```mermaid graph TD entity["entity"] --> person entity --> agent entity --> idp["identity-provider"] entity --> account entity --> secret entity --> key entity --> accessgrant["access-grant"] ``` ### Domain: cognition (12 types, all flat — the domain the agent's own logic runs on) ```mermaid graph TD entity["entity"] --> check entity --> signal entity --> classification entity --> execution entity --> feedback entity --> pattern entity --> skill entity --> approval entity --> document entity --> runbook entity --> investigation entity --> task["task — added after ADR-0014"] ``` Every one of the 60 types above is a direct or indirect child of `entity`; none is disconnected. The full flat list — every type with its exact `parent:` — lives in `seeds/ontology.yaml` directly; reproducing all 60 rows as a table here would duplicate this section rather than clarify it. **Finding: `task` is new since ADR-0014.** ADR-0014 (2026-07-08) documents 56 entity types under a hierarchy diagram that does not include `task` — four fewer than the 60 verified here, meaning more than just `task` was added in the interim (`task` accounts for one of the four) — [README.md §1.5](README.md#15-operational-concept--the-ooda-loop) and [framework.md §2](framework.md#2-the-goal--system-and-model-made-concrete) both describe the Task model as a 2026-07-11 addition (`plans/done/2026-07-11-goal-oriented-chat-control-panel.md`), after ADR-0014 was written. `task` is now entity type #60, `domain: cognition`, `layer: cognition`, described in the seed as *"A goal-structured unit of agent work — one chat/session elevated to a task with a plan, lifecycle status, and outcome."* This is exactly what Holt's Ontology principle predicts: a new concept in the Model ([the Task lifecycle View](README.md#34-functional-flow--the-task-lifecycle-f3f4f5-packaged-for-a-human)) required a new term in the Ontology before it could be modeled consistently — and the term was in fact added, not left implicit. ## 11b. Relationship Catalog **47 relationship types**, each with a fixed `source → target` type pair and a cardinality. This is the complete, current catalog — not the 5 illustrative example-graphs ADR-0014 used to gesture at a smaller set. **Finding: this catalog has grown since ADR-0014.** ADR-0014 (2026-07-08) titles its equivalent section "The Edge Catalog (34 edges)." Verified directly against `seeds/ontology.yaml` during this pass: **47** relationship types exist today — 13 more than ADR-0014 recorded. This is expected drift over an 8-day span of active development (the Task model alone plausibly added `involves`; `part-of` supports the `compose-stack` grouping), not a documentation error — ADR-0014 is a point-in-time record and is not edited after acceptance, per this repo's own convention ([docs/adr/README.md](../adr/README.md)). It is reported here so nobody treats ADR-0014's count as current. | Relationship | Source → Target | Cardinality | |---|---|---| | `hosts` | machine → compute-entity | one-to-many | | `runs-hypervisor` | machine → hypervisor | one-to-one | | `member-of` | proxmox-host → cluster | many-to-one | | `part-of` | docker-container → compose-stack | many-to-one | | `provides` | compute-entity → service | one-to-many | | `runs` | service → application | one-to-many | | `configured-by` | entity → config-repo | many-to-one | | `deploys-to` | deploy-pipeline → entity | many-to-one | | `routes-to` | ingress-route → service | many-to-one | | `secured-by` | ingress-route → identity-provider | many-to-one | | `uses-certificate` | ingress-route → certificate | many-to-one | | `authenticates-via` | service → identity-provider | many-to-one | | `in-zone` | dns-record → dns-zone | many-to-one | | `resolves-to` | dns-record → entity | many-to-one | | `depends-on` | service → service | many-to-many | | `connects-via` | compute-entity → network | many-to-many | | `has-interface` | compute-entity → network-interface | one-to-many | | `interface-on` | network-interface → network | many-to-one | | `mounts` | compute-entity → volume | many-to-many | | `stores-on` | compute-entity → storage-pool | many-to-many | | `contains` | storage-pool → volume | one-to-many | | `holds-dataset` | volume → dataset | one-to-many | | `backs-up-to` | entity → backup-target | many-to-many | | `powered-by` | machine → ups | many-to-one | | `located-at` | machine → site | many-to-one | | `registered-with` | domain-registration → vendor-dependency | many-to-one | | `owns` | person → agent | one-to-many | | `authenticates` | identity-provider → person | one-to-many | | `holds-grant` | agent → access-grant | one-to-many | | `grants` | access-grant → secret | many-to-one | | `can-decrypt` | compute-entity → secret | many-to-many | | `checks` | check → entity | many-to-one | | `raises` | check → signal | one-to-many | | `about` | entity → entity | many-to-many | | `classifies` | classification → signal | many-to-one | | `precedes` | classification → execution | one-to-one | | `targets` | execution → entity | many-to-one | | `requires-approval` | execution → approval | one-to-one | | `performs` | agent → execution | one-to-many | | `decides` | person → approval | one-to-many | | `produces` | execution → feedback | one-to-one | | `contributes-to` | feedback → pattern | many-to-many | | `informs` | pattern → skill | many-to-one | | `guides` | skill → classification | one-to-many | | `documents` | document → entity | many-to-one | | `involves` | task → entity | many-to-many | | `procedure-for` | runbook → entity | many-to-many | Grouped by theme, the same 47 rows read as five coherent sub-ontologies — this is the grouping ADR-0014 used, now complete rather than illustrative: ```mermaid graph LR subgraph Cognition["Cognition — the OODA edges"] CK["check"] -->|raises| SG["signal"] CK -->|checks| EN["entity"] CL["classification"] -->|classifies| SG CL -->|precedes| EX["execution"] EX -->|targets| EN EX -->|requires-approval| AP["approval"] AG["agent"] -->|performs| EX PR["person"] -->|decides| AP EX -->|produces| FB["feedback"] FB -->|contributes-to| PT["pattern"] PT -->|informs| SK["skill"] SK -->|guides| CL TK["task"] -->|involves| EN DC["document"] -->|documents| EN RB["runbook"] -->|procedure-for| EN end ``` ```mermaid graph LR subgraph Governance["Governance — identity and access"] P["person"] -->|owns| A["agent"] IDP["identity-provider"] -->|authenticates| P A -->|holds-grant| AG["access-grant"] AG -->|grants| S["secret"] CE["compute-entity"] -->|can-decrypt| S end ``` The remaining three groups (Infrastructure Topology, Network, Service Dependencies) are unchanged in shape from [ADR-0014 §4](../adr/0014-entity-model.md) — that ADR's diagrams for those three are still an accurate illustrative subset of the table above; only the Cognition and Governance groups gained new edges (`involves`, `part-of`) worth re-drawing. ## 11c. Lifecycle State Machines Six lifecycles are formally registered in `seeds/ontology.yaml`'s `lifecycles:` block, each a named state machine with `states`, `default_state`, `terminal_states`, and per-transition `requires:` — named checks that [internal/ontology](../../internal/ontology) implements in Go. This is the mechanism, not just the diagram: a transition without a satisfied `requires:` check is refused at the code level, for these six types. **Refinement to README.md's Behavior view.** The `execution` lifecycle as registered here has **13 states**, including a `verifying` state distinct from `executing`, and a recovery transition `timed_out → verifying` ("check if the command completed anyway" — the seed's own comment). [README.md §6.2](README.md#62-execution-state-machine--schema-defined-convention-enforced)'s Execution state diagram previously omitted `verifying` as a separate state and has been corrected there to match; the diagram below is the ontology-accurate version and the two now agree. ```mermaid stateDiagram-v2 [*] --> proposed proposed --> approved: operator-approval proposed --> auto_approved: autonomy-allows proposed --> denied approved --> executing: approval-token-valid approved --> expired: approval-ttl-elapsed auto_approved --> executing executing --> verified: verification-passed executing --> failed executing --> timed_out executing --> cancelled: operator-abort timed_out --> verifying: check if it finished anyway verifying --> verified: verification-passed verifying --> failed failed --> rolled_back: rollback-procedure-exists failed --> rollback_failed verified --> [*] denied --> [*] expired --> [*] cancelled --> [*] rolled_back --> [*] rollback_failed --> [*] ``` The other five, with their `requires:` gates named explicitly (abbreviated where a transition has no requirement): | Lifecycle | States | Terminal | Notable gated transition | |---|---|---|---| | `infrastructure` | planned, provisioning, active, migrating, failed, deprecated, destroyed | destroyed | `deprecated → destroyed` requires **five** checks at once: `backups-verified`, `secrets-revoked-and-rekeyed`, `ingress-and-dns-removed`, `no-inbound-edges`, `archaeology-entry` — the strictest single transition in the entire Ontology | | `signal` | raised, acknowledged, acting, muted, resolved, failed | resolved | `acknowledged → acting` requires `classification-exists` — the formal link between Orient and Decide, real in the Ontology even though [Roadmap §9.2](README.md#92-code-real--dead-code--schema-only-matrix) finds the classifier that would create that classification is dead code | | `approval` | pending, approved, denied, expired, revoked | denied, expired, revoked | `pending → approved` requires `token-verified`; `approved → revoked` requires `not-yet-executing` — you cannot revoke an approval whose action has already started | | `pattern` | hypothesized, validated, active, deprecated, invalidated | deprecated, invalidated | `hypothesized → validated` requires `evidence-count-5plus` **and** `confidence-0.7plus` jointly — matches [ADR-0006](../adr/0006-learning-proposal-only.md)'s Wilson-bound description exactly; `validated → active` requires `operator-approval`, annotated in the seed itself as *"S4: never automatic"* | | `skill` | drafted, tested, active, refined, failed, deprecated | deprecated | `tested → active` and `refined → active` both require `operator-approval` — a skill can be authored and tested autonomously but never self-promotes to active | **Finding: `task` has no registered lifecycle.** The `task` entity type (§11a) has a real, documented behavior — [README.md §3.4](README.md#34-functional-flow--the-task-lifecycle-f3f4f5-packaged-for-a-human) shows `planning → awaiting_approval → executing ⇄ awaiting_input → done`/`failed` as a state diagram, and it is enforced in application code (the `agent_sessions.status` column, checked in `cmd/nomos`). But `seeds/ontology.yaml`'s `lifecycles:` block registers only the six machines above — there is no `task:` entry alongside `infrastructure`, `signal`, `execution`, `approval`, `pattern`, `skill`. Practically: the five other governed types get their transition-gating for free from the shared `internal/ontology` machinery (per named `requires:` checks); the Task lifecycle is instead hand-coded in `cmd/nomos`'s Go logic, a structurally different (and unaudited-by-the-shared-mechanism) enforcement path for what is, in every other respect, a first-class Ontology citizen. This is a gap worth a deliberate decision — register `task` formally, or document explicitly that Task's lifecycle is intentionally application-layer rather than Ontology-layer — not an oversight this document is fixing by writing it down. ## 11d. Concrete Population What's actually instantiated versus merely possible in the type system — per [ADR-0014](../adr/0014-entity-model.md) §1, **not independently re-counted against the live database during this pass** (that would require DB access this documentation effort didn't use; the figures below are ADR-0014's, dated 2026-07-08, and should be treated as illustrative of shape rather than a current census): | Type | Count (as of ADR-0014) | Examples | |---|---|---| | `lxc` | 19 | jellyfin, caddy, dns, gitea, nextcloud, matrix | | `service` | 25 | caddy, authentik, dns, jellyfin, paperless, matrix | | `ingress-route` | 21 | `*.hubris.network` | | `config-repo` | 6 | caddy-conf, gitea-customizations, mule-image | | `proxmox-host` | 2 | hubris, strong | | `workstation` | 2 | mac-mini, republic-laptop | | `standalone-server` | 1 | netbird-vps | | `vm` | 2 | zimaos, haos | | `storage-pool` | 3 | local-lvm-hubris, library-hubris, ludo-lvm | | `volume` | 2 | library, media-local | **Why this View matters despite being the least current one here:** it is the check against over-abstraction Holt warns about (p. 35) — an Ontology with 60 types and 47 relationships is only worth having if real entities actually populate a meaningful fraction of it. 88 active entities across roughly a dozen concrete types (out of 55 non-abstract types) is a reasonable population for a homelab of this size; a future re-audit of this specific View is a cheap, well-scoped follow-up (query `entities GROUP BY type`) that this pass explicitly did not do, rather than silently assuming ADR-0014's numbers still hold. ## Keeping this document current Re-derive §11a-11c directly from `seeds/ontology.yaml` whenever it changes — these three Views are transcriptions of that file's structure, not independent judgment, so they go stale the moment the file changes and nobody re-runs the extraction. §11d is the one View here that was already known to be a point-in-time snapshot when written; re-verify it against live DB state before relying on it for a capacity or audit decision.