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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 00:17:15 +02:00

1637 lines
59 KiB
YAML

openapi: 3.1.0
info:
title: Oikos API
version: 1.0.0
contact: {name: dtoro}
license: {name: Private, identifier: LicenseRef-Private}
description: |
Control-plane API for the Oikos homelab OS. This file is the **source of
truth** (contract-first): Go server stubs are generated with oapi-codegen,
clients (homelab CLI, future UIs) from the same spec.
Conventions (plan R3-3):
- Errors are RFC 9457 `application/problem+json`.
- Lists use `{items, next_cursor}` with cursor pagination (default limit 50, max 200).
- Unsafe POSTs accept `Idempotency-Key` (24h replay window).
- Mutable resources carry `version`; GET returns `ETag`, PATCH requires `If-Match` (412 on mismatch).
- Timestamps are RFC 3339 UTC.
- Entities are addressable by UUID or slug (`host:hubris`).
The MCP interface (streamable HTTP, official Go SDK) is mounted at `/mcp`
on the same binary and is out of scope for this document; its tools wrap
the same service layer as these endpoints.
servers:
- url: /api/v1
tags:
- name: entities
description: "Inventory graph — entities and relationships"
- name: ontology
description: "Entity types, relationship types, lifecycles"
- name: signals
description: "Signals and checks (observe)"
- name: executions
description: "Executions, classifications, approvals (decide/act)"
- name: learning
description: "Patterns and skills (learn)"
- name: policy
description: "Risk classes, approval rules, autonomy"
- name: knowledge
description: "Knowledge graph search"
- name: observability
description: "Metrics, trends, audit, events, health"
- name: system
description: "Export, health"
security:
- bearerAuth: []
paths:
# ─── Entities ─────────────────────────────────────────────────────
/entities:
get:
tags: [entities]
operationId: listEntities
summary: List entities
x-required-scope: viewer
parameters:
- {name: type, in: query, schema: {type: string}, description: Filter by entity type (includes descendants)}
- {name: state, in: query, schema: {type: string}}
- {name: domain, in: query, schema: {type: string}}
- {name: layer, in: query, schema: {type: string}}
- {name: q, in: query, schema: {type: string}, description: Substring match on slug/name}
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Entity list
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Entity'}}
next_cursor: {type: [string, "null"]}
default: {$ref: '#/components/responses/Problem'}
post:
tags: [entities]
operationId: createEntity
summary: Create an entity
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/EntityCreate'}
responses:
'201':
description: Created
headers: {ETag: {$ref: '#/components/headers/ETag'}}
content:
application/json:
schema: {$ref: '#/components/schemas/Entity'}
default: {$ref: '#/components/responses/Problem'}
/entities/{id}:
parameters: [{$ref: '#/components/parameters/EntityId'}]
get:
tags: [entities]
operationId: getEntity
summary: Get entity by UUID or slug
x-required-scope: viewer
responses:
'200':
description: Entity detail
headers: {ETag: {$ref: '#/components/headers/ETag'}}
content:
application/json:
schema: {$ref: '#/components/schemas/Entity'}
default: {$ref: '#/components/responses/Problem'}
patch:
tags: [entities]
operationId: patchEntity
summary: Update attributes or transition lifecycle state
description: |
Lifecycle transitions are validated against the type's lifecycle_def;
illegal transitions return 409 (invalid-transition). Policy-gated
actions may return 403 (approval-required).
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IfMatch'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/EntityPatch'}
responses:
'200':
description: Updated entity
headers: {ETag: {$ref: '#/components/headers/ETag'}}
content:
application/json:
schema: {$ref: '#/components/schemas/Entity'}
default: {$ref: '#/components/responses/Problem'}
/entities/{id}/relations:
parameters: [{$ref: '#/components/parameters/EntityId'}]
get:
tags: [entities]
operationId: getEntityRelations
summary: Direct relationships of an entity (both directions)
x-required-scope: viewer
parameters:
- {name: rel_type, in: query, schema: {type: string}}
- {name: direction, in: query, schema: {type: string, enum: [out, in, both], default: both}}
responses:
'200':
description: Relationships
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Relationship'}}
default: {$ref: '#/components/responses/Problem'}
/relationships:
post:
tags: [entities]
operationId: createRelationship
summary: Create a relationship edge
description: Endpoint types validated against relationship_types (hierarchy-aware); cardinality enforced.
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/RelationshipCreate'}
responses:
'201':
description: Created
content:
application/json:
schema: {$ref: '#/components/schemas/Relationship'}
default: {$ref: '#/components/responses/Problem'}
delete:
tags: [entities]
operationId: endRelationship
summary: End a relationship (sets valid_to; the edge is kept for history)
x-required-scope: operator
parameters:
- {name: source, in: query, required: true, schema: {type: string}}
- {name: target, in: query, required: true, schema: {type: string}}
- {name: rel_type, in: query, required: true, schema: {type: string}}
responses:
'204': {description: Relationship ended}
default: {$ref: '#/components/responses/Problem'}
/graph:
get:
tags: [entities]
operationId: getGraph
summary: Subgraph for visualization (nodes + edges)
x-required-scope: viewer
parameters:
- {name: root, in: query, schema: {type: string}, description: Start entity (UUID or slug); omit for whole graph (capped)}
- {name: depth, in: query, schema: {type: integer, default: 2, maximum: 5}}
- {name: rel_type, in: query, schema: {type: array, items: {type: string}}, style: form, explode: true}
responses:
'200':
description: Graph view
content:
application/json:
schema: {$ref: '#/components/schemas/GraphView'}
default: {$ref: '#/components/responses/Problem'}
/entities/{id}/blast-radius:
parameters: [{$ref: '#/components/parameters/EntityId'}]
get:
tags: [entities]
operationId: getBlastRadius
summary: Entities affected if this entity fails
x-required-scope: viewer
parameters:
- {name: depth, in: query, schema: {type: integer, default: 3, maximum: 5}}
responses:
'200':
description: Affected entities with graph distance
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items:
type: object
required: [entity, depth]
properties:
entity: {$ref: '#/components/schemas/Entity'}
depth: {type: integer}
default: {$ref: '#/components/responses/Problem'}
# ─── Ontology ─────────────────────────────────────────────────────
/ontology:
get:
tags: [ontology]
operationId: getOntology
summary: Full ontology — entity types, relationship types, lifecycles
x-required-scope: viewer
responses:
'200':
description: Ontology
content:
application/json:
schema:
type: object
required: [entity_types, relationship_types, lifecycles]
properties:
entity_types: {type: array, items: {$ref: '#/components/schemas/EntityType'}}
relationship_types: {type: array, items: {$ref: '#/components/schemas/RelationshipType'}}
lifecycles: {type: array, items: {$ref: '#/components/schemas/LifecycleDef'}}
default: {$ref: '#/components/responses/Problem'}
/ontology/entity-types:
post:
tags: [ontology]
operationId: createEntityType
summary: Extend the ontology with a new entity type
description: Policy-gated as config_mutation (creates a meta-approval when required).
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/EntityTypeCreate'}
responses:
'201':
description: Created
content:
application/json:
schema: {$ref: '#/components/schemas/EntityType'}
'202': {$ref: '#/components/responses/PendingApproval'}
default: {$ref: '#/components/responses/Problem'}
/ontology/entity-types/{name}:
parameters:
- {name: name, in: path, required: true, schema: {type: string}}
patch:
tags: [ontology]
operationId: patchEntityType
summary: Update or deprecate an entity type
description: Hard delete is not supported while instances exist — deprecate instead (plan D3).
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IfMatch'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/EntityTypePatch'}
responses:
'200':
description: Updated
content:
application/json:
schema: {$ref: '#/components/schemas/EntityType'}
'202': {$ref: '#/components/responses/PendingApproval'}
default: {$ref: '#/components/responses/Problem'}
# ─── Signals + checks ─────────────────────────────────────────────
/signals:
get:
tags: [signals]
operationId: listSignals
summary: List signals
x-required-scope: viewer
parameters:
- {name: state, in: query, schema: {type: string}}
- {name: severity, in: query, schema: {type: string, enum: [info, warning, critical]}}
- {name: entity_id, in: query, schema: {type: string}}
- {name: kind, in: query, schema: {type: string}}
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Signals
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Signal'}}
next_cursor: {type: [string, "null"]}
default: {$ref: '#/components/responses/Problem'}
/signals/{id}/ack:
parameters: [{$ref: '#/components/parameters/EntityId'}]
post:
tags: [signals]
operationId: ackSignal
summary: Acknowledge a signal
x-required-scope: operator
responses:
'200': {$ref: '#/components/responses/SignalUpdated'}
default: {$ref: '#/components/responses/Problem'}
/signals/{id}/resolve:
parameters: [{$ref: '#/components/parameters/EntityId'}]
post:
tags: [signals]
operationId: resolveSignal
summary: Resolve a signal manually
x-required-scope: operator
requestBody:
content:
application/json:
schema:
type: object
properties: {note: {type: string}}
responses:
'200': {$ref: '#/components/responses/SignalUpdated'}
default: {$ref: '#/components/responses/Problem'}
/signals/{id}/mute:
parameters: [{$ref: '#/components/parameters/EntityId'}]
post:
tags: [signals]
operationId: muteSignal
summary: Mute a signal for a TTL
x-required-scope: operator
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [mute_until]
properties:
mute_until: {type: string, format: date-time}
note: {type: string}
responses:
'200': {$ref: '#/components/responses/SignalUpdated'}
default: {$ref: '#/components/responses/Problem'}
/checks:
get:
tags: [signals]
operationId: listChecks
summary: List check definitions
x-required-scope: viewer
parameters:
- {name: kind, in: query, schema: {type: string}}
- {name: target, in: query, schema: {type: string}}
- {name: enabled, in: query, schema: {type: boolean}}
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Checks
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Check'}}
next_cursor: {type: [string, "null"]}
default: {$ref: '#/components/responses/Problem'}
post:
tags: [signals]
operationId: createCheck
summary: Create a check (checks-as-data, plan R3-7)
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/CheckCreate'}
responses:
'201':
description: Created
content:
application/json:
schema: {$ref: '#/components/schemas/Check'}
default: {$ref: '#/components/responses/Problem'}
/checks/{id}:
parameters: [{$ref: '#/components/parameters/EntityId'}]
patch:
tags: [signals]
operationId: patchCheck
summary: Update or disable a check
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IfMatch'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/CheckPatch'}
responses:
'200':
description: Updated
content:
application/json:
schema: {$ref: '#/components/schemas/Check'}
default: {$ref: '#/components/responses/Problem'}
# ─── Executions / classifications / approvals ─────────────────────
/executions:
get:
tags: [executions]
operationId: listExecutions
summary: List executions
x-required-scope: viewer
parameters:
- {name: status, in: query, schema: {type: string}}
- {name: target, in: query, schema: {type: string}}
- {name: action, in: query, schema: {type: string}}
- {name: correlation_id, in: query, schema: {type: string}}
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Executions
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Execution'}}
next_cursor: {type: [string, "null"]}
default: {$ref: '#/components/responses/Problem'}
post:
tags: [executions]
operationId: requestExecution
summary: Request an execution (classify → approval check → enqueue)
description: |
The handler classifies the (entity, action), checks policy/autonomy,
and either enqueues the execution (auto-approved) or creates an
approval request and returns the execution in `proposed` state.
Also exposed to the agent role — this is the ONLY way agents act.
x-required-scope: agent
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ExecutionRequest'}
responses:
'201':
description: Execution created (may be pending approval)
content:
application/json:
schema: {$ref: '#/components/schemas/Execution'}
default: {$ref: '#/components/responses/Problem'}
/executions/{id}:
parameters: [{$ref: '#/components/parameters/EntityId'}]
get:
tags: [executions]
operationId: getExecution
summary: Execution status + result
x-required-scope: viewer
responses:
'200':
description: Execution
content:
application/json:
schema: {$ref: '#/components/schemas/Execution'}
default: {$ref: '#/components/responses/Problem'}
/executions/{id}/cancel:
parameters: [{$ref: '#/components/parameters/EntityId'}]
post:
tags: [executions]
operationId: cancelExecution
summary: Cancel a proposed/executing execution
x-required-scope: operator
responses:
'200':
description: Cancelled
content:
application/json:
schema: {$ref: '#/components/schemas/Execution'}
default: {$ref: '#/components/responses/Problem'}
/classifications:
get:
tags: [executions]
operationId: listClassifications
summary: Classifier decisions (the autonomous-decision audit trail)
x-required-scope: viewer
parameters:
- {name: signal_id, in: query, schema: {type: string}}
- {name: entity_id, in: query, schema: {type: string}}
- {name: route, in: query, schema: {type: string, enum: [auto-act, escalate, hold]}}
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Classifications
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Classification'}}
next_cursor: {type: [string, "null"]}
default: {$ref: '#/components/responses/Problem'}
/approvals:
get:
tags: [executions]
operationId: listApprovals
summary: List approvals
x-required-scope: viewer
parameters:
- {name: status, in: query, schema: {type: string, enum: [pending, approved, denied, expired, revoked]}}
- {name: kind, in: query, schema: {type: string, enum: [execution, policy-change, pattern-activation]}}
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Approvals
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Approval'}}
next_cursor: {type: [string, "null"]}
default: {$ref: '#/components/responses/Problem'}
/approvals/{id}/decision:
parameters: [{$ref: '#/components/parameters/EntityId'}]
post:
tags: [executions]
operationId: decideApproval
summary: Approve or deny (single-use token verified server-side)
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [decision]
properties:
decision: {type: string, enum: [approve, deny, revoke]}
note: {type: string}
responses:
'200':
description: Decision recorded
content:
application/json:
schema: {$ref: '#/components/schemas/Approval'}
default: {$ref: '#/components/responses/Problem'}
# ─── Learning ─────────────────────────────────────────────────────
/patterns:
get:
tags: [learning]
operationId: listPatterns
summary: List patterns
x-required-scope: viewer
parameters:
- {name: entity_type, in: query, schema: {type: string}}
- {name: action, in: query, schema: {type: string}}
- {name: status, in: query, schema: {type: string}}
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Patterns
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Pattern'}}
next_cursor: {type: [string, "null"]}
default: {$ref: '#/components/responses/Problem'}
/patterns/{id}:
parameters: [{$ref: '#/components/parameters/EntityId'}]
patch:
tags: [learning]
operationId: patchPattern
summary: Transition a pattern (activate / invalidate / deprecate)
description: Operator safety valve (plan SG7). Activation is policy-gated config_mutation (S4).
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IfMatch'}]
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
status: {type: string, enum: [validated, active, deprecated, invalidated]}
quarantined: {type: boolean}
note: {type: string}
responses:
'200':
description: Updated
content:
application/json:
schema: {$ref: '#/components/schemas/Pattern'}
'202': {$ref: '#/components/responses/PendingApproval'}
default: {$ref: '#/components/responses/Problem'}
/skills:
get:
tags: [learning]
operationId: listSkills
summary: List skills (latest version per skill)
x-required-scope: viewer
parameters:
- {name: applies_to, in: query, schema: {type: string}}
- {name: action, in: query, schema: {type: string}}
- {name: status, in: query, schema: {type: string}}
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Skills
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Skill'}}
next_cursor: {type: [string, "null"]}
default: {$ref: '#/components/responses/Problem'}
/skills/{id}:
parameters: [{$ref: '#/components/parameters/EntityId'}]
patch:
tags: [learning]
operationId: patchSkill
summary: Transition a skill or pin a version
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IfMatch'}]
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
status: {type: string, enum: [tested, active, deprecated]}
pinned_version: {type: integer}
note: {type: string}
responses:
'200':
description: Updated
content:
application/json:
schema: {$ref: '#/components/schemas/Skill'}
'202': {$ref: '#/components/responses/PendingApproval'}
default: {$ref: '#/components/responses/Problem'}
/skills/{id}/versions:
parameters: [{$ref: '#/components/parameters/EntityId'}]
get:
tags: [learning]
operationId: listSkillVersions
summary: Version history of a skill
x-required-scope: viewer
responses:
'200':
description: Versions
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Skill'}}
default: {$ref: '#/components/responses/Problem'}
# ─── Policy ───────────────────────────────────────────────────────
/policy/risk-classes:
get:
tags: [policy]
operationId: listRiskClasses
summary: List risk classes
x-required-scope: viewer
responses:
'200':
description: Risk classes
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/RiskClass'}}
default: {$ref: '#/components/responses/Problem'}
/policy/approval-rules:
get:
tags: [policy]
operationId: listApprovalRules
summary: List approval rules
x-required-scope: viewer
responses:
'200':
description: Rules
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/ApprovalRule'}}
default: {$ref: '#/components/responses/Problem'}
post:
tags: [policy]
operationId: createApprovalRule
summary: Propose a new approval rule (dual-control)
description: Creates a policy-change approval; the rule applies only after operator approval (plan S3).
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ApprovalRuleCreate'}
responses:
'202': {$ref: '#/components/responses/PendingApproval'}
default: {$ref: '#/components/responses/Problem'}
/policy/approval-rules/{id}:
parameters: [{$ref: '#/components/parameters/EntityId'}]
patch:
tags: [policy]
operationId: patchApprovalRule
summary: Propose a rule change (dual-control)
x-required-scope: operator
parameters: [{$ref: '#/components/parameters/IfMatch'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ApprovalRuleCreate'}
responses:
'202': {$ref: '#/components/responses/PendingApproval'}
default: {$ref: '#/components/responses/Problem'}
/policy/autonomy:
get:
tags: [policy]
operationId: getAutonomySettings
summary: Autonomy settings (kill-switch, never-auto-act list)
x-required-scope: viewer
responses:
'200':
description: Settings
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/AutonomySetting'}}
default: {$ref: '#/components/responses/Problem'}
patch:
tags: [policy]
operationId: patchAutonomySettings
summary: Propose autonomy changes (dual-control; kill-switch OFF is immediate)
description: |
Raising autonomy is dual-controlled (202 + approval). Lowering it —
setting `global.auto_act: "off"` or adding a never_auto_act key —
applies immediately (200): the kill-switch must never wait for an approval.
x-required-scope: operator
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: {type: string}
responses:
'200':
description: Applied (restriction)
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/AutonomySetting'}}
'202': {$ref: '#/components/responses/PendingApproval'}
default: {$ref: '#/components/responses/Problem'}
# ─── Knowledge ────────────────────────────────────────────────────
/knowledge/search:
get:
tags: [knowledge]
operationId: searchKnowledge
summary: Full-text search over knowledge entities (documents, runbooks)
x-required-scope: viewer
parameters:
- {name: q, in: query, required: true, schema: {type: string}}
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Hits
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/KnowledgeHit'}}
default: {$ref: '#/components/responses/Problem'}
/knowledge/{entity_id}:
parameters:
- {name: entity_id, in: path, required: true, schema: {type: string}}
get:
tags: [knowledge]
operationId: getEntityKnowledge
summary: All documents/runbooks linked to an entity
x-required-scope: viewer
responses:
'200':
description: Linked knowledge
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/KnowledgeHit'}}
default: {$ref: '#/components/responses/Problem'}
# ─── Observability ────────────────────────────────────────────────
/metrics:
get:
tags: [observability]
operationId: queryMetrics
summary: Query time-series metrics
x-required-scope: viewer
parameters:
- {name: entity_id, in: query, schema: {type: string}}
- {name: metric, in: query, schema: {type: array, items: {type: string}}, style: form, explode: true}
- {name: rollup, in: query, schema: {type: string, enum: [raw, 1h, 1d, auto], default: auto}}
- $ref: '#/components/parameters/FromTime'
- $ref: '#/components/parameters/ToTime'
responses:
'200':
description: Metric series
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/MetricSeries'}}
default: {$ref: '#/components/responses/Problem'}
/trends/{entity_id}:
parameters:
- {name: entity_id, in: path, required: true, schema: {type: string}}
get:
tags: [observability]
operationId: getTrends
summary: Trend analysis for all metrics on an entity
x-required-scope: viewer
parameters:
- $ref: '#/components/parameters/FromTime'
- $ref: '#/components/parameters/ToTime'
responses:
'200':
description: Trends
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Trend'}}
default: {$ref: '#/components/responses/Problem'}
/audit:
get:
tags: [observability]
operationId: queryAudit
summary: Audit log
x-required-scope: operator
parameters:
- {name: actor_type, in: query, schema: {type: string}}
- {name: actor_id, in: query, schema: {type: string}}
- {name: entity_id, in: query, schema: {type: string}}
- {name: action, in: query, schema: {type: string}}
- {name: correlation_id, in: query, schema: {type: string}}
- $ref: '#/components/parameters/FromTime'
- $ref: '#/components/parameters/ToTime'
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Audit entries (ts DESC)
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/AuditEntry'}}
next_cursor: {type: [string, "null"]}
default: {$ref: '#/components/responses/Problem'}
/events:
get:
tags: [observability]
operationId: queryEvents
summary: Historical events
x-required-scope: viewer
parameters:
- {name: type, in: query, schema: {type: string}}
- {name: entity_id, in: query, schema: {type: string}}
- {name: severity, in: query, schema: {type: string}}
- {name: correlation_id, in: query, schema: {type: string}}
- $ref: '#/components/parameters/FromTime'
- $ref: '#/components/parameters/ToTime'
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Events (ts DESC)
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Event'}}
next_cursor: {type: [string, "null"]}
default: {$ref: '#/components/responses/Problem'}
/events/stream:
get:
tags: [observability]
operationId: streamEvents
summary: Live event stream (SSE)
description: |
Server-Sent Events. Each event's `id` is the event row id (resume
with `Last-Event-ID`), `event` is the event type, `data` is the JSON
Event object. Heartbeat comments every 15s. Best-effort delivery —
bounded per-subscriber buffer, drop-oldest (plan P6); use GET /events
to backfill.
x-required-scope: viewer
parameters:
- {name: type, in: query, schema: {type: string}, description: Filter by event type prefix}
- {name: Last-Event-ID, in: header, schema: {type: string}}
responses:
'200':
description: SSE stream
content:
text/event-stream:
schema: {type: string}
default: {$ref: '#/components/responses/Problem'}
/agent-activity:
get:
tags: [observability]
operationId: queryAgentActivity
summary: Agent behavior log
x-required-scope: viewer
parameters:
- {name: agent_id, in: query, schema: {type: string}}
- {name: activity_type, in: query, schema: {type: string}}
- {name: entity_id, in: query, schema: {type: string}}
- $ref: '#/components/parameters/FromTime'
- $ref: '#/components/parameters/ToTime'
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Activity entries
content:
application/json:
schema:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/AgentActivity'}}
next_cursor: {type: [string, "null"]}
default: {$ref: '#/components/responses/Problem'}
/health:
get:
tags: [observability]
operationId: getFleetHealth
summary: Fleet health summary with trend indicators
x-required-scope: viewer
responses:
'200':
description: Health summary
content:
application/json:
schema: {$ref: '#/components/schemas/HealthSummary'}
default: {$ref: '#/components/responses/Problem'}
# ─── System ───────────────────────────────────────────────────────
/export:
get:
tags: [system]
operationId: exportSeeds
summary: Regenerate seed YAMLs from current DB state (DR / version control)
x-required-scope: operator
responses:
'200':
description: Seed bundle
content:
application/json:
schema:
type: object
required: [ontology, inventory, policy]
properties:
ontology: {type: string, description: YAML document}
inventory: {type: string, description: YAML document}
policy: {type: string, description: YAML document}
default: {$ref: '#/components/responses/Problem'}
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: |
Operator/viewer: Authentik OIDC JWT (validated in-API — Caddy
forward-auth is defense-in-depth, not the source of truth).
Agent: static bearer token from Infisical (scope `agent`).
parameters:
EntityId:
name: id
in: path
required: true
schema: {type: string}
description: UUID or slug (e.g. `host:hubris`)
Cursor:
name: cursor
in: query
schema: {type: string}
description: Opaque cursor from a previous response's next_cursor
Limit:
name: limit
in: query
schema: {type: integer, default: 50, maximum: 200, minimum: 1}
FromTime:
name: from
in: query
schema: {type: string, format: date-time}
ToTime:
name: to
in: query
schema: {type: string, format: date-time}
IfMatch:
name: If-Match
in: header
required: true
schema: {type: string}
description: ETag from a prior GET; 412 on version mismatch
IdempotencyKey:
name: Idempotency-Key
in: header
schema: {type: string, maxLength: 128}
description: Client-generated key; replays within 24h return the original response
headers:
ETag:
schema: {type: string}
description: Resource version for If-Match
responses:
Problem:
description: Error (RFC 9457)
content:
application/problem+json:
schema: {$ref: '#/components/schemas/Problem'}
PendingApproval:
description: Change staged; a meta-approval was created (dual-control)
content:
application/json:
schema: {$ref: '#/components/schemas/Approval'}
SignalUpdated:
description: Updated signal
content:
application/json:
schema: {$ref: '#/components/schemas/Signal'}
schemas:
Problem:
type: object
required: [title, status]
properties:
type: {type: string, format: uri, default: "about:blank"}
title: {type: string}
status: {type: integer}
detail: {type: string}
instance: {type: string}
errors:
type: array
description: Field-level validation errors (422)
items:
type: object
required: [field, reason]
properties:
field: {type: string}
reason: {type: string}
Entity:
type: object
required: [id, slug, type, name, version, created_at, updated_at]
properties:
id: {type: string, format: uuid}
slug: {type: string, examples: ["host:hubris"]}
type: {type: string}
name: {type: string}
state: {type: [string, "null"]}
attributes: {type: object}
maintenance_until: {type: [string, "null"], format: date-time}
version: {type: integer}
created_at: {type: string, format: date-time}
updated_at: {type: string, format: date-time}
EntityCreate:
type: object
required: [slug, type, name]
properties:
slug: {type: string}
type: {type: string, description: Must be a non-abstract entity type}
name: {type: string}
state: {type: string, description: Defaults to the lifecycle's default_state}
attributes: {type: object, description: Validated against the type's attribute_schema}
EntityPatch:
type: object
description: At least one of the fields must be present.
properties:
name: {type: string}
state: {type: string, description: Target lifecycle state (transition validated)}
attributes: {type: object, description: Merged; validated against attribute_schema}
maintenance_until: {type: [string, "null"], format: date-time}
Relationship:
type: object
required: [source, target, type, valid_from]
properties:
source: {type: string, description: Slug of source entity}
target: {type: string, description: Slug of target entity}
type: {type: string}
attributes: {type: [object, "null"]}
valid_from: {type: string, format: date-time}
valid_to: {type: [string, "null"], format: date-time}
RelationshipCreate:
type: object
required: [source, target, type]
properties:
source: {type: string, description: UUID or slug}
target: {type: string, description: UUID or slug}
type: {type: string}
attributes: {type: object}
GraphView:
type: object
required: [nodes, edges]
properties:
nodes: {type: array, items: {$ref: '#/components/schemas/Entity'}}
edges: {type: array, items: {$ref: '#/components/schemas/Relationship'}}
truncated: {type: boolean, description: True if node cap was hit}
EntityType:
type: object
required: [name, domain, layer, is_abstract, status]
properties:
name: {type: string}
parent_type: {type: [string, "null"]}
is_abstract: {type: boolean}
domain: {type: string}
layer: {type: string, enum: [meta, infrastructure, governance, cognition]}
description: {type: string}
lifecycle_id: {type: [string, "null"]}
attribute_schema: {type: [object, "null"], description: JSON Schema}
schema_version: {type: integer}
status: {type: string, enum: [active, deprecated]}
version: {type: integer}
EntityTypeCreate:
type: object
required: [name, domain, layer]
properties:
name: {type: string}
parent_type: {type: string}
is_abstract: {type: boolean, default: false}
domain: {type: string}
layer: {type: string, enum: [infrastructure, governance, cognition]}
description: {type: string}
lifecycle_id: {type: string}
attribute_schema: {type: object}
EntityTypePatch:
type: object
properties:
description: {type: string}
attribute_schema: {type: object}
status: {type: string, enum: [active, deprecated]}
RelationshipType:
type: object
required: [name, source_type, target_type, cardinality]
properties:
name: {type: string}
inverse: {type: [string, "null"]}
source_type: {type: string, description: May be abstract}
target_type: {type: string, description: May be abstract}
cardinality: {type: string, enum: [one-to-one, one-to-many, many-to-one, many-to-many]}
description: {type: string}
LifecycleDef:
type: object
required: [id, states, default_state, transitions]
properties:
id: {type: string}
states: {type: array, items: {type: string}}
default_state: {type: string}
terminal_states: {type: array, items: {type: string}}
transitions:
type: object
description: '{from: {to: {requires: [named-check, ...]}}}'
Signal:
type: object
required: [id, slug, kind, severity, state, occurrence_count, first_seen_at, last_seen_at]
properties:
id: {type: string, format: uuid}
slug: {type: string}
kind: {type: string, examples: [service-down, disk-threshold, drift, flapping]}
severity: {type: string, enum: [info, warning, critical]}
state: {type: string, enum: [raised, acknowledged, acting, muted, resolved, failed]}
target: {type: [string, "null"], description: Slug of the entity this concerns}
check_id: {type: [string, "null"]}
evidence: {type: [string, "null"]}
likely_cause: {type: [string, "null"]}
occurrence_count: {type: integer}
flap_count: {type: integer}
hold_down_until: {type: [string, "null"], format: date-time}
mute_until: {type: [string, "null"], format: date-time}
first_seen_at: {type: string, format: date-time}
last_seen_at: {type: string, format: date-time}
Check:
type: object
required: [id, slug, kind, interval_s, timeout_s, enabled, version]
properties:
id: {type: string, format: uuid}
slug: {type: string}
kind: {type: string, enum: [http, tcp, disk, cert-expiry, drift, ssh-script]}
target: {type: [string, "null"], description: Entity slug (instance-scoped)}
target_type: {type: [string, "null"], description: Entity type (type-scoped)}
config: {type: object, description: Validated per-kind}
interval_s: {type: integer}
timeout_s: {type: integer}
zone: {type: [string, "null"]}
enabled: {type: boolean}
version: {type: integer}
CheckCreate:
type: object
required: [slug, kind]
properties:
slug: {type: string}
kind: {type: string, enum: [http, tcp, disk, cert-expiry, drift, ssh-script]}
target: {type: string}
target_type: {type: string}
config: {type: object}
interval_s: {type: integer, default: 600}
timeout_s: {type: integer, default: 10}
zone: {type: string}
enabled: {type: boolean, default: true}
CheckPatch:
type: object
properties:
config: {type: object}
interval_s: {type: integer}
timeout_s: {type: integer}
enabled: {type: boolean}
Execution:
type: object
required: [id, slug, action, risk_class, status, correlation_id, created_at]
properties:
id: {type: string, format: uuid}
slug: {type: string}
target: {type: [string, "null"]}
action: {type: string}
risk_class: {type: string}
status:
type: string
enum: [proposed, approved, auto_approved, denied, expired, executing,
verifying, verified, failed, timed_out, cancelled, rolled_back,
rollback_failed]
classification_id: {type: [string, "null"]}
signal_id: {type: [string, "null"]}
approval_id: {type: [string, "null"]}
agent_id: {type: [string, "null"]}
skill_id: {type: [string, "null"]}
skill_version: {type: [integer, "null"]}
params: {type: object, description: Skill params (validated against params_schema)}
result: {type: [object, "null"]}
duration_ms: {type: [integer, "null"]}
verified: {type: boolean}
correlation_id: {type: string}
started_at: {type: [string, "null"], format: date-time}
completed_at: {type: [string, "null"], format: date-time}
created_at: {type: string, format: date-time}
ExecutionRequest:
type: object
required: [target, action]
properties:
target: {type: string, description: Entity UUID or slug}
action: {type: string, examples: [restart, cache-clear]}
params: {type: object}
signal_id: {type: string, description: Signal that motivated this (optional)}
reason: {type: string}
Classification:
type: object
required: [id, action, risk_class, route, reasoning, correlation_id, created_at]
properties:
id: {type: string, format: uuid}
signal_id: {type: [string, "null"]}
target: {type: [string, "null"]}
action: {type: string}
recommended_action: {type: [object, "null"]}
risk_class: {type: string}
route: {type: string, enum: [auto-act, escalate, hold]}
blast_radius: {type: array, items: {type: string}}
pattern_confidence: {type: [number, "null"]}
skill_id: {type: [string, "null"]}
autonomy_check: {type: string}
reasoning: {type: object}
correlation_id: {type: string}
created_at: {type: string, format: date-time}
Approval:
type: object
required: [id, slug, action, risk_class, kind, status, expires_at, created_at]
properties:
id: {type: string, format: uuid}
slug: {type: string}
subject: {type: [string, "null"], description: Entity slug the approval concerns}
action: {type: string}
risk_class: {type: string}
kind: {type: string, enum: [execution, policy-change, pattern-activation]}
payload: {type: [object, "null"], description: e.g. proposed policy diff}
status: {type: string, enum: [pending, approved, denied, expired, revoked]}
expires_at: {type: string, format: date-time}
decided_at: {type: [string, "null"], format: date-time}
decided_by: {type: [string, "null"]}
created_at: {type: string, format: date-time}
Pattern:
type: object
required: [id, slug, applies_type, action, pattern, confidence, evidence_count, status, version]
properties:
id: {type: string, format: uuid}
slug: {type: string}
applies_type: {type: string}
action: {type: string}
pattern: {type: string}
confidence: {type: number, description: "Wilson lower bound, capped by evidence_count/5"}
evidence_count: {type: integer}
success_count: {type: integer}
failure_count: {type: integer}
status: {type: string, enum: [hypothesized, validated, active, deprecated, invalidated]}
quarantined: {type: boolean}
version: {type: integer}
last_validated_at: {type: [string, "null"], format: date-time}
Skill:
type: object
required: [id, slug, name, version, action, status, procedure]
properties:
id: {type: string, format: uuid}
slug: {type: string}
name: {type: string}
version: {type: integer}
action: {type: string}
applies_type: {type: [string, "null"]}
procedure:
type: object
description: Structured steps/verify/rollback/params (plan R3-9)
required: [steps, verify]
properties:
params_schema: {type: object}
steps: {type: array, items: {$ref: '#/components/schemas/SkillStep'}}
verify: {type: array, items: {$ref: '#/components/schemas/SkillStep'}}
rollback: {type: array, items: {$ref: '#/components/schemas/SkillStep'}}
expected_duration_s: {type: integer}
known_failure_modes: {type: array, items: {type: string}}
pattern_ids: {type: array, items: {type: string}}
status: {type: string, enum: [drafted, tested, active, refined, failed, deprecated]}
success_rate: {type: [number, "null"]}
changed_by: {type: [string, "null"]}
change_reason: {type: [string, "null"]}
last_used_at: {type: [string, "null"], format: date-time}
SkillStep:
type: object
required: [runner, command]
properties:
name: {type: string}
runner: {type: string, enum: [ssh, http, internal]}
target: {type: string, description: Go template over params}
command: {type: string, description: Go template over params}
timeout_s: {type: integer, default: 60}
expect:
type: object
properties:
exit_code: {type: integer}
stdout_contains: {type: string}
retry:
type: object
properties:
attempts: {type: integer}
delay_s: {type: integer}
RiskClass:
type: object
required: [name, approval_required, autonomy_allowed]
properties:
name: {type: string}
description: {type: string}
approval_required: {type: string, enum: [none, operator, operator_confirmed]}
autonomy_allowed: {type: boolean}
ApprovalRule:
type: object
required: [id, action, risk_class, autonomy_level, version]
properties:
id: {type: string, format: uuid}
entity_type: {type: [string, "null"], description: May be abstract (inherits down)}
action: {type: string}
risk_class: {type: string}
autonomy_level: {type: string, enum: [auto, escalate, never]}
scope_entity: {type: [string, "null"], description: Entity slug for per-entity overrides}
version: {type: integer}
ApprovalRuleCreate:
type: object
required: [action, risk_class, autonomy_level]
properties:
entity_type: {type: string}
action: {type: string}
risk_class: {type: string}
autonomy_level: {type: string, enum: [auto, escalate, never]}
scope_entity: {type: string}
AutonomySetting:
type: object
required: [key, value, version]
properties:
key: {type: string, examples: [global.auto_act]}
value: {type: string}
version: {type: integer}
updated_at: {type: string, format: date-time}
KnowledgeHit:
type: object
required: [id, slug, type, title]
properties:
id: {type: string, format: uuid}
slug: {type: string}
type: {type: string, enum: [document, runbook, investigation]}
title: {type: string}
source_path: {type: [string, "null"]}
snippet: {type: [string, "null"], description: Highlighted match context}
linked_entities: {type: array, items: {type: string}}
rank: {type: [number, "null"]}
MetricSeries:
type: object
required: [entity_id, metric, rollup, samples]
properties:
entity_id: {type: string}
metric: {type: string}
rollup: {type: string, enum: [raw, 1h, 1d]}
samples:
type: array
items:
type: object
required: [ts]
properties:
ts: {type: string, format: date-time}
value: {type: [number, "null"], description: Raw sample value}
avg: {type: [number, "null"]}
min: {type: [number, "null"]}
max: {type: [number, "null"]}
count: {type: [integer, "null"]}
trend: {$ref: '#/components/schemas/Trend'}
Trend:
type: object
required: [metric, direction]
properties:
metric: {type: string}
direction: {type: string, enum: [improving, degrading, stable, unknown]}
slope: {type: [number, "null"], description: Linear fit per day}
anomaly: {type: boolean}
forecast: {type: [number, "null"], description: "Simple linear projection, 7d out"}
AuditEntry:
type: object
required: [id, ts, actor_type, action]
properties:
id: {type: integer}
ts: {type: string, format: date-time}
actor_type: {type: string, enum: [agent, operator, system, scheduler]}
actor_id: {type: [string, "null"]}
action: {type: string}
entity_id: {type: [string, "null"]}
method: {type: [string, "null"]}
path: {type: [string, "null"]}
status_code: {type: [integer, "null"]}
detail: {type: object}
source_ip: {type: [string, "null"]}
correlation_id: {type: [string, "null"]}
Event:
type: object
required: [id, ts, type, severity, source]
properties:
id: {type: integer}
ts: {type: string, format: date-time}
type: {type: string, examples: [signal.raised, execution.completed]}
entity_id: {type: [string, "null"]}
severity: {type: string, enum: [info, warning, critical]}
source: {type: string}
data: {type: object}
correlation_id: {type: [string, "null"]}
AgentActivity:
type: object
required: [id, ts, agent_id, activity_type]
properties:
id: {type: integer}
ts: {type: string, format: date-time}
agent_id: {type: string}
session_id: {type: [string, "null"]}
activity_type: {type: string, enum: [tool_call, reasoning, decision, mcp_query, escalation]}
tool_name: {type: [string, "null"]}
entity_id: {type: [string, "null"]}
input_summary: {type: [string, "null"]}
output_summary: {type: [string, "null"]}
duration_ms: {type: [integer, "null"]}
token_count: {type: [integer, "null"]}
success: {type: [boolean, "null"]}
correlation_id: {type: [string, "null"]}
HealthSummary:
type: object
required: [summary, entities]
properties:
summary:
type: object
required: [healthy, degraded, down, unknown]
properties:
healthy: {type: integer}
degraded: {type: integer}
down: {type: integer}
unknown: {type: integer}
entities:
type: array
items:
type: object
required: [slug, type, health]
properties:
slug: {type: string}
type: {type: string}
health: {type: string, enum: [healthy, degraded, down, unknown]}
trend: {type: [string, "null"], enum: [improving, degrading, stable, unknown, null]}
last_check_at: {type: [string, "null"], format: date-time}