oikos phase 0: ontology + inventory + policy seeds, OpenAPI contract, ADRs

- seeds/ontology.yaml: 59 entity types (5 abstract, is-a hierarchy), 46
  relationship types with cardinality, 6 lifecycles with terminal states
  and named precondition checks
- seeds/inventory.yaml: 110 entities / 142 relationships translated from
  legacy inventory.yaml (fleet, services, ingress, storage, governance,
  archaeology); thin spots marked for backfill
- seeds/policy.yaml: 4 risk classes, 27 approval rules (hierarchy-aware,
  per-entity overrides), autonomy kill-switch off (cold start)
- api/openapi.yaml: full v1 REST contract (40 paths), RFC 9457 errors,
  cursor pagination, idempotency, ETag/If-Match, scopes; redocly-clean
- docs/adr/0001-0010: initial architecture decision records
- scripts/validate-seeds.py: Phase 0 gate — hierarchy, lifecycles,
  endpoints, cardinality, policy cross-refs (0 errors)
- plan: layer CHECK gains 'meta' (root type), cardinality gains
  'many-to-one'

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 00:17:15 +02:00
parent ea3b2c3662
commit 18cb79caf9
18 changed files with 3741 additions and 3 deletions

534
seeds/inventory.yaml Normal file
View File

@@ -0,0 +1,534 @@
# Oikos inventory seed — entity instances + relationships.
#
# Translated from the legacy /inventory.yaml (2026-07-07). Bootstraps the
# entities/relationships tables (migration 002); after ingest the DB is
# authoritative and this file is regenerated by `GET /api/v1/export`.
#
# Slug conventions: <prefix>:<name> —
# host: (proxmox-host, standalone-server) · ws: (workstation) · lxc: · vm:
# service: · ingress: · repo: (config-repo) · pool: · volume: · mesh: · lan:
# zone: (dns-zone) · idp: · person: · agent: · cluster: · backup:
#
# `state:` omitted = the type's lifecycle default (active).
# Mount details (mount_point) are attributes on `mounts` edges.
# Known thin spots are marked # THIN: backfill later.
version: 1
entities:
# ─── Sites, networks ───────────────────────────────────────────────
- {slug: "site:home", type: site, name: home}
- {slug: "site:ionos-dc", type: site, name: ionos-dc,
attributes: {address: IONOS datacenter (VPS)}}
- slug: "lan:lab"
type: lan
name: lab
attributes: {subnet: 192.168.8.0/24}
- slug: "lan:household"
type: lan
name: household
attributes: {subnet: 192.168.178.0/24} # Fritz LAN; static route to lab subnet
- slug: "mesh:netbird"
type: mesh
name: netbird
attributes:
provider: netbird
subnet: 100.122.0.0/16
domain: netbird.selfhosted
- slug: "mesh:tailscale"
type: mesh
name: tailscale
state: deprecated # migration to netbird in progress (infrastructure/mesh.md)
attributes: {provider: tailscale}
- slug: "zone:hubris.network"
type: dns-zone
name: hubris.network
attributes: {zone: hubris.network, authority: "Technitium (LXC 107), split-horizon"}
- slug: "zone:netbird.selfhosted"
type: dns-zone
name: netbird.selfhosted
attributes: {zone: netbird.selfhosted, authority: netbird-mgmt}
# ─── Machines ──────────────────────────────────────────────────────
- slug: "cluster:homelab"
type: cluster
name: Homelab
attributes: {quorum: "2-node, no QDevice tiebreaker yet"}
- slug: "host:hubris"
type: proxmox-host
name: hubris
attributes:
os: linux
lan_ip: 192.168.8.77
mesh: {netbird: {ip: 100.122.38.109, fqdn: proxmox-server.netbird.selfhosted}}
ssh: {port: 22, netbird_port: 22022, user: root}
age_pubkey: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
- slug: "host:strong"
type: proxmox-host
name: strong
attributes:
os: linux
lan_ip: 192.168.178.181
ssh: {user: root}
age_pubkey: age1rtwvdct6avjkr3cyxv3vue3vqx4d524fjfr3vk7xrnvyrylnry5sm54sn4
note: >-
PVE 9.2.3 since 2026-07-01 (formerly workstation ludo-mini). Joined
Homelab cluster same day. Not yet netbird-enrolled — reachable via
household LAN / Fritz static route only.
- slug: "host:netbird-vps"
type: standalone-server
name: netbird-vps
attributes:
os: linux
provider: ionos
control_level: partial # managed via ssh from hubris; not a homelab client
public_ipv4: 82.165.190.79
mesh: {netbird: {ip: 100.122.165.149, fqdn: netbird-ionos.netbird.selfhosted}}
ssh: {user: root}
note: netbird mgmt+signal+relay+dashboard + coturn; sshd locked to hubris pubkey
- slug: "ws:mac-mini"
type: workstation
name: mac-mini
attributes:
os: macos
user: dtoro
lan_ip: 192.168.178.182
mesh: {netbird: {fqdn: mac-mini-234-17.netbird.selfhosted}}
age_pubkey: age1z62ff2ak9zj5ctcvaxwyyhedwjvlwgm2dkn9nk3wrwk8fkavcpmsqwc2vs
note: only macOS in fleet; future Oikos OS Docker host
- slug: "ws:republic-laptop"
type: workstation
name: republic-laptop
attributes:
os: linux
user: dtoro
mesh: {netbird: {fqdn: republic-laptop.netbird.selfhosted}}
# ─── LXCs ──────────────────────────────────────────────────────────
- {slug: "lxc:jellyfin", type: lxc, name: jellyfin,
attributes: {pve_id: 101, role: media-server, lan_ip: 192.168.8.246,
public_host: media.hubris.network,
note: "VAAPI transcode via Radeon 680M passthrough; migrated hubris→strong 2026-07-05"}}
- {slug: "lxc:nfs-export", type: lxc, name: nfs-export,
attributes: {pve_id: 102, role: storage-export, lan_ip: 192.168.8.200}}
- {slug: "lxc:paperless", type: lxc, name: paperless,
attributes: {pve_id: 103, role: document-archive, lan_ip: 192.168.8.130,
public_host: paperless.hubris.network}}
- {slug: "lxc:gitea", type: lxc, name: gitea,
attributes: {pve_id: 104, role: git-server, lan_ip: 192.168.8.121,
public_host: git.hubris.network,
note: "bare repos at /mnt/library/repos/dtoro/*.git"}}
- {slug: "lxc:apps", type: lxc, name: apps,
attributes: {pve_id: 105, role: docker-apps, lan_ip: 192.168.8.205,
age_pubkey: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0,
note: "legacy Oikos host; fallback during cutover (plan A6)"}}
- {slug: "lxc:auth-outpost", type: lxc, name: auth-outpost,
attributes: {pve_id: 106, role: authentik-gateway, lan_ip: 192.168.8.6}}
- {slug: "lxc:dns", type: lxc, name: dns,
attributes: {pve_id: 107, role: dns-server, lan_ip: 192.168.8.2}}
- {slug: "lxc:nextcloud", type: lxc, name: nextcloud,
attributes: {pve_id: 114, role: file-sync, lan_ip: 192.168.8.224,
public_host: cloud.hubris.network}}
- {slug: "lxc:elementsynapse", type: lxc, name: elementsynapse,
attributes: {pve_id: 118, role: matrix-server, lan_ip: 192.168.8.242,
public_host: matrix.hubris.network,
note: "migrated hubris→strong 2026-07-05"}}
- {slug: "lxc:sophia", type: lxc, name: sophia,
attributes: {pve_id: 119, role: workshop, lan_ip: 192.168.8.109}}
- {slug: "lxc:mule-images", type: lxc, name: mule-images,
attributes: {pve_id: 120, role: photo-management, lan_ip: 192.168.8.136,
public_host: photos.hubris.network}}
- {slug: "lxc:caddy", type: lxc, name: caddy,
attributes: {pve_id: 121, role: reverse-proxy, lan_ip: 192.168.8.175,
note: "terminates all *.hubris.network; /etc/caddy is a checkout of dtoro/caddy-conf"}}
- {slug: "lxc:arriman", type: lxc, name: arriman,
attributes: {pve_id: 122, role: arr-stack, lan_ip: 192.168.8.245,
public_hosts: [jellyseerr.hubris.network, qbit.hubris.network, sab.hubris.network],
note: "homarr/radarr/sonarr/lidarr/sab/qbit/bazarr/flaresolverr/prowlarr/jellyseerr; migrated to strong 2026-07-05"}}
- {slug: "lxc:trmnl", type: lxc, name: trmnl,
attributes: {pve_id: 128, role: trmnl-middleware, lan_ip: 192.168.8.211,
public_host: trmnl.hubris.network,
note: "not yet mesh/SOPS-enrolled"}}
- {slug: "lxc:house", type: lxc, name: house,
attributes: {pve_id: 129, role: family-planner, lan_ip: 192.168.8.244,
public_host: house.hubris.network,
age_pubkey: age1s07zs83ehtlg8jtwvr75ltc3c4cdlemfwjuxrwjtwkqxkl9tpggsyrzn2h,
note: "Yuvomi + WebDAV bridge to paperless; migrated to strong 2026-07-05"}}
- {slug: "lxc:grimmory", type: lxc, name: grimmory,
attributes: {pve_id: 130, role: book-library, lan_ip: 192.168.8.247,
public_host: books.hubris.network,
age_pubkey: age1uellsemnjrzgfg9fxw4jefpy05laxzggwnwhh6ny3wl7alyp6v8q0muxet}}
- {slug: "lxc:teddycloud", type: lxc, name: teddycloud,
attributes: {pve_id: 131, role: teddycloud, lan_ip: 192.168.8.150,
public_host: teddy.hubris.network,
note: "drift-caught 2026-07-06; no forward-auth gate on route; not a homelab client"}}
- {slug: "lxc:rclone", type: lxc, name: rclone,
attributes: {pve_id: 132, role: backup,
mesh: {netbird: {fqdn: rclone.netbird.selfhosted}},
age_pubkey: age1pwtdws2thdh7vzp2dzttl3zxgcs2tgpcsjsqgw3q04nyml4kvuqq467u4x}}
# THIN: hosting machine not recorded in legacy inventory — backfill hosts edge
- {slug: "lxc:seanime", type: lxc, name: seanime,
attributes: {pve_id: 133, role: anime-media-server, lan_ip: 192.168.8.248,
public_host: seanime.hubris.network,
note: "systemd service at /opt/seanime; uses qbittorrent on arriman"}}
- {slug: "lxc:romm", type: lxc, name: romm,
attributes: {pve_id: 134, role: rom-manager, lan_ip: 192.168.8.249,
public_host: roms.hubris.network,
note: "docker compose + MariaDB sidecar at /opt/romm"}}
# ─── VMs ───────────────────────────────────────────────────────────
- {slug: "vm:zimaos", type: vm, name: zimaos,
attributes: {pve_id: 100, role: nas-frontend-eval, lan_ip: 192.168.8.195,
public_host: zimaos.hubris.network}}
- {slug: "vm:haos", type: vm, name: haos,
attributes: {pve_id: 108, role: home-automation, lan_ip: 192.168.8.101}}
# ─── Storage ───────────────────────────────────────────────────────
- {slug: "pool:local-lvm-hubris", type: storage-pool, name: local-lvm (hubris),
attributes: {type: lvm}}
- {slug: "pool:ludo-lvm", type: storage-pool, name: ludo-lvm (strong),
attributes: {type: lvm}}
- {slug: "volume:library", type: volume, name: library,
attributes: {path: /mnt/library}} # THIN: owning pool unrecorded
- {slug: "volume:media-local", type: volume, name: media-local,
attributes: {path: /mnt/media_local}}
- {slug: "backup:proton-drive", type: backup-target, name: proton-drive,
attributes: {provider: proton, encrypted: true}}
# ─── Services ──────────────────────────────────────────────────────
- {slug: "service:proxmox-ui", type: service, name: proxmox_ui,
attributes: {url: "https://proxmox.hubris.network", port: 8006,
doc_page: knowledge/wiki/hosts/hubris.md,
risk_notes: "hypervisor UI — changes affect every guest on the node"}}
- {slug: "service:gitea", type: service, name: gitea,
attributes: {url: "https://git.hubris.network", port: 3000,
doc_page: knowledge/wiki/containers/104-gitea.md,
risk_notes: "hosts all config repos + deploy webhooks; outage blocks auto-deploy and sync"}}
- {slug: "service:caddy", type: service, name: caddy,
attributes: {doc_page: knowledge/wiki/containers/121-caddy.md,
risk_notes: "wide blast radius — every *.hubris.network route rides on it"}}
- {slug: "service:authentik", type: service, name: authentik,
attributes: {url: "https://auth.hubris.network",
doc_page: knowledge/wiki/containers/106-auth-outpost.md,
note: "core on VPS since 2026-05-31; LAN outpost = auth-outpost (LXC 106) 192.168.8.6:9000",
risk_notes: "SSO provider — outage locks login to OIDC/forward-auth services"}}
- {slug: "service:dns", type: service, name: dns,
attributes: {doc_page: knowledge/wiki/containers/107-dns.md,
risk_notes: "LAN-wide resolver — misconfig breaks name resolution for every client"}}
- {slug: "service:jellyfin", type: service, name: jellyfin,
attributes: {url: "https://media.hubris.network",
doc_page: knowledge/wiki/containers/101-jellyfin.md,
risk_notes: "native Authentik OIDC (no forward-auth gate); VAAPI depends on GPU passthrough on strong"}}
- {slug: "service:nextcloud", type: service, name: nextcloud,
attributes: {url: "https://cloud.hubris.network",
doc_page: knowledge/wiki/containers/114-nextcloud.md}}
- {slug: "service:paperless", type: service, name: paperless,
attributes: {url: "https://paperless.hubris.network",
doc_page: knowledge/wiki/containers/103-paperless.md,
risk_notes: "document archive — data irreplaceable; DB operations are destructive-class"}}
- {slug: "service:matrix", type: service, name: matrix,
attributes: {url: "https://matrix.hubris.network",
doc_page: knowledge/wiki/containers/118-elementsynapse.md,
risk_notes: "alert/approval channel for Oikos — outage silences agent escalation"}}
- {slug: "service:photos", type: service, name: photos,
attributes: {url: "https://photos.hubris.network",
doc_page: knowledge/wiki/containers/120-mule-images.md}}
- {slug: "service:arr-stack", type: service, name: arr_stack,
attributes: {doc_page: knowledge/wiki/containers/122-arriman.md,
note: "jellyseerr / qbit / sab on docker compose"}}
- {slug: "service:artifacto", type: service, name: artifacto,
attributes: {url: "https://artifacto.hubris.network",
doc_page: knowledge/wiki/containers/105-apps.md}}
- {slug: "service:trmnl", type: service, name: trmnl,
attributes: {url: "https://trmnl.hubris.network",
doc_page: knowledge/wiki/containers/128-trmnl.md,
note: "TRMNL e-ink plugin middleware (polled by TRMNL cloud)"}}
- {slug: "service:zimaos", type: service, name: zimaos,
attributes: {url: "https://zimaos.hubris.network",
doc_page: knowledge/wiki/vms/100-zimaos.md}}
- {slug: "service:haos", type: service, name: haos,
attributes: {doc_page: knowledge/wiki/vms/108-haos.md}}
- {slug: "service:teddycloud", type: service, name: teddycloud,
attributes: {url: "https://teddy.hubris.network",
doc_page: knowledge/wiki/containers/131-teddycloud.md,
risk_notes: "no forward-auth gate — reachable by anyone on LAN/mesh"}}
- {slug: "service:homelab-mcp", type: service, name: homelab_mcp,
attributes: {port: 9810, systemd_unit: homelab-mcp,
endpoint: "https://mcp.hubris.network/mcp",
doc_page: knowledge/wiki/infrastructure/homelab-context.md,
risk_notes: "agents' primary read surface — outage degrades every agent to grepping the clone"}}
- {slug: "service:secrets-issuance", type: service, name: secrets_issuance,
attributes: {port: 9820, systemd_unit: secrets-issuance,
endpoint: "https://secrets.hubris.network/issue",
doc_page: .agents/operations/agent-enrollment.md,
risk_notes: "identity issuance — security-sensitive; key operations are destructive-class"}}
# Services derived from hosts.public_host (no legacy services entry):
- {slug: "service:house", type: service, name: house,
attributes: {url: "https://house.hubris.network", note: "Yuvomi family planner (derived)"}}
- {slug: "service:grimmory", type: service, name: grimmory,
attributes: {url: "https://books.hubris.network", note: derived}}
- {slug: "service:seanime", type: service, name: seanime,
attributes: {url: "https://seanime.hubris.network", port: 43211, note: derived}}
- {slug: "service:romm", type: service, name: romm,
attributes: {url: "https://roms.hubris.network", note: derived}}
- {slug: "service:jellyseerr", type: service, name: jellyseerr,
attributes: {url: "https://jellyseerr.hubris.network", note: derived (arriman)}}
- {slug: "service:qbit", type: service, name: qbit,
attributes: {url: "https://qbit.hubris.network", note: derived (arriman)}}
- {slug: "service:sab", type: service, name: sab,
attributes: {url: "https://sab.hubris.network", note: "derived (arriman); forward-auth gated"}}
# ─── Config repos ──────────────────────────────────────────────────
- {slug: "repo:caddy-conf", type: config-repo, name: dtoro/caddy-conf}
- {slug: "repo:gitea-customizations", type: config-repo, name: dtoro/gitea-customizations}
- {slug: "repo:mule-image", type: config-repo, name: dtoro/mule-image}
- {slug: "repo:artifacto", type: config-repo, name: dtoro/Artifacto}
- {slug: "repo:terminalito", type: config-repo, name: dtoro/terminalito}
- {slug: "repo:homelab-docs", type: config-repo, name: dtoro/Homelab-Docs}
# ─── Ingress routes (Caddy, *.hubris.network) ──────────────────────
- {slug: "ingress:proxmox.hubris.network", type: ingress-route, name: proxmox.hubris.network}
- {slug: "ingress:git.hubris.network", type: ingress-route, name: git.hubris.network}
- {slug: "ingress:auth.hubris.network", type: ingress-route, name: auth.hubris.network}
- {slug: "ingress:media.hubris.network", type: ingress-route, name: media.hubris.network}
- {slug: "ingress:cloud.hubris.network", type: ingress-route, name: cloud.hubris.network}
- {slug: "ingress:paperless.hubris.network", type: ingress-route, name: paperless.hubris.network,
attributes: {forward_auth: true}}
- {slug: "ingress:matrix.hubris.network", type: ingress-route, name: matrix.hubris.network}
- {slug: "ingress:photos.hubris.network", type: ingress-route, name: photos.hubris.network}
- {slug: "ingress:artifacto.hubris.network", type: ingress-route, name: artifacto.hubris.network}
- {slug: "ingress:trmnl.hubris.network", type: ingress-route, name: trmnl.hubris.network}
- {slug: "ingress:zimaos.hubris.network", type: ingress-route, name: zimaos.hubris.network}
- {slug: "ingress:teddy.hubris.network", type: ingress-route, name: teddy.hubris.network,
attributes: {forward_auth: false}}
- {slug: "ingress:mcp.hubris.network", type: ingress-route, name: mcp.hubris.network}
- {slug: "ingress:secrets.hubris.network", type: ingress-route, name: secrets.hubris.network}
- {slug: "ingress:house.hubris.network", type: ingress-route, name: house.hubris.network}
- {slug: "ingress:books.hubris.network", type: ingress-route, name: books.hubris.network}
- {slug: "ingress:seanime.hubris.network", type: ingress-route, name: seanime.hubris.network}
- {slug: "ingress:roms.hubris.network", type: ingress-route, name: roms.hubris.network}
- {slug: "ingress:jellyseerr.hubris.network", type: ingress-route, name: jellyseerr.hubris.network}
- {slug: "ingress:qbit.hubris.network", type: ingress-route, name: qbit.hubris.network}
- {slug: "ingress:sab.hubris.network", type: ingress-route, name: sab.hubris.network,
attributes: {forward_auth: true}}
# ─── Governance ────────────────────────────────────────────────────
- {slug: "person:dtoro", type: person, name: dtoro,
attributes: {matrix_id: "@dtoro:avispero"}}
- {slug: "idp:authentik", type: identity-provider, name: authentik,
attributes: {issuer: "https://auth.hubris.network", auth_mode: both}}
- {slug: "agent:hermes", type: agent, name: hermes,
state: planned,
attributes: {gateway_port: 8092, note: "Oikos Phase 4 — Docker gateway mode"}}
- {slug: "agent:oikos", type: agent, name: oikos,
state: planned,
attributes: {note: "the OS control loop itself (scheduler/actuator) as an actor"}}
# ─── Archaeology (state: destroyed — kept for "what happened to X?") ─
- {slug: "lxc:claudio-bot", type: lxc, name: claudio-bot, state: destroyed,
attributes: {pve_id: 123, destroyed: "2026-06-04", reason: "replaced by Hermes Agent on mac-mini"}}
- {slug: "lxc:plato", type: lxc, name: plato, state: destroyed,
attributes: {pve_id: 126, destroyed: "2026-06-28", reason: "notes workspace decommissioned; data at /mnt/library/documents/plato"}}
- {slug: "lxc:mule-photos-new", type: lxc, name: mule-photos-new, state: destroyed,
attributes: {pve_id: 127, destroyed: "2026-05-22", reason: "PhotoPrism test stack promoted to LXC 120"}}
- {slug: "lxc:heaper", type: lxc, name: heaper, state: destroyed,
attributes: {pve_id: 116, destroyed: "2026-05-14", reason: "decommissioned; data at /mnt/library/heaper"}}
- {slug: "lxc:syncthing", type: lxc, name: syncthing, state: destroyed,
attributes: {pve_id: 109, destroyed: "2026-05-14", reason: "decommissioned; library subtree was empty"}}
- {slug: "lxc:seafile", type: lxc, name: seafile, state: destroyed,
attributes: {pve_id: 125, destroyed: "2026-05-13", reason: "Seafile Pro evaluation rejected"}}
- {slug: "lxc:arr-yunohost", type: lxc, name: arr-yunohost, state: destroyed,
attributes: {pve_id: 100, destroyed: "2026-04-28", reason: "migrated to docker stack on arriman (LXC 122)"}}
- {slug: "lxc:flaresolverr", type: lxc, name: flaresolverr, state: destroyed,
attributes: {pve_id: 106, destroyed: "2026-04-28", reason: "folded into the arriman docker compose"}}
- {slug: "lxc:marimo", type: lxc, name: marimo, state: destroyed,
attributes: {pve_id: 107, destroyed: "2026-04-28", reason: decommissioned}}
- {slug: "lxc:photoprism", type: lxc, name: photoprism, state: destroyed,
attributes: {pve_id: 110, destroyed: "2026-04-28", reason: "replaced by mule-images (LXC 120)"}}
- {slug: "lxc:karakeep", type: lxc, name: karakeep, state: destroyed,
attributes: {pve_id: 111, destroyed: "2026-04-28", reason: decommissioned}}
- {slug: "lxc:immich", type: lxc, name: immich, state: destroyed,
attributes: {pve_id: 112, destroyed: "2026-04-28", reason: "replaced by mule-images (LXC 120)"}}
- {slug: "lxc:reticulum", type: lxc, name: reticulum, state: destroyed,
attributes: {pve_id: 115, destroyed: "2026-04-28", reason: decommissioned}}
relationships:
# ─── Cluster membership ────────────────────────────────────────────
- {source: "host:hubris", target: "cluster:homelab", type: member-of}
- {source: "host:strong", target: "cluster:homelab", type: member-of}
# ─── Location ──────────────────────────────────────────────────────
- {source: "host:hubris", target: "site:home", type: located-at}
- {source: "host:strong", target: "site:home", type: located-at}
- {source: "ws:mac-mini", target: "site:home", type: located-at}
- {source: "host:netbird-vps", target: "site:ionos-dc", type: located-at}
# ─── Hosting (machine → guest) ─────────────────────────────────────
- {source: "host:hubris", target: "lxc:nfs-export", type: hosts}
- {source: "host:hubris", target: "lxc:paperless", type: hosts}
- {source: "host:hubris", target: "lxc:gitea", type: hosts}
- {source: "host:hubris", target: "lxc:apps", type: hosts}
- {source: "host:hubris", target: "lxc:auth-outpost", type: hosts}
- {source: "host:hubris", target: "lxc:dns", type: hosts}
- {source: "host:hubris", target: "lxc:nextcloud", type: hosts}
- {source: "host:hubris", target: "lxc:sophia", type: hosts}
- {source: "host:hubris", target: "lxc:mule-images", type: hosts}
- {source: "host:hubris", target: "lxc:caddy", type: hosts}
- {source: "host:hubris", target: "lxc:trmnl", type: hosts}
- {source: "host:hubris", target: "lxc:teddycloud", type: hosts}
- {source: "host:hubris", target: "vm:zimaos", type: hosts}
- {source: "host:hubris", target: "vm:haos", type: hosts}
- {source: "host:strong", target: "lxc:jellyfin", type: hosts}
- {source: "host:strong", target: "lxc:elementsynapse", type: hosts}
- {source: "host:strong", target: "lxc:arriman", type: hosts}
- {source: "host:strong", target: "lxc:house", type: hosts}
- {source: "host:strong", target: "lxc:grimmory", type: hosts}
- {source: "host:strong", target: "lxc:seanime", type: hosts}
- {source: "host:strong", target: "lxc:romm", type: hosts}
# THIN: lxc:rclone hosting machine unknown — backfill
# ─── Service provision (compute → service) ─────────────────────────
- {source: "host:hubris", target: "service:proxmox-ui", type: provides}
- {source: "lxc:gitea", target: "service:gitea", type: provides}
- {source: "lxc:caddy", target: "service:caddy", type: provides}
- {source: "host:netbird-vps", target: "service:authentik", type: provides}
- {source: "lxc:dns", target: "service:dns", type: provides}
- {source: "lxc:jellyfin", target: "service:jellyfin", type: provides}
- {source: "lxc:nextcloud", target: "service:nextcloud", type: provides}
- {source: "lxc:paperless", target: "service:paperless", type: provides}
- {source: "lxc:elementsynapse", target: "service:matrix", type: provides}
- {source: "lxc:mule-images", target: "service:photos", type: provides}
- {source: "lxc:arriman", target: "service:arr-stack", type: provides}
- {source: "lxc:arriman", target: "service:jellyseerr", type: provides}
- {source: "lxc:arriman", target: "service:qbit", type: provides}
- {source: "lxc:arriman", target: "service:sab", type: provides}
- {source: "lxc:apps", target: "service:artifacto", type: provides}
- {source: "lxc:apps", target: "service:homelab-mcp", type: provides}
- {source: "lxc:apps", target: "service:secrets-issuance", type: provides}
- {source: "lxc:trmnl", target: "service:trmnl", type: provides}
- {source: "vm:zimaos", target: "service:zimaos", type: provides}
- {source: "vm:haos", target: "service:haos", type: provides}
- {source: "lxc:teddycloud", target: "service:teddycloud", type: provides}
- {source: "lxc:house", target: "service:house", type: provides}
- {source: "lxc:grimmory", target: "service:grimmory", type: provides}
- {source: "lxc:seanime", target: "service:seanime", type: provides}
- {source: "lxc:romm", target: "service:romm", type: provides}
# ─── Ingress → service ─────────────────────────────────────────────
- {source: "ingress:proxmox.hubris.network", target: "service:proxmox-ui", type: routes-to}
- {source: "ingress:git.hubris.network", target: "service:gitea", type: routes-to}
- {source: "ingress:auth.hubris.network", target: "service:authentik", type: routes-to}
- {source: "ingress:media.hubris.network", target: "service:jellyfin", type: routes-to}
- {source: "ingress:cloud.hubris.network", target: "service:nextcloud", type: routes-to}
- {source: "ingress:paperless.hubris.network", target: "service:paperless", type: routes-to}
- {source: "ingress:matrix.hubris.network", target: "service:matrix", type: routes-to}
- {source: "ingress:photos.hubris.network", target: "service:photos", type: routes-to}
- {source: "ingress:artifacto.hubris.network", target: "service:artifacto", type: routes-to}
- {source: "ingress:trmnl.hubris.network", target: "service:trmnl", type: routes-to}
- {source: "ingress:zimaos.hubris.network", target: "service:zimaos", type: routes-to}
- {source: "ingress:teddy.hubris.network", target: "service:teddycloud", type: routes-to}
- {source: "ingress:mcp.hubris.network", target: "service:homelab-mcp", type: routes-to}
- {source: "ingress:secrets.hubris.network", target: "service:secrets-issuance", type: routes-to}
- {source: "ingress:house.hubris.network", target: "service:house", type: routes-to}
- {source: "ingress:books.hubris.network", target: "service:grimmory", type: routes-to}
- {source: "ingress:seanime.hubris.network", target: "service:seanime", type: routes-to}
- {source: "ingress:roms.hubris.network", target: "service:romm", type: routes-to}
- {source: "ingress:jellyseerr.hubris.network", target: "service:jellyseerr", type: routes-to}
- {source: "ingress:qbit.hubris.network", target: "service:qbit", type: routes-to}
- {source: "ingress:sab.hubris.network", target: "service:sab", type: routes-to}
# ─── Auth edges ────────────────────────────────────────────────────
- {source: "ingress:paperless.hubris.network", target: "idp:authentik", type: secured-by}
- {source: "ingress:sab.hubris.network", target: "idp:authentik", type: secured-by}
- {source: "service:jellyfin", target: "idp:authentik", type: authenticates-via}
- {source: "idp:authentik", target: "person:dtoro", type: authenticates}
# ─── Config repos ──────────────────────────────────────────────────
- {source: "service:caddy", target: "repo:caddy-conf", type: configured-by}
- {source: "service:gitea", target: "repo:gitea-customizations", type: configured-by}
- {source: "service:photos", target: "repo:mule-image", type: configured-by}
- {source: "service:artifacto", target: "repo:artifacto", type: configured-by}
- {source: "service:trmnl", target: "repo:terminalito", type: configured-by}
- {source: "service:homelab-mcp", target: "repo:homelab-docs", type: configured-by}
- {source: "service:secrets-issuance", target: "repo:homelab-docs", type: configured-by}
# ─── Service dependencies (blast-radius edges; grow over time) ─────
- {source: "service:paperless", target: "service:authentik", type: depends-on}
- {source: "service:homelab-mcp", target: "service:gitea", type: depends-on}
- {source: "service:jellyseerr", target: "service:jellyfin", type: depends-on}
- {source: "service:seanime", target: "service:qbit", type: depends-on}
- {source: "service:house", target: "service:paperless", type: depends-on}
- {source: "service:sab", target: "service:authentik", type: depends-on}
# ─── Network membership ────────────────────────────────────────────
- {source: "host:hubris", target: "lan:lab", type: connects-via}
- {source: "host:hubris", target: "mesh:netbird", type: connects-via}
- {source: "host:strong", target: "lan:household", type: connects-via}
- {source: "ws:mac-mini", target: "lan:household", type: connects-via}
- {source: "ws:mac-mini", target: "mesh:netbird", type: connects-via}
- {source: "ws:republic-laptop", target: "mesh:netbird", type: connects-via}
- {source: "host:netbird-vps", target: "mesh:netbird", type: connects-via}
- {source: "lxc:rclone", target: "mesh:netbird", type: connects-via}
- {source: "lxc:jellyfin", target: "lan:lab", type: connects-via}
- {source: "lxc:nfs-export", target: "lan:lab", type: connects-via}
- {source: "lxc:paperless", target: "lan:lab", type: connects-via}
- {source: "lxc:gitea", target: "lan:lab", type: connects-via}
- {source: "lxc:apps", target: "lan:lab", type: connects-via}
- {source: "lxc:apps", target: "mesh:tailscale", type: connects-via}
- {source: "lxc:auth-outpost", target: "lan:lab", type: connects-via}
- {source: "lxc:dns", target: "lan:lab", type: connects-via}
- {source: "lxc:nextcloud", target: "lan:lab", type: connects-via}
- {source: "lxc:elementsynapse", target: "lan:lab", type: connects-via}
- {source: "lxc:sophia", target: "lan:lab", type: connects-via}
- {source: "lxc:mule-images", target: "lan:lab", type: connects-via}
- {source: "lxc:caddy", target: "lan:lab", type: connects-via}
- {source: "lxc:arriman", target: "lan:lab", type: connects-via}
- {source: "lxc:trmnl", target: "lan:lab", type: connects-via}
- {source: "lxc:house", target: "lan:lab", type: connects-via}
- {source: "lxc:grimmory", target: "lan:lab", type: connects-via}
- {source: "lxc:teddycloud", target: "lan:lab", type: connects-via}
- {source: "lxc:seanime", target: "lan:lab", type: connects-via}
- {source: "lxc:romm", target: "lan:lab", type: connects-via}
- {source: "vm:zimaos", target: "lan:lab", type: connects-via}
- {source: "vm:haos", target: "lan:lab", type: connects-via}
# ─── Storage ───────────────────────────────────────────────────────
- {source: "pool:ludo-lvm", target: "volume:media-local", type: contains}
- {source: "host:hubris", target: "volume:library", type: mounts,
attributes: {mount_point: /mnt/library}}
- {source: "lxc:jellyfin", target: "volume:media-local", type: mounts,
attributes: {mount_point: /mnt/media_local}}
- {source: "lxc:paperless", target: "volume:library", type: mounts,
attributes: {mount_point: /mnt/library}}
- {source: "lxc:gitea", target: "volume:library", type: mounts,
attributes: {mount_point: /mnt/library}}
- {source: "lxc:apps", target: "volume:library", type: mounts,
attributes: {mount_point: /mnt/library}}
- {source: "lxc:nextcloud", target: "volume:library", type: mounts,
attributes: {mount_point: /mnt/library}}
- {source: "lxc:sophia", target: "volume:library", type: mounts,
attributes: {mount_point: /mnt/library}}
- {source: "lxc:mule-images", target: "volume:library", type: mounts,
attributes: {mount_point: /mnt/library}}
- {source: "lxc:arriman", target: "volume:media-local", type: mounts,
attributes: {mount_point: /mnt/media_local}}
- {source: "lxc:grimmory", target: "volume:media-local", type: mounts,
attributes: {mount_point: /mnt/media_local}}
- {source: "lxc:teddycloud", target: "volume:library", type: mounts,
attributes: {mount_point: /mnt/library}}
- {source: "lxc:seanime", target: "volume:media-local", type: mounts,
attributes: {mount_point: /mnt/media_local/anime}}
- {source: "lxc:romm", target: "volume:media-local", type: mounts,
attributes: {mount_point: /mnt/media_local}}
- {source: "lxc:jellyfin", target: "pool:ludo-lvm", type: stores-on}
- {source: "lxc:arriman", target: "pool:ludo-lvm", type: stores-on}
- {source: "lxc:grimmory", target: "pool:ludo-lvm", type: stores-on}
- {source: "lxc:romm", target: "pool:ludo-lvm", type: stores-on}
- {source: "lxc:teddycloud", target: "pool:local-lvm-hubris", type: stores-on}
- {source: "lxc:rclone", target: "backup:proton-drive", type: backs-up-to}
# ─── Governance ────────────────────────────────────────────────────
- {source: "person:dtoro", target: "agent:hermes", type: owns}
- {source: "person:dtoro", target: "agent:oikos", type: owns}

