# 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.
