The MIDAS control plane manages the authority model: surfaces, profiles, agents, and grants. It provides apply, plan (dry-run), and surface lifecycle operations.
Control plane resources are defined as YAML documents using the midas.accept.io/v1 API version. Multiple documents can be combined in a single bundle using --- separators.
Every document requires:
apiVersion: midas.accept.io/v1
kind: Surface | Agent | Profile | Grant | Capability | Process | BusinessService | BusinessServiceCapability
metadata:
id: <identifier>Defines a governed decision boundary. The surface defines what is governed; the profile defines how much authority is permitted.
apiVersion: midas.accept.io/v1
kind: Surface
metadata:
id: surf-payment-release
name: Payment Release
labels:
team: payments
spec:
description: Governs autonomous payment release decisions
domain: payments
category: financial
risk_tier: high
status: active
decision_type: operational
reversibility_class: conditionally_reversible
minimum_confidence: 0.80
business_owner: Head of Payments
technical_owner: payments-platform-team
process_id: proc-payment-release
required_context:
fields:
- name: account_id
type: string
required: true
description: Target account identifier
- name: payment_ref
type: string
required: true
consequence_types:
- id: monetary-gbp
name: Monetary (GBP)
measure_type: financial
currency: GBP
compliance_frameworks:
- PCI-DSS
- FCA-COBS
audit_retention_hours: 17520
subject_required: trueKey spec fields:
| Field | Type | Description |
|---|---|---|
category |
string | Free-text classification (e.g. financial, customer_data, compliance, operational). Required. |
risk_tier |
string | low, medium, or high. Required. |
status |
string | active, inactive, or deprecated is required by the validator, but the apply path always persists newly-applied Surfaces as review. Approve via POST /v1/controlplane/surfaces/{id}/approve to bring a Surface from review to active. |
process_id |
string | Owning Process ID. Required. Must reference a Process that exists in the store or in the same bundle. |
domain |
string | Business domain. Defaults to "default" if omitted. |
decision_type |
string | strategic, tactical, or operational. Default: operational. |
reversibility_class |
string | reversible, conditionally_reversible, or irreversible. Default: conditionally_reversible. |
minimum_confidence |
float64 | Floor confidence for this surface (0.0–1.0). |
fail_mode_policy_id |
string | Optional surface-level reference to an active FailModePolicy. Acts as the surface override in the resolution hierarchy (Surface → BusinessService → deployment default). |
required_context.fields |
array | Context keys that all profiles on this surface must receive. |
Registers an autonomous actor.
apiVersion: midas.accept.io/v1
kind: Agent
metadata:
id: agent-payments-prod
name: Payments Automation Service
spec:
type: automation
runtime:
model: payments-engine
version: "3.0.0"
provider: internal
status: activeAgent types: llm_agent, workflow, automation, copilot, rpa.
Defines authority limits for a surface. Multiple profiles per surface are supported.
apiVersion: midas.accept.io/v1
kind: Profile
metadata:
id: prof-payments-standard
name: Standard Payment Authority
spec:
surface_id: surf-payment-release
authority:
decision_confidence_threshold: 0.85
consequence_threshold:
type: monetary
amount: 10000
currency: GBP
input_requirements:
required_context:
- account_id
- payment_ref
policy:
reference: "rego://payments/auto_approve_v1"
fail_mode: closed
lifecycle:
status: active
effective_from: "2026-01-01T00:00:00Z"
version: 1Profile review semantics. As with Surfaces, the apply path always persists newly-applied Profiles in
reviewstatus regardless oflifecycle.statusin the document. Approve viaPOST /v1/controlplane/profiles/{id}/approveto bring a Profile fromreviewtoactive.
Consequence threshold types: monetary, financial, risk_rating.
monetary is the canonical control-plane value for amount/currency
thresholds. financial is accepted as a compatibility alias and is the
Postgres persistence value. Both forms require amount and currency.
For risk_rating type, use risk_rating instead of amount:
consequence_threshold:
type: risk_rating
risk_rating: mediumLinks an agent to a profile. The grant carries no governance semantics; those all live on the profile.
apiVersion: midas.accept.io/v1
kind: Grant
metadata:
id: grant-payments-agent-standard
spec:
agent_id: agent-payments-prod
profile_id: prof-payments-standard
granted_by: user-governance-lead
effective_from: "2026-01-01T00:00:00Z"
status: activeGrant statuses: active, suspended, revoked, expired.
Defines a logical business domain that groups related processes. Capabilities can be hierarchical via parent_capability_id.
apiVersion: midas.accept.io/v1
kind: Capability
metadata:
id: cap-lending
name: Lending
spec:
description: Consumer and commercial lending operations
status: active
owner: lending-team
parent_capability_id: ""Key spec fields:
| Field | Type | Description |
|---|---|---|
status |
string | active, inactive, or deprecated. Required. |
owner |
string | Owning team or system. Optional. |
parent_capability_id |
string | Parent capability for hierarchical grouping. Optional. Must be a valid ID format if provided. |
Capabilities are immutable once created via apply. Applying a Capability with an existing ID returns conflict.
Defines a governed action that belongs to a BusinessService. Every Process must reference a BusinessService via business_service_id. Processes can be hierarchical via parent_process_id.
In the v1 service-led model, Process belongs to a BusinessService, not directly to a Capability. The relationship between a Process and a Capability is indirect, via BusinessService → BusinessServiceCapability → Capability.
apiVersion: midas.accept.io/v1
kind: Process
metadata:
id: proc-loan-origination
name: Loan Origination
spec:
description: End-to-end loan origination flow
status: active
owner: lending-team
business_service_id: bs-consumer-lending
parent_process_id: ""Key spec fields:
| Field | Type | Description |
|---|---|---|
business_service_id |
string | Owning BusinessService. Required. Must reference a BusinessService that exists in the store or in the same bundle. |
status |
string | active, inactive, or deprecated. Required. |
description |
string | Human-readable description. Optional. |
owner |
string | Owning team or system. Optional. |
parent_process_id |
string | Parent Process. Optional. Must reference a Process in the store or the same bundle. |
Processes are immutable once created via apply. Applying a Process with an existing ID returns conflict.
Defines an organizational service offering that processes belong to.
apiVersion: midas.accept.io/v1
kind: BusinessService
metadata:
id: bs-consumer-lending
name: Consumer Lending
spec:
description: Retail lending products for individual consumers
service_type: customer_facing
status: active
owner_id: lending-teamKey spec fields:
| Field | Type | Description |
|---|---|---|
service_type |
string | customer_facing, internal, or technical. Required. |
status |
string | active or deprecated. Required. (BusinessService status is narrower than other Kinds — inactive is not accepted.) |
description |
string | Human-readable description. Optional. |
owner_id |
string | Owning team or system. Optional. |
Business services are immutable once created via apply. Applying a BusinessService with an existing ID returns conflict.
Declares an M:N link between a BusinessService and a Capability — the canonical Capability ↔ BusinessService relationship in the v1 service-led model. A BusinessService is enabled by zero or more Capabilities; a Capability enables zero or more BusinessServices.
The metadata.id is a synthetic control-plane handle used for bundle identity and duplicate detection; it is not stored in the business_service_capabilities table. The junction row carries no lifecycle of its own — no origin, managed, replaces, or status fields.
apiVersion: midas.accept.io/v1
kind: BusinessServiceCapability
metadata:
id: bsc-consumer-lending-fraud-detection
spec:
business_service_id: bs-consumer-lending
capability_id: cap-fraud-detectionKey spec fields:
| Field | Type | Description |
|---|---|---|
business_service_id |
string | BusinessService ID. Required. Must reference a BusinessService that exists in the store or in the same bundle. |
capability_id |
string | Capability ID. Required. Must reference a Capability that exists in the store or in the same bundle. |
BusinessServiceCapability links are immutable once created via apply. Applying a link with the same (business_service_id, capability_id) pair returns conflict.
| process_id | string | Process ID. Required. Must reference a process that exists in the store or in the same bundle. |
| business_service_id | string | Business service ID. Required. Must reference a business service that exists in the store or in the same bundle. |
POST /v1/controlplane/apply
Content-Type: application/yaml, application/x-yaml, or text/yaml
Maximum body size: 10 MiB
Parses the YAML bundle, validates all documents, and persists valid resources.
Surface persistence is fully implemented. Agent, Profile, and Grant persistence is also implemented when the corresponding repositories are wired in (which they are in the default startup). The control plane uses all four repositories.
Response:
{
"results": [
{"kind": "Surface", "id": "surf-payment-release", "status": "created"},
{"kind": "Agent", "id": "agent-payments-prod", "status": "created"},
{"kind": "Profile", "id": "prof-payments-standard","status": "created"},
{"kind": "Grant", "id": "grant-payments-agent-standard", "status": "created"}
]
}Resource statuses: created, conflict, error, unchanged.
If validation fails for any document:
{
"results": [],
"validation_errors": [
{
"kind": "Surface",
"id": "surf-payment-release",
"field": "spec.minimum_confidence",
"message": "minimum_confidence must be between 0.0 and 1.0"
}
]
}Idempotency note: Apply behaviour differs by resource kind. Surfaces and Profiles create a new version on reapply. Agents and Grants return conflict if the ID already exists (they are immutable after creation). See Modification model for the full rules.
POST /v1/controlplane/plan
Same request format as apply. Returns a structured plan describing what would happen if the bundle were applied. No writes occur.
curl -s -X POST http://localhost:8080/v1/controlplane/plan \
-H "Content-Type: application/yaml" \
--data-binary @bundle.yaml | jq .Response:
{
"entries": [
{
"kind": "Surface",
"id": "surf-payment-release",
"action": "create",
"document_index": 1,
"decision_source": "persisted_state"
},
{
"kind": "Grant",
"id": "grant-existing",
"action": "conflict",
"document_index": 4,
"message": "resource already exists",
"decision_source": "persisted_state"
}
],
"would_apply": false,
"invalid_count": 0,
"conflict_count": 1,
"create_count": 3
}Entry actions: create, conflict, invalid, unchanged.
decision_source values:
persisted_state— action was determined by a repository lookupbundle_dependency— action was determined by a cross-document reference within the bundlevalidation— action was determined by a validation failure
would_apply is true only when invalid_count == 0 and create_count > 0.
Plan entries carry three optional, additive fields. They are purely informational and never affect apply semantics, would_apply, invalid_count, or conflict_count.
create_kind — emitted only when action: "create". Values:
new— the planner found no prior row for this resourcenew_version— an existing versioned lineage was found; a new version will be appended (Surface or Profile)
diff — emitted only for create_kind: "new_version" entries whose kind is Surface or Profile. Carries a fields array of changed scalar fields with before and after values. No diff is emitted for plain new creates or for any other resource kind (Agent, Grant, Capability, Process, BusinessService, BusinessServiceCapability).
warnings — advisory signals attached to an entry. Warnings never change the entry's action, never contribute to invalid_count or conflict_count, and never affect would_apply. They surface reviewer-relevant context that does not rise to the level of a validation failure. Warning codes:
| Code | Trigger |
|---|---|
REF_SURFACE_TERMINAL |
Profile references a Surface whose latest persisted version is deprecated or retired |
REF_PROFILE_TERMINAL |
Grant references a Profile whose latest persisted version is deprecated or retired |
REF_PROCESS_TERMINAL |
Surface references a Process whose status is deprecated |
REF_CAPABILITY_TERMINAL |
Process references a Capability whose status is deprecated |
Warnings fire only when the reference is resolved against persisted state; a reference satisfied by a same-bundle create (decision_source: "bundle_dependency") does not produce a warning, since the in-bundle resource is new and not terminal.
Example entry with a new_version diff and a terminal-reference warning:
{
"kind": "Surface",
"id": "surf-payment-release",
"action": "create",
"document_index": 1,
"decision_source": "persisted_state",
"create_kind": "new_version",
"diff": {
"fields": [
{"field": "spec.minimum_confidence", "before": 0.5, "after": 0.8}
]
},
"warnings": [
{
"code": "REF_PROCESS_TERMINAL",
"severity": "warning",
"message": "referenced process \"payments.v1\" is deprecated; referrer will be linked to a terminal-state process",
"field": "spec.process_id",
"related_kind": "Process",
"related_id": "payments.v1"
}
]
}MIDAS persists three independent audit trails. They are deliberately kept separate and not collapsed into a single model:
- Runtime decision audit (
audit_events) — hash-chained per-envelope event log emitted during decision evaluation. Each envelope has a sequence-ordered trail anchored by SHA-256prev_hash/event_hashfields and verified byVerifyAuditIntegrity. Not touched by the control-plane admin work. - Control-plane resource audit (
controlplane_audit_events, served atGET /v1/controlplane/audit) — plain append-only resource-lifecycle log. One row per surface/profile/agent/grant create or lifecycle action (approve, deprecate, suspend, revoke, reinstate). - Platform administrative audit (
platform_admin_audit_events, served atGET /v1/platform/admin-audit) — append-only log of principal-keyed platform-administrative actions. First-pass coverage:apply.invoked(request-level, one record per apply HTTP request, additive to per-resource rows),password.changed,bootstrap.admin_created.
The administrative audit intentionally does not use hash chaining in this release. Records are first-class persisted artifacts, not log output. The repository interface is append-only — there is no update or delete API. The record never contains password material, tokens, or secrets. See GET /v1/platform/admin-audit in the HTTP API reference for the record shape and query parameters.
POST /v1/controlplane/surfaces/{id}/approve
Promotes a surface from review to active. The surface must be in review status.
curl -s -X POST \
http://localhost:8080/v1/controlplane/surfaces/surf-payment-release/approve \
-H "Content-Type: application/json" \
-d '{
"submitted_by": "user-requester",
"approver_id": "user-governance-lead",
"approver_name": "Governance Lead"
}' | jq .Response:
{
"surface_id": "surf-payment-release",
"status": "active",
"approved_by": "user-governance-lead"
}Emits a surface.approved outbox event if the dispatcher is enabled.
POST /v1/controlplane/surfaces/{id}/deprecate
Moves a surface from active to deprecated. The surface must be in active status.
curl -s -X POST \
http://localhost:8080/v1/controlplane/surfaces/surf-payment-release/deprecate \
-H "Content-Type: application/json" \
-d '{
"deprecated_by": "user-governance-lead",
"reason": "Superseded by surf-payment-release-v2",
"successor_id": "surf-payment-release-v2"
}' | jq .successor_id is optional. When provided it must be a valid identifier.
Response:
{
"surface_id": "surf-payment-release",
"status": "deprecated",
"deprecation_reason": "Superseded by surf-payment-release-v2",
"successor_surface_id": "surf-payment-release-v2"
}Emits a surface.deprecated outbox event if the dispatcher is enabled.
The apply endpoint enforces an explicit modification model for each resource kind. Understanding this model is essential for operating MIDAS safely.
Applying a Surface with an existing ID creates a new version. The new version enters review status and must be explicitly approved before it becomes active. The currently active version continues to serve evaluations until a new version is approved.
| Scenario | Result |
|---|---|
| New surface ID | Created as version 1 in review status |
Existing ID (latest in any status except review) |
New version created (version N+1) in review status |
Existing ID (latest already in review) |
Conflict — resolve the pending review first |
To modify a surface: re-apply the updated YAML. The plan response shows action: create with a new version number. Then approve the new version.
Applying a Profile with an existing logical ID creates a new version with an incremented version number. Profile versions are immutable once created. The active version used by the runtime is determined by status = active and effective_from <= evaluation_timestamp.
| Scenario | Result |
|---|---|
| New profile ID | Created as version 1 |
| Existing ID | New version created (version N+1) |
To modify a profile: re-apply the updated YAML. The new version is created immediately but must be activated via the profile's own lifecycle transitions (not via apply) before the runtime uses it.
Agents are immutable once created via apply. Applying an Agent with an existing ID returns conflict. This prevents accidental identity changes that could break in-flight evaluations.
| Scenario | Result |
|---|---|
| New agent ID | Created |
| Existing ID | Conflict |
To change an agent's configuration, register a new agent with a new ID, then update grants to reference the new agent.
Grants are immutable once created via apply. Applying a Grant with an existing ID returns conflict. State transitions (revoke, suspend, reactivate) are separate operations.
| Scenario | Result |
|---|---|
| New grant ID | Created |
| Existing ID | Conflict |
To change authority binding: revoke the existing grant (via the revoke endpoint or database operation), then create a new grant with the updated profile reference.
Capabilities, Processes, BusinessServices, and BusinessServiceCapability links are immutable once created via apply. Applying any of these with an existing ID returns conflict.
| Scenario | Result |
|---|---|
| New ID | Created |
| Existing ID | Conflict |
Structural resources do not have versioning or lifecycle endpoints. Status changes are made via update operations outside the apply path.
| Kind | Reapply same ID | Version field | State transitions |
|---|---|---|---|
| Surface | New version | Yes (1, 2, 3…) | review → active → deprecated via lifecycle endpoints |
| Profile | New version | Yes (1, 2, 3…) | Via profile lifecycle |
| Agent | Conflict | No | Operational state via separate update |
| Grant | Conflict | No | active ↔ suspended → revoked via separate endpoints |
| Capability | Conflict | No | — |
| Process | Conflict | No | — |
| BusinessService | Conflict | No | — |
| BusinessServiceCapability | Conflict | No | — |
Version resolution at evaluation time selects the version where:
status = activeeffective_from <= evaluation_timestamp
When multiple versions satisfy the condition (possible during a migration window), the version with the highest effective_from not exceeding the evaluation time is selected.
Latest vs active distinction:
GET /v1/surfaces/{id}returns the latest version (highest version number, regardless of status). This may be a version inreview.- The runtime resolves the active version — the one with
status = activeandeffective_from <= now. These are often different during a governance review cycle.
This means threshold and policy changes are safe to roll out with a future effective_from date — existing evaluations continue against the current active version until the new version's effective date is reached.
The parser rejects any document with:
- Missing or empty
apiVersion apiVersionother thanmidas.accept.io/v1- Missing or empty
kind kindother thanSurface,Agent,Profile,Grant,Capability,Process,BusinessService, orBusinessServiceCapability- Missing
metadata.id
Structural validation also checks:
minimum_confidencemust be in[0.0, 1.0](Surface)- Enum fields (
decision_type,reversibility_class, etc.) must contain valid values if provided spec.surface_idon Profile must reference a known surface (either in the store or within the same bundle)spec.agent_idandspec.profile_idon Grant must reference known agents and profilesspec.process_idon Surface is required and must be a valid ID format; the referenced process must exist in the store or in the same bundlespec.business_service_idon Process is required and must be a valid ID format; the referenced BusinessService must exist in the store or in the same bundlespec.statuson Capability and Process must beactive,inactive, ordeprecatedspec.statuson BusinessService must beactiveordeprecatedspec.service_typeon BusinessService must becustomer_facing,internal, ortechnicalspec.business_service_idandspec.capability_idon BusinessServiceCapability must reference known entities
All control plane endpoints return application/json error bodies:
{"error": "content-type must be application/yaml, application/x-yaml, or text/yaml"}| Status | Condition |
|---|---|
400 |
Body too large (>10 MiB), malformed YAML, missing required fields |
415 |
Wrong Content-Type for apply/plan endpoints |
404 |
Surface not found for approve/deprecate |
409 |
Surface not in required status for approve (must be review) or deprecate (must be active) |
501 |
Service not configured |