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>
510 lines
27 KiB
Markdown
510 lines
27 KiB
Markdown
# 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.
|