- 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>
1637 lines
59 KiB
YAML
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}
|