Adds the Oikos agent-OS kernel: oikos/policy.yaml (risk classes + approval rules for every homelab/MCP command), oikos/ontology.yaml (8-domain systems model, typed relationships, node lifecycle), and OIKOS.md (OODA loop operating brief, linked from AGENTS.md). Extends inventory.yaml with a stable service contract (doc_page, config_repo, risk_notes) on all 17 services, and a structured archaeology: section for the 13 destroyed LXCs (was scattered comments + a narrative table). Fixes stale drift found in the process: authentik's backend pointed at a retired LXC (124); core has run on the VPS since 2026-05-31. Adds oikos/gen-topology.py, generating infrastructure/topology.md (Mermaid compute/ingress + storage views) from inventory.yaml. build_host_files.py now carries state/storage/depends_on into generated hosts/*.yaml. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
138 lines
5.7 KiB
YAML
138 lines
5.7 KiB
YAML
# Oikos ontology — the systems model of the homelab.
|
|
#
|
|
# This file defines the closed vocabulary Oikos reasons with: entity
|
|
# types (grouped into eight domains), typed relationships (with inverses),
|
|
# and the node lifecycle. inventory.yaml holds the *instances*; this file
|
|
# defines what those instances and their fields MEAN, so agents, the
|
|
# decision classifier, and the topology generator interpret them
|
|
# identically. See OIKOS.md for the operating model.
|
|
#
|
|
# Rule of completeness: if something can break, be changed, or hold data,
|
|
# it has an entity type here and edges to the things it touches.
|
|
|
|
domains:
|
|
physical:
|
|
description: Hardware and environment.
|
|
entity_types: [site, machine, ups, sensor, peripheral]
|
|
compute:
|
|
description: Things that execute workloads.
|
|
entity_types: [proxmox-host, lxc, vm, workstation, external-host, device]
|
|
network:
|
|
description: How things reach each other.
|
|
entity_types: [lan, mesh, dns-zone, dns-record, ingress-route, certificate, firewall-rule]
|
|
storage:
|
|
description: Where data lives and how it survives.
|
|
entity_types: [storage-pool, volume, mount, backup-target, dataset]
|
|
software:
|
|
description: What runs and how it is configured and shipped.
|
|
entity_types: [service, application, config-repo, package-set, deploy-pipeline]
|
|
identity_access:
|
|
description: Who and what may do which things.
|
|
entity_types: [person, identity-provider, account, secret, key, access-grant]
|
|
operations:
|
|
description: The OS's own working objects.
|
|
entity_types: [agent, runbook, plan, change, incident, signal, approval, report]
|
|
external:
|
|
description: Dependencies outside the lab's control.
|
|
entity_types: [domain-registration, cloud-service, isp-link, vendor-dependency]
|
|
|
|
# Relationships. `source:` says which inventory/repo data expresses the edge
|
|
# today (thin = not yet structured, derive from docs until backfilled).
|
|
relationships:
|
|
hosts:
|
|
inverse: runs-on
|
|
example: hubris hosts lxc:apps
|
|
source: hosts.<lxc>.host + pve_id
|
|
provides:
|
|
inverse: provided-by
|
|
example: lxc:apps provides service:homelab_mcp
|
|
source: hosts.<name>.runs + services.<svc>.backend
|
|
mounts:
|
|
inverse: mounted-by
|
|
example: lxc:jellyfin mounts /mnt/media_local from strong
|
|
source: hosts.<name>.mounts (extend with from:)
|
|
stores-on:
|
|
inverse: stores-for
|
|
example: lxc:jellyfin rootfs stores-on storage-pool:ludo-lvm
|
|
source: hosts.<name>.storage (new field)
|
|
routes-to:
|
|
inverse: routed-via
|
|
example: ingress-route:media.hubris.network routes-to service:jellyfin
|
|
source: services.<svc>.url/public_host + dtoro/caddy-conf
|
|
resolves-to:
|
|
inverse: resolved-from
|
|
example: dns-record:media.hubris.network resolves-to caddy lan_ip
|
|
source: Technitium split-horizon zone (LXC 107) + dns-sync job
|
|
secured-by:
|
|
inverse: secures
|
|
example: ingress-route:paperless secured-by identity-provider:authentik
|
|
source: caddy-conf forward-auth blocks + service auth notes
|
|
authenticates-via:
|
|
inverse: authenticates
|
|
example: service:jellyfin authenticates-via authentik (native OIDC)
|
|
source: services.<svc>.auth (new field, from risk_notes/docs)
|
|
connects-via:
|
|
inverse: connects
|
|
example: workstation:mac-mini connects-via mesh:netbird
|
|
source: hosts.<name>.mesh
|
|
can-decrypt:
|
|
inverse: readable-by
|
|
example: lxc:apps can-decrypt secret:gitea-pat
|
|
source: .sops.yaml path rules + hosts.<name>.age_pubkey
|
|
configured-by:
|
|
inverse: configures
|
|
example: lxc:caddy configured-by config-repo:dtoro/caddy-conf
|
|
source: services.<svc>.config_repo (new field)
|
|
deploys-to:
|
|
inverse: deployed-from
|
|
example: deploy-pipeline:webhook-10 deploys-to /opt/homelab-mcp on lxc:apps
|
|
source: infrastructure/auto-deploy.md table
|
|
monitors:
|
|
inverse: monitored-by
|
|
example: agent:scheduler monitors service:* (Week 3)
|
|
source: oikos/scheduler config
|
|
depends-on:
|
|
inverse: dependency-of
|
|
example: service:paperless depends-on service:authentik
|
|
source: hosts/services depends_on (new field)
|
|
backs-up-to:
|
|
inverse: backup-of
|
|
example: dataset:nextcloud-data backs-up-to backup-target:proton-drive
|
|
source: infrastructure backups docs → structured field (thin)
|
|
documents:
|
|
inverse: documented-by
|
|
example: containers/101-jellyfin.md documents lxc:jellyfin
|
|
source: generated see_also / services.<svc>.doc_page
|
|
powered-by:
|
|
inverse: powers
|
|
example: machine:hubris powered-by ups (future, thin record)
|
|
source: physical domain (thin)
|
|
registered-with:
|
|
inverse: registrar-of
|
|
example: domain-registration:hubris.network registered-with registrar
|
|
source: external domain (thin)
|
|
|
|
# Node lifecycle. Stored as `state:` on each inventory host entry
|
|
# (absent = active, for backward compatibility). Transitions are runbooks
|
|
# (Week 2); drift detectors (Week 3) verify declared state matches reality.
|
|
lifecycle:
|
|
states: [planned, provisioning, active, migrating, deprecated, destroyed]
|
|
default: active
|
|
transitions:
|
|
planned->provisioning:
|
|
requires: [inventory-entry, ip-reserved, storage-pool-chosen, doc-page-stub]
|
|
provisioning->active:
|
|
requires: [age-key-enrolled-if-needed, mesh-joined-if-needed,
|
|
ingress-live-if-public, health-check-answering,
|
|
doc-page-complete, ledger-entry]
|
|
active->migrating:
|
|
requires: [preflight, backup-verified]
|
|
migrating->active:
|
|
requires: [post-verify, caddy-backends-checked, mounts-checked, docs-updated]
|
|
active->deprecated:
|
|
requires: [replacement-live-or-role-retired]
|
|
complete_when: no inbound depends-on / routes-to edges remain
|
|
deprecated->destroyed:
|
|
requires: [backups-verified, secrets-revoked-and-rekeyed,
|
|
ingress-and-dns-removed, archaeology-entry, ledger-entry]
|