openapi: 3.0.3
info:
  title: SOAT Formations API
  version: 1.0.0
  description: >
    API for managing Formations — a CloudFormation-inspired declarative
    deployment layer that lets you describe an entire AI agent stack in a
    single template and deploy it with one API call.
  contact:
    name: SOAT Team
    url: https://github.com/ttoss/soat
servers:
  - url: '{baseUrl}'
    description: Base URL of your SOAT deployment (e.g. https://your-soat.com or http://localhost:5047)
    variables:
      baseUrl:
        description: The base URL of your SOAT deployment
        default: http://localhost:5047
tags:
  - name: Formations
    description: Manage declarative formation stacks
security:
  - bearerAuth: []
paths:
  /api/v1/formations/validate:
    post:
      tags:
        - Formations
      summary: Validate a formation template
      description: >
        Validates a formation template without creating any resources.
        Returns a list of errors and warnings. Accepts the template as a
        JSON object or as a YAML/JSON string.
      operationId: validateFormation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                template:
                  $ref: '#/components/schemas/FormationTemplateInput'
                parameters:
                  type: object
                  additionalProperties:
                    type: string
                  description: >
                    Runtime parameter values that override or supply template
                    parameter defaults. Keys must match parameter names declared
                    in `template.parameters`. When provided, the validation
                    result also reports required parameters that are still
                    missing after applying these values.
                  nullable: true
      responses:
        '200':
          description: Validation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationResult'
        '401':
          description: Unauthorized

  /api/v1/formations/plan:
    post:
      tags:
        - Formations
      summary: Plan a formation deployment
      description: >
        Computes a diff between the desired template and the current stack state
        without making any changes. Returns the list of planned actions.
      operationId: planFormation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - template
              properties:
                project_id:
                  x-soat-ref: projects
                  type: string
                  description: Project ID. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise.
                  example: proj_V1StGXR8Z5jdHi6B
                formation_id:
                  x-soat-ref: formations
                  type: string
                  description: >-
                    Existing formation ID to compare against. Omit for new
                    formation planning.
                template:
                  $ref: '#/components/schemas/FormationTemplateInput'
                parameters:
                  type: object
                  additionalProperties:
                    type: string
                  description: >
                    Runtime parameter values that override or supply template
                    parameter defaults. Keys must match parameter names declared
                    in `template.parameters`. A parameter declared with
                    `use_previous_value: true` may be omitted to reuse its
                    stored value.
                  nullable: true
      responses:
        '200':
          description: Plan result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanResult'
        '400':
          description: Bad Request
        '401':
          description: Unauthorized
        '403':
          description: Forbidden

  /api/v1/formations:
    get:
      tags:
        - Formations
      summary: List formations
      description: Returns all formation stacks for a project
      operationId: listFormations
      parameters:
        - name: project_id
          in: query
          description: Project ID (required if not using project key auth)
          schema:
            type: string
            example: proj_V1StGXR8Z5jdHi6B
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: List of formations
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Formation'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '403':
          description: Forbidden

    post:
      tags:
        - Formations
      summary: Create a new formation
      description: >
        Validates the template, creates the formation record, then provisions
        all declared resources in dependency order.


        A **template-shape** error is refused with `400`. A **deploy** failure
        is not: the operation ran, so the formation is returned with `201` and
        `status: "failed"`, and `error` explains why (the resources created
        before the failure are rolled back). Read `status` — a `2xx` here means
        the deploy was attempted, not that it worked. The `builtin` CLI exits
        non-zero on that body so `create-formation && …` does not lie.
      operationId: createFormation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - template
              properties:
                project_id:
                  x-soat-ref: projects
                  type: string
                  description: Project ID. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise.
                  example: proj_V1StGXR8Z5jdHi6B
                name:
                  type: string
                  description: Human-readable name for the formation stack
                  example: my-agent-stack
                template:
                  $ref: '#/components/schemas/FormationTemplateInput'
                parameters:
                  type: object
                  additionalProperties:
                    type: string
                  description: >
                    Runtime parameter values that override or supply template
                    parameter defaults. Keys must match parameter names declared
                    in `template.parameters`. Required parameters (those without
                    a default) must be provided here.
                  nullable: true
                metadata:
                  type: object
                  additionalProperties: true
                  nullable: true
                  description: >
                    Static annotations stored on the formation record. This
                    field is NOT a substitution site: `sub`/`param`/`ref`
                    expressions are rejected with 400
                    (`FORMATION_INVALID_METADATA`). For deploy-time
                    substitution use the template's top-level `metadata` block,
                    which is resolved into `resolved_metadata`.
      responses:
        '201':
          description: Formation created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Formation'
        '400':
          description: Bad Request
        '401':
          description: Unauthorized
        '403':
          description: >-
            Forbidden — either the caller may not operate on formations in the
            project, or it lacks an action a resource this template declares
            requires. `error.meta.denied_actions` names every missing action;
            nothing is applied.
        '409':
          description: Formation with this name already exists

  /api/v1/formations/{formation_id}:
    get:
      tags:
        - Formations
      summary: Get a specific formation
      description: Returns the formation stack including its current resources.
      operationId: getFormation
      parameters:
        - name: formation_id
          in: path
          required: true
          schema:
            type: string
          example: form_V1StGXR8Z5jdHi6B
      responses:
        '200':
          description: Formation details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Formation'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not Found

    put:
      tags:
        - Formations
      summary: Update an formation
      description: >
        Applies a new template to the formation. Resources are created,
        updated, or deleted to reconcile the current state with the desired
        state.


        A **template-shape** error is refused with `400`. A **deploy** failure
        is not: the operation ran, so the formation is returned with `200` and
        `status: "failed"`, and `error` explains why. Read `status` — a `2xx`
        here means the deploy was attempted, not that it worked. The `builtin` CLI
        exits non-zero on that body so `update-formation && …` does not lie.


        A deploy that replaced a resource and could not delete the superseded
        one answers `status: "active"` with
        `error.code: "FORMATION_REPLACE_CLEANUP_FAILED"` — the desired state is
        realised, and `error.meta.failures` names every resource still live. The
        next deploy retries the disposal.
      operationId: updateFormation
      parameters:
        - name: formation_id
          in: path
          required: true
          schema:
            type: string
          example: form_V1StGXR8Z5jdHi6B
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                template:
                  $ref: '#/components/schemas/FormationTemplateInput'
                parameters:
                  type: object
                  additionalProperties:
                    type: string
                  description: >
                    Runtime parameter values that override or supply template
                    parameter defaults. Keys must match parameter names declared
                    in `template.parameters`. Required parameters (those without
                    a default) must be provided here, unless the parameter is
                    declared with `use_previous_value: true`, in which case
                    omitting it reuses the previously stored value.
                  nullable: true
                metadata:
                  type: object
                  additionalProperties: true
                  nullable: true
                  description: >
                    Static annotations stored on the formation record. This
                    field is NOT a substitution site: `sub`/`param`/`ref`
                    expressions are rejected with 400
                    (`FORMATION_INVALID_METADATA`). For deploy-time
                    substitution use the template's top-level `metadata` block,
                    which is resolved into `resolved_metadata`.
      responses:
        '200':
          description: Updated formation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Formation'
        '400':
          description: Bad Request
        '401':
          description: Unauthorized
        '403':
          description: >-
            Forbidden — either the caller may not operate on formations in the
            project, or it lacks an action a resource this template declares
            requires. `error.meta.denied_actions` names every missing action;
            nothing is applied.
        '404':
          description: Not Found

    delete:
      tags:
        - Formations
      summary: Delete an formation
      description: >
        Deletes the formation stack and all its managed resources in reverse
        dependency order.


        A resource the platform refuses to delete on its own — most often an
        agent that has generation or trace history — fails the teardown with
        `409 FORMATION_DELETE_FAILED`, naming every blocking resource in
        `error.meta.failures`. Resolve the blockers (for an agent, `DELETE
        /api/v1/agents/{agent_id}?force=true` also removes its generations and
        traces, and `deletion_policy: retain` exempts it from teardown entirely)
        and delete the formation again.


        A refusal the platform can foresee is found by a pre-flight, before the
        first delete: nothing is removed, and the formation stays `active` and
        intact for the retry. An unforeseeable error surfaces mid-teardown
        instead, where resources deleted before the blocker stay deleted and the
        formation is left in `delete_failed`. The error message states which
        happened.
      operationId: deleteFormation
      parameters:
        - name: formation_id
          in: path
          required: true
          schema:
            type: string
          example: form_V1StGXR8Z5jdHi6B
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                required:
                  - success
        '401':
          description: Unauthorized
        '403':
          description: >-
            Forbidden — either the caller may not operate on formations in the
            project, or it lacks an action a resource this template declares
            requires. `error.meta.denied_actions` names every missing action;
            nothing is applied.
        '404':
          description: Not Found
        '409':
          description: >
            One or more resources could not be deleted
            (`FORMATION_DELETE_FAILED`). `error.meta.failures` lists each one as
            `{ logical_id, resource_type, error }`. The `message` says whether
            the pre-flight caught it (nothing deleted, formation still `active`)
            or it surfaced mid-teardown (formation left in `delete_failed`).

  /api/v1/formations/{formation_id}/events:
    get:
      tags:
        - Formations
      summary: List formation operation events
      description: >
        Returns all operations (create, update, delete) with their event logs
        for the formation, ordered chronologically.
      operationId: listFormationEvents
      parameters:
        - name: formation_id
          in: path
          required: true
          schema:
            type: string
          example: form_V1StGXR8Z5jdHi6B
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: List of operations
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/FormationOperation'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not Found

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  schemas:
    FormationTemplateInput:
      description: >
        A formation template supplied as either a JSON object or a YAML/JSON
        string. When a string is provided the server parses it with a YAML
        parser (JSON is valid YAML) before processing.
      oneOf:
        - $ref: '#/components/schemas/FormationTemplate'
        - type: string
          description: YAML or JSON string representation of a FormationTemplate

    FormationTemplate:
      type: object
      required:
        - resources
      properties:
        parameters:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/ParameterDeclaration'
          description: >
            Declared parameters for this template. Each parameter may have a
            default value and an optional description. Parameters without a
            default must be supplied in the `parameters` field of the deploy
            request.
          nullable: true
        resources:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/ResourceDeclaration'
          description: Map of logical resource IDs to resource declarations
        outputs:
          type: object
          additionalProperties: true
          description: >
            Map of output names to values. Values may use `{ "ref": "logicalId" }`
            to reference physical IDs of created resources, or `{ "param": "ParamName" }`
            and `{ "sub": "text ${ParamName}" }` to embed parameter values.
          nullable: true
        metadata:
          type: object
          additionalProperties: true
          nullable: true
          description: >
            Arbitrary metadata attached to the template. Supports the same
            substitution as `outputs`: `{ "ref": "logicalId" }` resolves to a
            created resource's physical ID, and `{ "param": "ParamName" }` /
            `{ "sub": "text ${ParamName}" }` embed parameter values. The raw
            expressions are preserved here; the resolved values from the last
            deploy are exposed on the formation's `resolved_metadata` field.

    ParameterDeclaration:
      type: object
      properties:
        type:
          type: string
          description: Parameter type (currently only 'string' is supported)
          example: string
        default:
          type: string
          description: Default value used when the parameter is not supplied at deploy time
          nullable: true
        description:
          type: string
          description: Human-readable description of what this parameter represents
          nullable: true
        no_echo:
          type: boolean
          description: >
            When true, the parameter value should be treated as sensitive and
            not echoed in logs or UI. Analogous to NoEcho in CloudFormation.
          nullable: true
        use_previous_value:
          type: boolean
          description: >
            When true, omitting this parameter on update reuses its previously
            stored value instead of failing the required-parameter check —
            analogous to CloudFormation's UsePreviousValue, declared in the
            template. An explicitly supplied value still overrides. Has no
            effect on create (there is no previous value yet). The value is
            reused only where the underlying resource retains it (e.g. a
            secret's encrypted value); otherwise the last-applied value is used.
          nullable: true

    AgentResourceProperties:
      description: >-
        Creates an AI agent backed by a provider. The agent handles requests,
        runs tools, and can be attached to actors. Exactly one of
        `ai_provider_id` or `model_route_id` must be declared. Switching an
        existing agent between the two declares the new field together with an
        explicit `null` for the old one.
      type: object
      additionalProperties: false
      properties:
        ai_provider_id:
          x-soat-ref: ai-providers
          type: string
          nullable: true
          description: >-
            Public ID of the AI provider to pin. Mutually exclusive with
            `model_route_id`.
        model_route_id:
          x-soat-ref: model-routes
          type: string
          nullable: true
          description: >-
            Public ID of a model route in the same project — the agent's
            completion model is resolved through the route's ordered targets
            with failover. Mutually exclusive with `ai_provider_id` and `model`.
        name:
          type: string
          nullable: true
          description: Agent display name
        instructions:
          type: string
          nullable: true
          description: System instructions for the agent
        model:
          type: string
          nullable: true
          description: Model identifier (overrides provider default)
        tool_bindings:
          type: array
          nullable: true
          items:
            type: object
          description: >-
            Tools to attach, one binding object per tool: `{ tool_id }`.
            Tool-call gating is owned by guardrails (attached via `guardrail_ids`
            on the project, agent, or tool), not by the binding. Inline `tool`
            entries are not supported in templates; declare a tool resource and
            reference it via `tool_id` (a `{ "ref": … }` to a tool resource in
            the same template resolves at deploy time).
        max_steps:
          type: integer
          nullable: true
          description: Maximum number of agentic steps per generation
        tool_choice:
          nullable: true
          description: 'Controls how the model selects tools. Accepts a string (`"auto"`, `"required"`) or an object (`{ "type": "tool", "tool_name": "my_tool" }`).'
        stop_conditions:
          type: array
          nullable: true
          description: Conditions that stop the agent's work early — turn-scoped (`has_tool_call`) or chain-scoped (`max_chain_generations`).
          items:
            type: object
            properties:
              type:
                type: string
                description: 'Condition type — `has_tool_call` or `max_chain_generations`'
              tool_name:
                type: string
                nullable: true
                description: Tool name to match when type is `has_tool_call`
              max_generations:
                type: integer
                nullable: true
                description: Generations the continuation chain may reach when type is `max_chain_generations`
        active_tool_ids:
          x-soat-ref: tools
          type: array
          nullable: true
          items:
            type: string
          description: Subset of the bound tools that are active
        guardrail_ids:
          x-soat-ref: guardrails
          type: array
          nullable: true
          items:
            type: string
          description: Guardrails attached at the agent scope.
        step_rules:
          type: array
          nullable: true
          description: Per-step overrides applied during multi-step generation. Steps not covered by a rule use the agent defaults.
          items:
            type: object
            properties:
              step:
                type: integer
                description: 1-indexed step number this rule applies to
              tool_choice:
                type: object
                nullable: true
                description: 'Tool choice override for this step, e.g. `auto`, `required`, or `{ type: tool, tool_name: search }`'
              active_tool_ids:
                x-soat-ref: tools
                type: array
                nullable: true
                items:
                  type: string
                description: Tool IDs active on this step
        boundary_policy:
          type: object
          nullable: true
          description: Restricts which SOAT actions the agent may invoke. Evaluated as the intersection with the caller's own policy.
          properties:
            statement:
              type: array
              description: List of IAM policy statements
              items:
                type: object
                properties:
                  effect:
                    type: string
                    description: 'Effect — `Allow` or `Deny`'
                  action:
                    type: array
                    items:
                      type: string
                    description: IAM action strings, e.g. `memories:*` or `agents:DeleteAgent`
                  resource:
                    type: array
                    nullable: true
                    items:
                      type: string
                    description: Resource SRN patterns (optional; omit to match all resources)
        temperature:
          type: number
          nullable: true
          description: Sampling temperature
        max_context_messages:
          type: integer
          nullable: true
          description: Maximum number of recent messages to include in the context window sent to the model. When null, all messages are included.
        single_session_per_actor:
          type: boolean
          nullable: true
          description: When true, only one open session per actor_id is allowed for this agent.
        trace_content_mode:
          type: string
          nullable: true
          description: >-
            Agent-scope zero-retention setting (`full` or `none`). `null`
            inherits the project's setting. `full` is refused when the project's
            own mode is `none`.
        on_approval_expiry:
          type: string
          nullable: true
          description: >-
            What happens when a held tool call expires un-approved:
            `terminate` (the default when null) ends the chain, `react` spawns
            a continuation that reports the staleness to the agent.
        knowledge_config:
          type: object
          nullable: true
          description: Knowledge retrieval configuration. When set, relevant documents and memory entries are injected into every generation.
          properties:
            memory_ids:
              x-soat-ref: memories
              type: array
              items:
                type: string
              description: Public IDs of memories to retrieve from
            memory_tags:
              type: array
              items:
                type: string
              description: Retrieve from all memories matching these tags
            document_ids:
              x-soat-ref: documents
              type: array
              items:
                type: string
              description: Public IDs of documents to retrieve from
            document_paths:
              type: array
              items:
                type: string
              description: Retrieve from all documents matching these path prefixes
            min_score:
              type: number
              description: Minimum similarity score (0–1) for retrieved chunks
            limit:
              type: integer
              description: Maximum number of chunks to inject
            write_memory_id:
              x-soat-ref: memories
              type: string
              nullable: true
              description: Public ID of the memory the agent can write to. When set, a `write_memory` tool is automatically available to the agent.
            extraction:
              description: Automatic fact extraction from completed generation turns (requires write_memory_id). Pass `true` to enable with defaults, or an object to customize the provider, model, and prompt used for the extraction completion.
              oneOf:
                - type: boolean
                - type: object
                  properties:
                    enabled:
                      type: boolean
                      description: Defaults to true when the object form is used. Set false to keep the configuration but disable extraction.
                    ai_provider_id:
                      x-soat-ref: ai-providers
                      type: string
                      description: AI provider override for extraction calls. Must belong to the agent's project. Its default_model becomes the model fallback.
                    model:
                      type: string
                      description: Model override for extraction calls.
                    prompt:
                      type: string
                      description: Replaces the default task instructions. The JSON response contract and the conversation transcript are always appended by the server.
        output_schema:
          type: object
          nullable: true
          description: JSON Schema describing the structured object the model must return. Non-streaming generations are constrained to this schema; the parsed value is returned as `output.object`.

    ActorResourceProperties:
      description: >-
        Creates a stateful conversation actor that wraps an agent or chat
        session and optionally links to a memory store.
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          description: Actor display name
        external_id:
          type: string
          nullable: true
          description: External identifier for idempotent actor creation
        instructions:
          type: string
          nullable: true
          description: Persona-specific instructions
        agent_id:
          x-soat-ref: agents
          type: string
          nullable: true
          description: Linked agent ID (mutually exclusive with chat_id)
        chat_id:
          x-soat-ref: chats
          type: string
          nullable: true
          description: Linked chat ID (mutually exclusive with agent_id)

    AiProviderResourceProperties:
      description: >-
        Configures an LLM provider connection (API key, model, endpoint)
        that agents use to generate responses.
      type: object
      additionalProperties: false
      required:
        - name
        - provider
        - default_model
      properties:
        name:
          type: string
          description: Provider display name
        provider:
          type: string
          enum:
            - openai
            - anthropic
            - google
            - xai
            - groq
            - ollama
            - azure
            - bedrock
            - vertex
            - gateway
            - custom
          description: Provider type
        default_model:
          type: string
          description: Default model identifier (e.g. gpt-4o, claude-3-7-sonnet)
        secret_id:
          x-soat-ref: secrets
          type: string
          nullable: true
          description: Public ID of the secret containing the API key
        base_url:
          type: string
          nullable: true
          description: Custom base URL for the provider API (self-hosted or proxy)
        config:
          type: object
          nullable: true
          description: Provider-specific extra configuration

    ToolResourceProperties:
      description: >-
        Defines a tool (HTTP endpoint, MCP server, SOAT action, or pipeline)
        that agents can invoke during a generation.
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          description: Tool display name
        type:
          type: string
          nullable: true
          description: Tool type hint (e.g. http, mcp, builtin, pipeline)
        description:
          type: string
          nullable: true
          description: Tool description shown to the model
        parameters:
          type: object
          nullable: true
          description: JSON Schema describing the tool's input parameters (free-form, user-defined)
        execute:
          type: object
          nullable: true
          description: HTTP execution configuration. Required for `http` tools.
          properties:
            url:
              type: string
              description: 'Endpoint URL. Supports `{param}` placeholders resolved from tool arguments.'
            method:
              type: string
              nullable: true
              description: 'HTTP method (default: `POST`)'
            headers:
              type: object
              nullable: true
              description: Static headers included in every request
            body_mode:
              type: string
              nullable: true
              description: >-
                Request body encoding for `POST`/`PUT`/`PATCH`: `json`
                (default) or `multipart`. Incompatible with
                `auth.type: aws_sigv4`.
            auth:
              type: object
              nullable: true
              description: >-
                Computed request credential. `type` is `aws_sigv4` (with
                `region`, `service`, `access_key_id`, `secret_access_key` and
                optional `session_token`) or `gcp_service_account` (with
                `credentials` and `scopes`). Credential fields accept
                `{{secret:...}}` references.
        mcp:
          type: object
          nullable: true
          description: MCP server connection configuration. Required for `mcp` tools.
          properties:
            url:
              type: string
              description: MCP server URL
            headers:
              type: object
              nullable: true
              description: Headers included in every MCP request
        actions:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            Allowlist of actions the tool exposes. For `builtin` tools: SOAT
            platform action names. For `mcp` tools: an optional allowlist of MCP
            tool names to scope the server surface (`null` exposes every tool).
        denied_actions:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            For `mcp` tools: an optional denylist of MCP tool names to hide.
            Applied after `actions` and taking precedence over it — the
            ergonomic way to scope a read+write MCP server read-only by denying
            just the write tools. `null` denies nothing.
        context_keys:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            Optional allowlist of `tool_context` keys forwarded to this tool as
            prefixed context headers. `null` or omitted forwards every key;
            `[]` forwards none. The server-pinned identity keys (`session_id`,
            `actor_id`, `actor_external_id`) are always forwarded, and a key
            consumed by a `{{context:<key>}}` token in this tool's own headers
            is substituted regardless of this list.
        preset_parameters:
          type: object
          nullable: true
          description: Pre-filled parameter values injected at execution time
        pipeline:
          type: object
          nullable: true
          description: >-
            Pipeline definition for `pipeline` tools: an ordered `steps` array,
            each invoking another tool by `tool_id` (optional `action`) with an
            `input` built from earlier results via JSON Logic over
            `{ input, steps }`, plus an optional `output` mapping. Step `input`
            keys and `var` paths use camelCase (the runtime form). Free-form,
            user-defined.
        output_mapping:
          type: object
          nullable: true
          description: >-
            Universal JSON Logic mapping applied to the tool's raw result, for
            every tool type. Evaluated over `{ output: <raw result> }`, e.g.
            `{ "var": "output.text" }`. For `pipeline` tools this runs after
            the pipeline's own `output` mapping.
        guardrail_ids:
          x-soat-ref: guardrails
          type: array
          nullable: true
          items:
            type: string
          description: Guardrails attached at the tool scope.

    DatasetResourceProperties:
      description: >-
        Declares an evaluation dataset — the named fixture suite an eval runs an
        agent against. Its test cases are declared separately as `dataset_item`
        resources, so an item curated through the API is never collateral of a
        formation apply. Deleting the dataset deletes its items and the evals
        bound to it.
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          description: Dataset name, unique within the project
        description:
          type: string
          nullable: true
          description: Optional description

    DatasetItemResourceProperties:
      description: >-
        One test case in a dataset: the messages sent to the agent under test
        and, optionally, the reference answer scorers compare against. Editing
        or removing an item never rewrites a run that already scored it — each
        result froze its own copy.
      type: object
      additionalProperties: false
      required:
        - dataset_id
        - input
      properties:
        dataset_id:
          x-soat-ref: datasets
          type: string
          description: Public ID of the parent dataset (or ref expression)
        input:
          type: array
          items:
            type: object
          description: The messages sent to the agent, as `{role, content}` objects
        expected_output:
          type: string
          nullable: true
          description: Reference answer for exact_match / contains / embedding_similarity / llm_judge scorers
        metadata:
          type: object
          nullable: true
          additionalProperties: true
          description: >-
            Free-form tags on the case, e.g. `{"topic": "billing"}`

    EvalResourceProperties:
      description: >-
        Binds an agent under test to a dataset and the scorers its outputs are
        judged by. `pass_threshold` is the pass rate a run must reach for its
        `passed` verdict — the gate an agent-version promotion consumes.
      type: object
      additionalProperties: false
      required:
        - name
        - agent_id
        - dataset_id
        - scorers
      properties:
        name:
          type: string
          description: Eval name, unique within the project
        agent_id:
          x-soat-ref: agents
          type: string
          description: Public ID of the agent under test (or ref expression)
        dataset_id:
          x-soat-ref: datasets
          type: string
          description: Public ID of the dataset to run against (or ref expression)
        scorers:
          type: array
          items:
            type: object
          description: >-
            Scorer configs — `exact_match`, `contains`, `json_logic`,
            `output_schema`, `embedding_similarity`, `llm_judge`, or `tool`.
            Same shape as the evals REST contract.
        pass_threshold:
          type: number
          nullable: true
          description: >-
            0–1. A run passes when its pass rate over non-errored items reaches
            this. Omit for a run that reports scores without a verdict.

    DocumentResourceProperties:
      description: >-
        Stores a text document in a project, optionally indexing it for
        knowledge retrieval.
      type: object
      additionalProperties: false
      required:
        - content
      properties:
        content:
          type: string
          description: Document text content
        path:
          type: string
          nullable: true
          description: Virtual path for organising the document
        filename:
          type: string
          nullable: true
          description: Original filename
        title:
          type: string
          nullable: true
          description: Document title
        metadata:
          type: object
          nullable: true
          description: Arbitrary metadata key-value pairs
        tags:
          type: object
          nullable: true
          description: Tag key-value pairs for filtering
        chunk_strategy:
          type: string
          enum: [page, whole, size]
          description: >-
            How to split the content into embeddable chunks, matching
            `POST /documents`. `whole` (default) stores the content as a single
            chunk; `size` splits into fixed-size character windows with overlap.
            `page` is equivalent to `whole` for plain text.
          default: whole
        chunk_size:
          type: integer
          description: Window size in characters when `chunk_strategy=size`. Defaults to 1000.
        chunk_overlap:
          type: integer
          description: Overlap in characters between consecutive windows when `chunk_strategy=size`. Defaults to 200.

    MemoryResourceProperties:
      description: >-
        Creates a named memory store that actors can read from and write to
        across conversations.
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          description: Memory display name
        description:
          type: string
          nullable: true
          description: What this memory stores
        tags:
          type: array
          nullable: true
          items:
            type: string
          description: Tag strings for filtering

    MemoryEntryResourceProperties:
      description: Adds a single text entry to a memory store.
      type: object
      additionalProperties: false
      required:
        - memory_id
        - content
      properties:
        memory_id:
          x-soat-ref: memories
          type: string
          description: Public ID of the parent memory (or ref expression)
        content:
          type: string
          description: Text content of the memory entry
        source_type:
          type: string
          enum: [manual, agent, extraction, orchestration]
          description: How this entry was created (defaults to manual)
        tags:
          type: array
          nullable: true
          items:
            type: string
          description: Per-entry tag strings for entry-granularity filtering
        metadata:
          type: object
          nullable: true
          additionalProperties: true
          description: Arbitrary structured metadata attached to the entry

    ModelRouteResourceProperties:
      description: >-
        Declares a model route within the formation's project: a named, ordered
        list of provider+model failover targets with retry and circuit-breaker
        configuration. Consumers reference it through their own `model_route_id`,
        or inherit it as the project's `default_model_route_id`.
      type: object
      additionalProperties: false
      required:
        - name
        - targets
      properties:
        name:
          type: string
          description: Route name, unique within the project
        targets:
          type: array
          description: >-
            Ordered failover targets, tried in array order. Each entry is
            `{ ai_provider_id, model, timeout_seconds?, max_retries? }`; every
            provider must belong to this project, and the total attempt budget
            (sum of `1 + max_retries`) is capped at 10.
          items:
            type: object
        retry_on:
          type: array
          description: >-
            Which failure classes fail over: any of `provider_error`, `timeout`,
            `rate_limited`. Defaults to all three. Deterministic rejections
            (400-class, auth, content policy) never fail over.
          items:
            type: string
        failure_threshold:
          type: integer
          nullable: true
          description: >-
            Consecutive retryable failures before a target is skipped (default 3)
        cooldown_seconds:
          type: integer
          nullable: true
          description: How long a tripped target is skipped before being probed again (default 60)

    WebhookResourceProperties:
      description: >-
        Registers an HTTPS endpoint to receive SOAT platform event
        notifications.
      type: object
      additionalProperties: false
      required:
        - name
        - url
        - events
      properties:
        name:
          type: string
          description: Webhook display name
        description:
          type: string
          nullable: true
          description: Optional description
        url:
          type: string
          description: HTTPS endpoint that receives event payloads
        events:
          type: array
          items:
            type: string
          description: Event types to subscribe to (e.g. memory.updated)

    TriggerResourceProperties:
      description: >-
        Binds a starter (manual, webhook, schedule, or event) to an executable
        target (orchestration, agent, tool, or eval). Firings run under the
        confined run-as identity of the caller who deployed the formation, so a
        firing never exceeds what that caller could do directly.
      type: object
      additionalProperties: false
      required:
        - name
        - type
        - target_type
        - target_id
      properties:
        name:
          type: string
          description: Trigger display name (unique within the project)
        description:
          type: string
          nullable: true
          description: Optional description
        type:
          type: string
          enum:
            - manual
            - webhook
            - schedule
            - event
          description: Starter type. Immutable after creation
        target_type:
          type: string
          enum:
            - orchestration
            - agent
            - tool
            - eval
          description: The kind of resource this trigger activates
        target_id:
          type: string
          description: >-
            Public ID of the target resource. Use { "ref": "LogicalId" } to
            reference an orchestration, agent, tool, or eval defined in the
            template.
        action:
          type: string
          nullable: true
          description: Tool targets only — the action for builtin/mcp tools
        input:
          type: object
          nullable: true
          description: Static input shallow-merged under each firing's runtime input
        cron:
          type: string
          nullable: true
          description: 5-field cron expression (UTC). Required when type is schedule
        event_pattern:
          type: string
          nullable: true
          description: >-
            Internal-event subscription pattern (`*`, `prefix.*`, or an exact
            event name). Required when type is event, rejected otherwise
        active:
          type: boolean
          description: Whether the trigger fires (default true)
        policy_id:
          x-soat-ref: policies
          type: string
          nullable: true
          description: >-
            Optional boundary policy that further confines the run-as identity

    ApiKeyResourceProperties:
      description: >-
        Creates an API key scoped to the formation's project and optionally
        restricted by a set of policies. The key is owned by the caller who
        deployed the formation, exactly as one created through the API is, so it
        never carries more access than they already have.
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          description: Human-readable label for the API key
        policy_ids:
          x-soat-ref: policies
          type: array
          items:
            type: string
          description: >-
            Optional list of policy public IDs that further restrict the key's
            permissions

    ChatResourceProperties:
      description: >-
        Creates a chat within the formation's project, connected either to an AI
        provider or — by declaring no provider — to the project's
        `default_model_route_id`.
      type: object
      additionalProperties: false
      properties:
        ai_provider_id:
          x-soat-ref: ai-providers
          type: string
          nullable: true
          description: >-
            Public ID of the AI provider to use for this chat. Omit (or declare
            `null`) to inherit the project's `default_model_route_id`, which
            requires the project to have one and cannot be combined with `model`.
        name:
          type: string
          nullable: true
          description: Human-readable label for the chat
        instructions:
          type: string
          nullable: true
          description: System message to set the assistant behaviour
        model:
          type: string
          nullable: true
          description: Model override; defaults to the AI provider's default model

    ConversationResourceProperties:
      description: >-
        Creates a conversation within the formation's project.
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
          nullable: true
          description: Human-readable label for the conversation
        status:
          type: string
          description: Initial status of the conversation (open or closed)
        actor_id:
          x-soat-ref: actors
          type: string
          nullable: true
          description: Public ID of an actor to associate with this conversation


    FileResourceProperties:
      description: >-
        Registers a file record within the formation's project.
      type: object
      additionalProperties: false
      properties:
        prefix:
          type: string
          nullable: true
          description: Directory within the project. Optional; defaults to / (root). Combined with filename to form the file's key (path).
        filename:
          type: string
          nullable: true
          description: Original / download name and the key's leaf segment.
        content_type:
          type: string
          nullable: true
          description: MIME type of the file
        size:
          type: integer
          nullable: true
          description: File size in bytes
        metadata:
          type: string
          nullable: true
          description: JSON string with additional metadata

    PolicyResourceProperties:
      description: >-
        Creates an access-control policy within the formation's project.
      type: object
      additionalProperties: false
      required:
        - document
      properties:
        name:
          type: string
          nullable: true
          description: Human-readable label for the policy
        description:
          type: string
          nullable: true
          description: Description of what the policy grants
        document:
          type: object
          additionalProperties: true
          description: Policy document containing an array of statements

    SecretResourceProperties:
      description: >-
        Creates an encrypted secret within the formation's project.
      type: object
      additionalProperties: false
      required:
        - name
        - value
      properties:
        name:
          type: string
          description: Human-readable label for the secret
        value:
          type: string
          description: The secret value to encrypt and store

    ProjectPriceResourceProperties:
      description: >-
        Upserts a project-scoped price row so a deployed stack produces
        billing-grade usage cost with no out-of-band pricing step. The row is
        keyed on (provider, model, component, effective_from) within the
        formation's project — the middle pricing tier that covers every one of
        the project's instances of a given provider slug. When `effective_from`
        is omitted the price takes effect at deploy time, so generations run
        right after deploy are priced.
      type: object
      additionalProperties: false
      required:
        - provider
        - model
        - component
        - unit
        - unit_price
      properties:
        provider:
          type: string
          description: SKU vendor slug the price applies to (e.g. openai, anthropic, soat)
        model:
          type: string
          description: SKU identifier — the model id for LLM SKUs, the platform unit otherwise
        component:
          type: string
          description: The billable component this row prices (input_tokens, output_tokens, cached_tokens, compute_second, …)
        unit:
          type: string
          description: Unit the unit_price is denominated in (token, compute_second, …); must match the metered component's unit
        unit_price:
          type: number
          description: USD per unit. Must be a non-negative number
        meter_type:
          type: string
          description: Meter type this SKU belongs to (defaults to llm_tokens)
        effective_from:
          type: string
          format: date-time
          description: >-
            Timestamp from which this price applies. Omit to take effect at
            deploy time. The row with the latest effective_from at or before
            now() prices a call.

    SessionResourceProperties:
      description: >-
        Creates a session attached to an agent within the formation's project.
      type: object
      additionalProperties: false
      required:
        - agent_id
      properties:
        agent_id:
          x-soat-ref: agents
          type: string
          description: Public ID of the agent that owns this session
        name:
          type: string
          nullable: true
          description: Human-readable label for the session
        actor_id:
          x-soat-ref: actors
          type: string
          nullable: true
          description: Public ID of an actor to associate with this session
        auto_generate:
          type: boolean
          description: Whether to automatically generate a response when messages are sent
        inactivity_ttl_seconds:
          type: integer
          description: Number of seconds of inactivity after which the session expires. 0 means never expires.
        tool_context:
          type: object
          additionalProperties: true
          nullable: true
          description: Optional context object passed to tool calls

    IngestionRuleResourceProperties:
      description: >-
        Routes a file content_type to a converter (tool or agent) so
        ingestion can turn non-native files (images, audio, scanned PDFs)
        into Documents. See the Ingestion Rules module docs for the matching
        and converter-invocation model.
      type: object
      additionalProperties: false
      required:
        - content_type_glob
      properties:
        content_type_glob:
          type: string
          description: MIME type glob matched against a file's content_type (e.g. image/*, audio/mpeg, application/pdf)
        tool_id:
          x-soat-ref: tools
          type: string
          nullable: true
          description: Converter tool ID (mutually exclusive with agent_id)
        agent_id:
          x-soat-ref: agents
          type: string
          nullable: true
          description: Converter agent ID (mutually exclusive with tool_id)
        action:
          type: string
          nullable: true
          description: Operation id, required for builtin/mcp tool converters
        preset_parameters:
          type: object
          nullable: true
          description: Merged into the tool input before invocation (tool converters only)
        native_extraction:
          type: string
          nullable: true
          description: >-
            For native types (PDF/text): `first` (default) converts only
            when native extraction yields no text; `skip` always converts.
        file_delivery:
          type: string
          nullable: true
          description: How the file reaches a tool converter — base64 (default) or download_url
        chunk_strategy:
          type: string
          nullable: true
          description: Default chunk strategy (page/whole/size), overridable per ingest request
        chunk_size:
          type: integer
          nullable: true
          description: Default window size in characters for the size strategy
        chunk_overlap:
          type: integer
          nullable: true
          description: Default overlap in characters for the size strategy
        metadata:
          type: object
          nullable: true
          description: Arbitrary JSON metadata

    OrchestrationResourceProperties:
      description: >-
        Creates a DAG orchestration that wires agents, tools, and knowledge
        lookups into a repeatable pipeline within the formation's project. Node
        resource references (`agent_id`, `tool_id`, `memory_id`,
        `orchestration_id`) accept `{ "ref": "LogicalId" }` expressions to point
        at other resources declared in the same template — the basis for
        deploying an agent "squad" (a team of agents plus the flow that
        coordinates them) as a single stack.
      type: object
      additionalProperties: false
      required:
        - name
        - nodes
        - edges
      properties:
        name:
          type: string
          description: Human-readable name for the orchestration
        description:
          type: string
          nullable: true
          description: Optional description of what the orchestration does
        nodes:
          type: array
          description: >-
            Ordered list of node definitions. A node's resource references
            (`agent_id`, `tool_id`, `memory_id`, `orchestration_id`) may use
            `{ "ref": "LogicalId" }` to bind to other resources in the template.
          items:
            type: object
            additionalProperties: true
        edges:
          type: array
          description: Directed connections between nodes
          items:
            type: object
            additionalProperties: true
        state_schema:
          type: object
          nullable: true
          additionalProperties: true
          description: Optional JSON Schema describing the run state
        input_schema:
          type: object
          nullable: true
          additionalProperties: true
          description: Optional JSON Schema describing the run input

    WorkflowResourceProperties:
      description: >-
        Creates a workflow — a state-machine definition (named states, allowed
        transitions, guards, and per-state automation) that tasks live in. State
        and transition dispatch references (`agent_id`, `orchestration_id`,
        `tool_id` inside an `on_enter` block) accept `{ "ref": "LogicalId" }`
        expressions to point at agents, orchestrations or tools declared in the
        same template, so a workflow plus the agents and tools that service its
        states can deploy as one stack. Mirrors
        the workflows REST contract (`states`, `transitions`, `payload_schema`).
      type: object
      additionalProperties: false
      required:
        - name
        - states
        - transitions
      properties:
        name:
          type: string
          description: Human-readable name for the workflow, unique within the project
        description:
          type: string
          nullable: true
          description: Optional description of what the workflow models
        states:
          type: array
          description: >-
            Named states. Exactly one must be `initial: true`; any number may be
            `terminal: true`. A `kind: human` state parks the task until a
            transition fires; an `on_enter` block dispatches one agent generation
            or orchestration run on entry.
          items:
            type: object
            additionalProperties: true
        transitions:
          type: array
          description: >-
            Named, directional moves between states. Each has `from` (source
            states) and `to` (one target), an optional JSON Logic `guard`, and an
            optional `requires_approval` gate.
          items:
            type: object
            additionalProperties: true
        payload_schema:
          type: object
          nullable: true
          additionalProperties: true
          description: Optional JSON Schema describing a task's payload

    QuotaResourceProperties:
      description: >-
        Creates a quota — a project-scoped cap that blocks (`enforce`) or reports
        (`monitor`) when a windowed aggregate is exceeded. `requests` quotas are
        enforced by the request middleware; `tokens`/`cost_usd` quotas at the
        pre-generation check. Mirrors the quotas REST contract; `scope`,
        `metric`, and `window` are immutable after creation (only `limit`,
        `mode`, and `on_unpriced` update).
      type: object
      additionalProperties: false
      required:
        - scope
        - metric
        - window
        - limit
      properties:
        scope:
          type: string
          enum: [project, api_key, agent, actor]
          description: The scope the quota applies to
        scope_ref:
          type: string
          nullable: true
          description: >-
            Public id of the api key / agent / actor the quota applies to. For
            `api_key` and `agent` scope, NULL means all entities of that scope
            type in the project. For `actor` scope, NULL means one budget *per*
            actor rather than a pooled total across all actors.
        metric:
          type: string
          enum: [requests, tokens, cost_usd]
          description: The metric being capped
        window:
          type: string
          enum: [rolling_1m, rolling_1h, rolling_24h, calendar_month]
          description: The window over which the metric is aggregated
        limit:
          type: number
          description: >-
            The cap. Positive integer for requests/tokens; fractional allowed for
            cost_usd.
        mode:
          type: string
          enum: [enforce, monitor]
          description: enforce blocks with 429; monitor fires the webhook only
        on_unpriced:
          type: string
          enum: [block, allow]
          description: >-
            Only for metric cost_usd. What an enforce quota does over a pricing
            blackout — block (the default) refuses generations with 409
            QUOTA_UNENFORCEABLE, allow accepts the unmeasurable spend. See the
            quotas REST contract.

    GuardrailResourceProperties:
      description: >-
        Creates a guardrail — an action-class document (`class`/`guard`) that
        gates tool-call autonomy. Attach it to a tool or agent via that
        resource's `guardrail_ids` (a `{ "ref": … }` to this resource in the
        same template resolves to its physical id at deploy time). Mirrors the
        guardrails REST contract; `class`/`default_class`/`guard`/`escalate`
        are flattened here from the REST API's single `document` object.
      type: object
      additionalProperties: false
      required:
        - name
        - class
      properties:
        name:
          type: string
          description: Human-readable name
        description:
          type: string
          nullable: true
          description: Optional description
        class:
          description: >-
            A class literal (`A` / `B` / `C` / `D`) or a JSON Logic expression
            returning one. An invalid result resolves to `default_class`.
          oneOf:
            - type: string
              enum: [A, B, C, D]
            - type: object
        default_class:
          type: string
          enum: [A, B, C, D]
          description: >-
            Applied when the `class` expression returns anything other than a
            valid class. Defaults to `C` (fail-closed).
        guard:
          type: object
          nullable: true
          description: >-
            A single JSON Logic expression; when the call classifies as `B` it
            executes only if this evaluates truthy.
        escalate:
          type: boolean
          nullable: true
          description: When true, a passing guard still files an approval item.
        context_tool_id:
          x-soat-ref: tools
          type: string
          nullable: true
          description: >-
            Optional tool the platform calls at evaluation time to fetch fresh
            guardrail context.
        context_mode:
          type: string
          nullable: true
          enum: [merge, replace, null]
          description: >-
            How tool-fetched context combines with the caller-supplied context.

    ResourceDeclaration:
      type: object
      required:
        - type
        - properties
      properties:
        type:
          type: string
          pattern: '^[a-z][a-z0-9_]*$'
          description: >
            Resource type. The built-in types are `ai_provider`, `tool`,
            `agent`, `actor`, `api_key`, `chat`, `conversation`, `dataset`,
            `dataset_item`, `document`, `file`, `guardrail`, `ingestion_rule`,
            `memory`, `memory_entry`, `model_route`, `eval`, `orchestration`,
            `policy`, `project_price`, `quota`, `secret`, `session`, `webhook`,
            `trigger` and `workflow`.


            This is deliberately not an enum: a deployment operator can
            register additional resource types backed by their own handler, and
            those are declared here exactly like a built-in one. The set a given
            deployment accepts is authoritative in the server, which rejects an
            unregistered type with `VALIDATION_FAILED` and lists what it does
            support.
        properties:
          type: object
          additionalProperties: true
          description: >
            Resource properties, as authored in the template and echoed back
            verbatim. The allowed fields, required fields, and field types for
            each resource `type` are defined by the corresponding
            `<Type>ResourceProperties` schema in this document (e.g.
            `model_route` → `ModelRouteResourceProperties`), which the server
            enforces at validate/deploy time. The declaration itself is
            free-form here because property values may be substitution
            expressions rather than final values: `{ "ref": "logicalId" }`
            references another resource's physical ID,
            `{ "param": "ParamName" }` substitutes a parameter value, and
            `{ "sub": "text ${ParamName}" }` interpolates parameters into a
            string.
        depends_on:
          type: array
          items:
            type: string
          description: Explicit dependency list. In addition to implicit `ref` dependencies.
          nullable: true
        deletion_policy:
          type: string
          enum:
            - delete
            - retain
          description: >
            Controls what happens to the physical resource when it is removed
            from the stack. `delete` (default) deletes the physical resource.
            `retain` keeps the physical resource alive and only removes the
            formation record. Omit it to get `delete`; an explicit `null` is
            rejected.
        metadata:
          type: object
          additionalProperties: true
          nullable: true

    FormationResource:
      type: object
      properties:
        id:
          type: string
          description: Public ID of the resource record
        logical_id:
          type: string
          description: Logical identifier from the template
        resource_type:
          type: string
          description: Resource type (e.g. agent, memory)
        physical_resource_id:
          type: string
          nullable: true
          description: Public ID of the physical SOAT resource
        status:
          type: string
          enum:
            - pending
            - created
            - updated
            - deleted
            - failed
          description: Current resource status

    Formation:
      type: object
      properties:
        id:
          type: string
          description: Public ID of the formation
          example: form_V1StGXR8Z5jdHi6B
        project_id:
          x-soat-ref: projects
          type: string
          description: Project public ID
        name:
          type: string
          description: Human-readable formation name
        template:
          $ref: '#/components/schemas/FormationTemplate'
        outputs:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: Resolved output values after stack deployment
        status:
          type: string
          enum:
            - creating
            - active
            - updating
            - failed
            - deleting
            - deleted
            - delete_failed
          description: Formation status
        metadata:
          type: object
          additionalProperties: true
          nullable: true
          description: >
            Static annotations stored on the formation record (supplied at
            create/update). Not a substitution site — `sub`/`param`/`ref`
            expressions are rejected. Use the template's top-level `metadata`
            block for deploy-time substitution (see `resolved_metadata`).
        resolved_metadata:
          type: object
          additionalProperties: true
          nullable: true
          description: >
            The template's top-level `metadata` block after parameter (`sub`/`param`)
            and resource (`ref`) substitution at the last deploy. Null when the
            template declares no metadata.
        resolved_parameters:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: >
            Parameter values applied at the last deploy, for auditability.
            `no_echo` parameters are masked (`***`). Null when the template
            declares no parameters.
        error:
          allOf:
            - $ref: '#/components/schemas/FormationError'
          nullable: true
          description: >
            Why the formation is `failed` or `delete_failed`, in the same
            `{ code, message, meta }` shape as an error response. Null in every
            other status, and cleared by the next successful deploy. This is the
            reason a `2xx` deploy response can report `status: "failed"` without
            a second call to `list-formation-events`.


            One case carries an error while the formation is `active`:
            `FORMATION_REPLACE_CLEANUP_FAILED`, when a deploy replaced a
            resource and the superseded one could not be deleted. The desired
            state is realised, so the deploy succeeded — but the old resource is
            still live, and `meta.failures` names it. It stays on the formation
            as pending cleanup and is retried on the next deploy or teardown,
            which clears the error once it is gone.
        resources:
          type: array
          items:
            $ref: '#/components/schemas/FormationResource'
          description: Resources managed by this formation (present on get/create/update)
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ValidationError:
      type: object
      properties:
        path:
          type: string
          description: JSON path to the field with the error
        message:
          type: string
          description: Error description

    ValidationResult:
      type: object
      properties:
        valid:
          type: boolean
        errors:
          type: array
          items:
            $ref: '#/components/schemas/ValidationError'
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/ValidationError'

    PlanChange:
      type: object
      properties:
        logical_id:
          type: string
        resource_type:
          type: string
        action:
          type: string
          enum:
            - create
            - update
            - delete
            - no-op
        physical_resource_id:
          type: string
          description: >-
            The existing resource's physical ID. Present for update / no-op /
            delete actions, absent for create.
        diff:
          type: object
          description: >-
            Resolved desired-state properties (post parameter/ref
            substitution) and, when available, the current live or
            last-applied properties they were compared against. Omitted when
            neither side could be computed (e.g. an unregistered resource
            type).
          properties:
            desired:
              type: object
              additionalProperties: true
            current:
              type: object
              additionalProperties: true
              nullable: true

    PlanResult:
      type: object
      properties:
        changes:
          type: array
          items:
            $ref: '#/components/schemas/PlanChange'
        unauthorized_actions:
          type: array
          description: >-
            The per-resource actions the caller may not perform. A formation may
            only do what the caller could do directly, so applying this template
            would be refused while any of these remain. Absent when the caller
            may perform every action the plan implies. A plan itself changes
            nothing, so it reports them rather than failing.
          items:
            $ref: '#/components/schemas/UnauthorizedFormationAction'

    UnauthorizedFormationAction:
      type: object
      required:
        - logical_id
        - resource_type
        - action
      properties:
        logical_id:
          type: string
          description: The template's own name for the resource.
          example: MyGuardrail
        resource_type:
          type: string
          description: The declared resource type.
          example: guardrail
        action:
          type: string
          description: The action the caller lacks.
          example: guardrails:CreateGuardrail

    FormationError:
      type: object
      description: >-
        Why a deploy or teardown failed, in the one error shape the API has.
        Carried on the formation itself and on the operation that failed.
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: >-
            The failing operation's error code (`VALIDATION_FAILED`,
            `RESOURCE_NOT_FOUND`, `FORMATION_DELETE_FAILED`,
            `FORMATION_REPLACE_CLEANUP_FAILED`, …), or `UNKNOWN` when the
            underlying failure carried no code.
          example: VALIDATION_FAILED
        message:
          type: string
          description: The failure, as reported by the resource that raised it.
          example: >-
            dataset_id is immutable: item 'dsit_V1StGXR8Z5jdHi6B' belongs to
            'dset_V1StGXR8Z5jdHi6B'. Declare a new dataset_item instead.
        meta:
          type: object
          additionalProperties: true
          description: >-
            Context for the failure. A failed apply names the resource that
            broke it (`logical_id`, `resource_type`); a failed teardown lists
            every blocker under `failures`, and so does a succeeded deploy that
            could not dispose of a replaced resource — there each entry adds the
            `physical_resource_id` still live.
          example:
            logical_id: case1
            resource_type: dataset_item

    FormationEvent:
      type: object
      properties:
        timestamp:
          type: string
          format: date-time
        logical_id:
          type: string
        resource_type:
          type: string
        action:
          type: string
          description: >-
            What the deploy did to the resource: `create`, `update`, `delete`,
            `no-op`, `rollback` (a resource created earlier in this deploy that
            was walked back after a later failure), or `rollback-skipped` (a
            `deletion_policy: retain` resource left standing by that unwind).
          example: rollback
        status:
          type: string
          enum:
            - succeeded
            - failed
        physical_resource_id:
          type: string
          nullable: true
        error:
          type: string
          nullable: true

    FormationOperation:
      type: object
      properties:
        id:
          type: string
          description: Public ID of the operation
        operation_type:
          type: string
          enum:
            - validate
            - plan
            - create
            - update
            - delete
        status:
          type: string
          enum:
            - pending
            - running
            - succeeded
            - failed
        events:
          type: array
          items:
            $ref: '#/components/schemas/FormationEvent'
          nullable: true
        plan:
          allOf:
            - $ref: '#/components/schemas/PlanResult'
          nullable: true
        error:
          allOf:
            - $ref: '#/components/schemas/FormationError'
          nullable: true
          description: >-
            Why this operation failed. Null for a succeeded or running
            operation. The same bag the formation itself carries while that
            failure is its current state.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
