Files
oikos/docs/mbse/ontology.md
dtoro 55781984c7
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled
docs(mbse): add MBSE system model, framework, component and ontology views
Four cross-linked documents under docs/mbse/, structured after Jon Holt's
Systems Engineering Demystified (2nd ed.): Framework = Ontology + Viewpoints,
producing a Model made of Views.

- framework.md — the Ontology (SE meta-concepts + Oikos's domain ontology)
  and an 11-entry Viewpoint catalog (two repeating: Component, Ontology).
- README.md — the Model's 9 concern-based Views (mission, requirements,
  functional/physical architecture, interfaces, behavior, V&V, risk, roadmap).
- components.md — 8 per-component Views going one layer deeper into each
  running part of the system's own internal structure.
- ontology.md — 4 Views on the domain ontology itself: entity type
  hierarchy (split into 9 digestible per-domain diagrams), full relationship
  catalog, lifecycle state machines with their requires: gates, and concrete
  population.

Grounded in direct verification against source (grep/read), not just
existing docs — every finding is graded verified vs. per-research-pass.
Surfaced several real, previously undocumented findings along the way:
the policy kill-switch (global.auto_act/never_auto_act) is checked only by
dead code and an unstarted actuator package, so it doesn't gate the live
run path; internal/actuator and internal/learning are compiled but never
started by any process; the relationship catalog grew from 34 to 47 types
since ADR-0014; and task has no registered lifecycle_defs entry despite
having a documented, code-enforced state machine.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-17 21:00:14 +02:00

445 lines
20 KiB
Markdown

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