936
seeds/ontology.yaml Normal file
View File

@@ -0,0 +1,936 @@
# Oikos ontology seed — the systems model of the homelab.
#
# Bootstraps entity_types / relationship_types / lifecycle_defs on first
# deploy (migration 001). After ingest the DB is authoritative; this file
# is regenerated by `GET /api/v1/export` for DR + version control.
#
# Conventions:
# - entity type names are kebab-case
# - `parent:` builds the is-a hierarchy; `abstract: true` types cannot be
# instantiated (validation walks the hierarchy for relationship
# endpoints and policy rules — plan R3-1)
# - `layer:` one of meta | infrastructure | governance | cognition
# - relationship `cardinality:` describes source→target multiplicity:
# one-to-one | one-to-many | many-to-one | many-to-many
# - relationship endpoints may name abstract types
# - lifecycle transition `requires:` entries are NAMED CHECKS implemented
# in Go (internal/ontology); the DB stores which checks gate a
# transition, the code implements them
# - mount details (mount_point, options) live as ATTRIBUTES on `mounts`
# edges, not as a separate entity type
#
# 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.
version: 1
# ─── Lifecycles ────────────────────────────────────────────────────────
lifecycles:
infrastructure:
states: [planned, provisioning, active, migrating, failed, deprecated, destroyed]
default_state: active # legacy inventory entries without state are active
terminal_states: [destroyed]
transitions:
planned:
provisioning: {requires: [inventory-entry, ip-reserved, storage-pool-chosen, doc-page-stub]}
destroyed: {requires: [cancelled-note]}
provisioning:
active: {requires: [age-key-enrolled-if-needed, mesh-joined-if-needed,
ingress-live-if-public, health-check-answering,
doc-page-complete]}
failed: {requires: []}
active:
migrating: {requires: [preflight, backup-verified]}
deprecated: {requires: [replacement-live-or-role-retired]}
failed: {requires: []}
migrating:
active: {requires: [post-verify, caddy-backends-checked, mounts-checked, docs-updated]}
failed: {requires: []}
failed:
active: {requires: [recovery-verified]}
deprecated: {requires: [write-off-note]}
deprecated:
active: {requires: [un-deprecate-note]}
destroyed: {requires: [backups-verified, secrets-revoked-and-rekeyed,
ingress-and-dns-removed, no-inbound-edges,
archaeology-entry]}
signal:
states: [raised, acknowledged, acting, muted, resolved, failed]
default_state: raised
terminal_states: [resolved]
transitions:
raised:
acknowledged: {requires: []}
muted: {requires: [mute-ttl-set]}
resolved: {requires: [condition-cleared]}
acknowledged:
acting: {requires: [classification-exists]}
resolved: {requires: []}
muted: {requires: [mute-ttl-set]}
acting:
resolved: {requires: [verification-passed]}
raised: {requires: [retry-budget-remaining]}
failed: {requires: []}
failed:
acknowledged: {requires: [operator-retry]}
muted:
raised: {requires: [mute-ttl-expired]}
execution:
states: [proposed, approved, auto_approved, denied, expired, executing,
verifying, verified, failed, timed_out, cancelled, rolled_back,
rollback_failed]
default_state: proposed
terminal_states: [verified, failed, denied, expired, cancelled,
rolled_back, rollback_failed]
transitions:
proposed:
approved: {requires: [operator-approval]}
auto_approved: {requires: [autonomy-allows]}
denied: {requires: []}
approved:
executing: {requires: [approval-token-valid]}
expired: {requires: [approval-ttl-elapsed]}
auto_approved:
executing: {requires: []}
executing:
verified: {requires: [verification-passed]}
failed: {requires: []}
timed_out: {requires: []}
cancelled: {requires: [operator-abort]}
timed_out:
verifying: {requires: []} # check if the command completed anyway
verifying:
verified: {requires: [verification-passed]}
failed: {requires: []}
failed:
rolled_back: {requires: [rollback-procedure-exists]}
rollback_failed: {requires: []}
approval:
states: [pending, approved, denied, expired, revoked]
default_state: pending
terminal_states: [denied, expired, revoked]
transitions:
pending:
approved: {requires: [token-verified]}
denied: {requires: []}
expired: {requires: [ttl-elapsed]}
approved:
revoked: {requires: [not-yet-executing]}
pattern:
states: [hypothesized, validated, active, deprecated, invalidated]
default_state: hypothesized
terminal_states: [deprecated, invalidated]
transitions:
hypothesized:
validated: {requires: [evidence-count-5plus, confidence-0.7plus]}
invalidated: {requires: []}
validated:
active: {requires: [operator-approval]} # S4: never automatic
invalidated: {requires: []}
active:
deprecated: {requires: []}
invalidated: {requires: [contradicting-evidence]}
skill:
states: [drafted, tested, active, refined, failed, deprecated]
default_state: drafted
terminal_states: [deprecated]
transitions:
drafted:
tested: {requires: [test-execution-recorded]}
deprecated: {requires: []}
tested:
active: {requires: [operator-approval]}
failed: {requires: []}
failed:
drafted: {requires: []}
active:
refined: {requires: [new-version-created]}
deprecated: {requires: []}
refined:
active: {requires: [operator-approval]}
# ─── Entity types ──────────────────────────────────────────────────────
# domain: physical | compute | network | storage | software | external |
# identity | cognition
entity_types:
# Root
entity:
abstract: true
domain: meta
layer: meta
description: Root abstract type. Relationship endpoints that accept any
entity (documented-by, procedure-for, checks) reference this.
# ── Infrastructure / physical ──
site:
parent: entity
domain: physical
layer: infrastructure
lifecycle: infrastructure
description: Physical location (home, VPS datacenter).
attributes: {type: object, properties: {address: {type: string}}}
ups:
parent: entity
domain: physical
layer: infrastructure
lifecycle: infrastructure
description: Uninterruptible power supply.
attributes: {type: object, properties: {vendor: {type: string}, va: {type: integer}}}
sensor:
parent: entity
domain: physical
layer: infrastructure
lifecycle: infrastructure
description: Environmental sensor.
peripheral:
parent: entity
domain: physical
layer: infrastructure
lifecycle: infrastructure
description: Attached hardware (GPU, e-ink display, dongle).
# ── Infrastructure / compute ──
compute-entity:
parent: entity
abstract: true
domain: compute
layer: infrastructure
description: Anything that executes workloads (machine, VM, container).
machine:
parent: compute-entity
abstract: true
domain: compute
layer: infrastructure
description: Physical machine. Always instantiated as a subtype.
attributes:
type: object
properties:
cpu_arch: {type: string}
ram_gb: {type: number}
os: {type: string, enum: [linux, macos]}
lan_ip: {type: string}
mesh: {type: object}
ssh: {type: object}
age_pubkey: {type: string}
proxmox-host:
parent: machine
domain: compute
layer: infrastructure
lifecycle: infrastructure
description: Machine running Proxmox VE.
attributes:
type: object
properties: {pve_version: {type: string}}
standalone-server:
parent: machine
domain: compute
layer: infrastructure
lifecycle: infrastructure
description: Machine outside PVE management (e.g. external VPS).
attributes:
type: object
properties:
hypervisor: {type: string}
provider: {type: string}
control_level: {type: string, enum: [full, partial, none]}
public_ipv4: {type: string}
workstation:
parent: machine
domain: compute
layer: infrastructure
lifecycle: infrastructure
description: Operator machine (may also host services, e.g. mac-mini).
attributes:
type: object
properties: {user: {type: string}}
appliance:
parent: machine
domain: compute
layer: infrastructure
lifecycle: infrastructure
description: Vendor appliance with limited management access.
attributes:
type: object
properties: {vendor: {type: string}, model: {type: string}}
vm:
parent: compute-entity
domain: compute
layer: infrastructure
lifecycle: infrastructure
description: Virtual machine.
attributes:
type: object
properties:
pve_id: {type: integer}
vcpus: {type: integer}
memory_mb: {type: integer}
lan_ip: {type: string}
public_host: {type: string}
role: {type: string}
container:
parent: compute-entity
abstract: true
domain: compute
layer: infrastructure
description: OS-level container (LXC or Docker).
attributes:
type: object
properties: {runtime: {type: string}}
lxc:
parent: container
domain: compute
layer: infrastructure
lifecycle: infrastructure
description: Proxmox LXC container.
attributes:
type: object
properties:
pve_id: {type: integer}
lan_ip: {type: string}
public_host: {type: string}
public_hosts: {type: array, items: {type: string}}
role: {type: string}
mesh: {type: object}
age_pubkey: {type: string}
destroyed: {type: string}
reason: {type: string}
docker-container:
parent: container
domain: compute
layer: infrastructure
lifecycle: infrastructure
description: Docker container (the OS models its own stack with these).
attributes:
type: object
properties: {image: {type: string}}
hypervisor:
parent: entity
domain: compute
layer: infrastructure
lifecycle: infrastructure
description: Hypervisor software running on a machine (PVE, KVM, OrbStack).
attributes:
type: object
properties: {type: {type: string}, version: {type: string}}
# ── Infrastructure / network ──
network:
parent: entity
abstract: true
domain: network
layer: infrastructure
description: A network things connect to.
lan:
parent: network
domain: network
layer: infrastructure
lifecycle: infrastructure
description: Local area network.
attributes: {type: object, properties: {subnet: {type: string}}}
mesh:
parent: network
domain: network
layer: infrastructure
lifecycle: infrastructure
description: Overlay mesh network (NetBird, Tailscale).
attributes:
type: object
properties:
provider: {type: string}
subnet: {type: string}
domain: {type: string}
vlan:
parent: network
domain: network
layer: infrastructure
lifecycle: infrastructure
description: Tagged VLAN.
attributes: {type: object, properties: {tag: {type: integer}}}
network-interface:
parent: entity
domain: network
layer: infrastructure
description: Optional per-interface refinement (mac, ip). The seed uses
coarse connects-via edges; interfaces can be backfilled later.
attributes: {type: object, properties: {mac: {type: string}, ip: {type: string}}}
dns-zone:
parent: entity
domain: network
layer: infrastructure
lifecycle: infrastructure
description: DNS zone (e.g. split-horizon hubris.network).
attributes: {type: object, properties: {zone: {type: string}, authority: {type: string}}}
dns-record:
parent: entity
domain: network
layer: infrastructure
description: Individual DNS record.
attributes:
type: object
properties: {name: {type: string}, record_type: {type: string}, value: {type: string}}
ingress-route:
parent: entity
domain: network
layer: infrastructure
lifecycle: infrastructure
description: Public hostname → upstream mapping (Caddy).
attributes:
type: object
properties:
pattern: {type: string}
upstream: {type: string}
forward_auth: {type: boolean}
certificate:
parent: entity
domain: network
layer: infrastructure
description: TLS certificate.
attributes: {type: object, properties: {issuer: {type: string}, expires: {type: string}}}
firewall-rule:
parent: entity
domain: network
layer: infrastructure
description: Firewall / port-forward rule.
# ── Infrastructure / storage ──
storage-pool:
parent: entity
domain: storage
layer: infrastructure
lifecycle: infrastructure
description: Storage pool (LVM, ZFS, NFS).
attributes:
type: object
properties:
type: {type: string}
capacity_gb: {type: number}
volume:
parent: entity
domain: storage
layer: infrastructure
lifecycle: infrastructure
description: Named volume / dataset within a pool. Mount details live as
attributes on `mounts` edges.
attributes: {type: object, properties: {size_gb: {type: number}, path: {type: string}}}
backup-target:
parent: entity
domain: storage
layer: infrastructure
lifecycle: infrastructure
description: Where backups land (Proton Drive, PBS).
attributes: {type: object, properties: {provider: {type: string}, encrypted: {type: boolean}}}
dataset:
parent: entity
domain: storage
layer: infrastructure
description: Logical data collection worth tracking independently of its
volume (e.g. paperless documents).
# ── Infrastructure / software ──
service:
parent: entity
domain: software
layer: infrastructure
lifecycle: infrastructure
description: A running service with consumers.
attributes:
type: object
properties:
url: {type: string}
port: {type: integer}
health: {type: string}
endpoint: {type: string}
systemd_unit: {type: string}
doc_page: {type: string}
risk_notes: {type: string}
note: {type: string}
application:
parent: entity
domain: software
layer: infrastructure
description: Deployed application/package a service runs.
attributes: {type: object, properties: {version: {type: string}}}
config-repo:
parent: entity
domain: software
layer: infrastructure
description: Git repo holding tracked configuration.
attributes:
type: object
properties: {url: {type: string}, branch: {type: string}}
deploy-pipeline:
parent: entity
domain: software
layer: infrastructure
description: Automated deploy path (webhook → script).
attributes:
type: object
properties: {trigger: {type: string}, target_path: {type: string}}
package-set:
parent: entity
domain: software
layer: infrastructure
description: Managed package baseline for a host class.
cluster:
parent: entity
domain: software
layer: infrastructure
lifecycle: infrastructure
description: Proxmox cluster.
attributes: {type: object, properties: {quorum: {type: string}}}
compose-stack:
parent: entity
domain: software
layer: infrastructure
lifecycle: infrastructure
description: Docker Compose stack (the Oikos OS itself is one).
attributes: {type: object, properties: {path: {type: string}}}
# ── Infrastructure / external ──
domain-registration:
parent: entity
domain: external
layer: infrastructure
description: Registered public domain.
attributes: {type: object, properties: {registrar: {type: string}, expires: {type: string}}}
cloud-service:
parent: entity
domain: external
layer: infrastructure
description: External SaaS/cloud dependency.
isp-link:
parent: entity
domain: external
layer: infrastructure
description: Internet uplink.
vendor-dependency:
parent: entity
domain: external
layer: infrastructure
description: Vendor the lab depends on (registrar, IONOS, Proton).
# ── Governance / identity ──
person:
parent: entity
domain: identity
layer: governance
description: Human actor (operator).
attributes:
type: object
properties: {matrix_id: {type: string}, oidc_sub: {type: string}, email: {type: string}}
agent:
parent: entity
domain: identity
layer: governance
lifecycle: infrastructure # agents are deployed/retired like infrastructure
description: Software agent actor (Hermes, the Oikos control loop).
attributes:
type: object
properties:
provider: {type: string}
model: {type: string}
gateway_port: {type: integer}
identity-provider:
parent: entity
domain: identity
layer: governance
description: OIDC / forward-auth provider (Authentik).
attributes:
type: object
properties:
issuer: {type: string}
client_id: {type: string}
auth_mode: {type: string, enum: [oidc, forward-auth, both]}
account:
parent: entity
domain: identity
layer: governance
description: An account a person/agent holds on a service.
secret:
parent: entity
domain: identity
layer: governance
description: Managed secret (Infisical path).
attributes:
type: object
properties: {path: {type: string}, rotation_days: {type: integer}}
key:
parent: entity
domain: identity
layer: governance
description: Cryptographic key (SSH, age).
access-grant:
parent: entity
domain: identity
layer: governance
description: Grant of access to a secret/scope.
attributes: {type: object, properties: {scope: {type: string}, expires: {type: string}}}
# ── Cognition ──
check:
parent: entity
domain: cognition
layer: cognition
description: Probe definition (checks-as-data, R3-7). Typed row in check_defs.
signal:
parent: entity
domain: cognition
layer: cognition
lifecycle: signal
description: Something needing attention. Typed row in signals.
classification:
parent: entity
domain: cognition
layer: cognition
description: A classifier decision with full reasoning. Typed row in classifications.
execution:
parent: entity
domain: cognition
layer: cognition
lifecycle: execution
description: An action the OS performed. Typed row in executions.
feedback:
parent: entity
domain: cognition
layer: cognition
description: What was learned from an execution. Typed row in feedback.
pattern:
parent: entity
domain: cognition
layer: cognition
lifecycle: pattern
description: Generalized rule extracted from feedback. Typed row in patterns.
skill:
parent: entity
domain: cognition
layer: cognition
lifecycle: skill
description: Codified, versioned procedure. Typed rows in skills.
approval:
parent: entity
domain: cognition
layer: cognition
lifecycle: approval
description: Operator approval request/decision. Typed row in approvals.
document:
parent: entity
domain: cognition
layer: cognition
description: Knowledge document ingested from docs/.
attributes:
type: object
properties: {title: {type: string}, source_path: {type: string}, content_hash: {type: string}}
runbook:
parent: entity
domain: cognition
layer: cognition
description: Step-by-step procedure for an entity/action.
attributes:
type: object
properties: {risk_class: {type: string}, source_path: {type: string}}
investigation:
parent: entity
domain: cognition
layer: cognition
description: Recorded investigation/postmortem.
# ─── Relationship types ────────────────────────────────────────────────
# cardinality is source→target: e.g. `hosts` one-to-many = one machine
# hosts many compute entities; each hosted entity has one hosting machine.
relationship_types:
# Infrastructure topology
hosts:
inverse: runs-on
source: machine
target: compute-entity
cardinality: one-to-many
description: Machine hosts a VM/container (hubris hosts lxc:apps).
runs-hypervisor:
inverse: hypervisor-on
source: machine
target: hypervisor
cardinality: one-to-one
description: Machine runs hypervisor software.
member-of:
inverse: has-member
source: proxmox-host
target: cluster
cardinality: many-to-one
description: PVE host belongs to a cluster.
part-of:
inverse: comprises
source: docker-container
target: compose-stack
cardinality: many-to-one
description: Docker container belongs to a compose stack.
provides:
inverse: provided-by
source: compute-entity
target: service
cardinality: one-to-many
description: Compute entity provides a service (lxc:gitea provides service:gitea).
runs:
inverse: run-by
source: service
target: application
cardinality: one-to-many
description: Service runs an application.
configured-by:
inverse: configures
source: entity
target: config-repo
cardinality: many-to-one
description: Entity's config is tracked in a repo (mutations = commit+push).
deploys-to:
inverse: deployed-by
source: deploy-pipeline
target: entity
cardinality: many-to-one
description: Pipeline deploys to a service/host.
routes-to:
inverse: routed-via
source: ingress-route
target: service
cardinality: many-to-one
description: Public hostname routes to a service.
secured-by:
inverse: secures
source: ingress-route
target: identity-provider
cardinality: many-to-one
description: Route gated by forward-auth.
uses-certificate:
inverse: certifies
source: ingress-route
target: certificate
cardinality: many-to-one
description: Route served with this certificate.
authenticates-via:
inverse: authenticates-service
source: service
target: identity-provider
cardinality: many-to-one
description: Service uses native OIDC (jellyfin authenticates-via authentik).
in-zone:
inverse: contains-record
source: dns-record
target: dns-zone
cardinality: many-to-one
description: Record belongs to a zone.
resolves-to:
inverse: resolved-from
source: dns-record
target: entity
cardinality: many-to-one
description: Record points at an ingress route or host.
depends-on:
inverse: dependency-of
source: service
target: service
cardinality: many-to-many
description: Runtime dependency (blast-radius edge).
connects-via:
inverse: connects
source: compute-entity
target: network
cardinality: many-to-many
description: Coarse network membership (host on LAN / mesh).
has-interface:
inverse: interface-of
source: compute-entity
target: network-interface
cardinality: one-to-many
description: Optional per-interface refinement.
interface-on:
inverse: has-endpoint
source: network-interface
target: network
cardinality: many-to-one
description: Interface attaches to a network.
# Storage
mounts:
inverse: mounted-by
source: compute-entity
target: volume
cardinality: many-to-many
description: Compute entity mounts a volume. Edge attributes carry
mount_point and options.
stores-on:
inverse: stores-for
source: compute-entity
target: storage-pool
cardinality: many-to-many
description: Rootfs/data lives on a pool.
contains:
inverse: contained-in
source: storage-pool
target: volume
cardinality: one-to-many
description: Pool contains a volume.
holds-dataset:
inverse: dataset-on
source: volume
target: dataset
cardinality: one-to-many
description: Volume holds a tracked dataset.
backs-up-to:
inverse: backup-of
source: entity
target: backup-target
cardinality: many-to-many
description: Entity's data is backed up to a target.
# Physical / external
powered-by:
inverse: powers
source: machine
target: ups
cardinality: many-to-one
description: Machine on UPS power.
located-at:
inverse: location-of
source: machine
target: site
cardinality: many-to-one
description: Machine's physical site.
registered-with:
inverse: registrar-of
source: domain-registration
target: vendor-dependency
cardinality: many-to-one
description: Domain registered with a registrar.
# Governance
owns:
inverse: owned-by
source: person
target: agent
cardinality: one-to-many
description: Person owns/controls an agent.
authenticates:
inverse: authenticated-by
source: identity-provider
target: person
cardinality: one-to-many
description: IdP authenticates a person.
holds-grant:
inverse: granted-to
source: agent
target: access-grant
cardinality: one-to-many
description: Agent holds an access grant.
grants:
inverse: granted-by
source: access-grant
target: secret
cardinality: many-to-one
description: Grant covers a secret.
can-decrypt:
inverse: readable-by
source: compute-entity
target: secret
cardinality: many-to-many
description: Host can decrypt a secret (legacy SOPS; Infisical grants later).
# Cognition
checks:
inverse: checked-by
source: check
target: entity
cardinality: many-to-one
description: Check probes an entity.
raises:
inverse: raised-by
source: check
target: signal
cardinality: one-to-many
description: Check raised a signal.
about:
inverse: subject-of
source: signal
target: entity
cardinality: many-to-one
description: Signal concerns an entity.
classifies:
inverse: classified-as
source: classification
target: signal
cardinality: many-to-one
description: Classification of a signal.
precedes:
inverse: follows
source: classification
target: execution
cardinality: one-to-one
description: Classification that led to an execution.
targets:
inverse: targeted-by
source: execution
target: entity
cardinality: many-to-one
description: Execution acts on an entity.
requires-approval:
inverse: approves
source: execution
target: approval
cardinality: one-to-one
description: Execution gated by an approval.
performs:
inverse: performed-by
source: agent
target: execution
cardinality: one-to-many
description: Agent performed an execution.
decides:
inverse: decided-by
source: person
target: approval
cardinality: one-to-many
description: Person decided an approval.
produces:
inverse: produced-by
source: execution
target: feedback
cardinality: one-to-one
description: Execution produced feedback.
contributes-to:
inverse: built-from
source: feedback
target: pattern
cardinality: many-to-many
description: Feedback supports a pattern.
informs:
inverse: informed-by
source: pattern
target: skill
cardinality: many-to-one
description: Pattern informs a skill.
guides:
inverse: guided-by
source: skill
target: classification
cardinality: one-to-many
description: Skill guided a classification.
documents:
inverse: documented-by
source: document
target: entity
cardinality: many-to-one
description: Document describes an entity.
procedure-for:
inverse: has-procedure
source: runbook
target: entity
cardinality: many-to-many
description: Runbook applies to an entity.

