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

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.