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>
20 KiB
Oikos — Ontology Views
Companion to the Framework, the Model, and the Component Views. 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 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), 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
"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 | What can exist, and how is it classified? |
| 11b. Relationship Catalog | How can two entities be connected, and with what multiplicity? |
| 11c. Lifecycle State Machines | What states can a governed entity be in, and what gates each transition? |
| 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
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)
graph TD
entity["entity"] --> site
entity --> ups
entity --> sensor
entity --> peripheral
Domain: compute (11 types — the deepest nesting in the Ontology)
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)
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)
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), 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)
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)
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)
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)
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 and
framework.md §2
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)
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). 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:
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
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 — 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 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'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.
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 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'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
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 §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.