103
seeds/policy.yaml Normal file
View File

@@ -0,0 +1,103 @@
# Oikos policy seed — risk classes, approval rules, autonomy settings.
#
# Bootstraps risk_classes / approval_rules / autonomy_settings (migration
# 005). After ingest the DB is authoritative; runtime policy changes go
# through the dual-control meta-approval flow (plan S3) and are exported
# back here via `GET /api/v1/export`.
#
# Adapted from legacy oikos/policy.yaml (2026-07-07):
# - `commands:`/`mcp_tools:` maps are gone — read endpoints and MCP read
# tools are read_only by construction and never classified; mutating
# API calls classify via approval_rules below.
# - `actions:` map became approval_rules keyed on (entity_type, action);
# entity types may be abstract (rule inherits down the hierarchy,
# most-specific match wins: scope_entity > concrete type > ancestor).
# - `service_overrides:` became scope_entity rules.
# - `lifecycle_overrides:` became autonomy_settings keys read by the
# classifier.
version: 1
risk_classes:
read_only:
description: Observes state; cannot change anything.
approval_required: none
autonomy_allowed: true
reversible_low:
description: >-
Changes runtime state in a way a single follow-up command undoes
(restart, cache clear, sync pull). No config or data changes.
approval_required: none # still subject to global.auto_act + rules below
autonomy_allowed: true
config_mutation:
description: >-
Changes tracked configuration or deployed software: repo edit + push,
deploy trigger, Caddy/Gitea/app config, package upgrades. Reversible
via git, but affects other consumers.
approval_required: operator # Matrix ✅/❌ (single-use HMAC token)
autonomy_allowed: false
destructive:
description: >-
Destroys or irreversibly alters data/entities: container destroy,
disk format, DB wipe, secret rotation, client revocation.
approval_required: operator_confirmed
autonomy_allowed: false
approval_rules:
# ── Generic rules on (possibly abstract) types ──
- {entity_type: service, action: restart, risk_class: reversible_low, autonomy_level: auto}
- {entity_type: service, action: cache-clear, risk_class: reversible_low, autonomy_level: auto}
- {entity_type: service, action: sync-pull, risk_class: reversible_low, autonomy_level: auto}
- {entity_type: service, action: db-wipe, risk_class: destructive, autonomy_level: never}
- {entity_type: compose-stack, action: restart, risk_class: reversible_low, autonomy_level: auto}
- {entity_type: docker-container, action: restart, risk_class: reversible_low, autonomy_level: auto}
- {entity_type: machine, action: apt-upgrade, risk_class: config_mutation, autonomy_level: escalate}
- {entity_type: machine, action: reboot, risk_class: config_mutation, autonomy_level: escalate}
- {entity_type: machine, action: format-disk, risk_class: destructive, autonomy_level: never}
- {entity_type: config-repo, action: edit, risk_class: config_mutation, autonomy_level: escalate}
- {entity_type: deploy-pipeline, action: trigger, risk_class: config_mutation, autonomy_level: escalate}
- {entity_type: lxc, action: create, risk_class: config_mutation, autonomy_level: escalate}
- {entity_type: lxc, action: migrate, risk_class: config_mutation, autonomy_level: escalate}
- {entity_type: lxc, action: restart, risk_class: reversible_low, autonomy_level: auto}
- {entity_type: lxc, action: destroy, risk_class: destructive, autonomy_level: never}
- {entity_type: vm, action: restart, risk_class: reversible_low, autonomy_level: auto}
- {entity_type: vm, action: destroy, risk_class: destructive, autonomy_level: never}
- {entity_type: dns-record, action: change, risk_class: config_mutation, autonomy_level: escalate}
- {entity_type: ingress-route, action: change, risk_class: config_mutation, autonomy_level: escalate}
- {entity_type: storage-pool, action: change, risk_class: destructive, autonomy_level: never}
- {entity_type: secret, action: rotate, risk_class: destructive, autonomy_level: never}
- {entity_type: key, action: revoke, risk_class: destructive, autonomy_level: never}
# ── Governance objects (the OS's own levers — always operator-gated) ──
- {entity_type: pattern, action: activate, risk_class: config_mutation, autonomy_level: never}
- {entity_type: skill, action: activate, risk_class: config_mutation, autonomy_level: never}
# ── Per-entity overrides (wide blast radius) ──
- {entity_type: service, action: restart, scope_entity: "service:caddy",
risk_class: config_mutation, autonomy_level: escalate}
# everything *.hubris.network rides on caddy
- {entity_type: service, action: restart, scope_entity: "service:dns",
risk_class: config_mutation, autonomy_level: escalate}
# LAN-wide resolver
- {entity_type: service, action: restart, scope_entity: "service:authentik",
risk_class: config_mutation, autonomy_level: escalate}
# SSO provider — restart locks logins fleet-wide
autonomy_settings:
# Global kill-switch. Cold start = off: the agent escalates everything
# until patterns validate and the operator raises this (plan: trust is earned).
global.auto_act: "off" # off | reversible_low
# Per-entity hard blocks (checked even when global.auto_act is on)
never_auto_act.service:caddy: "true"
never_auto_act.service:dns: "true"
never_auto_act.service:authentik: "true"
never_auto_act.host:hubris: "true"
never_auto_act.host:strong: "true"
# Lifecycle-state classifier overrides (from legacy lifecycle_overrides)
lifecycle_override.provisioning.config_mutation: reversible_low
# no dependents yet — config changes are cheap
lifecycle_override.deprecated.refuse: new-inbound-edges
lifecycle_override.destroyed.refuse: all
# any action targeting a destroyed entity raises a drift signal instead