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>
This commit is contained in:
1610
docs/mbse/README.md
Normal file
1610
docs/mbse/README.md
Normal file
File diff suppressed because it is too large
Load Diff
509
docs/mbse/components.md
Normal file
509
docs/mbse/components.md
Normal file
@@ -0,0 +1,509 @@
|
||||
# Oikos — Component Views
|
||||
|
||||
> Companion to [the Model](README.md) and [the Framework](framework.md).
|
||||
> Where README.md's nine Views cut across the whole system by *concern*
|
||||
> (requirements, behavior, risk...), this document cuts across it by
|
||||
> *component* — one View per running part of the System, going one layer
|
||||
> deeper into its own internal structure than the whole-system Views do.
|
||||
> Per [framework.md](framework.md) §4, each section below is still a View
|
||||
> and must still answer Holt's three questions; they're stated once per
|
||||
> section rather than as a separate table, since here the Stakeholder is
|
||||
> almost always the same ("an engineer about to change this component")
|
||||
> and the Notation is the same (prose + Mermaid) throughout.
|
||||
|
||||
**How to use this alongside the other two documents:** if you're deciding
|
||||
*whether something belongs in the Model*, read [framework.md](framework.md).
|
||||
If you're asking *what does the system do and why*, read
|
||||
[README.md](README.md). If you're about to **change code in a specific
|
||||
package** and want to know its internal shape, its own state, and what's
|
||||
already known to be broken or dormant inside it before you touch it, read
|
||||
the relevant section here.
|
||||
|
||||
## Contents
|
||||
|
||||
| Component | Path | Status |
|
||||
|---|---|---|
|
||||
| [1. oikos api](#1-oikos-api) | `internal/httpapi`, `internal/mcp`, `internal/policy` | ✅ live — the decision/execution gate |
|
||||
| [2. oikos scheduler](#2-oikos-scheduler) | `internal/scheduler`, `internal/checkdefaults` | ✅ live — the observe loop |
|
||||
| [3. oikos notifier](#3-oikos-notifier) | `internal/notifier` | ✅ live — approval delivery |
|
||||
| [4. nomos](#4-nomos-agent-gateway) | `cmd/nomos` | ✅ live — the agent, unauthenticated gateway |
|
||||
| [5. web control room](#5-web-control-room) | `web/src` | ✅ live — standalone SPA |
|
||||
| [6. PostgreSQL/TimescaleDB](#6-postgresqltimescaledb) | `migrations/`, `seeds/` | ✅ live — the System's own source of truth |
|
||||
| [7. Dormant components](#7-dormant-components) | `internal/actuator`, `internal/learning` | 🔴 compiled, never started |
|
||||
| [8. Auxiliary components](#8-auxiliary-components) | `cmd/webhook`, `cmd/desktop` | ✅ live — deploy + packaging, not decision logic |
|
||||
|
||||
---
|
||||
|
||||
## 1. oikos api
|
||||
|
||||
**Stakeholders:** engineers extending the MCP tool surface, the `run` gate,
|
||||
or REST endpoints; anyone debugging why a specific command was or wasn't
|
||||
classified the way they expected. **Why this View earns its place:** this
|
||||
is the single component where the highest-consequence findings in
|
||||
[Risk & Safety](README.md#8-risk--safety) live — extending it without
|
||||
knowing its internal shape is how the kill-switch gap and the
|
||||
two-classifiers problem happened in the first place.
|
||||
|
||||
### oikos api — Internal structure
|
||||
|
||||
| File | Lines | Role |
|
||||
|---|---|---|
|
||||
| `internal/httpapi/server.go` | 842 | `NewHandler` (routing entry, L75), `combinedAuth` (L229), OIDC JWKS discovery/fetch/validate (L337-524), `GetActor` (L526), OIDC config/token/callback handlers (L575-712), `ListenAndServe` (L811) |
|
||||
| `internal/httpapi/impl.go` | 1,639 | Entity CRUD, lifecycle transitions + preconditions (per [ADR-0014](../adr/0014-entity-model.md)) |
|
||||
| `internal/httpapi/phase3.go` | 2,627 | Executions, approvals (`DecideApproval`), `sshExec`, `executeApprovedAction`, autonomy-settings read/write endpoints — the **largest single file in the component** |
|
||||
| `internal/httpapi/sse.go` | 366 | `LISTEN/NOTIFY` fan-out, ring-buffer replay |
|
||||
| `internal/httpapi/activity.go` | 215 | `agent_activity` read endpoints |
|
||||
| `internal/httpapi/knowledge.go` | 286 | Knowledge search/content endpoints |
|
||||
| `internal/httpapi/dashboard.go` | 172 | `dashboard/summary` |
|
||||
| `internal/httpapi/learning_view.go` | 129 | `learning/timeline`, `learning/trend` |
|
||||
| `internal/httpapi/problem.go` | 71 | RFC 9457 `problem+json` error envelope |
|
||||
| `internal/httpapi/default_checks.go` | 13 | Thin wrapper calling `internal/checkdefaults` on entity creation |
|
||||
| `internal/mcp/server.go` | 1,691 | All 33 MCP tool registrations (`get_entity` at L76 through `list_my_secrets` at L753), `sshExec` (L1031), `resolveExecTarget` (L1223), **`classifyAndGate`** (L1264-1417) |
|
||||
| `internal/policy/command.go` | 174 | `ClassifyCommand` (L108) — the **live** classifier, `computeCommandRisk` (L127), `allSegmentsReadOnly` (L157), `riskRank` (L26) |
|
||||
| `internal/policy/classify.go` | 157 | `ClassifySignal` (L46) — **dead code, zero callers** (see [Roadmap §9.2](README.md#92-code-real--dead-code--schema-only-matrix)) |
|
||||
|
||||
### oikos api — internal call structure: the `run` gate, by file
|
||||
|
||||
README.md's [§3.3](README.md#3-functional-architecture) shows the *decision
|
||||
logic* of the `run` gate. This shows the *code path* — which file hands off
|
||||
to which — because they're not the same question: the decision flowchart
|
||||
tells you what happens, this tells you where to go fix it.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
MCP["mcp/server.go\nrun tool handler, L366"] --> GATE["mcp/server.go\nclassifyAndGate, L1264"]
|
||||
GATE --> RESOLVE["mcp/server.go\nresolveExecTarget, L1223"]
|
||||
GATE --> CLASSIFY["policy/command.go\nClassifyCommand, L108"]
|
||||
CLASSIFY --> RISK["policy/command.go\ncomputeCommandRisk, L127\nallSegmentsReadOnly, L157"]
|
||||
GATE -->|read_only or window open| EXEC["mcp/server.go\nsshExec, L1031"]
|
||||
GATE -->|otherwise| APPROVAL["phase3.go\ncreateApproval"]
|
||||
APPROVAL -->|operator decides| DECIDE["phase3.go\nDecideApproval"]
|
||||
DECIDE --> EXEC2["phase3.go\nsshExec\n(separate implementation)"]
|
||||
|
||||
style CLASSIFY fill:#e8f5e9,stroke:#2e7d32
|
||||
style EXEC fill:#fff3e0,stroke:#e65100
|
||||
style EXEC2 fill:#fff3e0,stroke:#e65100
|
||||
```
|
||||
|
||||
The two orange boxes are the same finding stated visually: `mcp/server.go`
|
||||
and `phase3.go` each have **their own `sshExec`**, independently written,
|
||||
not sharing an implementation. Fix one path's SSH handling and the other is
|
||||
untouched — verified during the Roadmap audit, not assumed.
|
||||
|
||||
### oikos api — Interfaces this component owns
|
||||
|
||||
Full catalogs live in [README.md §5](README.md#5-interfaces-icd) (33 MCP
|
||||
tools, REST groups, SSE event types) — not repeated here. What's specific
|
||||
to *this* component's internal ownership: `internal/mcp/server.go` owns
|
||||
every MCP tool; `internal/httpapi/{impl,phase3,sse,activity,knowledge,
|
||||
dashboard,learning_view}.go` own the REST surface between them, split by
|
||||
resource area rather than by file size; `internal/policy/command.go` is a
|
||||
pure function library with no HTTP surface of its own, called only from
|
||||
`classifyAndGate`.
|
||||
|
||||
### oikos api — Status and known issues
|
||||
|
||||
All of the following are detailed with evidence in
|
||||
[Roadmap & Traceability](README.md#9-roadmap--traceability) and
|
||||
[Risk & Safety](README.md#8-risk--safety) — cross-referenced here so an
|
||||
engineer opening this specific package sees them before making a change,
|
||||
not after:
|
||||
|
||||
- `policy.ClassifySignal` (in this component) is dead code; the schema it
|
||||
reads (`autonomy_settings.global.auto_act`, `never_auto_act.*`) is
|
||||
therefore not enforced by anything in the live request path — [§8.1](README.md#81-the-kill-switch-gap-verified-most-important-finding-in-this-model).
|
||||
- `notifier.VerifyApprovalToken` (a different component, §3 below) is dead;
|
||||
`phase3.go:DecideApproval` reimplements token verification inline instead
|
||||
of calling it.
|
||||
- `domain.Execution`'s state constants are descriptive only — `phase3.go`
|
||||
writes ad-hoc SQL string statuses that don't map 1:1 onto them.
|
||||
- SSH host key verification is disabled (`InsecureIgnoreHostKey`) on the
|
||||
actuation path — open gap B4.
|
||||
|
||||
---
|
||||
|
||||
## 2. oikos scheduler
|
||||
|
||||
**Stakeholders:** engineers adding a new probe kind or debugging why a
|
||||
signal did or didn't fire. **Why this View earns its place:** the
|
||||
scheduler is the only component that runs unattended on a fixed interval
|
||||
with no operator or agent triggering it — its failure modes look different
|
||||
from every request-driven component above.
|
||||
|
||||
### oikos scheduler — Internal structure
|
||||
|
||||
| File | Lines | Role |
|
||||
|---|---|---|
|
||||
| `internal/scheduler/scheduler.go` | 761 | Everything — no sub-packages |
|
||||
| `internal/scheduler/init.go` | 13 | `RunnerForMain()` — the only thing `cmd/oikos`'s `scheduler` role calls |
|
||||
| `internal/checkdefaults/defaults.go` | — | `ForEntityType` (L60-123), `Ensure` (L144-204), `DefaultInterval` (L133-142) — default check provisioning on entity creation |
|
||||
|
||||
Key functions inside `scheduler.go`: `Run` (L36-64, the tick loop, default
|
||||
30s), `runCheckPass` (L67-94, loads `check_defs`, dispatches with a
|
||||
10-worker `errgroup` limit), `runCheck` (L104-186), `executeCheck` (L237-254,
|
||||
the kind dispatcher), `resolveSignal` (L206-225), `evaluateSeverity`
|
||||
(L737-760), `staleSweep` (L286-333, 3× fastest interval / 5 min floor).
|
||||
|
||||
### oikos scheduler — behavior specific to this component: the probe dispatch
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
TICK["Run tick, every 30s"] --> LOAD["ListEnabledCheckDefs"]
|
||||
LOAD --> DISPATCH["executeCheck: dispatch by kind"]
|
||||
DISPATCH --> HTTP["checkHTTP, L336"]
|
||||
DISPATCH --> TCP["checkTCP, L393"]
|
||||
DISPATCH --> DISK["checkDisk, L424"]
|
||||
DISPATCH --> CERT["checkCertExpiry, L474"]
|
||||
DISPATCH --> PING["checkPing, L545"]
|
||||
DISPATCH --> SSH["checkSSHScript, L613"]
|
||||
HTTP & TCP & DISK & CERT & PING & SSH --> RESULT["checkResult struct\nhealth, signalKind, evidence, metrics"]
|
||||
RESULT -->|healthy| RESOLVE["resolveSignal\nraw SQL, bypasses Signal.CanTransition"]
|
||||
RESULT -->|unhealthy| UPSERT["UpsertSignal\ndedup by target+kind"]
|
||||
RESULT --> METRICS["INSERT metric_samples"]
|
||||
RESULT --> STATUS["UpsertEntityStatus"]
|
||||
```
|
||||
|
||||
`checkSSHScript` (L613-724) is the odd one out: it shells out to the system
|
||||
`ssh` binary directly (`BatchMode=yes`, `StrictHostKeyChecking=no`) rather
|
||||
than using a Go SSH library, restricted to scripts matching
|
||||
`^[a-z][a-z0-9_-]+\.sh$` at a fixed path `/opt/oikos/checks/<script>`. The
|
||||
18 scripts it can run (`cpu_check.sh`, `disk_usage_check.sh`,
|
||||
`docker_health_check.sh`, `zfs_check.sh`, …) live in `checks/` in this repo
|
||||
and are auto-deployed to every enrolled client by `tools/setup-checks.sh`
|
||||
(per AGENTS.md §8) — this component's actual probe logic is split between
|
||||
Go code here and shell scripts version-controlled elsewhere in the repo.
|
||||
|
||||
### oikos scheduler — Interfaces this component owns
|
||||
|
||||
No external API — this is the one component with no inbound interface at
|
||||
all, only outbound: SSH to the fleet (probes), and writes to
|
||||
`metric_samples`/`signals`/`entity_status`/`events` that every other
|
||||
component reads. It is a pure producer.
|
||||
|
||||
### oikos scheduler — Status and known issues
|
||||
|
||||
- Never calls `policy.ClassifySignal` — signals it raises sit as
|
||||
`state='raised'` with no automatic classification; whatever consumes
|
||||
them downstream (the agent, the console) does its own interpretation.
|
||||
- `resolveSignal` updates `raised → resolved` via raw SQL, bypassing the
|
||||
one enforced state machine in the domain layer
|
||||
(`domain.Signal.CanTransition`) — the specific transition happens to be
|
||||
legal today, but nothing would stop a future change from making it not.
|
||||
|
||||
---
|
||||
|
||||
## 3. oikos notifier
|
||||
|
||||
**Stakeholders:** engineers debugging a missed or duplicate Matrix alert,
|
||||
or extending the approval-delivery mechanism to a new channel.
|
||||
**Why this View earns its place:** this is the one component whose entire
|
||||
job is bridging an asynchronous human decision into the same-shaped
|
||||
synchronous decision every other component expects — worth understanding
|
||||
in isolation before assuming "approval" means one simple thing.
|
||||
|
||||
### oikos notifier — Internal structure
|
||||
|
||||
All in `internal/notifier/notifier.go` (305 lines, one file, no
|
||||
sub-packages): `Run` (L25-47, two tickers — 15s for pending approvals, 30s
|
||||
for reaction polling), `processPendingApprovals` (L65-115, generates the
|
||||
token/hash lazily on first pass), `generateApprovalToken` (L275-283,
|
||||
HMAC-SHA256 over approval ID + nanosecond timestamp), `hashToken`
|
||||
(L302-305, only the hash is stored), `sendMatrixAlert` (L231-272),
|
||||
`pollReactions`/`checkReaction` (L118-201), `callDecideApproval`
|
||||
(L204-228), `VerifyApprovalToken` (L286-300, **dead code**).
|
||||
|
||||
### oikos notifier — Behavior specific to this component
|
||||
|
||||
The full sequence (Matrix + console paths converging on one decision
|
||||
endpoint) is in [README.md §6.4](README.md#64-sequence--the-run-primitive-end-to-end).
|
||||
Specific to this component in isolation: it never calls into
|
||||
`internal/httpapi` directly except through one HTTP call
|
||||
(`callDecideApproval`, an ordinary client request to
|
||||
`POST /api/v1/approvals/{id}/decision`) — the notifier and the API process
|
||||
communicate **only through the database and one HTTP endpoint**, never
|
||||
through shared Go state, which is why the header comment in `notifier.go`
|
||||
calls this a "DB rendezvous pattern."
|
||||
|
||||
### oikos notifier — Interfaces this component owns
|
||||
|
||||
Outbound only: the Matrix client-server API
|
||||
(`PUT /rooms/.../send/m.room.message`, `GET /relations/.../m.annotation`)
|
||||
and one outbound call to the API's own approval-decision endpoint. No
|
||||
inbound interface — nothing calls into the notifier process.
|
||||
|
||||
### oikos notifier — Status and known issues
|
||||
|
||||
- `VerifyApprovalToken` is dead code; `phase3.go:DecideApproval` (a
|
||||
different component, §1 above) reimplements the same hash-compare logic
|
||||
inline rather than calling it — a single source of truth for token
|
||||
verification does not currently exist.
|
||||
- Open gap A2: `alert_sent_at` is written *after* the send attempt, so a
|
||||
failed UPDATE re-sends the alert on the next poll; no dedup beyond that,
|
||||
and reaction-polling API calls are unbounded.
|
||||
|
||||
---
|
||||
|
||||
## 4. nomos (agent gateway)
|
||||
|
||||
**Stakeholders:** engineers changing agent behavior, adding a task tool, or
|
||||
investigating a stuck/duplicated task. **Why this View earns its place:**
|
||||
this is the largest component by line count (4,681 lines across six files)
|
||||
and the one with the most active recent bug-fix history
|
||||
(`plans/2026-07-11-nomos-agent-code-review.md`,
|
||||
`plans/done/2026-07-14-post-fix-session-remainders.md`) — its internal
|
||||
shape is not obvious from outside.
|
||||
|
||||
### nomos — Internal structure
|
||||
|
||||
| File | Lines | Role |
|
||||
|---|---|---|
|
||||
| `cmd/nomos/main.go` | 914 | Gateway HTTP server (`:8092`), `/query`/`/chat`/`/sessions` routes, the hand-rolled Streamable-HTTP MCP client (`mcpClient`, per-session pooled) |
|
||||
| `cmd/nomos/store.go` | 1,472 | Persistence — sessions, messages, `logActivity` |
|
||||
| `cmd/nomos/agent.go` | 861 | The agentic loop itself; model config (L62-120); `maxIterations = 40` (L24, a **hard-coded constant**, not read from `nomos/config.yaml`'s `max_iterations: 15` — the two disagree, see status below) |
|
||||
| `cmd/nomos/tasks.go` | 416 | The five nomos-local task tools: `set_goal`, `propose_plan`, `update_plan_step`, `ask_operator`, `complete_task` — handled in-process, never forwarded to `internal/mcp` |
|
||||
| `cmd/nomos/continue.go` | 347 | The auto-continuation worker — polls `nomos_plan_executions` |
|
||||
| `cmd/nomos/assent.go` | 183 | `isAssent`/`isTypedConfirmation` — regex word-boundary matching (fixed 2026-07-11 after a false-positive bug where "yesterday" matched "yes") |
|
||||
|
||||
### nomos — Behavior specific to this component
|
||||
|
||||
The Task lifecycle and auto-continuation sequences are in
|
||||
[README.md §3.4](README.md#34-functional-flow--the-task-lifecycle-f3f4f5-packaged-for-a-human)
|
||||
and [§6.5](README.md#65-sequence--plan-auto-continuation-the-system-is-the-event-loop).
|
||||
Specific to this component: the LLM sees a **union of two tool sources** —
|
||||
the 33 tools fetched live from `api`'s `/mcp` endpoint via `tools/list`,
|
||||
plus the 5 local task tools in `tasks.go` — and `agent.go`'s per-call
|
||||
routing decides in-process versus forwarded with no visible seam to the
|
||||
model itself. A hidden `_session_id` is injected into forwarded calls on
|
||||
the wire (never in the model-visible arguments) so `internal/mcp/server.go`
|
||||
can scope assent/destructive windows per task.
|
||||
|
||||
### nomos — Interfaces this component owns
|
||||
|
||||
| Route | Auth |
|
||||
|---|---|
|
||||
| `GET /healthz` | none |
|
||||
| `POST /query` (structured tool call or a pointer to `/chat`) | **none** |
|
||||
| `POST /chat` (SSE, the real agentic loop) | **none** |
|
||||
| `GET/POST /sessions`, `/sessions/{id}` | **none** |
|
||||
|
||||
This entire interface is unauthenticated — full detail in
|
||||
[README.md §5.4](README.md#54-nomos-http-interface-cmdnomos-port-8092).
|
||||
Outbound: a pooled MCP client to `api`, and chat completions to OpenRouter
|
||||
(`data_collection: deny` pinned, default model `deepseek/deepseek-v4-pro`).
|
||||
|
||||
### nomos — Status and known issues
|
||||
|
||||
- **C1, the most consequential open gap involving this component**: zero
|
||||
authentication on the entire gateway, including the ability to grant
|
||||
chat-assent approvals with no credential check. Explicitly deferred by
|
||||
operator instruction, not an oversight — see
|
||||
[README.md §8.4](README.md#84-known-open-security-gaps).
|
||||
- `nomos/config.yaml`'s `max_iterations: 15` does not match the enforced
|
||||
Go constant (`40`) — one of the two is stale.
|
||||
- Dual `agent_activity` logging: both this component's `store.logActivity`
|
||||
and `internal/mcp/server.go`'s `withActivityLogging` (a different
|
||||
component) log the same forwarded tool call. Not confirmed whether this
|
||||
is an intentional two-sided audit trail or accidental duplication.
|
||||
|
||||
---
|
||||
|
||||
## 5. web control room
|
||||
|
||||
**Stakeholders:** the operator, directly; engineers changing the UI's data
|
||||
model or adding a page. **Why this View earns its place:** this is the only
|
||||
component with no server-side logic of its own — understanding it means
|
||||
understanding what it *doesn't* do (it is not the system of record for
|
||||
anything) as much as what it does.
|
||||
|
||||
### web control room — Internal structure
|
||||
|
||||
Nine pages under `web/src/pages/` (Svelte 5, hash-based routing, no router
|
||||
library):
|
||||
|
||||
| Page | Lines | Shows |
|
||||
|---|---|---|
|
||||
| `Overview.svelte` | 262 | Task dashboard — fleet/health/signal summary cards, the task list, entry point for launching a new chat |
|
||||
| `Chat.svelte` | 410 | The conversation UI — streaming, `TaskContextPanel`, tool-call rendering, inline approvals |
|
||||
| `Ops.svelte` | 240 | Approvals queue, recent activity/execution feed, approve/deny/cancel |
|
||||
| `Signals.svelte` | 171 | Alert/signal triage — severity filter, ack/resolve/mute |
|
||||
| `KnowledgeBase.svelte` | 263 | Entity browser — force-directed graph view and table view |
|
||||
| `Knowledge.svelte` | 186 | Free-text knowledge search |
|
||||
| `Learning.svelte` | 174 | Pattern/skill telemetry — the one page whose backing data source
|
||||
(`internal/learning`) is dormant per §7 below, so this page currently shows
|
||||
whatever accumulated before the engine stopped being called, not a live
|
||||
feed |
|
||||
| `Config.svelte` | 202 | Auth/connection screen — static bearer token or OIDC login |
|
||||
| `EntityDetail.svelte` | 7 | Thin wrapper, deep-link target |
|
||||
|
||||
Shared logic under `web/src/lib/`: `config.ts` (`fetchWithAuth`, the single
|
||||
wrapper every API call goes through), `oidc.ts`, `api.ts`, `tasks.ts`,
|
||||
`stores/events.ts` (the always-on SSE connection), plus task-specific
|
||||
components (`TaskContextPanel.svelte`, `GoalHeader.svelte`,
|
||||
`PlanProgress.svelte`, `OperatorQuestion.svelte`, `SessionGraph.svelte`).
|
||||
|
||||
### web control room — Behavior specific to this component
|
||||
|
||||
Two data-flow patterns, not one: most pages fetch REST on mount and
|
||||
re-fetch on a relevant SSE event; `Chat.svelte`'s `TaskContextPanel` is
|
||||
driven by the **always-on global event stream**
|
||||
(`web/src/lib/stores/events.ts`), not the per-turn chat SSE connection —
|
||||
deliberately, so the live context panel stays populated during
|
||||
server-side auto-continuation (§4 above) when no chat turn is actually
|
||||
open, and survives a tab reload.
|
||||
|
||||
### web control room — Interfaces this component owns
|
||||
|
||||
None inbound — it is a pure consumer of `internal/httpapi`'s REST and SSE
|
||||
interfaces (full catalog: [README.md §5](README.md#5-interfaces-icd)).
|
||||
`fetchWithAuth` resolves config per request rather than at import time, so
|
||||
the same build works same-origin (production, Vite dev proxy) or
|
||||
cross-origin (the Wails desktop webview, §8).
|
||||
|
||||
### web control room — Status and known issues
|
||||
|
||||
Standalone deploy, versioned and released independently of the `oikos`
|
||||
binary — see [README.md §4.5](README.md#45-build--release-artifacts) for
|
||||
why "deployed" means two different release cadences depending on whether
|
||||
you mean the container or the desktop app.
|
||||
|
||||
---
|
||||
|
||||
## 6. PostgreSQL/TimescaleDB
|
||||
|
||||
**Stakeholders:** anyone writing a migration, or reasoning about what
|
||||
"the system's source of truth" actually means (see
|
||||
[framework.md §2](framework.md#2-the-goal--system-and-model-made-concrete)
|
||||
for why that phrase needs disambiguating from the engineering Model).
|
||||
**Why this View earns its place:** every other component in this document
|
||||
either reads from or writes to this one; it is the only component every
|
||||
other component has in common.
|
||||
|
||||
### PostgreSQL/TimescaleDB — Internal structure
|
||||
|
||||
20 forward-only, idempotent migrations
|
||||
(`001_ontology.up.sql` … `020_session_reliability.up.sql`,
|
||||
[ADR-0008](../adr/0008-forward-only-migrations.md)). Four TimescaleDB
|
||||
hypertables, all created in `006_observability.up.sql`:
|
||||
`metric_samples`, `audit_log`, `events`, `agent_activity` — each with
|
||||
continuous aggregates and retention policies.
|
||||
|
||||
The recurring structural pattern across this schema, per
|
||||
[ADR-0014](../adr/0014-entity-model.md) §7: **dual entities** —
|
||||
`check_defs`, `signals`, `classifications`, `executions`, `feedback`,
|
||||
`patterns`, `skills`, `approvals`, `knowledge_entities`, and (as of
|
||||
migration 018) `agent_sessions`-as-`task` all have an
|
||||
`entity_id UUID PK REFERENCES entities(id)`, meaning every specialized row
|
||||
is simultaneously a node in the general entity graph — this is what lets
|
||||
`get_relations`/`get_blast_radius` work uniformly over signals, tasks, and
|
||||
infrastructure alike without a special case for each.
|
||||
|
||||
Partial unique indexes provide snapshot semantics without an application-
|
||||
level lock: `relationships` (current edges only), `signals` (one open
|
||||
signal per entity+kind), `patterns` (one per type+action).
|
||||
|
||||
### PostgreSQL/TimescaleDB — Behavior specific to this component
|
||||
|
||||
`008_event_notify.up.sql`'s `pg_notify` trigger on `events` INSERT is the
|
||||
entire mechanism behind [README.md's SSE interface](README.md#55-server-sent-events-internalhttpapissego)
|
||||
— the database, not the API process, is what decides an event happened;
|
||||
the API process is just a fan-out listener.
|
||||
|
||||
### PostgreSQL/TimescaleDB — Interfaces this component owns
|
||||
|
||||
Every Go component in this document connects directly (via `sqlc`-generated
|
||||
queries, `internal/db`) — there is no ORM abstraction layer and no
|
||||
component-specific access restriction beyond what each service's own
|
||||
Postgres role grants (notably: the learning engine's DB role has no grants
|
||||
on governance/autonomy tables, per
|
||||
[ADR-0006](../adr/0006-learning-proposal-only.md) — a structural guarantee
|
||||
that would matter the moment §7's dormant learning engine is wired back in).
|
||||
`seeds/*.yaml` + `oikos seed`/`oikos export` form the bootstrap/DR
|
||||
interface — the database can be regenerated from seeds, and seeds can be
|
||||
regenerated from the database.
|
||||
|
||||
### PostgreSQL/TimescaleDB — Status and known issues
|
||||
|
||||
A shared-Postgres single point of failure across every service is a
|
||||
documented residual risk in [ADR-0007](../adr/0007-threat-model.md), not a
|
||||
newly discovered one.
|
||||
|
||||
---
|
||||
|
||||
## 7. Dormant components
|
||||
|
||||
**Stakeholders:** anyone deciding whether to revive auto-act, or tempted to
|
||||
extend `internal/actuator`/`internal/learning` believing them to be the
|
||||
live implementation. **Why this View earns its place:** these are the two
|
||||
components most likely to mislead an engineer navigating by package name —
|
||||
both are substantial, well-written, and compile cleanly into the `oikos`
|
||||
binary, and neither runs.
|
||||
|
||||
### Dormant components — Internal structure
|
||||
|
||||
| File | Lines | Role, and why it's dormant |
|
||||
|---|---|---|
|
||||
| `internal/actuator/actuator.go` | 505 | `Run` (L24-41, 10s ticker), `processAutoActSignals`, kill-switch checks (L47, L69 — `getAutonomySetting` for `global.auto_act` and `never_auto_act.<slug>`), a real circuit breaker (L156-202, threshold + cooldown), advisory locking (L87-99) — **but the executor itself is a hardcoded stub**, `{"success": true, "message": "stub execution"}` (L124-137), and `Run()` is never called by `cmd/oikos/main.go` or any `docker-compose.yml` service |
|
||||
| `internal/actuator/ssh.go` | 291 | `ExecuteProcedure` (L126-230) — a fully-built step-by-step SSH runner with `classifySSHError` (L77-108: network/auth/timeout/remote), per-step timeouts, verify-step semantics. **Zero callers anywhere in the codebase.** |
|
||||
| `internal/learning/learning.go` | 183 | `Run` (L20-41, hourly ticker), `extractPatterns` (L45-81), `processGroup` (L83-169, Wilson lower-bound confidence, evidence≥5 ∧ confidence≥0.7 → `validated`, anomaly quarantine at >10 same-key events per pass) — algorithmically faithful to [ADR-0006](../adr/0006-learning-proposal-only.md), **never started by any process** |
|
||||
|
||||
### Why this matters more than "unused code"
|
||||
|
||||
`internal/actuator/actuator.go` is where the policy kill-switch
|
||||
(`global.auto_act`, `never_auto_act.*`) is actually checked in Go — the
|
||||
*only* other place is the dead `policy.ClassifySignal`. Reviving auto-act
|
||||
and fixing the kill-switch gap
|
||||
([README.md §8.1](README.md#81-the-kill-switch-gap-verified-most-important-finding-in-this-model))
|
||||
are, structurally, **the same piece of work** — whoever picks up
|
||||
`internal/actuator/actuator.go:47-69` is the person who also resolves
|
||||
REQ-DEC-5. This is stated explicitly here because it is not obvious from
|
||||
reading the Risk & Safety or Roadmap views in isolation; it only becomes
|
||||
visible once you've read this component's code.
|
||||
|
||||
### Dormant components — Status and known issues
|
||||
|
||||
This entire section *is* the known issue — see
|
||||
[README.md §9.1](README.md#91-the-north-star-general-gated-execution)
|
||||
("revive auto-act," the one item the `general-gated-execution` plan's own
|
||||
header still marks open) and
|
||||
[§9.4](README.md#94-suggested-next-steps-informational--not-a-commitment-not-a-plan)
|
||||
item 1 and 3 for the two decisions this leaves open: wire these back in, or
|
||||
delete them and correct the "Phase 3 — DONE" claim in
|
||||
`OIKOS.md`/ADR-0014 that currently overstates what's running.
|
||||
|
||||
---
|
||||
|
||||
## 8. Auxiliary components
|
||||
|
||||
Two small, single-purpose components that support deployment and
|
||||
packaging rather than decision logic — given lighter treatment here
|
||||
deliberately, since no Stakeholder identified in this document's other
|
||||
sections needs their internals to make a change elsewhere.
|
||||
|
||||
**`cmd/webhook`** (96 lines, one file) — the Gitea push-to-deploy receiver.
|
||||
Verifies `X-Hub-Signature-256` HMAC-SHA256 against `WEBHOOK_HMAC_SECRET`
|
||||
using `hmac.Equal`, responds `202` immediately, then runs
|
||||
`scripts/deploy.sh` asynchronously. Full deploy sequence:
|
||||
[README.md §4.2](README.md#42-deployment-topology-mac-mini-docker-compose)
|
||||
and §7.2.
|
||||
|
||||
**`cmd/desktop`** (784 lines) — the Wails-wrapped desktop shell. Bundles
|
||||
`web/dist` into a native binary, adds an OIDC login flow through a local
|
||||
HTTP server + system browser, and a `SaveConfig` bridge the web bundle
|
||||
calls when running inside the desktop webview (`web/src/lib/config.ts`'s
|
||||
`saveToDesktop()`). Contains no decision logic of its own — it is
|
||||
packaging for component 5 (§5 above), not a new component in the
|
||||
functional sense.
|
||||
|
||||
---
|
||||
|
||||
## Keeping this document current
|
||||
|
||||
The same discipline as README.md's closing note applies here, scoped to
|
||||
components: when a file listed in a "Internal structure" table is renamed,
|
||||
split, or gains a new responsibility, update that row. When a "Status and
|
||||
known issues" bullet is resolved, remove it — and check whether removing it
|
||||
also resolves an entry in
|
||||
[README.md §9](README.md#9-roadmap--traceability), since most of the
|
||||
findings here were first surfaced there and are repeated in this document
|
||||
for proximity to the code, not because they're independently tracked in
|
||||
two places.
|
||||
414
docs/mbse/framework.md
Normal file
414
docs/mbse/framework.md
Normal file
@@ -0,0 +1,414 @@
|
||||
# Oikos — MBSE Framework, Ontology & Viewpoints
|
||||
|
||||
> Companion to [the system Model](README.md). Where `README.md` **is** the
|
||||
> Model — the populated Views — this document is the **Framework**: the
|
||||
> template those Views were built from. It follows Jon Holt, *Systems
|
||||
> Engineering Demystified* (2nd ed., 2023), Ch. 2, "Model-Based Systems
|
||||
> Engineering," almost to the letter — the terms below (Model, View,
|
||||
> Viewpoint, Notation, Ontology, Framework, Process Set, Compliance) are
|
||||
> Holt's, not a paraphrase, because the whole point of adopting an Ontology
|
||||
> is to stop each document inventing its own vocabulary.
|
||||
|
||||
> **Holt's core claim, stated once so it doesn't need restating per
|
||||
> section:** *"When the Ontology and the Viewpoints are put together, they
|
||||
> form what is known as a Framework. A Framework is created as a template,
|
||||
> or blueprint, for a complete Model."* (Ch. 2, p. 40). Ontology is, in
|
||||
> Holt's words, "arguably the single most important part of MBSE as all of
|
||||
> the other elements that make up MBSE are ultimately traceable back to the
|
||||
> Ontology" (p. 40).
|
||||
|
||||
## 1. MBSE in a Slide — applied to Oikos
|
||||
|
||||
Holt's book converges the whole chapter into one diagram known across the
|
||||
Systems Engineering community as "MBSE in a slide" (Holt & Perry, 2019),
|
||||
extended with Implementation and Compliance. Below is that same structure
|
||||
with every box filled in for this specific repository, not left generic.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph APPROACH["APPROACH \n what must be produced, and how"]
|
||||
FW["Framework \n Ontology + Viewpoints \n = this document"]
|
||||
PS["Process Set \n ADRs, plans, this repo's\nreview/CI conventions"]
|
||||
end
|
||||
|
||||
subgraph GOAL["GOAL \n why any of this exists"]
|
||||
SYS["System \n the hubris homelab,\ngoverned by Oikos"]
|
||||
MDL["Model \n README.md \n the nine Views"]
|
||||
end
|
||||
|
||||
subgraph VIS["VISUALIZATION \n how it is communicated"]
|
||||
NOT["Notation \n Markdown + Mermaid"]
|
||||
DIA["Diagrams \n flowchart, stateDiagram,\nsequenceDiagram, classDiagram"]
|
||||
end
|
||||
|
||||
subgraph IMPL["IMPLEMENTATION"]
|
||||
TOOL["Tools \n git, a Markdown renderer,\nMermaid; no dedicated\nSysML tool"]
|
||||
end
|
||||
|
||||
subgraph COMP["COMPLIANCE"]
|
||||
BP["Best Practice \n ISO 42010 viewpoint and view\nterminology, informally aligned,\nnot certified"]
|
||||
end
|
||||
|
||||
FW --> MDL
|
||||
PS --> MDL
|
||||
MDL --> SYS
|
||||
NOT --> DIA
|
||||
DIA --> MDL
|
||||
TOOL --> NOT
|
||||
TOOL --> FW
|
||||
BP --> PS
|
||||
BP --> FW
|
||||
```
|
||||
|
||||
| Holt's concept | Generic definition (Ch. 2) | Oikos instantiation |
|
||||
|---|---|---|
|
||||
| **System** | The thing Systems Engineering exists to develop | The **hubris homelab** — hosts, LXCs, VMs, services, network — *and* the Oikos control plane that governs it. See note below on the reflexive boundary. |
|
||||
| **Model** | The abstraction of the System; the single source of truth for engineering knowledge about it | [README.md](README.md) — the nine-View system model |
|
||||
| **View** | A validated collection of information within the Model | Each of README.md's nine numbered sections |
|
||||
| **Viewpoint** | The template for a View — the stored answers to *which Stakeholders, why, what information* | §4 of this document — the Viewpoint catalog |
|
||||
| **Ontology** | The domain-specific language every Viewpoint's content is expressed in | §3 of this document, and — distinctively for this system — literally implemented in code as `seeds/ontology.yaml` |
|
||||
| **Notation** | The spoken/visual language used to communicate a View | Markdown prose + Mermaid diagrams (flowchart, stateDiagram-v2, sequenceDiagram, classDiagram) |
|
||||
| **Diagram** | One rendering of a View through a Notation's lens | Each Mermaid block in README.md |
|
||||
| **Framework** | Ontology + Viewpoints, together | This document |
|
||||
| **Process Set** | The steps for developing and using the Framework — the "how" | This repo's ADR process ([docs/adr/](../adr/)), design-doc process (`plans/`), and the research-then-write method used to build README.md (see its own header note on verified vs. per-research-pass findings) |
|
||||
| **Tool** | What implements the Notation and the Framework | Git + a Markdown/Mermaid renderer for the Notation; no dedicated MBSE tool enforces the Framework — see §6 for the honest gap this leaves |
|
||||
| **Compliance** | Demonstrating the approach meets external best practice | §5 |
|
||||
|
||||
**On the System's boundary being reflexive.** Most MBSE textbook examples
|
||||
model a System that is wholly separate from the engineering process
|
||||
describing it (a car, a radar). Oikos is not: the System being modeled
|
||||
*is* an autonomous control system, and the Model describing it (this
|
||||
documentation) sits outside a boundary that the System itself polices with
|
||||
its own internal "model" — the Postgres database, which
|
||||
[ADR-0003](../adr/0003-db-native-ontology-yaml-seeds.md) calls the runtime
|
||||
single source of truth. These are two different, non-competing uses of
|
||||
"model": the **engineering Model** (this doc set) is Holt's sense — a
|
||||
human-facing abstraction for realizing the System successfully. The
|
||||
**runtime database** is an operational sense — the System's own record of
|
||||
its current state, which the engineering Model *describes* but does not
|
||||
*replace*. Conflating the two would suggest this documentation is
|
||||
authoritative over live state, which it explicitly is not — README.md's
|
||||
own verification discipline (verified vs. per-research-pass) exists
|
||||
precisely because the engineering Model can drift from what the database
|
||||
and code actually do.
|
||||
|
||||
## 2. The Goal — System and Model, made concrete
|
||||
|
||||
**The System**, enumerated (this is "taking all the components of the
|
||||
system," per Holt's instruction that a valid View must be traceable to
|
||||
real Stakeholders and real information — not an abstract diagram):
|
||||
|
||||
| Component | Role |
|
||||
|---|---|
|
||||
| `oikos api` (`internal/httpapi`, `internal/mcp`) | REST + MCP server, the decision/execution gate |
|
||||
| `oikos scheduler` (`internal/scheduler`) | Observe loop — probes, signals |
|
||||
| `oikos notifier` (`internal/notifier`) | Approval delivery — Matrix, token issuance |
|
||||
| `nomos` (`cmd/nomos`) | The AI agent — MCP client, task/plan orchestration |
|
||||
| `web` (Svelte 5 SPA) | Control-room UI |
|
||||
| PostgreSQL/TimescaleDB | The System's own runtime source of truth |
|
||||
| The managed fleet | Hosts, LXCs, VMs, services under Oikos's governance |
|
||||
| `internal/actuator`, `internal/learning` | Compiled into the System, **not** currently part of its running behavior — see README.md §9.2 |
|
||||
|
||||
**The Model** is [README.md](README.md) in full: nine Views (Mission,
|
||||
Requirements, Functional Architecture, Physical Architecture, Interfaces,
|
||||
Behavior, Verification & Validation, Risk & Safety, Roadmap &
|
||||
Traceability). Per Holt's consistency test (p. 35): *"If there is a set of
|
||||
Views where each View is consistent with all other Views, then it is a
|
||||
Model. If there is a set of Views where each View is not consistent with
|
||||
all other Views, then it is data."* README.md's own cross-referencing
|
||||
(§8.1's kill-switch finding surfaced in §2's requirement, §3's function
|
||||
table, and §9's roadmap alike) is what keeps it a Model rather than nine
|
||||
unrelated documents.
|
||||
|
||||
## 3. The Ontology — Oikos's domain-specific language
|
||||
|
||||
Holt's Ontology has two jobs: it is the vocabulary every Viewpoint's
|
||||
content must be expressed in, and it is what makes Views from different
|
||||
parts of the Model comparable rather than coincidentally similar-looking.
|
||||
Oikos needs this at two levels, and — unusually for a Holt-style
|
||||
exercise — one of them was **already built in code**, not invented for
|
||||
this documentation pass.
|
||||
|
||||
### 3.1 Layer A — the SE meta-ontology (concepts used to talk *about* the Model)
|
||||
|
||||
This is the vocabulary this Framework document and README.md are written
|
||||
in. It is Holt's own vocabulary, restated as a concept diagram rather than
|
||||
prose, per his own example in the book (a Need Description View "visualized
|
||||
using UML Notation — specifically, a Diagram known as the class diagram,
|
||||
where each need is represented as a UML class," p. 38):
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class System
|
||||
class Model {
|
||||
+isSingleSourceOfTruth bool
|
||||
}
|
||||
class View {
|
||||
+stakeholders
|
||||
+value
|
||||
+information
|
||||
}
|
||||
class Viewpoint {
|
||||
+stakeholderQuestion
|
||||
+valueQuestion
|
||||
+informationQuestion
|
||||
}
|
||||
class Notation
|
||||
class Diagram
|
||||
class Ontology
|
||||
class Framework
|
||||
class ProcessSet
|
||||
class Stakeholder
|
||||
|
||||
Model "1" --> "1" System : abstracts
|
||||
Model "1" o-- "many" View : is made up of
|
||||
View ..|> Viewpoint : conforms to
|
||||
View "1" --> "1..many" Diagram : visualized through
|
||||
Diagram "many" --> "1" Notation : belongs to
|
||||
Viewpoint "many" --> "1" Ontology : traces terminology to
|
||||
Framework "1" o-- "1" Ontology : contains
|
||||
Framework "1" o-- "many" Viewpoint : contains
|
||||
Stakeholder "many" --> "many" Viewpoint : interested in
|
||||
```
|
||||
|
||||
### 3.2 Layer B — the Oikos domain ontology (the concepts inside the Views)
|
||||
|
||||
This is the part that already exists as running code, not something this
|
||||
documentation pass invented: `seeds/ontology.yaml` (952 lines, ingested by
|
||||
migration `001_ontology.up.sql` into the `entity_types`/`relationship_types`
|
||||
tables) *is* Holt's Ontology for this System — a machine-enforced
|
||||
domain-specific language that every Signal, Execution, Approval, and Task
|
||||
discussed anywhere in the Model traces back to. The full treatment — all 60
|
||||
entity types, the complete 47-relationship catalog (verified directly
|
||||
against the seed file; [ADR-0014](../adr/0014-entity-model.md) §1/§4
|
||||
records an earlier 2026-07-08 snapshot of 56 types and 34 relationships,
|
||||
since grown), and all six registered lifecycle state machines — is
|
||||
[ontology.md](ontology.md), Viewpoint 11 below. What follows here is the
|
||||
condensed version, sufficient only to make this section's point:
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Entity {
|
||||
+UUID id
|
||||
+string slug
|
||||
+string type
|
||||
+string state
|
||||
}
|
||||
class ComputeEntity
|
||||
class Network
|
||||
class Container
|
||||
class Service
|
||||
class Agent
|
||||
class Signal {
|
||||
+string kind
|
||||
+string severity
|
||||
+string state
|
||||
}
|
||||
class Classification {
|
||||
+string riskClass
|
||||
+string route
|
||||
}
|
||||
class Execution {
|
||||
+string status
|
||||
}
|
||||
class Approval {
|
||||
+string status
|
||||
}
|
||||
class Pattern {
|
||||
+float confidence
|
||||
+string status
|
||||
}
|
||||
class Skill
|
||||
class Task {
|
||||
+string goal
|
||||
+string status
|
||||
+string outcome
|
||||
}
|
||||
class KnowledgeEntity
|
||||
|
||||
Entity <|-- ComputeEntity
|
||||
Entity <|-- Network
|
||||
Entity <|-- Container
|
||||
Entity <|-- Service
|
||||
Entity <|-- Agent
|
||||
Entity <|-- Signal
|
||||
Entity <|-- Classification
|
||||
Entity <|-- Execution
|
||||
Entity <|-- Approval
|
||||
Entity <|-- Pattern
|
||||
Entity <|-- Skill
|
||||
Entity <|-- Task
|
||||
Entity <|-- KnowledgeEntity
|
||||
|
||||
Signal "many" --> "1" Entity : about
|
||||
Classification "1" --> "1" Signal : classifies
|
||||
Classification "1" --> "1" Execution : precedes
|
||||
Execution "many" --> "1" Entity : targets
|
||||
Execution "many" --> "1" Agent : performs
|
||||
Approval "1" --> "1" Execution : decides
|
||||
Task "1" --> "many" Execution : requests via run
|
||||
Task "many" --> "many" Entity : involves
|
||||
KnowledgeEntity "many" --> "many" Entity : about
|
||||
KnowledgeEntity "many" --> "1" Task : outcome_of
|
||||
Pattern "1" --> "many" Execution : informed_by
|
||||
```
|
||||
|
||||
**This is the elegant accident worth naming plainly:** Oikos was not built
|
||||
by someone following Holt's method, yet its own architecture independently
|
||||
arrived at "the domain concepts are an Ontology, ingested once, and
|
||||
everything else traces back to it" — `seeds/ontology.yaml` → DB tables →
|
||||
every entity, signal, execution, and relationship in the system. That is
|
||||
Holt's Ontology principle, implemented as infrastructure rather than as a
|
||||
documentation artifact. The gap is not that the Ontology is missing; it's
|
||||
that, until this document, nothing had stated the correspondence between
|
||||
"the ontology" as oikos's engineers already use the word and "the Ontology"
|
||||
as Holt's MBSE method uses it. They are the same thing, at the domain
|
||||
layer.
|
||||
|
||||
### 3.3 Where Layer A and Layer B meet
|
||||
|
||||
Layer A (SE meta-ontology) is what makes README.md's Views *disciplined* —
|
||||
each one answers Holt's three questions (§4 below). Layer B (the Oikos
|
||||
domain ontology) is what makes README.md's Views *say the same thing
|
||||
consistently* — "risk class," "entity," "signal," and "execution" mean one
|
||||
thing throughout the whole Model because they mean one thing in
|
||||
`seeds/ontology.yaml`, not because nine separately-written documents
|
||||
happened to agree.
|
||||
|
||||
## 4. The Viewpoint Catalog — the Framework's template for Views
|
||||
|
||||
Per Holt (p. 39-40), a Viewpoint stores the answers to three questions —
|
||||
*which Stakeholders, why (what value), what information* — plus a fourth,
|
||||
*what Notation* — so that every View built from it is automatically
|
||||
consistent. Below is that template applied retroactively to each of
|
||||
README.md's nine Views, which is itself a useful audit: a View that can't
|
||||
honestly answer these four questions is not a valid View by Holt's own
|
||||
test (p. 35), and is a candidate for removal.
|
||||
|
||||
| Viewpoint | Which Stakeholders (§1.2) | Why — what value | What information | Notation |
|
||||
|---|---|---|---|---|
|
||||
| **1. Mission & Context** | Operator; future engineers/agents onboarding | Establishes why design choices elsewhere aren't arbitrary; sets the system boundary so later Views don't have to re-litigate scope | Mission statement, stakeholder table, mission drivers, boundary diagram, operational concept | Prose + flowchart |
|
||||
| **2. Requirements** | Operator; anyone implementing against a requirement | Traces every "the system shall" back to a source and forward to an implementation status, so intent and reality can be compared | Requirement ID, statement, source, status, organized by OODA phase + NFRs | Structured table |
|
||||
| **3. Functional Architecture** | Engineers extending decision/execution logic | Prevents the single most expensive mistake in this codebase — extending the wrong package because it has the right name | Function decomposition, function-to-component allocation (expected vs. actual owner), the `run` gate flow, the Task lifecycle | flowchart + allocation table |
|
||||
| **4. Physical Architecture** | Operator deploying/debugging the stack; on-call | Answers "what is actually running and where" independent of what the code *could* do | Component block diagram, deployment topology, trust zones, external couplings | flowchart |
|
||||
| **5. Interfaces (ICD)** | Anyone integrating a new MCP client, or reading/writing the API | A single place to find every tool/route/event without reading source | MCP tool catalog, REST groups, SSE event types, auth model | Tables |
|
||||
| **6. Behavior** | Engineers reasoning about a specific flow (an approval, a task) end to end | State machines and sequences are where "is this actually enforced" questions get answered, not functional prose | Signal/Execution/Approval state machines, `run`-gate sequence, auto-continuation sequence, Task sequence | stateDiagram-v2 + sequenceDiagram |
|
||||
| **7. Verification & Validation** | Operator deciding whether to trust a change; anyone auditing test coverage | Distinguishes "we checked this" from "we assume this" | CI pipeline, evals, health checks as continuous verification, deploy/rollback, explicit list of what's *not* covered | Prose + flowchart |
|
||||
| **8. Risk & Safety** | Operator; anyone reasoning about blast radius of agent autonomy | The single highest-consequence question this Model answers: what actually stops a bad action | Kill-switch gap finding, defense-in-depth layers, threat model, known open gaps, what's structurally guaranteed | Prose + tables |
|
||||
| **9. Roadmap & Traceability** | Operator planning what to fix next; future documentation maintainers | The authoritative status matrix every other View's ✅/⚠/❌ marker derives from | North-star status, code-real/dead-code/schema-only matrix, doc/code divergences, suggested next steps | Tables |
|
||||
| **10. Component** *(repeating Viewpoint — one View per component)* | An engineer about to change a specific package | Prevents extending the wrong implementation of something that exists twice (§9.2's dead-code/live-code pairs), or missing a known issue local to that package | Internal structure (files, key functions, file:line), behavior specific to that component, interfaces it owns, known issues — instantiated once per component in [components.md](components.md) | flowchart + tables |
|
||||
| **11. Ontology** *(repeating Viewpoint — one View per ontology facet)* | An engineer adding/changing an entity or relationship type; anyone checking whether a term used elsewhere in the Model traces back to something real | Is the check against Holt's own biggest MBSE risk (p. 35) applied to the Ontology itself — prevents treating the domain vocabulary as informal prose when it's actually a machine-enforced schema with real transition gates | Entity type hierarchy, relationship catalog, lifecycle state machines with their `requires:` gates, concrete population — instantiated as four Views in [ontology.md](ontology.md) | graph + stateDiagram-v2 + tables |
|
||||
|
||||
**Three things this table makes visible that weren't visible before:**
|
||||
|
||||
1. Every Viewpoint's "why" is stated in terms of a **decision or mistake it
|
||||
prevents**, not merely a topic it covers — closer to Holt's requirement
|
||||
that a View "must add value" (p. 35) than a topic-based table of
|
||||
contents would be.
|
||||
2. Viewpoints 10 and 11 are structurally different from 1-9: each is a
|
||||
**repeating Viewpoint** — one template, instantiated multiple times.
|
||||
Viewpoint 10 produces eight Views, once per component
|
||||
([components.md](components.md)); Viewpoint 11 produces four, once per
|
||||
ontology facet ([ontology.md](ontology.md)). Holt's method doesn't
|
||||
forbid this; a Viewpoint is a template, and nothing says a template can
|
||||
only be used once.
|
||||
3. There is still no Viewpoint in this catalog for "document every class
|
||||
exhaustively regardless of whether anyone asked" — Viewpoints 10 and 11
|
||||
are scoped to named, narrow Stakeholder questions ("an engineer about to
|
||||
change this component," "an engineer adding a new type"), not a blanket
|
||||
documentation mandate.
|
||||
Per Holt's own worked example (the Need Description View, p. 38-39), a
|
||||
collection of information that can't name an interested Stakeholder is
|
||||
not a View; it would just be generated documentation nobody reads,
|
||||
which is the exact failure mode Holt calls out as the biggest risk in
|
||||
adopting MBSE (p. 35).
|
||||
|
||||
## 5. Compliance
|
||||
|
||||
Holt names three categories of best-practice source (p. 46) a Framework
|
||||
can be checked against. Being direct about which apply here and which
|
||||
don't, rather than implying certification that doesn't exist:
|
||||
|
||||
| Category | Holt's examples | Oikos's position |
|
||||
|---|---|---|
|
||||
| **Process-based standards** (how work is done) | ISO 15288 | Not formally adopted. This repo's own process conventions (ADRs, `plans/`, PR review) are the de facto Process Set — informally rigorous, not standards-mapped. |
|
||||
| **Framework-based standards** (what information is produced) | ISO 42010, MODAF, DoDAF, NAF, UAF, Zachman | **Informally aligned, not certified.** This Framework borrows ISO 42010's Viewpoint/View vocabulary (which Holt's own method is built on) but has not been checked against the standard's actual conformance clauses. Say this plainly rather than imply an audit that hasn't happened. |
|
||||
| **Application-based standards** (domain-specific: safety, security, usability) | — | Partially present in spirit: [Risk & Safety](README.md#8-risk--safety) documents a real threat model and known gaps, but there is no adopted external security standard (e.g., no formal threat-modeling framework like STRIDE was used — the threat model in ADR-0007 is bespoke). |
|
||||
|
||||
The honest summary: this Framework's compliance posture is **methodological
|
||||
alignment with ISO 42010's core idea (Stakeholders → concerns → Viewpoints
|
||||
→ Views), not standards certification.** Claiming more than that would
|
||||
itself violate the documentation set's own governing discipline (state
|
||||
verified findings as verified, not aspirational ones as achieved).
|
||||
|
||||
## 6. Tools — Implementation, and its honest limit
|
||||
|
||||
Holt is specific that a good MBSE tool does two things: it *implements the
|
||||
Notation* (enforces SysML's syntax/semantics the way a word processor
|
||||
enforces spelling) and it *implements the Framework* (has the Ontology and
|
||||
Viewpoints "programmed into it" as a profile, p. 44-45).
|
||||
|
||||
Neither is true here, and it matters to say so:
|
||||
|
||||
- **Notation tooling**: Markdown + Mermaid, rendered by GitHub/a Markdown
|
||||
viewer. Mermaid's flowchart/stateDiagram/sequenceDiagram/classDiagram
|
||||
grammars are enforced (a malformed diagram fails to render — as
|
||||
happened once already in this documentation effort and was fixed), but
|
||||
there is no semantic check that, say, a state machine diagram in
|
||||
[§6](README.md#6-behavior) actually matches the Go code's real
|
||||
transitions. That check was done by hand, once, for this pass — it will
|
||||
drift the moment the code changes and nobody re-verifies it.
|
||||
- **Framework tooling**: there is no tool with this Ontology or these
|
||||
Viewpoints "programmed in." Nothing prevents a future edit to README.md
|
||||
from adding a View that fails Holt's three-question test, or from
|
||||
introducing a term that doesn't trace back to `seeds/ontology.yaml`.
|
||||
The only enforcement mechanism is a human (or an agent) re-reading this
|
||||
Framework document before extending the Model — which is precisely why
|
||||
this document needed to exist as a separate, explicit artifact rather
|
||||
than staying implicit in how README.md happened to get organized.
|
||||
|
||||
## 7. Process Set — how this Framework is developed and used
|
||||
|
||||
Holt separates Framework (what) from Process Set (how) specifically so
|
||||
that different projects can share one Framework under different levels of
|
||||
rigor (p. 41-42). For this repository, the Process Set is:
|
||||
|
||||
1. **Establishing a new Viewpoint**: propose it here in §4, answering all
|
||||
four questions before writing the View it justifies. If it can't answer
|
||||
them, per Holt's own rule (p. 35), it doesn't get written.
|
||||
2. **Extending the Ontology**: changes to `seeds/ontology.yaml` are the
|
||||
authoritative act — this document's §3.2 is a description of that file,
|
||||
not an independent source, and must be re-derived from it if it drifts.
|
||||
3. **Updating a View**: per README.md's own closing section ("Keeping this
|
||||
model current"), a code change updates the View whose Viewpoint claims
|
||||
that information, and — if it resolves or introduces a finding in
|
||||
[§9 Roadmap & Traceability](README.md#9-roadmap--traceability) — that
|
||||
matrix is updated in the same pass.
|
||||
4. **Compliance review**: informal, human-in-the-loop (§5) — there is no
|
||||
scheduled re-audit; drift is caught opportunistically, the same way the
|
||||
kill-switch gap in [§8.1](README.md#81-the-kill-switch-gap-verified-most-important-finding-in-this-model)
|
||||
was caught by direct verification during a documentation pass rather
|
||||
than by a standing process designed to catch it.
|
||||
|
||||
## 8. Relationship between this Framework and the Model
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
ONT["Ontology \n seeds and ADR-0014"] --> FW["Framework \n this document"]
|
||||
VP["Viewpoint catalog \n Section 4 of this document"] --> FW
|
||||
FW --> MDL["Model \n README.md, Viewpoints 1 to 9"]
|
||||
FW --> CV["Model \n components.md, Viewpoint 10\nrepeated per component"]
|
||||
FW --> OV["Model \n ontology.md, Viewpoint 11\nrepeated per ontology facet"]
|
||||
MDL --> V1["View 1..9"]
|
||||
CV --> V2["View 10a..10h"]
|
||||
OV --> V3["View 11a..11d"]
|
||||
```
|
||||
|
||||
Read [README.md](README.md) for the Model's concern-based Views,
|
||||
[components.md](components.md) for its component-based Views, and
|
||||
[ontology.md](ontology.md) for the Ontology's own full treatment (the
|
||||
sketch in §3 above is deliberately condensed). Read this document when you
|
||||
are deciding whether a new View belongs in any of the three, when a term in
|
||||
the Model feels like it's drifted from what `seeds/ontology.yaml` actually
|
||||
defines, or when onboarding someone who needs to understand not just *what
|
||||
the system is* but *why this documentation is shaped the way it is*.
|
||||
444
docs/mbse/ontology.md
Normal file
444
docs/mbse/ontology.md
Normal file
@@ -0,0 +1,444 @@
|
||||
# Oikos — Ontology Views
|
||||
|
||||
> Companion to [the Framework](framework.md), [the Model](README.md), and
|
||||
> [the Component Views](components.md). Holt calls Ontology "arguably the
|
||||
> single most important part of MBSE, as all of the other elements that
|
||||
> make up MBSE are ultimately traceable back to [it]" (*Systems Engineering
|
||||
> Demystified*, 2nd ed., Ch. 2, p. 40). [framework.md §3](framework.md#3-the-ontology--oikoss-domain-specific-language)
|
||||
> sketched this in condensed form (13 classes) to make one point: Oikos's
|
||||
> domain ontology already exists as running code, not documentation. This
|
||||
> document is the fuller treatment that sketch promised — a repeating
|
||||
> Viewpoint (registered as Viewpoint 11 in
|
||||
> [framework.md §4](framework.md#4-the-viewpoint-catalog--the-frameworks-template-for-views)),
|
||||
> instantiated as four Views below.
|
||||
|
||||
**Every fact in this document was read directly from `seeds/ontology.yaml`
|
||||
during this pass** (not carried over from ADR-0014's summary, though it is
|
||||
cross-checked against it) — where the two disagree, that disagreement is
|
||||
itself reported as a finding, not silently reconciled.
|
||||
|
||||
**Stakeholders for all four Views below:** engineers adding a new entity or
|
||||
relationship type, anyone reasoning about whether a lifecycle transition is
|
||||
actually gated or just documented, and anyone deciding whether a term used
|
||||
elsewhere in this documentation set means what they think it means.
|
||||
**Why they earn their place:** every Viewpoint in
|
||||
[framework.md §4](framework.md#4-the-viewpoint-catalog--the-frameworks-template-for-views)
|
||||
"traces terminology to the Ontology" (§3.1 of that document) — these four
|
||||
Views are where that tracing actually terminates. **Notation:** tables
|
||||
(the source data), Mermaid `graph`/`stateDiagram-v2` (the structure).
|
||||
|
||||
## Contents
|
||||
|
||||
| View | Answers |
|
||||
|---|---|
|
||||
| [11a. Entity Type Hierarchy](#11a-entity-type-hierarchy) | What can exist, and how is it classified? |
|
||||
| [11b. Relationship Catalog](#11b-relationship-catalog) | How can two entities be connected, and with what multiplicity? |
|
||||
| [11c. Lifecycle State Machines](#11c-lifecycle-state-machines) | What states can a governed entity be in, and what gates each transition? |
|
||||
| [11d. Concrete Population](#11d-concrete-population) | What's actually instantiated, versus merely possible? |
|
||||
|
||||
---
|
||||
|
||||
## 11a. Entity Type Hierarchy
|
||||
|
||||
60 entity types, 5 abstract (cannot be instantiated directly — they exist
|
||||
only as polymorphic relationship endpoints and `is-a` parents), organized
|
||||
by `domain:` (8 values) and `layer:` (4 values: meta, infrastructure,
|
||||
governance, cognition).
|
||||
|
||||
| Layer | Domains it contains | Entity type count |
|
||||
|---|---|---|
|
||||
| `meta` | meta | 1 (`entity`, the abstract root) |
|
||||
| `infrastructure` | physical, compute, network, storage, software, external | 40 |
|
||||
| `governance` | identity | 7 |
|
||||
| `cognition` | cognition | 12 |
|
||||
|
||||
| Domain | Count | Abstract types in this domain |
|
||||
|---|---|---|
|
||||
| compute | 11 | `compute-entity`, `machine`, `container` |
|
||||
| network | 10 | `network` |
|
||||
| cognition | 12 | *(none)* |
|
||||
| identity | 7 | *(none)* |
|
||||
| software | 7 | *(none)* |
|
||||
| external | 4 | *(none)* |
|
||||
| storage | 4 | *(none)* |
|
||||
| physical | 4 | *(none)* |
|
||||
| meta | 1 | `entity` |
|
||||
|
||||
One 60-node diagram doesn't fit on a screen and, worse, tempts you to fall
|
||||
back on subgraph grouping instead of explicit edges for the flatter
|
||||
domains — which is what an earlier version of this section did: most leaf
|
||||
types were boxed together visually but had no drawn `-->` from `entity` at
|
||||
all. Split by domain instead, every type below has an explicit parent
|
||||
edge — nothing is implied by proximity alone.
|
||||
|
||||
### Layer overview
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
entity["entity — abstract root\nlayer: meta"] --> INFRA["infrastructure layer\n40 types — physical, compute, network,\nstorage, software, external"]
|
||||
entity --> GOV["governance layer\n7 types — identity domain"]
|
||||
entity --> COG["cognition layer\n12 types"]
|
||||
```
|
||||
|
||||
### Domain: physical (4 types, all flat)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
entity["entity"] --> site
|
||||
entity --> ups
|
||||
entity --> sensor
|
||||
entity --> peripheral
|
||||
```
|
||||
|
||||
### Domain: compute (11 types — the deepest nesting in the Ontology)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
entity["entity"] --> ce["compute-entity — abstract"]
|
||||
entity --> hypervisor
|
||||
ce --> machine["machine — abstract"]
|
||||
ce --> vm
|
||||
ce --> container["container — abstract"]
|
||||
machine --> proxmoxhost["proxmox-host"]
|
||||
machine --> standalone["standalone-server"]
|
||||
machine --> workstation
|
||||
machine --> appliance
|
||||
container --> lxc
|
||||
container --> dockercontainer["docker-container"]
|
||||
```
|
||||
|
||||
### Domain: network (10 types)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
entity["entity"] --> net["network — abstract"]
|
||||
entity --> netiface["network-interface"]
|
||||
entity --> dnszone["dns-zone"]
|
||||
entity --> dnsrecord["dns-record"]
|
||||
entity --> ingress["ingress-route"]
|
||||
entity --> certificate
|
||||
entity --> firewallrule["firewall-rule"]
|
||||
net --> lan
|
||||
net --> mesh
|
||||
net --> vlan
|
||||
```
|
||||
|
||||
### Domain: storage (4 types, all flat)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
entity["entity"] --> storagepool["storage-pool"]
|
||||
entity --> volume
|
||||
entity --> backuptarget["backup-target"]
|
||||
entity --> dataset
|
||||
```
|
||||
|
||||
`storage-pool`/`volume`/`dataset` look like they should nest (a pool
|
||||
*contains* volumes, a volume *holds* datasets) — they don't, in the type
|
||||
hierarchy. That containment is a **relationship** (`contains`,
|
||||
`holds-dataset`, [§11b](#11b-relationship-catalog)), not an `is-a` parent.
|
||||
Worth stating plainly since the two are easy to conflate: `parent:` says
|
||||
"this is a kind of that"; a relationship says "this instance is connected
|
||||
to that instance." Storage is the domain where the difference is most
|
||||
visible.
|
||||
|
||||
### Domain: software (7 types, all flat)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
entity["entity"] --> service
|
||||
entity --> application
|
||||
entity --> configrepo["config-repo"]
|
||||
entity --> deploypipeline["deploy-pipeline"]
|
||||
entity --> packageset["package-set"]
|
||||
entity --> cluster
|
||||
entity --> composestack["compose-stack"]
|
||||
```
|
||||
|
||||
### Domain: external (4 types, all flat)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
entity["entity"] --> domainreg["domain-registration"]
|
||||
entity --> cloudservice["cloud-service"]
|
||||
entity --> isplink["isp-link"]
|
||||
entity --> vendordep["vendor-dependency"]
|
||||
```
|
||||
|
||||
### Domain: identity (governance layer, 7 types, all flat)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
entity["entity"] --> person
|
||||
entity --> agent
|
||||
entity --> idp["identity-provider"]
|
||||
entity --> account
|
||||
entity --> secret
|
||||
entity --> key
|
||||
entity --> accessgrant["access-grant"]
|
||||
```
|
||||
|
||||
### Domain: cognition (12 types, all flat — the domain the agent's own logic runs on)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
entity["entity"] --> check
|
||||
entity --> signal
|
||||
entity --> classification
|
||||
entity --> execution
|
||||
entity --> feedback
|
||||
entity --> pattern
|
||||
entity --> skill
|
||||
entity --> approval
|
||||
entity --> document
|
||||
entity --> runbook
|
||||
entity --> investigation
|
||||
entity --> task["task — added after ADR-0014"]
|
||||
```
|
||||
|
||||
Every one of the 60 types above is a direct or indirect child of `entity`;
|
||||
none is disconnected. The full flat list — every type with its exact
|
||||
`parent:` — lives in `seeds/ontology.yaml` directly; reproducing all 60
|
||||
rows as a table here would duplicate this section rather than clarify it.
|
||||
|
||||
**Finding: `task` is new since ADR-0014.** ADR-0014 (2026-07-08) documents
|
||||
56 entity types under a hierarchy diagram that does not include `task` —
|
||||
four fewer than the 60 verified here, meaning more than just `task` was
|
||||
added in the interim (`task` accounts for one of the four) —
|
||||
[README.md §1.5](README.md#15-operational-concept--the-ooda-loop) and
|
||||
[framework.md §2](framework.md#2-the-goal--system-and-model-made-concrete)
|
||||
both describe the Task model as a 2026-07-11 addition
|
||||
(`plans/done/2026-07-11-goal-oriented-chat-control-panel.md`), after
|
||||
ADR-0014 was written. `task` is now entity type #60, `domain: cognition`,
|
||||
`layer: cognition`, described in the seed as *"A goal-structured unit of
|
||||
agent work — one chat/session elevated to a task with a plan, lifecycle
|
||||
status, and outcome."* This is exactly what Holt's Ontology principle
|
||||
predicts: a new concept in the Model
|
||||
([the Task lifecycle View](README.md#34-functional-flow--the-task-lifecycle-f3f4f5-packaged-for-a-human))
|
||||
required a new term in the Ontology before it could be modeled
|
||||
consistently — and the term was in fact added, not left implicit.
|
||||
|
||||
## 11b. Relationship Catalog
|
||||
|
||||
**47 relationship types**, each with a fixed `source → target` type pair
|
||||
and a cardinality. This is the complete, current catalog — not the
|
||||
5 illustrative example-graphs ADR-0014 used to gesture at a smaller set.
|
||||
|
||||
**Finding: this catalog has grown since ADR-0014.** ADR-0014 (2026-07-08)
|
||||
titles its equivalent section "The Edge Catalog (34 edges)." Verified
|
||||
directly against `seeds/ontology.yaml` during this pass: **47** relationship
|
||||
types exist today — 13 more than ADR-0014 recorded. This is expected drift
|
||||
over an 8-day span of active development (the Task model alone plausibly
|
||||
added `involves`; `part-of` supports the `compose-stack` grouping), not a
|
||||
documentation error — ADR-0014 is a point-in-time record and is not edited
|
||||
after acceptance, per this repo's own convention
|
||||
([docs/adr/README.md](../adr/README.md)). It is reported here so nobody
|
||||
treats ADR-0014's count as current.
|
||||
|
||||
| Relationship | Source → Target | Cardinality |
|
||||
|---|---|---|
|
||||
| `hosts` | machine → compute-entity | one-to-many |
|
||||
| `runs-hypervisor` | machine → hypervisor | one-to-one |
|
||||
| `member-of` | proxmox-host → cluster | many-to-one |
|
||||
| `part-of` | docker-container → compose-stack | many-to-one |
|
||||
| `provides` | compute-entity → service | one-to-many |
|
||||
| `runs` | service → application | one-to-many |
|
||||
| `configured-by` | entity → config-repo | many-to-one |
|
||||
| `deploys-to` | deploy-pipeline → entity | many-to-one |
|
||||
| `routes-to` | ingress-route → service | many-to-one |
|
||||
| `secured-by` | ingress-route → identity-provider | many-to-one |
|
||||
| `uses-certificate` | ingress-route → certificate | many-to-one |
|
||||
| `authenticates-via` | service → identity-provider | many-to-one |
|
||||
| `in-zone` | dns-record → dns-zone | many-to-one |
|
||||
| `resolves-to` | dns-record → entity | many-to-one |
|
||||
| `depends-on` | service → service | many-to-many |
|
||||
| `connects-via` | compute-entity → network | many-to-many |
|
||||
| `has-interface` | compute-entity → network-interface | one-to-many |
|
||||
| `interface-on` | network-interface → network | many-to-one |
|
||||
| `mounts` | compute-entity → volume | many-to-many |
|
||||
| `stores-on` | compute-entity → storage-pool | many-to-many |
|
||||
| `contains` | storage-pool → volume | one-to-many |
|
||||
| `holds-dataset` | volume → dataset | one-to-many |
|
||||
| `backs-up-to` | entity → backup-target | many-to-many |
|
||||
| `powered-by` | machine → ups | many-to-one |
|
||||
| `located-at` | machine → site | many-to-one |
|
||||
| `registered-with` | domain-registration → vendor-dependency | many-to-one |
|
||||
| `owns` | person → agent | one-to-many |
|
||||
| `authenticates` | identity-provider → person | one-to-many |
|
||||
| `holds-grant` | agent → access-grant | one-to-many |
|
||||
| `grants` | access-grant → secret | many-to-one |
|
||||
| `can-decrypt` | compute-entity → secret | many-to-many |
|
||||
| `checks` | check → entity | many-to-one |
|
||||
| `raises` | check → signal | one-to-many |
|
||||
| `about` | entity → entity | many-to-many |
|
||||
| `classifies` | classification → signal | many-to-one |
|
||||
| `precedes` | classification → execution | one-to-one |
|
||||
| `targets` | execution → entity | many-to-one |
|
||||
| `requires-approval` | execution → approval | one-to-one |
|
||||
| `performs` | agent → execution | one-to-many |
|
||||
| `decides` | person → approval | one-to-many |
|
||||
| `produces` | execution → feedback | one-to-one |
|
||||
| `contributes-to` | feedback → pattern | many-to-many |
|
||||
| `informs` | pattern → skill | many-to-one |
|
||||
| `guides` | skill → classification | one-to-many |
|
||||
| `documents` | document → entity | many-to-one |
|
||||
| `involves` | task → entity | many-to-many |
|
||||
| `procedure-for` | runbook → entity | many-to-many |
|
||||
|
||||
Grouped by theme, the same 47 rows read as five coherent sub-ontologies —
|
||||
this is the grouping ADR-0014 used, now complete rather than illustrative:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph Cognition["Cognition — the OODA edges"]
|
||||
CK["check"] -->|raises| SG["signal"]
|
||||
CK -->|checks| EN["entity"]
|
||||
CL["classification"] -->|classifies| SG
|
||||
CL -->|precedes| EX["execution"]
|
||||
EX -->|targets| EN
|
||||
EX -->|requires-approval| AP["approval"]
|
||||
AG["agent"] -->|performs| EX
|
||||
PR["person"] -->|decides| AP
|
||||
EX -->|produces| FB["feedback"]
|
||||
FB -->|contributes-to| PT["pattern"]
|
||||
PT -->|informs| SK["skill"]
|
||||
SK -->|guides| CL
|
||||
TK["task"] -->|involves| EN
|
||||
DC["document"] -->|documents| EN
|
||||
RB["runbook"] -->|procedure-for| EN
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph Governance["Governance — identity and access"]
|
||||
P["person"] -->|owns| A["agent"]
|
||||
IDP["identity-provider"] -->|authenticates| P
|
||||
A -->|holds-grant| AG["access-grant"]
|
||||
AG -->|grants| S["secret"]
|
||||
CE["compute-entity"] -->|can-decrypt| S
|
||||
end
|
||||
```
|
||||
|
||||
The remaining three groups (Infrastructure Topology, Network, Service
|
||||
Dependencies) are unchanged in shape from
|
||||
[ADR-0014 §4](../adr/0014-entity-model.md) — that ADR's diagrams for those
|
||||
three are still an accurate illustrative subset of the table above; only
|
||||
the Cognition and Governance groups gained new edges (`involves`,
|
||||
`part-of`) worth re-drawing.
|
||||
|
||||
## 11c. Lifecycle State Machines
|
||||
|
||||
Six lifecycles are formally registered in `seeds/ontology.yaml`'s
|
||||
`lifecycles:` block, each a named state machine with `states`,
|
||||
`default_state`, `terminal_states`, and per-transition `requires:` — named
|
||||
checks that [internal/ontology](../../internal/ontology) implements in Go.
|
||||
This is the mechanism, not just the diagram: a transition without a
|
||||
satisfied `requires:` check is refused at the code level, for these six
|
||||
types.
|
||||
|
||||
**Refinement to README.md's Behavior view.** The `execution` lifecycle as
|
||||
registered here has **13 states**, including a `verifying` state distinct
|
||||
from `executing`, and a recovery transition `timed_out → verifying`
|
||||
("check if the command completed anyway" — the seed's own comment).
|
||||
[README.md §6.2](README.md#62-execution-state-machine--schema-defined-convention-enforced)'s
|
||||
Execution state diagram previously omitted `verifying` as a separate
|
||||
state and has been corrected there to match; the diagram below is the
|
||||
ontology-accurate version and the two now agree.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> proposed
|
||||
proposed --> approved: operator-approval
|
||||
proposed --> auto_approved: autonomy-allows
|
||||
proposed --> denied
|
||||
approved --> executing: approval-token-valid
|
||||
approved --> expired: approval-ttl-elapsed
|
||||
auto_approved --> executing
|
||||
executing --> verified: verification-passed
|
||||
executing --> failed
|
||||
executing --> timed_out
|
||||
executing --> cancelled: operator-abort
|
||||
timed_out --> verifying: check if it finished anyway
|
||||
verifying --> verified: verification-passed
|
||||
verifying --> failed
|
||||
failed --> rolled_back: rollback-procedure-exists
|
||||
failed --> rollback_failed
|
||||
verified --> [*]
|
||||
denied --> [*]
|
||||
expired --> [*]
|
||||
cancelled --> [*]
|
||||
rolled_back --> [*]
|
||||
rollback_failed --> [*]
|
||||
```
|
||||
|
||||
The other five, with their `requires:` gates named explicitly (abbreviated
|
||||
where a transition has no requirement):
|
||||
|
||||
| Lifecycle | States | Terminal | Notable gated transition |
|
||||
|---|---|---|---|
|
||||
| `infrastructure` | planned, provisioning, active, migrating, failed, deprecated, destroyed | destroyed | `deprecated → destroyed` requires **five** checks at once: `backups-verified`, `secrets-revoked-and-rekeyed`, `ingress-and-dns-removed`, `no-inbound-edges`, `archaeology-entry` — the strictest single transition in the entire Ontology |
|
||||
| `signal` | raised, acknowledged, acting, muted, resolved, failed | resolved | `acknowledged → acting` requires `classification-exists` — the formal link between Orient and Decide, real in the Ontology even though [Roadmap §9.2](README.md#92-code-real--dead-code--schema-only-matrix) finds the classifier that would create that classification is dead code |
|
||||
| `approval` | pending, approved, denied, expired, revoked | denied, expired, revoked | `pending → approved` requires `token-verified`; `approved → revoked` requires `not-yet-executing` — you cannot revoke an approval whose action has already started |
|
||||
| `pattern` | hypothesized, validated, active, deprecated, invalidated | deprecated, invalidated | `hypothesized → validated` requires `evidence-count-5plus` **and** `confidence-0.7plus` jointly — matches [ADR-0006](../adr/0006-learning-proposal-only.md)'s Wilson-bound description exactly; `validated → active` requires `operator-approval`, annotated in the seed itself as *"S4: never automatic"* |
|
||||
| `skill` | drafted, tested, active, refined, failed, deprecated | deprecated | `tested → active` and `refined → active` both require `operator-approval` — a skill can be authored and tested autonomously but never self-promotes to active |
|
||||
|
||||
**Finding: `task` has no registered lifecycle.** The `task` entity type
|
||||
(§11a) has a real, documented behavior —
|
||||
[README.md §3.4](README.md#34-functional-flow--the-task-lifecycle-f3f4f5-packaged-for-a-human)
|
||||
shows `planning → awaiting_approval → executing ⇄ awaiting_input → done`/`failed`
|
||||
as a state diagram, and it is enforced in application code (the
|
||||
`agent_sessions.status` column, checked in `cmd/nomos`). But
|
||||
`seeds/ontology.yaml`'s `lifecycles:` block registers only the six
|
||||
machines above — there is no `task:` entry alongside `infrastructure`,
|
||||
`signal`, `execution`, `approval`, `pattern`, `skill`. Practically: the
|
||||
five other governed types get their transition-gating for free from the
|
||||
shared `internal/ontology` machinery (per named `requires:` checks); the
|
||||
Task lifecycle is instead hand-coded in `cmd/nomos`'s Go logic, a
|
||||
structurally different (and unaudited-by-the-shared-mechanism) enforcement
|
||||
path for what is, in every other respect, a first-class Ontology citizen.
|
||||
This is a gap worth a deliberate decision — register `task` formally, or
|
||||
document explicitly that Task's lifecycle is intentionally
|
||||
application-layer rather than Ontology-layer — not an oversight this
|
||||
document is fixing by writing it down.
|
||||
|
||||
## 11d. Concrete Population
|
||||
|
||||
What's actually instantiated versus merely possible in the type system —
|
||||
per [ADR-0014](../adr/0014-entity-model.md) §1, **not independently
|
||||
re-counted against the live database during this pass** (that would
|
||||
require DB access this documentation effort didn't use; the figures below
|
||||
are ADR-0014's, dated 2026-07-08, and should be treated as illustrative of
|
||||
shape rather than a current census):
|
||||
|
||||
| Type | Count (as of ADR-0014) | Examples |
|
||||
|---|---|---|
|
||||
| `lxc` | 19 | jellyfin, caddy, dns, gitea, nextcloud, matrix |
|
||||
| `service` | 25 | caddy, authentik, dns, jellyfin, paperless, matrix |
|
||||
| `ingress-route` | 21 | `*.hubris.network` |
|
||||
| `config-repo` | 6 | caddy-conf, gitea-customizations, mule-image |
|
||||
| `proxmox-host` | 2 | hubris, strong |
|
||||
| `workstation` | 2 | mac-mini, republic-laptop |
|
||||
| `standalone-server` | 1 | netbird-vps |
|
||||
| `vm` | 2 | zimaos, haos |
|
||||
| `storage-pool` | 3 | local-lvm-hubris, library-hubris, ludo-lvm |
|
||||
| `volume` | 2 | library, media-local |
|
||||
|
||||
**Why this View matters despite being the least current one here:** it is
|
||||
the check against over-abstraction Holt warns about (p. 35) — an Ontology
|
||||
with 60 types and 47 relationships is only worth having if real entities
|
||||
actually populate a meaningful fraction of it. 88 active entities across
|
||||
roughly a dozen concrete types (out of 55 non-abstract types) is a
|
||||
reasonable population for a homelab of this size; a future re-audit of this
|
||||
specific View is a cheap, well-scoped follow-up (query `entities GROUP BY
|
||||
type`) that this pass explicitly did not do, rather than silently assuming
|
||||
ADR-0014's numbers still hold.
|
||||
|
||||
## Keeping this document current
|
||||
|
||||
Re-derive §11a-11c directly from `seeds/ontology.yaml` whenever it changes
|
||||
— these three Views are transcriptions of that file's structure, not
|
||||
independent judgment, so they go stale the moment the file changes and
|
||||
nobody re-runs the extraction. §11d is the one View here that was already
|
||||
known to be a point-in-time snapshot when written; re-verify it against
|
||||
live DB state before relying on it for a capacity or audit decision.
|
||||
Reference in New Issue
Block a user