openapi: 3.1.0
info:
  title: Necto platform API
  version: 0.1.0
  description: 'The HTTP surface serving three views: the developer creates a governed

    agent, the security owner reviews the policy diff, the operator works

    the agent card. No workflow or marketplace endpoint. **The contract

    changes before the implementation does, never after.**


    The platform API is a private in-cluster service with no public hostname;

    a public endpoint will be published only once external reach is decided.

    '
security:
- assertion: []
tags:
- name: agents
  description: 'Developer view: create a governed agent'
- name: releases
  description: 'Security owner view: policy diff and approval'
- name: operations
  description: 'Operator view: agent card, evidence, stop, revoke'
- name: promotions
  description: 'Application customer plane: promote one application through dev, stage
    and prod'
- name: collaboration
  description: 'Application customer plane: the tenant''s private collaboration roles
    and their first administrative surface'
- name: applications
  description: 'Application customer plane: register the primary customer object and
    read it back'
- name: collaboration
  description: 'Application customer plane: the tenant''s private collaboration journal
    and version history'
paths:
  /agents:
    get:
      tags:
      - agents
      x-necto-served: false
      summary: List governed agents
      description: Not yet available.
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Agents visible to the caller
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AgentSummary'
  /agents/profile/compile:
    post:
      tags:
      - agents
      x-necto-served: false
      summary: Compile a profile and return its artifacts
      description: Not yet available.
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Compiled artifacts and their hashes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompiledProfile'
        '422':
          description: Unknown field, missing required field, or denied default
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /agents/{agentId}/changes:
    post:
      tags:
      - agents
      x-necto-served: false
      summary: Open a pull request changing this agent's profile
      description: Not yet available.
      parameters:
      - $ref: '#/components/parameters/agentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - profile
              properties:
                profile:
                  type: string
                  description: necto.yaml content
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: Pull request opened
          content:
            application/json:
              schema:
                type: object
                required:
                - pullRequestUrl
                properties:
                  pullRequestUrl:
                    type: string
                    format: uri
  /tasks/compile:
    post:
      tags:
      - agents
      summary: 'Compile a `kind: Task` and check that it narrows, never widens'
      description: 'A task compiles to a restriction set intersected with the target

        agent''s already-approved compiled artifacts. Because a task widens no

        compiled artifact, a narrowed result needs no approval. If the

        intersection is not exactly what the task requested, compiling is

        refused with the offending field, never a coerced narrowing.

        '
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: The narrowing result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskCompileResult'
        '404':
          description: No such agent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'The task did not parse, or it widened the agent''s authority;

            either way the offending field is named. A widening refusal also

            carries `requested` and `granted`.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /tasks:
    get:
      tags:
      - agents
      summary: List scheduled tasks
      description: 'Every `kind: Task` apply that carried a `schedule`. A one-shot
        task,

        one with no `schedule`, runs once and is never listed here, the same

        way `nectoctl apply -f necto.yaml` never lists a run.


        A machine caller reads only the scheduled tasks of the one agent its

        operation is scoped to, whether or not it names that agent; naming

        another agent is refused. A human caller reads every scheduled

        task.

        '
      parameters:
      - name: agentId
        in: query
        required: false
        schema:
          type: string
        description: 'The agent whose scheduled tasks to list. Optional for a

          machine caller, whose own operation already scopes the answer;

          naming another agent is refused. A human caller reads every

          agent''s tasks.

          '
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Scheduled tasks visible to the caller
          content:
            application/json:
              schema:
                type: object
                required:
                - tasks
                properties:
                  tasks:
                    type: array
                    items:
                      $ref: '#/components/schemas/TaskSummary'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /releases:
    get:
      tags:
      - releases
      x-necto-served: false
      summary: List releases awaiting review
      description: Not yet available.
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Releases
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ReleaseSummary'
    post:
      tags:
      - releases
      summary: Compile a profile and open a release
      description: 'Compiles, opens a release awaiting approval, and stops; it never

        changes a running agent. `via: pull_request` is the default and the

        required carrier in a customer production environment; `via: direct`

        records the profile in Necto''s release store instead of a git commit,

        and is refused with 422 there. `imageDigest` is the prebuilt artifact

        the release binds -- an E2B template build id (a UUID), built outside

        this process -- and the governed run starts its sandbox

        from it. Optional only for a release that binds no artifact; a governed

        run refuses such a release rather than starting from a stage''s default

        template. A template name, alias or tag is refused: it is movable, and a

        rebuild would change what runs with no new release. An OCI image digest

        is refused until an OCI path exists; only E2B template ids are accepted

        today.


        A release for an agent the platform does not hold registers it from

        this profile: its diff is computed against a profile that grants

        nothing, so every source, tool, egress destination and authority

        change shows as added. A release for an agent the platform holds is

        diffed against the profile that agent''s last release registered.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - agentId
              - profile
              properties:
                agentId:
                  type: string
                profile:
                  type: string
                  description: necto.yaml content
                via:
                  type: string
                  enum:
                  - pull_request
                  - direct
                  default: pull_request
                imageDigest:
                  type: string
                  description: 'The prebuilt artifact the release binds: an E2B template

                    build id (a UUID), never a template name, alias or tag, and

                    never an OCI image digest until an OCI path exists.

                    '
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: Release opened
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReleaseSummary'
        '422':
          description: '`via: direct` refused because the context environment is

            production, the profile failed to compile, `agentId` differs

            from the profile''s `name`, or `agentId` or `profile` is a

            count where an identity is required: a count is never an

            identity, and no release is created from one

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /releases/{releaseId}/diff:
    get:
      tags:
      - releases
      summary: The compiled policy diff
      description: 'Computed on compiled artifacts, not on file text. A formatting-only

        change returns `noPolicyChange: true` with an empty `changes` array.

        The release''s own approval window is `approvalExpiresAt`, and the

        recorded standing approval is rendered beside the diff with its own

        `expiresAt` when one is recorded and not revoked, so a client can

        warn before either lapses and renew it while the window admits a

        second approval.

        '
      parameters:
      - $ref: '#/components/parameters/releaseId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: 'Policy diff, with the release''s approval window, the recorded

            standing approval when one is live, and the standing-approval

            proposal the approver would approve beside it. The proposal is

            the deployment''s own resolution, never a request value; absent

            when nothing resolves, or replaced by the recorded revocation

            when the release''s grant has been revoked.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyDiff'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /releases/{releaseId}/tests:
    get:
      tags:
      - releases
      summary: Required-test results for this release
      parameters:
      - $ref: '#/components/parameters/releaseId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: One entry per required test, with its stored artifact
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RequiredTestResult'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /releases/{releaseId}/approval:
    post:
      tags:
      - releases
      summary: Approve or reject the compiled diff hash
      description: '**The approver approves a hash, not a description.** The request

        carries `diffHash`; if it no longer matches the release''s current

        hash the approval is refused with 409. Refused with 409 when a

        required test failed, or when a citation no longer resolves.

        '
      parameters:
      - $ref: '#/components/parameters/releaseId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - decision
              - diffHash
              properties:
                decision:
                  type: string
                  enum:
                  - approve
                  - reject
                diffHash:
                  type: string
                reasonCode:
                  type: string
                  enum:
                  - not-needed
                  - wrong-scope
                  - test-failure
                  - citation-gone
                  - unsafe-change
                  - other
                  description: 'A rejection''s structured reason code, from the closed

                    set. A rejection requires one; an approval carries

                    neither a code nor a reason text. A code outside the

                    set is refused 422 with `field: reasonCode`, and

                    nothing is recorded.

                    '
                reason:
                  type: string
                  maxLength: 200
                  description: 'Optional free text stored beside a rejection''s code,

                    shown beside the decision and escaped on render. It is

                    never parsed for ids or commands, and a text over 200

                    characters is refused 422 with `field: reason`.

                    '
                standingApproval:
                  type: object
                  description: 'The standing Slack grant the approver gives with this

                    approval: the channel id the diff read exposed, the caps

                    and the expiry the approver confirms. `channelId` is an

                    echo of the deployment''s resolution -- the API refuses any

                    other value -- and only an `approve` decision may carry it.

                    '
                  required:
                  - channelId
                  - caps
                  - expiresAt
                  properties:
                    channelId:
                      type: string
                      description: 'The channel id the approver saw on

                        `GET /releases/{releaseId}/diff`, echoed back. It

                        must equal the deployment''s resolution, never a

                        channel id the caller chose.

                        '
                    caps:
                      type: object
                      description: 'The standing caps, echoed as the approver confirms

                        them. A different value is refused, never stored.

                        '
                      required:
                      - perRun
                      - perDay
                      properties:
                        perRun:
                          type: integer
                        perDay:
                          type: integer
                    expiresAt:
                      type: string
                      format: date-time
                      description: 'The grant''s expiry, at most 30 days after the

                        decision. A later instant is refused, never

                        shortened silently.

                        '
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Decision recorded in Necto's evidence store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApprovalRecord'
        '403':
          description: 'The caller authenticated and Necto grants them no approval

            authority. No identity-provider claim changes this. Distinct from

            401, and never a read-only 200. A machine client never

            approves a release at all: its approval request is refused 403

            even when its client-principal record lists this route.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '409':
          description: 'Hash no longer current (approval void), required test failed,
            an

            unresolvable citation, a widening release approved by the customer

            without the Necto approval the two-party rule needs, or a standing

            approval that does not match the deployment''s resolution: the echoed

            channel id is not the resolved one, the release''s agent has no

            scheduled task, or the deliver alias resolves to no channel. `title`

            names which, because a caller cannot act on them the same way. A

            widening release needs both parties'' distinct approvals on one hash:

            a customer''s approval without the Necto approval is refused and

            nothing is recorded, while a Necto approval recorded before the

            customer''s own is one of two and the release is not approved and

            not runnable until the customer''s own distinct approval binds the

            same hash. A non-widening release is approvable by the customer''s

            security owner alone.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '410':
          description: 'Unapproved release expired after seven days. What would have
            been

            approved is no longer approvable.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'The body is malformed: `decision` or `diffHash` is missing,

            `diffHash` is a count where a release identity is required, a

            rejection carries no `reasonCode`, an unknown `reasonCode`, a

            `reason` over 200 characters, or a code or text on an

            approval; `standingApproval` is malformed, names caps other

            than the standing approval''s, or an expiry that is not a

            timezone-aware instant inside the standing approval''s window,

            or rides a `reject`; or the release''s own compiled profile

            does not compile `write_external`. `field` names which, and

            nothing is recorded.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /releases/{releaseId}/standing-approval/revoke:
    post:
      tags:
      - releases
      summary: Revoke the release's standing Slack approval
      description: '**Any human holding approval authority for this agent may

        revoke, and the act is recorded.** Revoking is never harder

        than granting, so the grant''s original approver is not required.

        Every standing grant the release''s current diff hash bounds is

        revoked, never only the latest one; the revocation is checked at

        the next scheduled mint and never interrupts a send already in

        flight (the named lag). The grants are not edited: the revocation

        stands beside them. A machine principal may never approve or

        revoke.

        '
      parameters:
      - $ref: '#/components/parameters/releaseId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Revocation recorded in Necto's evidence store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StandingApprovalRevocation'
        '403':
          description: 'The caller authenticated and Necto grants them no approval

            authority, or the caller is a machine principal. A machine

            principal may never approve or revoke; no identity-provider

            claim changes either.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '404':
          description: 'No release under this id, or no standing approval is recorded

            for the release''s current diff hash.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications:
    get:
      tags:
      - applications
      summary: List the deployment's applications
      description: 'Every application this deployment holds, in creation order. The

        tenant is the deployment''s own; no request field selects it, and

        another customer''s applications are never listed.

        '
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Applications visible to the caller
          content:
            application/json:
              schema:
                type: object
                required:
                - applications
                properties:
                  applications:
                    type: array
                    items:
                      $ref: '#/components/schemas/Application'
        '403':
          $ref: '#/components/responses/NoAuthority'
    post:
      tags:
      - applications
      summary: Register an application
      description: '**The application is the primary customer object.** One

        application keeps one stable identity across `dev`, `stage` and

        `prod`; this route registers that identity, and every environment,

        plan, deployment and record below keys on it. The body names the

        application and the source it is created from — one of the four

        accepted sources. The field set is closed: there is no field for

        code, a credential or persistent data, and an unexpected field is

        refused rather than ignored. The tenant is the deployment''s own and

        never a request field; the creator is the assertion-resolved

        subject, never a body field.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              - source
              properties:
                name:
                  type: string
                  description: The application's display name
                source:
                  $ref: '#/components/schemas/ApplicationSource'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: Application registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Application'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '422':
          description: 'A field is missing or malformed, the source kind is not one
            of

            the four accepted sources, the OCI reference is not addressed by

            digest, or the body carries a field outside the closed set.

            `field` names it.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}:
    get:
      tags:
      - applications
      summary: Read one application
      description: 'One application by its stable identity, or `404` when this deployment

        holds none — including an application of another customer, which is not

        readable through this deployment''s store.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: The application
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Application'
        '404':
          description: No such application for this deployment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /applications/{applicationId}/services:
    get:
      tags:
      - applications
      summary: Read the managed state one application environment owns
      description: '**One logical database and role, bucket and queue per application

        environment**, each with its own state and its own credential

        *reference*. Identifiers only — no field carries a credential value

        and no field carries payload content. The environment is the

        deployment''s own selection; an environment the application holds no

        state for answers an empty list. Another customer''s application is

        not one this deployment holds, and its read is a `404`.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      - $ref: '#/components/parameters/environment'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: The managed state this application environment owns
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApplicationServices'
        '404':
          description: No such application for this deployment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'The `environment` query parameter is missing, or names an

            environment outside `dev`, `stage` and `prod`. `field` names it.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
    post:
      tags:
      - applications
      summary: Set up the managed state one application environment owns
      description: '**The platform derives every name and reference; the request names

        only the environment.** The logical database and role, bucket and

        queue names, and their credential references are the

        application-environment derivation''s, never body fields. Setup is per

        service: while a provider does not answer, that service''s state is

        recorded `failed`, never reported as success, and no credential is

        issued for it. A body field outside the closed set is refused

        rather than ignored. The tenant is the deployment''s own; no request

        field selects it.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApplicationServiceSetup'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: The state recorded for this application environment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApplicationServices'
        '404':
          description: No such application for this deployment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'A field is missing or malformed, the environment is not

            one of the three, or the body carries a field outside the

            closed set. `field` names it.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /applications/{applicationId}/secrets:
    post:
      tags:
      - applications
      summary: Record a secret's reference
      description: '**The value is held by the secret manager; the platform records
        a

        reference.** The body carries the secret''s name and value exactly

        once; the answer carries the reference the platform derived from

        the tenant, the application, the environment and the name, and never

        the value. No record the platform writes holds a value. A blank

        name or value is refused rather than defaulted. The tenant is the

        deployment''s own; no request field selects it.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApplicationSecret'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: The secret's name and recorded reference
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SecretReference'
        '404':
          description: No such application for this deployment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'A field is missing or malformed, the environment is not

            one of the three, or the body carries a field outside the

            closed set. `field` names it.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /applications/{applicationId}/plans:
    get:
      tags:
      - applications
      summary: Read the application's plan versions
      description: 'Every plan version this application holds, oldest first. A version
        is

        immutable and identified by a content hash over the tenant key, the plan

        and its parent, so reading one back is reading what was recorded —

        never a later correction. `provenance` states, per plan field, whether

        the winning value came from detection or an explicit value. The tenant

        is the deployment''s own and no request field selects it; another

        customer''s application is not one this deployment holds, and its read

        is a `404`.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: The application's plan versions, oldest first
          content:
            application/json:
              schema:
                type: object
                required:
                - applicationId
                - versions
                properties:
                  applicationId:
                    type: string
                  versions:
                    type: array
                    items:
                      $ref: '#/components/schemas/ApplicationPlanVersion'
        '404':
          description: No such application for this deployment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
    post:
      tags:
      - applications
      summary: Detect a plan and record its version
      description: '**Detection produces the visible, editable plan; the plan grants
        no

        authority.** The application''s registered source selects the

        construction path: a repository or template is detected through the

        adapter''s closed input contract, a Dockerfile through the minimal

        read, and a prebuilt image skips detection. A key outside a closed

        contract, a language outside the fixture-tested set, and a detection

        that does not resolve to one plan are all refused rather than

        guessed; support is claimed per tested fixture only. `saved` is the

        saved project setting level; a field no level supplies is a refusal,

        never a default. The resolved plan is minted as an immutable version

        and answered with its per-field provenance. A detection resolving to

        the plan the application already holds answers with that version and

        records nothing new. The tenant is the deployment''s own and no request

        field selects it.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApplicationPlanDetection'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: Plan version recorded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApplicationPlanVersion'
        '200':
          description: 'The plan the application already holds; identical content
            is

            the same version, so nothing new is recorded

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApplicationPlanVersion'
        '404':
          description: No such application for this deployment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '422':
          description: 'A body field outside the closed set, a key outside the

            adapter''s or the saved setting''s closed contract, a language

            outside the fixture-tested set, a detection that does not

            resolve to one plan, or a Dockerfile the minimal read cannot

            resolve. `field` names it.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}/plans/corrections:
    post:
      tags:
      - applications
      summary: Correct the current plan version
      description: '**A correction is a new immutable version, never a mutation of

        the one it corrects.** The body''s `values` is the closed

        `necto.app.yaml` schema — the correctable plan fields. A key

        outside it, `toolchain` and `source` included, is refused as

        unknown, and `customer_id` is refused like any other unknown key

        because the deployment supplies the tenant key, never a request.

        Every field the correction does not set keeps the value and the

        provenance it had; every field it sets carries the entered level.

        The corrected plan must differ from the current version: identical

        content is the same version, refused rather than recorded as a second

        one. The corrected version''s parent is the current version, so a

        correction never forks the lineage. An application with no recorded

        plan has nothing to correct and answers `404`.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApplicationPlanCorrection'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: Corrected plan version recorded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApplicationPlanVersion'
        '404':
          description: 'No such application for this deployment, or the application

            holds no plan version to correct

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '422':
          description: '`values` is missing or not the closed `necto.app.yaml`

            schema, a key outside it is refused as unknown, or the

            correction changes nothing — identical content is the same

            version, not a new one. `field` names it.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}/guardrails:
    get:
      tags:
      - applications
      summary: Read the application's graph and default guardrails
      description: '**The read-only graph and the default guardrails, resolved for
        one

        application''s accepted plan version.** Every field carries the source

        it came from and the accepted plan version it was read at. The plan

        is topology only: a graph field is never compiled into a guardrail,

        so a tool, source, destination or credential appears only where

        the governed profile declares it. The placement is computed from

        the accepted plan''s requested tier and the governed profile, never

        customer-forced. A refusal — a conflicting placement or a governed

        profile that does not compile — is visible in `refusals` with the

        policy decision''s own reason and safe metadata, never a generic

        application error. The tenant is the deployment''s own; another

        customer''s application is a `404`.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: The graph, the default guardrails and any visible refusal
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApplicationGuardrails'
        '404':
          description: 'No such application for this deployment, or the application

            holds no accepted plan version.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /applications/{applicationId}/deployments:
    post:
      tags:
      - applications
      summary: Deploy the accepted plan to dev
      description: '**A dev deployment gets an automatic record; no approval.** The

        accepted plan version is named by its content hash and reconciled

        into the application environment''s resources: standard components

        become restricted Kubernetes resources and governed components

        runtime start requests. Placement is computed by the platform — the

        namespace from the tenant, application and environment, and the tier

        from the accepted plan and governed profile — and neither is read

        from a body field. The governed profile hash and the

        compiled-artifact hashes on the record are the platform''s own

        derived values. A deployment whose governed profile or compiled

        artifacts differ from the environment head record''s is refused

        without an approval; the deployment-approval path is not built yet.

        Nothing reaches a cluster here; the live adapters are a later ticket.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApplicationDeployment'
      responses:
        '503':
          description: 'The namespace derivation or the tier classification seam is
            not

            wired. Refused fail-closed; never defaulted.


            The state store is busy when another writer holds its write

            lock past the store''s own wait: the request is answered `503`

            with a `Retry-After` header before the route acts. The store

            releases its own lock before refusing, so later operations

            proceed.

            '
          headers:
            Retry-After:
              schema:
                type: integer
              description: 'Seconds to wait before retrying a busy state store.

                '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: Deployed; the environment's automatic deployment record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeploymentRecord'
        '404':
          description: No such application for this deployment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '409':
          description: 'The deployment changes the governed profile or the compiled

            artifacts the environment head record carries, and no approval is

            bound to the change. The deployment-approval path is not built

            yet, so the deploy refuses rather than landing unapproved.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'A body field outside the closed set, a missing or malformed

            image digest, an accepted plan hash the application does not

            hold, a governed component with no registered governed

            profile, or a placement the platform refuses. `field` names it.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}/promotions:
    post:
      tags:
      - promotions
      summary: Open a promotion request to the next environment
      description: '**One hop at a time, dev to stage to prod; skipping a stage is
        a

        refusal, never a warning.** The request carries only an image digest,

        an accepted plan version and identifiers — there is no field that

        could carry a credential or persistent data, and none is accepted.

        The governed profile and compiled-artifact hashes the approvers sign

        are the platform''s own derivation from the application''s registered

        governed profile, never request fields. The tenant is the deployment''s

        own; no request field selects it. The target

        environment''s namespace is derived by the platform from

        `(customer, application, environment)` and never read from this body;

        the runtime tier is computed from the requested tier

        and the governed profile, never posted. Both seams

        refuse fail-closed with 503 while unwired.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - fromEnvironment
              - toEnvironment
              - imageDigest
              - acceptedPlanHash
              - sourceRevision
              - detectorVersion
              - builderVersion
              - requestedTier
              properties:
                fromEnvironment:
                  type: string
                  enum:
                  - dev
                  - stage
                  - prod
                toEnvironment:
                  type: string
                  enum:
                  - dev
                  - stage
                  - prod
                imageDigest:
                  type: string
                  description: sha256:<hex>; an OCI image addressed by digest, never
                    a tag
                acceptedPlanHash:
                  type: string
                sourceRevision:
                  type: string
                detectorVersion:
                  type: string
                builderVersion:
                  type: string
                requestedTier:
                  type: string
                  enum:
                  - standard
                  - governed
                  description: the accepted plan's requested tier; the platform computes
                    placement
                tests:
                  type: array
                  description: Test-name and artifact-URL identifier pairs — the record
                    links tests, never their content
                  items:
                    type: object
                    required:
                    - name
                    - artifactUrl
                    properties:
                      name:
                        type: string
                      artifactUrl:
                        type: string
      responses:
        '429':
          $ref: '#/components/responses/RateLimited'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '201':
          description: Promotion request opened, awaiting two distinct approvers
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PromotionRequest'
        '422':
          description: 'A field is missing or malformed, the environments name a hop
            that

            is not the next one, the image is not addressed by digest, or the

            requested tier is unknown. `field` names it.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '503':
          description: 'The namespace derivation or the tier classification seam is
            not

            wired. Refused fail-closed; never defaulted.


            The state store is busy when another writer holds its write

            lock past the store''s own wait: the request is answered `503`

            with a `Retry-After` header before the route acts. The store

            releases its own lock before refusing, so later operations

            proceed.

            '
          headers:
            Retry-After:
              schema:
                type: integer
              description: 'Seconds to wait before retrying a busy state store.

                '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /promotions/{promotionId}/approval:
    post:
      tags:
      - promotions
      summary: Approve or reject a promotion, bound to the combined hash
      description: '**The approver approves a hash, not a description.** Two distinct,

        assertion-resolved approvers are required before a promotion may

        execute. The requester cannot approve their own promotion; a subject

        has one decision per promotion; an approval naming anything but the

        promotion''s current combined hash is refused. The

        approval records the target environment''s head deployment record at

        decision time, and expires with the deployment''s approval window.

        '
      parameters:
      - $ref: '#/components/parameters/promotionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - decision
              - combinedHash
              properties:
                decision:
                  type: string
                  enum:
                  - approve
                  - reject
                combinedHash:
                  type: string
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Decision recorded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PromotionApproval'
        '403':
          description: 'The caller authenticated and Necto grants them no approval

            authority. No identity-provider claim changes this. A machine

            principal never approves a promotion either: its request is

            refused 403 even when its client-principal record lists this

            route.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '409':
          description: 'A named refusal: `self-approval refused`, `approver decided

            twice`, or `promotion hash mismatch` — `title` names which,

            because a caller cannot act on all of them the same way.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /promotions/{promotionId}/execute:
    post:
      tags:
      - promotions
      summary: Execute an approved promotion
      description: 'Executes only when two distinct assertion-resolved approvals bind

        the promotion''s combined hash, neither has expired, the target

        environment''s head deployment record is still the one the approvals

        recorded, and a private-e2b target''s placement gate is proven.

        The platform recomputes the governed profile hash

        and the compiled-artifact hashes from current state: when they differ

        from what the approvals signed, the approval is void and nothing is

        recorded as deployed. The new deployment record lands through a

        compare-and-swap on the target head: two concurrent executes succeed

        exactly once. No override parameter exists.

        '
      parameters:
      - $ref: '#/components/parameters/promotionId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: Promoted; the deployment record for the target environment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeploymentRecord'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '409':
          description: 'A named refusal: `promotion rejected`, `insufficient approvers`,

            `approval expired`, `approval void` (the governed profile or

            compiled artifacts changed after approval), `target changed` (head

            moved, or the compare-and-swap lost), or `gate unproven` for a

            private-e2b target. `title` names which.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}/journal:
    get:
      tags:
      - collaboration
      summary: The application's collaboration mutation journal, newest first
      description: '**Every mutation records the actor and the resulting version.**

        The append-only journal is read one application at a time, newest

        first, tie-broken by insertion order the way the durable

        backend''s `rowid` does. Every field is an identifier — subject,

        object id, version — never a comment body, prompt or payload.

        The deployment''s own tenant key is carried on the record and is

        never selected by the request. A caller whose assertion verified

        and for whom Necto holds no authority record is refused 403; a

        machine principal is refused by its own client-principal record.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: The application's mutation journal, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                - applicationId
                - mutations
                properties:
                  applicationId:
                    type: string
                  mutations:
                    type: array
                    items:
                      $ref: '#/components/schemas/MutationRecord'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '404':
          description: 'No application is registered in this deployment under this

            id. A cross-tenant application is not registered here either:

            the registry is tenant-keyed and this read never crosses it.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}/history:
    get:
      tags:
      - collaboration
      summary: The application's version history, derived at read time
      description: '**Read-only by shape.** The history is derived at query time

        from the stores that already own the rows — the accepted plan

        versions, the deployment records, the release records and the run

        registry — and writes nothing. Every entry carries the anchor

        kind, the identifier its store knows it by, the time and the

        actor where the store has them. A plan version has no clock and

        no actor on the artifact; a run record has no actor; the entry

        reports the absence rather than inventing one. A plan version''s

        parent is carried by hash. The plan store has no application

        key, so the tenant''s plan versions are part of every application

        read; a version is not attributed to one application by a key

        that does not exist. The deployment store''s only list-shaped

        read is the record in force per environment, so the history lists

        the current head and not every superseded record. The tenant is

        the deployment''s own and is never taken from the request.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: The application's version history, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                - applicationId
                - entries
                properties:
                  applicationId:
                    type: string
                  entries:
                    type: array
                    items:
                      $ref: '#/components/schemas/HistoryEntry'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '404':
          description: 'No application is registered in this deployment under this

            id. A cross-tenant application is not registered here either:

            the registry is tenant-keyed and this read never crosses it.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}/collaborators:
    post:
      tags:
      - collaboration
      summary: Grant a collaboration role on an application
      description: '**The first administrative surface.** The caller resolves in

        Necto''s authority store — the operator is the first administrator

        — and the granted subject is named by its `issuer` and `subject`,

        never by an email address. The grant is per application; the store

        is bound to the deployment''s own tenant key, so no request, claim

        or header selects another. Every grant appends a `role_grant`

        record to the mutation journal before the store moves. An

        `approving` grant alone does not approve: approval still reads

        the authority record.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - issuer
              - subject
              - role
              properties:
                issuer:
                  type: string
                  description: The issuer half of the granted subject's key
                subject:
                  type: string
                  description: 'The stable subject identifier; an email address is

                    refused

                    '
                role:
                  type: string
                  enum:
                  - viewing
                  - editing
                  - deploying
                  - approving
                  - stopping
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: Grant recorded in Necto's journal and held by the store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CollaboratorGrant'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '404':
          description: 'No application is registered in this deployment under this

            id. A cross-tenant application is not registered here either:

            the registry is tenant-keyed and this route never crosses it.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '409':
          description: 'The subject already holds a grant on this application. A role

            change is a revocation and a new grant, never an edit.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'The body is malformed or carries a field the closed set

            does not name; `issuer`, `subject` or `role` is missing or

            blank; the role is not one of the five; or the subject looks

            like an email address. `field` names which, and nothing is

            recorded. Never a 500.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}/collaborators/{subject}:
    delete:
      tags:
      - collaboration
      summary: Revoke a collaboration role on an application
      description: '**Revoking is never harder than granting.** The caller resolves

        in Necto''s authority store; the grant is removed, never edited,

        and the revocation appends a `role_grant` record to the mutation

        journal. A revocation with no grant in force is refused 409:

        every recorded revocation is attached to the version it removed.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      - name: subject
        in: path
        required: true
        schema:
          type: string
        description: The stable subject identifier of the grant to revoke
      - name: issuer
        in: query
        required: true
        schema:
          type: string
        description: 'The issuer half of the grant''s key; a subject is only

          unique within its issuer

          '
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Revocation recorded in Necto's journal
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CollaboratorGrant'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '404':
          description: 'No application is registered in this deployment under this

            id. A cross-tenant application is not registered here either.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '409':
          description: 'The subject holds no grant on this application to revoke.

            A no-op revocation is refused, never filed.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'The `issuer` query parameter is missing or blank, or the

            path''s subject looks like an email address. `field` names

            which, and nothing is recorded. Never a 500.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}/clone:
    post:
      tags:
      - collaboration
      summary: Clone an application's plan version into a new application
      description: '**A clone is a new application carrying the source plan version''s

        topology only.** The clone''s new plan version is minted field by

        field from the source plan''s ten topology fields, with the source

        version hash as its `parent`; the source version, application and

        repository are unchanged. No approval, deployment, release, run, role

        grant, credential, secret or managed-state resource is carried:

        none has a plan field or a parameter to travel through, and each

        environment of the clone resolves its own secrets. One

        `plan_version` `cloned` record is appended to the mutation

        journal, naming the actor, the source version hash and the clone''s

        version hash; a record the journal refuses leaves no clone. The

        source version is read through this deployment''s own tenant-keyed

        plan registry, so another customer''s version is not found — refused

        `404`, never cloned. The caller needs an `editing` collaboration

        grant on the source application; without one the clone is refused

        `403`. The field set is closed: a field outside it is refused

        rather than ignored, and a blank or malformed field is refused

        `422`, never a 500.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - sourceVersion
              - newApplicationId
              properties:
                sourceVersion:
                  type: string
                  description: 'The source plan version hash (`sha256:<hex>`);

                    the clone''s new version is parented to it

                    '
                newApplicationId:
                  type: string
                  description: 'The clone''s new stable application id; the clone''s

                    plan version is keyed by it and no request field

                    selects the tenant

                    '
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: 'The clone''s new plan version recorded; the source

            application and its version are unchanged

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApplicationClone'
        '403':
          description: 'The caller resolves in Necto''s authority store but holds
            no

            `editing` grant on the source application, or the source

            version is another customer''s. Nothing is cloned either way.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '404':
          description: 'No application is registered in this deployment under this

            id, or the application holds no plan version with this hash.

            A cross-tenant source version is not one this deployment''s

            registry holds either.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '409':
          description: 'The clone''s new application id is already registered.

            A clone mints a new identity, never overwrites one.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'The body is malformed or not valid UTF-8, or it carries a

            field the closed set does not name; `sourceVersion` or

            `newApplicationId` is missing, blank or not a string.

            `field` names it, and nothing is cloned. Never a 500.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}/comments:
    get:
      tags:
      - collaboration
      summary: List one application's comments, newest first
      description: '**One row per comment, and a redacted comment carries no body.**

        Each item is the newest row of one comment: the identifier,

        the application, the anchor, the actor and the time of that

        newest row, whether it is a redaction, and how many revisions

        the store holds. A live comment carries the newest body; a

        redacted comment carries no body at all, because a redaction

        hides the body rather than overwriting it. The optional

        `anchorKind` and `anchorId` filters narrow the list to one

        anchor, and both are needed. The read needs the caller''s

        `viewing` grant on this application: no grant is 403, and the

        store is bound to the deployment''s tenant key, so another

        customer''s comments are never listed. An unknown

        anchor kind is refused 422 rather than answered with a whole

        application''s comments.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      - name: anchorKind
        in: query
        required: false
        schema:
          type: string
          enum:
          - plan_version
          - deployment
          - run
          - policy_version
        description: 'The anchor kind to filter by; `anchorId` is needed beside

          it.

          '
      - name: anchorId
        in: query
        required: false
        schema:
          type: string
        description: The anchor's own identifier to filter by.
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: One application's comments, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                - applicationId
                - comments
                properties:
                  applicationId:
                    type: string
                  comments:
                    type: array
                    items:
                      $ref: '#/components/schemas/Comment'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '404':
          description: 'No application is registered in this deployment under this

            id. A cross-tenant application is not registered here

            either.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'An anchor filter is malformed: `anchorKind` is not one

            of the four anchor kinds, `anchorKind` is given without

            `anchorId`, or `anchorId` is blank or given without

            `anchorKind`. `field` names which, and nothing is read.

            Never a 500.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
    post:
      tags:
      - collaboration
      summary: Record a comment on an application
      description: '**The body is user content and enters no trail.** The caller

        needs the `editing` grant on this application: a `viewing` grant

        alone is refused 403, and no grant is refused 403 before

        anything is recorded. The actor is the caller''s resolved subject

        key and the time is the platform clock, never request fields.

        The comment store appends the row and writes the one `comment`

        journal record; the route writes no second record. The response

        carries the recorded comment''s identifiers and never the body:

        the body is read back through the list route, and no comment

        body enters the journal or the evidence timeline. A comment id

        the store already holds is

        refused 409: an edit is a revision, never a second creation.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - commentId
              - anchor
              - body
              properties:
                commentId:
                  type: string
                  description: 'The comment''s own identifier, stable across its

                    revisions.

                    '
                anchor:
                  $ref: '#/components/schemas/CommentAnchor'
                body:
                  type: string
                  description: 'The comment content. A blank body is refused 422,

                    and a non-UTF-8 body is refused 422 and never

                    reaches the store.

                    '
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: 'Recorded in the comment store and the mutation

            journal; the comment''s identifiers and revision count.

            The body is not echoed.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Comment'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '404':
          description: 'No application is registered in this deployment under this

            id. A cross-tenant application is not registered here

            either.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '409':
          description: 'The comment id is already recorded in this store. An edit

            is a revision of the same comment id, never a second

            creation.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'The body is malformed or carries a field the closed set

            does not name; `commentId`, `anchor` or `body` is missing,

            blank or not a string; the anchor kind is not one of the

            four; or the body is not UTF-8. `field` names which, and

            nothing is recorded. Never a 500.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}/comments/{commentId}/redactions:
    post:
      tags:
      - collaboration
      summary: Redact a comment
      description: '**A redaction appends a revision; it never mutates or deletes

        one.** The caller needs the `editing` grant on this application:

        a `viewing` grant is refused 403, and no grant is refused 403

        before the comment is read. The actor is the caller''s resolved

        subject key and the time is the platform clock, never request

        fields. The store appends one redaction row carrying the version

        of the revision it hides; the hidden revision stays in the store

        and the journal keeps both versions, never overwritten and never

        deleted. The response carries the new row''s identifiers —

        including the redacted revision — and never a body: a redacted

        body enters neither the journal nor the evidence timeline. A

        comment this deployment does not hold is 404, and a comment

        already redacted is 409: a redaction is final, and a later

        revision cannot put the body back. A comment of another

        application in this tenant is not this application''s: the

        `editing` grant is per application, so the redaction never

        reaches it.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      - name: commentId
        in: path
        required: true
        schema:
          type: string
        description: 'The comment''s own identifier, stable across its

          revisions.

          '
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: 'Recorded in the comment store and the mutation

            journal: the redaction''s identifiers, the redacted

            revision id and the revision count. No body is echoed.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Comment'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '404':
          description: 'No application is registered in this deployment under this

            id, or the application holds no comment under this comment

            id. A cross-tenant comment is not held here either, and a

            comment of another application in this tenant is not this

            application''s either: the `editing` grant is per application.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '409':
          description: 'The comment is already redacted. A redaction is final

            and a second one is refused, never filed.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'The store refused the redaction rather than filing it:

            the platform clock is not after the comment''s newest

            revision, so the append would break the append-only order.

            `field` names the comment, and nothing is recorded. Never

            a 500.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /applications/{applicationId}/budget:
    get:
      tags:
      - operations
      summary: The application's run budget for its current window
      description: 'The per-application run budget: the cap on runs in one fixed

        window, the window''s own count and the runs remaining. The claim

        that enforces it happens at run admission, in the same step as the

        run''s own registration, so a start past the cap refuses before any

        sandbox starts and records no run. Runs are the counted dimension;

        a spend dimension joins when the metered instrument records it.

        No budget recorded is a 404: the read answers the record and never

        a default cap. The tenant is the deployment''s own; no request field

        selects it.

        '
      parameters:
      - $ref: '#/components/parameters/applicationId'
      responses:
        '429':
          $ref: '#/components/responses/RateLimited'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '404':
          description: No run budget is recorded for this application.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '200':
          description: The current window's budget
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApplicationBudget'
  /agents/{agentId}/card:
    get:
      tags:
      - operations
      summary: The agent catalogue card
      description: '**Denied requests are returned alongside successful runs, not

        behind a filter.**

        '
      parameters:
      - $ref: '#/components/parameters/agentId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Agent card
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentCard'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /agents/{agentId}/behaviour/baseline:
    get:
      tags:
      - operations
      summary: What is usual for this agent, by release regime
      description: 'The baseline a run is compared against: the documents,

        destinations, tool order, records-per-run and run-seconds the

        agent''s release regimes have actually shown. A baseline is keyed

        to one agent, one compiled profile and one release, so a release

        opens a new regime instead of being reported as drift on release

        day; a prior regime''s baseline is kept for comparison and never

        scores the next one. `transitions` names where one regime''s

        baseline ended and the next began.


        **The four dimensions are facts, never a score.** `state` is

        `ready` when the regime has enough observed history to compare

        against, and `insufficient_volume` when it does not: the state

        scores nothing, and the comparison carries no verdict rather

        than a low one. `observedRuns` is the number of the regime''s

        finished runs the baseline summarises. Identifiers only: document

        ids, destination names and tool names, never content, passages,

        prompts or credentials.

        '
      parameters:
      - $ref: '#/components/parameters/agentId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: The agent's regime-keyed baseline
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BehaviourBaseline'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '404':
          description: No such agent is registered.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /agents/{agentId}/runs:
    post:
      tags:
      - operations
      summary: Start a governed run
      description: '**Returns identifiers only**: no document text, no raw prompts,
        no

        credential values. The run is asynchronous; watch

        `/runs/{runId}/timeline?watch=1` for the ordered `TimelineEntry`

        stream.


        A registered machine client may start the run from a schedule tick by

        sending a `trigger`. The run then takes its input from the scheduled

        task the trigger names, records the tick on the run''s timeline, and

        carries the trigger on the card''s run summary. A caller that is a

        person, a trigger that names no task of this agent, an instant that

        is not a tick of the task''s schedule, or a tick outside its own

        period is refused, and nothing starts. A tick is outside its own

        period when it is more than one minute after the current instant,

        or more than one period of the task''s own schedule plus a short

        transport margin before it: the tick is stale once the next tick

        exists. A tick refused as stale is recorded as a skipped tick on

        the agent''s schedule stream, while a tick refused before its

        period is due is recorded on that stream under the distinct kind

        `schedule_refused`, without a tick key, so the refusal does not

        pre-decide it. A repeated trigger key starts no second sandbox:

        the response carries the run the key already names.

        '
      parameters:
      - $ref: '#/components/parameters/agentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                input:
                  type: string
                  description: 'The caller''s message for this run. Required for a
                    run

                    with no trigger; a scheduled run takes its input from

                    the scheduled task the trigger names.

                    '
                trigger:
                  $ref: '#/components/schemas/ScheduleTriggerRequest'
      responses:
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: 'The trigger key already named a run, so no second sandbox

            starts. The response carries the existing run id and

            `replayed: true`.

            '
          content:
            application/json:
              schema:
                type: object
                required:
                - runId
                - replayed
                properties:
                  runId:
                    type: string
                  replayed:
                    type: boolean
        '202':
          description: 'Run accepted. The sandbox is stopped as soon as the

            entrypoint returns, and the stop is recorded on the run''s

            timeline: `sandbox_killed` when it stopped, or

            `sandbox_stop_failed` when it did not, so a sandbox left

            running is never reported as stopped. The substrate''s

            inactivity timeout stays only as the backstop for a crashed

            coordinator path, and the run''s own exit result is unchanged

            by a failed stop. The moment the entrypoint returns, the

            run''s timeline gains `run_finished` with the exit code and

            the stdout/stderr byte counts, never the output.

            '
          content:
            application/json:
              schema:
                type: object
                required:
                - runId
                properties:
                  runId:
                    type: string
                  startedAt:
                    type: string
                    format: date-time
                  artifactDigest:
                    type: string
                    description: 'The prebuilt artifact the sandbox ran from: an E2B

                      template build id (a UUID). Present only for a governed

                      run; the in-process reference workload starts no sandbox

                      and names no artifact.

                      '
                  sandboxId:
                    type: string
                    description: 'The substrate''s own sandbox identifier. Present
                      only

                      for a governed run.

                      '
                  exitCode:
                    type: integer
                    description: 'The artifact entrypoint''s exit result. Present
                      only

                      for a governed run; `0` ran, and the entrypoint''s

                      own non-zero code is recorded unchanged.

                      '
        '409':
          description: 'The agent is contained, a request arrives while the agent

            already has a run that has not finished, the tick is outside

            its own period, the tick is already recorded as skipped, or

            the previous run''s sandbox did not stop: it stays registered

            as live and this run starts no second sandbox, so no agent ever

            has two live sandboxes.

            One agent has one live run: a second start, triggered or not,

            starts no second sandbox, and a triggered one is recorded as a

            skipped tick with reason `overlap`. An agent with no approved

            release starts nothing: a release whose approval is rejected,

            void or expired, and a widening release whose second distinct

            approval has not arrived, are not approved releases, so the run is

            refused before a sandbox exists. Stopping an agent prevents

            new runs as well as killing the running one; a stop that let the

            next run start would be a pause. Returning to service is a

            release, not this call succeeding on a retry. A stop that lands

            while a run is starting kills the sandbox that started, and the

            kill is recorded on that run''s timeline, so no sandbox is left

            running after a stop. A tick that is refused is recorded as a

            skipped tick on the agent''s schedule stream, with reason

            `contained`, `overlap` or `refused`: a run the release gate

            refuses is an authority refusal, and so is a tick outside its own

            period. A tick refused by a gate is never also a run, and a

            repeat of its key answers the skip rather than a run. A tick

            outside its own period is

            audited on that stream; only one whose period is over is

            recorded with its tick key. A tick already recorded as skipped

            is never caught up: the refusal names the reason it was recorded

            with.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          description: 'The caller may not start a triggered run: a trigger comes
            from

            a registered machine client, and a person''s trigger is refused.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'The request names neither a trigger nor an input, or the

            trigger is malformed: its key does not match the task id and

            the scheduled instant, or it names no task of this agent, or the

            instant is not a tick of that task''s schedule. A malformed

            trigger names no tick the scheduler could have observed, so

            nothing is recorded as a skipped tick. A triggered run the

            governed path then refuses without authority — a profile that does

            not compile — is recorded as a skipped tick with reason

            `failed`.


            The application''s run budget is exhausted: the claim past the

            cap refuses before the run is registered and starts no sandbox,

            and a triggered tick is recorded as a skipped tick with reason

            `refused`.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '503':
          description: 'The substrate is unavailable: no run entrypoint is configured,

            the sandbox did not start, or it did not execute the run. A

            triggered tick refused before its run is recorded — no run

            entrypoint — is recorded as a skipped tick on the agent''s

            schedule stream, with reason `failed`: the substrate failing is

            not the authority refusing. A triggered tick whose run record

            exists and whose sandbox then fails is a failed run: its key

            names that run, the failure is recorded on the run''s own

            timeline (`sandbox_start_failed` when the sandbox did not

            start, `sandbox_exec_failed` and the kill when it did not

            execute the run), no skipped tick is written beside it, and a

            repeat of its key answers the recorded run.


            The state store is busy when another writer holds its write

            lock past the store''s own wait: the run route answers `503`

            with a `Retry-After` header and starts no sandbox. The store

            releases its own lock before refusing, so later operations

            proceed.

            '
          headers:
            Retry-After:
              schema:
                type: integer
              description: 'Seconds to wait before retrying a busy state store.

                '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /agents/{agentId}/schedule-events:
    get:
      tags:
      - operations
      summary: The agent's schedule stream, newest first
      description: 'Every tick this agent''s scheduler recorded, newest first: the

        accepted ticks beside the ticks that did not run. An accepted tick

        is the run''s own `schedule_fired` entry; a stale tick is the keyed

        `schedule_skipped` with reason `refused`; a tick refused before

        it could have run is the distinct `schedule_refused` with reason

        `future` and no key; the remaining skips carry `contained`,

        `overlap`, `failed` or `missed`. Each entry carries its tick key

        where one exists, its task id and its scheduled instant beside the

        instant the platform recorded it. A human caller

        with authority for the agent reads the stream; a machine client and

        a workload-originated call are refused, because the scheduler

        reports ticks and never reads them back. An unknown agent is refused

        like the card. `limit` defaults to 50 and a value outside 1 to 200

        is refused by name rather than clamped.

        '
      parameters:
      - $ref: '#/components/parameters/agentId'
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          default: 50
          minimum: 1
          maximum: 200
        description: 'How many of the newest entries to return. Default 50; a value

          outside 1 to 200 is refused rather than clamped.

          '
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: 'The agent''s schedule stream, newest first: the accepted

            ticks and the ticks that did not run, each with its

            timestamps; the ticks that did not run carry their reason.

            '
          content:
            application/json:
              schema:
                type: object
                required:
                - agentId
                - entries
                properties:
                  agentId:
                    type: string
                  entries:
                    type: array
                    items:
                      $ref: '#/components/schemas/TimelineEntry'
        '403':
          description: 'The caller authenticated and Necto holds no authority record
            for

            the subject, or the caller is a machine client or a

            workload-originated call. The schedule stream is read by a human

            caller with authority for the agent; a machine client reports

            ticks and never reads them back.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '404':
          description: No such agent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'The limit is not an integer, or it is outside 1 to 200. The

            refusal names the field rather than clamping the value, so a

            caller never reads a shorter answer as the one it asked for.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
    post:
      tags:
      - operations
      summary: Record a tick the scheduler observed as missed
      description: '**Machine-only.** A scheduler reports a tick it did not fire because

        it was past its grace window. The API recomputes the trigger key from

        the task id and the scheduled instant and records a skipped tick with

        reason `missed` on the agent''s schedule stream. A key that already

        names a run or a skipped tick is a no-op: the scheduler''s boot

        look-back reports every past-grace tick it can see, and the API is

        what de-duplicates. A caller that is a person, or a request that

        does not name a missed tick of this agent, is refused.

        '
      parameters:
      - $ref: '#/components/parameters/agentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScheduleEventRequest'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: 'The skipped tick is recorded, or the key already named a run

            or a skipped tick and nothing new is written.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  recorded:
                    type: boolean
        '403':
          description: 'The caller may not report a missed tick: schedule events come

            from a registered machine client, and a person''s report is

            refused.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'The report is malformed: its key does not match the task id

            and the scheduled instant, or it names no task of this agent,

            or the instant is not a tick of that task''s schedule.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /agents/{agentId}/actions:
    post:
      tags:
      - operations
      summary: Mint and execute one standing-approved send
      description: '**The scheduled-run send surface.**

        A scheduled run mints one fresh, ordinary, single-use

        `ActionEnvelope` under the standing approval it names and executes

        it through the existing single-use consumption path: no second

        consumption model, no reusable envelope, and no adapter change.

        The grant approves one channel and one message schema; the envelope

        names the grant in `approvalRef` and the grant''s approver in

        `caller`, so the adapter''s existing per-call revalidation is the

        check the send passes. Every refusal the standing-approval rules

        define is this route''s own refusal, never a 500: an unknown grant

        is 404; a revoked grant, a channel outside the grant, a URL host

        the profile''s allowlist does not admit and a tool the compiled

        allowlist does not permit are 403; a release change that voided the

        grant is 409; an expired grant is 410; a payload outside the

        approved schema, a field over its length cap, a title or price

        carrying URL-like text, or a url that is not exactly one absolute

        https URL is 422; and a mint over the per-run or per-day cap is

        429.

        **A workload-originated call is authorised by its own run.** The

        console''s workload relay verifies a DPoP-bound workload token and

        mints the assertion with the verified workload claims beside the

        sandbox instance id as `sub`: `agent_id`, `run_id`, `tool` and

        `audience`. A call carrying those claims is refused on every route

        but this one, and on this route it is refused unless that run

        exists, is live, was started by Necto for this agent, runs in that

        instance, the tool is one the run delivered, the instance''s

        workload tokens have not been revoked by a stop, this request''s

        `runId` and `tool` are the assertion''s, and the forwarded

        `audience` is the verified tool''s own per-tool audience

        `necto:<agentId>:<tool>`. The revocation is read from the

        deployment''s own revocation list by the instance the assertion

        names; a deployment with no such list to read, or a list that

        cannot be read, refuses the call rather than assuming the

        instance is not revoked.

        **Only the run''s own workload call may spend the standing

        approval.** A session assertion is refused here with 403, whatever

        authority it holds: a human approves each send through the ordinary

        approval surface. The per-run cap is counted on the run the relay

        verified, never on a value in this request, so a request naming

        another run is refused and cannot reset it.

        '
      parameters:
      - $ref: '#/components/parameters/agentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - grantId
              - runId
              - channel
              - tool
              - payload
              properties:
                grantId:
                  type: string
                  description: The standing approval the send is minted under
                runId:
                  type: string
                  description: 'The scheduled run this mint is for. The per-run cap
                    is

                    counted on the run the relay verified, never on this

                    value; a request naming another run is refused and cannot

                    reset it.

                    '
                channel:
                  type: string
                  description: 'The channel the run intends to post to. The grant
                    is

                    scoped to exactly one channel id; anything else is refused.

                    '
                tool:
                  type: string
                  description: 'The tool the run invokes. The relay''s verified tool

                    claim must name exactly this tool, and the tool is

                    revalidated against the agent''s compiled action

                    allowlist at execution time.

                    '
                payload:
                  type: object
                  required:
                  - title
                  - price
                  - url
                  description: 'The one message schema the grant approves, exactly:

                    an extra or missing field is refused, never dropped or

                    supplied. Every field is bounded: a longer value is

                    refused before any pattern runs. The URL must be exactly

                    one absolute https URL -- no whitespace, no second URL

                    and no userinfo -- whose host the profile''s compiled

                    egress allowlist admits. The title and price are plain

                    text: URL-like text in either -- a scheme, `www.`, a

                    domain whose final label the IANA root zone delegates,

                    or an obfuscated spelling of one -- is refused, so a

                    URL cannot ride a field the host check does not read.

                    '
                  properties:
                    title:
                      type: string
                      maxLength: 200
                    price:
                      type: string
                      maxLength: 300
                    url:
                      type: string
                      maxLength: 2048
      responses:
        '429':
          description: 'Two limits, told apart by the body. The surface''s rate limit

            refused the request before the route ran: `rate limited` with

            `Retry-After`. The standing approval is at its per-run or

            per-day cap: the title names the cap.

            '
          headers:
            Retry-After:
              description: Seconds to wait before retrying, on the surface's refusal.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '201':
          description: The send was executed once, by identifier
          content:
            application/json:
              schema:
                type: object
                required:
                - envelopeId
                - idempotencyToken
                - channel
                - messageTs
                properties:
                  envelopeId:
                    type: string
                  idempotencyToken:
                    type: string
                    description: 'Derived from the envelope hash, never minted at

                      execution; the ledger consumes it exactly once.

                      '
                  channel:
                    type: string
                  messageTs:
                    type: string
                    description: 'Slack''s own identifier for the message it created.

                      The message text is never returned or recorded.

                      '
        '404':
          description: 'No such agent, or no standing approval is recorded under

            the named grant id

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '409':
          description: 'The agent is contained, the grant is void because the release

            changed after approval, or the agent has no approved release to

            bind the grant to

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '410':
          description: The standing approval expired; the next mint is refused
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          description: 'The deployment has not opted in to the standing approval,
            the

            caller is not the run''s own workload call, the request''s run is

            not the verified run, the forwarded audience is not the verified

            tool''s, the instance''s workload tokens were revoked by a stop, no

            workload-token revocation list is composed to read, that list

            could not be read, the grant was revoked, the channel is outside

            its scope, the URL host is not one the profile''s allowlist admits,

            or the compiled allowlist does not permit the tool

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: 'A required field is missing, the payload is not the approved

            message schema, a field is over its length cap, the title or

            price carries URL-like text, the url is not exactly one

            absolute https URL, or the profile did not compile

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '502':
          description: 'Slack refused the send or could not be reached. The detail

            carries Slack''s own error code, never the message text.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '503':
          description: 'The deployment composes no Slack transport, or its Slack

            credential is not configured.


            The state store is busy when another writer holds its write

            lock past the store''s own wait: a write that precedes the

            action it records -- the credential-novelty entry, the identity

            assertion or the authority journal -- fails closed, so the

            request is answered `503` with a `Retry-After` header before

            the route acts. The store releases its own lock before

            refusing, so later operations proceed. The per-request audit

            record is written after the outcome exists: its write is

            retried a bounded number of times, and if the store stays

            busy the response stands and one structured warning names the

            gap.

            '
          headers:
            Retry-After:
              description: 'Seconds to wait before retrying a busy state store.

                '
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /agents/{agentId}/egress-refusals:
    post:
      tags:
      - operations
      summary: Record one destination a governed run's egress refused
      description: '**The relayed refusal report.** A newly onboarded agent starts

        with zero egress permissions, so every destination it tries is

        refused; the sandbox reports the refused destination''s identifiers

        and the platform records them for the customer''s review.

        **Identifiers only**: the host, the port and the protocol, the

        run''s governed-component identity and the run, the platform''s own

        instant and the bounded attempt counter; no payload, content or

        capture field exists.

        **A workload-originated call is authorised by its own run.** The

        console''s workload relay verifies a DPoP-bound workload token and

        mints the assertion with the verified workload claims beside the

        sandbox instance id as `sub`. A call carrying those claims is

        refused on every route but the relayed routes, and on this route it

        is refused unless that run exists, is live, was started by Necto

        for the path''s agent, runs in that instance, the instance''s

        workload tokens have not been revoked by a stop, and the tool is one

        the run delivered. The revocation is read from the deployment''s own

        revocation list by the instance the assertion names; a deployment with

        no such list to read, or a list that cannot be read, refuses the

        call rather than assuming the instance is not revoked.

        **The body''s field set is closed**: `host`, `port` and `protocol`

        only, and an unexpected key is refused. The tenant key, the run''s

        governed-component identity and the run come from the deployment and

        the verified claims, never from this body: the body carries no

        `runId`, so a caller cannot file a refusal under another run.

        **A repeat observation coalesces into its row''s counter** up to the

        platform''s cap, and a new destination past the run''s cap is not

        recorded: the answer is `202` with `recorded: false` and

        `reason: flood-cap`, and the run is not failed. The cap value is

        set by the change that lands the row, never by this document.

        '
      parameters:
      - $ref: '#/components/parameters/agentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
              - host
              - port
              - protocol
              properties:
                host:
                  type: string
                  description: 'The named host the run''s egress refused. A wildcard,

                    a CIDR, a scheme, a port or a path is refused; the

                    host is normalized before it is stored.

                    '
                port:
                  type: integer
                  minimum: 1
                  maximum: 65535
                  description: The TCP port the run tried
                protocol:
                  type: string
                  enum:
                  - tcp
                  - tls
                  description: The connection's closed protocol set
      responses:
        '201':
          description: 'The refused destination was recorded; a repeat

            observation answers the same way with its counter coalesced

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ObservedDestination'
        '202':
          description: 'The run already recorded the platform''s cap of distinct

            destinations; this new one is not recorded and the run is not

            failed

            '
          content:
            application/json:
              schema:
                type: object
                required:
                - recorded
                - reason
                properties:
                  recorded:
                    type: boolean
                    enum:
                    - false
                  reason:
                    type: string
                    enum:
                    - flood-cap
        '403':
          $ref: '#/components/responses/NoAuthority'
        '422':
          description: 'A required field is missing, the host is not a named host,
            the

            port is outside 1-65535, the protocol is not `tcp` or `tls`, or

            the body carries a field the report does not declare

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/StateBusy'
  /runs/{runId}/timeline:
    get:
      tags:
      - operations
      summary: One run, end to end
      description: 'Identifiers and decisions only: no document text, no raw prompts.

        **Dropped candidates are included with their reason**; a timeline

        showing only what was retrieved hides the control that was exercised.

        `watch=1` upgrades the response to a Server-Sent Events stream, one

        `TimelineEntry` per event; omit it for the current snapshot.

        **A workload-originated read is authorised by its own run**: the

        console''s workload relay mints the assertion with the verified

        workload claims beside the sandbox instance id as `sub`, and a call

        carrying those claims is refused on every route but this one and the

        actions route. The read is refused unless that run exists, is live,

        was started by Necto for the assertion''s agent, runs in that

        instance, the instance''s workload tokens have not been revoked by a

        stop, and the tool is one the run delivered. The revocation is

        read from the deployment''s own revocation list by the instance the

        assertion names; a deployment with no such list to read, or a list

        that cannot be read, refuses the read rather than assuming the

        instance is not revoked. A session assertion is decided exactly as

        before. **Every entry carries its own evidence class**: `semantic`

        for a kind the four behavioural dimensions read, `infrastructure`

        for every other kind. An entry presented as semantic agent

        evidence is checked by `require_semantic` before it is presented,

        and a refusal is the named `409` whose `detail` names the kind.

        '
      parameters:
      - $ref: '#/components/parameters/runId'
      - name: watch
        in: query
        required: false
        schema:
          type: boolean
          default: false
        description: Stream new entries as Server-Sent Events instead of returning
          a snapshot
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Immutable timeline entries, or an SSE stream when `watch=1`
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TimelineEntry'
            text/event-stream:
              schema:
                type: string
                description: 'One Server-Sent Events `data:` frame per `TimelineEntry`,

                  each frame a JSON-encoded `TimelineEntry`.

                  '
        '403':
          $ref: '#/components/responses/NoAuthority'
        '409':
          description: 'An entry presented as semantic agent evidence that the

            partition does not read as semantic: the refusal names the kind

            in `detail`, because a caller has to know what was handed

            over. A word and the kind, never a number and never a

            semantic claim.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /runs/{runId}/chat:
    get:
      tags:
      - operations
      summary: One run's chat stream, as AG-UI events
      description: 'The small optional chat surface, behind a Necto-owned interface:

        AG-UI''s event names on Server-Sent Events, so the protocol adapter

        may be replaced without changing the application or the evidence

        models. **Every event is derived from the run registry and the run''s

        own timeline**: run state is the registry''s own rows, and tool

        activity is the run''s own credential-injection entries. Identifiers

        and decisions only: no message text, no tool arguments and no raw

        chain-of-thought is emitted or stored, because the platform stores

        none. The caller is authorised by the same record every run read

        uses: a caller Necto holds no authority record for is `403`, and a

        run Necto never registered is `404` rather than an empty stream.

        '
      parameters:
      - $ref: '#/components/parameters/runId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: 'One Server-Sent Events `data:` frame per event, each frame
            a

            JSON-encoded event whose `type` is an event name this

            interface emits.

            '
          content:
            text/event-stream:
              schema:
                type: string
                description: 'One Server-Sent Events `data:` frame per event. Message

                  content, tool arguments and reasoning are never emitted:

                  the platform stores none.

                  '
        '403':
          $ref: '#/components/responses/NoAuthority'
        '404':
          description: No such run was started by the platform.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /runs/{runId}/behaviour:
    get:
      tags:
      - operations
      summary: One run's behaviour against its regime's baseline
      description: 'The same four dimensions for one run — documents read,

        destinations contacted, tools and their order, records opened

        and seconds elapsed — plus the comparison against the baseline

        of the regime that run belongs to, as it stood before the run

        started. Only that regime''s runs that finished before this run

        started count, so a later or unfinished run never widens the

        baseline an earlier run is compared against. A run is never

        compared with another regime''s baseline or with itself, and the

        response names the regime it was compared against.


        **Refused attempts are observations, never absences**: an

        attempt the platform refused is returned with the rule that

        refused it. `state` is `ready` when the regime has enough

        observed history to compare against, and

        `insufficient_volume` when it does not: the state scores

        nothing, and the comparison carries no verdict rather than a low

        one. Identifiers only: document ids, destination names and

        tool names, never content, passages, prompts or credentials.

        '
      parameters:
      - $ref: '#/components/parameters/runId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: The run's observed behaviour and its comparison
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BehaviourObservation'
        '403':
          $ref: '#/components/responses/NoAuthority'
        '404':
          description: No such run was started by the platform.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /workload/revocation:
    get:
      tags:
      - operations
      summary: Whether the calling workload instance's tokens are revoked
      description: '**The console workload relay''s own read, before it forwards a

        relayed request.** The instance is the one the verified console

        workload assertion names as its subject; the tenant is the

        deployment''s own and is never taken from a claim or a request

        value. The read takes no path or query parameter, performs no

        action and reads no run: it answers for a stopped instance too,

        so a revoked presentation is refused however fresh its proof is.

        Answers `{"revoked": false}` while the instance''s tokens are

        live, and `{"revoked": true}` once a stop has revoked them.

        **A caller that is not a workload assertion is refused**: a

        human session and a machine principal are 403. A deployment whose

        revocation list is absent or unreadable is answered 503, so the

        relay forwards nothing rather than a presentation nobody checked.

        '
      responses:
        '200':
          description: Whether the instance's workload tokens are revoked
          content:
            application/json:
              schema:
                type: object
                required:
                - revoked
                properties:
                  revoked:
                    type: boolean
                    description: 'True once a stop revoked this instance''s outstanding

                      workload tokens. The read answers this for a stopped

                      instance as well.

                      '
        '403':
          description: 'The caller is not a workload assertion: a human session or
            a

            machine principal.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          description: 'The deployment holds no readable workload-token revocation

            list, so no instance can be answered about.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /agents/{agentId}/rollback:
    post:
      tags:
      - operations
      x-necto-served: false
      summary: Release a previous version of this agent
      description: Not yet available.
      parameters:
      - $ref: '#/components/parameters/agentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - toVersion
              properties:
                toVersion:
                  type: string
                  description: A prior currentVersion from this agent's card
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '201':
          description: Release opened, awaiting approval
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReleaseSummary'
  /agents/{agentId}/authority/check:
    get:
      tags:
      - operations
      summary: Whether this agent may use a tool, without invoking it
      description: 'Answers `nectoctl auth can-i`. Evaluates the agent''s compiled
        action

        allowlist for the calling identity; never invokes the tool and never

        mutates anything.

        '
      parameters:
      - $ref: '#/components/parameters/agentId'
      - name: tool
        in: query
        required: true
        schema:
          type: string
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: The authority level, or a refusal reason
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthorityCheck'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /agents/{agentId}/stop:
    post:
      tags:
      - operations
      summary: Stop the agent immediately
      description: '**Not a release.** No pull request, no approval, no build in this

        path. Safe to call on an already-stopped agent; still recorded.

        '
      parameters:
      - $ref: '#/components/parameters/agentId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '202':
          description: 'Stop accepted and recorded. `observedSeconds` is how long
            **this**

            stop took, measured, not a promised ceiling. No bound is stated

            until one has been measured for the selected runtime and connector.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  observedSeconds:
                    type: number
                  workloadStopped:
                    type: string
                  recordedAt:
                    type: string
                    format: date-time
        '403':
          description: The caller holds no operator authority for this agent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /agents/{agentId}/revoke:
    post:
      tags:
      - operations
      summary: Revoke the agent's credentials upstream
      description: 'Invalidates upstream, verified against the upstream rather than
        against

        Vault''s own view of its lease. Distinct from stop: nothing revokes a

        Vault lease when a workload dies.

        '
      parameters:
      - $ref: '#/components/parameters/agentId'
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '202':
          description: 'Revoke accepted and recorded. Each entry in `revocations`
            states

            whether the upstream supports revocation at all and whether an

            upstream call made afterwards actually failed. **A connector whose

            credential is not Necto''s to invalidate reports

            `supported: false`** rather than a success nobody can verify.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  recordedAt:
                    type: string
                    format: date-time
                  revocations:
                    type: array
                    items:
                      type: object
                      required:
                      - connector
                      - supported
                      - verified
                      - detail
                      properties:
                        connector:
                          type: string
                        supported:
                          type: boolean
                        verified:
                          type: boolean
                        detail:
                          type: string
        '403':
          description: The caller holds no operator authority for this agent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /agents/{agentId}/export:
    post:
      tags:
      - operations
      summary: Export a review bundle for a period
      description: 'The export is itself an action, recorded in the timeline. The
        bundle

        carries identifiers and decisions, never content. **Assembled in the

        request**, from what the evidence store holds, so it returns with the

        identifier. `complete` is false when the period predates the

        retention cutoff: older entries may be purged.

        '
      parameters:
      - $ref: '#/components/parameters/agentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - from
              - to
              properties:
                from:
                  type: string
                  format: date-time
                to:
                  type: string
                  format: date-time
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: The review bundle for the period
          content:
            application/json:
              schema:
                type: object
                required:
                - exportId
                - bundle
                properties:
                  exportId:
                    type: string
                  bundle:
                    $ref: '#/components/schemas/ReviewBundle'
        '422':
          description: '`from` or `to` is missing, is not an ISO-8601 instant, carries
            no

            offset, or the period ends before it starts

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          $ref: '#/components/responses/NoAuthority'
  /whoami:
    get:
      tags:
      - operations
      summary: The caller's own identity and approval authority
      description: 'Derived from the audience-bound assertion, never from the browser.

        Answers `nectoctl auth whoami`, and is distinct from

        `/agents/{agentId}/authority/check`, which answers for an agent

        rather than for the caller.

        '
      responses:
        '503':
          $ref: '#/components/responses/StateBusy'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: Caller identity
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WhoAmI'
        '403':
          description: 'The assertion verified, and Necto holds no authority record
            for

            this subject. Distinct from 401: the caller is who they say they

            are, and that is not by itself authority. Deny by default, never

            a read-only view of someone else''s.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
  /federation/providers:
    get:
      tags:
      - operations
      summary: This deployment's federation registry records
      description: '**Console service identity.** The console server reads the

        committed federation registry live over its existing east-west

        path and keeps no copy of its own: the upstream issuers, the

        named endpoint hosts and the allowed verification bases this

        deployment''s records hold, so a registry record and a console

        provider config cannot disagree silently. The read is the console

        server''s own call, never a signed-in person''s: an assertion that

        is not the console''s service identity (`iss` = the deployment''s

        console issuer, `sub` = `necto-console`) is refused. Tenant- and

        deployment-scoped: the records are the deployment''s customer

        id''s, read from the deployment and never from a request, claim or

        header. Identifiers and display fields only, never a secret: no

        client credential, domain or endpoint path is in the record or the

        answer. `limit` defaults to 200 and a value outside 1 to 500 is

        refused by name rather than clamped.

        '
      parameters:
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          default: 200
          minimum: 1
          maximum: 500
        description: 'How many records to return. Default 200; a value outside 1 to

          500 is refused rather than clamped.

          '
      responses:
        '503':
          description: 'This deployment has no federation registry composed, so the
            route

            refuses rather than answering an empty list. A missing or malformed

            committed source refuses the boot through the loader instead.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '429':
          $ref: '#/components/responses/RateLimited'
        '200':
          description: This deployment's federation registry records, by issuer
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FederationProvider'
        '403':
          description: 'The assertion verified and the caller is not the console

            server''s own service identity: a signed-in person, a machine

            client and a workload-originated call are refused. The

            registry read is the console''s, not a user''s.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: '`limit` is not an integer, or it is outside 1 to 500. The

            refusal names the field rather than clamping the value.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Problem'
components:
  responses:
    RateLimited:
      description: 'The surface''s rate limit refused the request before the route
        ran.

        One limit for the surface: a token bucket per caller composite key

        when the request authenticated, one global ceiling for unauthenticated

        requests. The audit record carries `outcome: refused`.

        '
      headers:
        Retry-After:
          description: 'Seconds to wait before retrying.

            '
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Problem'
    StateBusy:
      description: 'The state store is busy when another writer holds its write lock

        past the store''s own wait. A write that precedes the action it

        records -- the credential-novelty entry, the identity assertion or

        the authority journal -- fails closed: the request is answered

        `503` with a `Retry-After` header before the route acts. The

        store releases its own lock before refusing, so later operations

        proceed. The per-request audit record is written after the

        outcome exists: its write is retried a bounded number of times,

        and if the store stays busy the response stands and one

        structured warning names the gap.

        '
      headers:
        Retry-After:
          description: 'Seconds to wait before retrying a busy state store.

            '
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Problem'
    NoAuthority:
      description: 'The assertion verified and Necto holds no authority record for
        this

        subject. The record is read from Necto''s authority store at the

        moment of the request, never from a claim inside the assertion.

        Distinct from the role-specific 403s: approval requires

        `may_approve`, stop and revoke require operator authority. A

        machine client is authorised differently: its committed

        client-principal record alone lists the operations it may call, and

        an operation it does not list is refused 403.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Problem'
  securitySchemes:
    assertion:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'Short-lived, **audience-bound** identity assertion, verified by
        the

        API and never trusted from the browser. A human caller presents the

        console''s assertion; a machine caller presents a Kubernetes

        projected service-account token (issuer = the cluster''s

        service-account issuer, audience = `necto-api`, lifetime at most

        600 seconds). The customer''s identity provider proves who someone

        is; Necto alone decides what they may approve. Under federation

        the console assertion carries `upstream_identity_path`: ordered

        `issuer`, `subject`, `verification_basis` entries, origin first.

        Malformed paths are refused with 401. A machine caller is

        authorised by its committed client-principal record alone: an

        operation the record does not list is refused with 403, and a

        machine caller never approves a release.

        '
  parameters:
    agentId:
      name: agentId
      in: path
      required: true
      schema:
        type: string
    releaseId:
      name: releaseId
      in: path
      required: true
      schema:
        type: string
    applicationId:
      name: applicationId
      in: path
      required: true
      schema:
        type: string
    environment:
      name: environment
      in: query
      required: true
      schema:
        type: string
        enum:
        - dev
        - stage
        - prod
    promotionId:
      name: promotionId
      in: path
      required: true
      schema:
        type: string
    runId:
      name: runId
      in: path
      required: true
      schema:
        type: string
  schemas:
    Problem:
      type: object
      required:
      - title
      - status
      properties:
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        field:
          type: string
          description: The offending profile field, for a 422
        requested:
          type: string
          description: 'What the caller asked for, on a `kind: Task` widening refusal

            from `POST /tasks/compile`: the single source, tool or authority

            value named by `field`.

            '
        granted:
          type: string
          description: 'What the target already grants, alongside `requested`: the

            profile''s own authority for a widened `authority`, or its

            comma-joined `data`/`tools` list for a source or tool the

            profile does not list.

            '
    AgentSummary:
      type: object
      properties:
        agentId:
          type: string
        name:
          type: string
        owner:
          type: string
        purpose:
          type: string
        environment:
          type: string
        authority:
          type: string
          enum:
          - read_only
          - draft_only
          - write_reversible
          - write_external
        currentVersion:
          type: string
    CompiledProfile:
      type: object
      description: 'The compiled artifacts, each content-addressed, plus the containment

        plan added on acceptance.

        '
      properties:
        profileHash:
          type: string
        runtimeConfig:
          type: object
          additionalProperties: true
        retrievalPermissions:
          type: object
          additionalProperties: true
        credentialScopes:
          type: object
          additionalProperties: true
        actionAllowlist:
          type: object
          additionalProperties: true
        requiredTestMatrix:
          type: object
          additionalProperties: true
        evidenceHeader:
          type: object
          additionalProperties: true
        containmentPlan:
          type: object
          additionalProperties: true
          description: 'Five questions for this agent: who may trigger containment,
            what

            triggers it, what changes immediately, what evidence remains, who

            can restore service. Declaring a plan is not evidence that

            containment works.

            '
        artifactHashes:
          type: object
          additionalProperties:
            type: string
    ReleaseSummary:
      type: object
      properties:
        releaseId:
          type: string
        agentId:
          type: string
        diffHash:
          type: string
        state:
          type: string
          enum:
          - awaiting_approval
          - approved
          - rejected
          - void
          - expired
        expiresAt:
          type: string
          format: date-time
          description: Unapproved releases expire after seven days
        via:
          type: string
          enum:
          - pull_request
          - direct
          description: 'How the profile text was recorded. `direct` is refused when
            the

            calling context''s environment is production.

            '
        imageDigest:
          type: string
          description: 'The prebuilt artifact the release binds. Present only for
            a

            release that binds one; absent reads as absent rather than as a

            blank a reviewer might take for a value.

            '
    PolicyDiff:
      type: object
      required:
      - diffHash
      - noPolicyChange
      - changes
      - approvalExpiresAt
      properties:
        diffHash:
          type: string
        noPolicyChange:
          type: boolean
          description: True for a formatting-only change
        approvalExpiresAt:
          type: string
          format: date-time
          description: 'The release''s own approval window: the instant the release

            stops being approvable, computed by the platform from the

            recorded release, never by the caller. Present on every diff

            read, after the window passed included.

            '
        changes:
          type: array
          items:
            type: object
            required:
            - kind
            - direction
            - subject
            properties:
              kind:
                type: string
                enum:
                - data
                - tool
                - egress
                - approval
                - containment
              direction:
                type: string
                enum:
                - added
                - removed
              subject:
                type: string
              documentCount:
                type: integer
                description: 'Required for `data`. "Added a source" without a number
                  is not

                  a security review.

                  '
              authority:
                type: string
                description: Required for `tool`
              sensitivity:
                type: string
        standingApproval:
          $ref: '#/components/schemas/StandingApprovalProposal'
        standingApprovalGrant:
          $ref: '#/components/schemas/StandingApprovalGrant'
        standingApprovalRevoked:
          $ref: '#/components/schemas/StandingApprovalRevoked'
    StandingApprovalGrant:
      type: object
      description: 'The standing approval the release has recorded, beside the diff:

        the grant the next scheduled run mints under when one is

        spendable, otherwise the release''s latest recorded grant, whose

        expiry has passed. Present when a grant is recorded and not

        revoked; absent when the release has no grant, and replaced by

        `standingApprovalRevoked` when the recorded grant was revoked. A

        grant past its own expiry is still rendered, because the expiry is

        the fact a client warns on and the approval route''s own refusal is

        the enforcement.

        '
      required:
      - grantId
      - channelId
      - approver
      - grantedAt
      - expiresAt
      properties:
        grantId:
          type: string
          description: 'The grant the next scheduled run mints under when one is

            spendable, otherwise the release''s latest recorded grant.

            '
        channelId:
          type: string
          description: The one channel the grant is scoped to
        approver:
          type: string
          description: The human whose approval the grant records
        grantedAt:
          type: string
          format: date-time
          description: The instant the grant was recorded
        expiresAt:
          type: string
          format: date-time
          description: 'The instant the grant stops being usable. A second

            approval before the release''s own window passes records a

            fresh grant; after it passes, renewal is a new release.

            '
    StandingApprovalProposal:
      type: object
      description: 'What the approver would approve beside the diff: the release''s

        agent''s scheduled task deliver alias resolved to one Slack channel

        id by the deployment''s composition, never a request value. Absent

        when the deployment registers no channel for the alias, the agent

        has no scheduled task, or the release''s profile does not compile

        `write_external`.

        '
      required:
      - channelId
      - messageSchema
      - perRunCap
      - perDayCap
      - maxExpiryDays
      properties:
        channelId:
          type: string
          description: 'The one channel id the deployment resolved for the deliver

            alias. The approver echoes it back; the API refuses a value

            that is not this one.

            '
        messageSchema:
          type: array
          description: The one message schema the grant approves
          items:
            type: string
        perRunCap:
          type: integer
          description: The per-run cap, never a request value
        perDayCap:
          type: integer
          description: The per-day cap, never a request value
        maxExpiryDays:
          type: integer
          description: The expiry ceiling; a shorter expiry is the approver's choice
    StandingApprovalRevoked:
      type: object
      description: 'The recorded revocation of the release''s standing approval,

        beside the diff. Present instead of `standingApproval` when the

        release''s recorded grant has been revoked, so a client that does

        not know this field shows no approval rather than one that still

        looks live.

        '
      required:
      - grantId
      - revokedBy
      - revokedAt
      properties:
        grantId:
          type: string
        revokedBy:
          type: string
          description: The human whose act ended the grant
        revokedAt:
          type: string
          format: date-time
    StandingApprovalRevocation:
      type: object
      description: 'The recorded revocation: one entry per grant the release''s

        current diff hash bounds, each naming the human whose act it

        was. The grants are not edited and not deleted: these records

        stand beside them, and the next mint is refused from `revokedAt`

        on.

        '
      required:
      - releaseId
      - grants
      properties:
        releaseId:
          type: string
        grants:
          type: array
          items:
            type: object
            required:
            - grantId
            - channelId
            - revokedBy
            - revokedAt
            properties:
              grantId:
                type: string
              channelId:
                type: string
              revokedBy:
                type: string
                description: The human whose act ended this grant
              revokedAt:
                type: string
                format: date-time
    RequiredTestResult:
      type: object
      properties:
        name:
          type: string
          enum:
          - retrieval_scope
          - denied_access
          - expired_credential
          - blocked_egress
          - output_format
        passed:
          type: boolean
        artifactUrl:
          type: string
          description: The stored output. A pass without an artifact is a claim.
        blocksRelease:
          type: boolean
          description: Always true on failure
    ApprovalRecord:
      type: object
      properties:
        releaseId:
          type: string
        diffHash:
          type: string
        decision:
          type: string
          enum:
          - approve
          - reject
        approver:
          type: string
        party:
          type: string
          enum:
          - customer
          - necto
          description: 'The side of the two-approver rule the resolved

            authority record named when the decision was made. A widening

            release needs one `customer` and one `necto` approval by two

            distinct subjects; a non-widening release is approvable by the

            customer''s own security owner alone.

            '
        decidedAt:
          type: string
          format: date-time
        requiresTwoParties:
          type: boolean
          description: 'Whether the release widened egress or tool authority against

            the release the platform ran when the decision was made.

            `true` marks a decision that needs one `customer` and one

            `necto` approval by two distinct subjects; an absent field

            is a one-party decision. Forward-only: a previous build

            refuses a record that states `true` rather than reading one

            party as two.

            '
        fallback:
          type: boolean
          description: 'Whether the decision is the Necto-operator fallback: the

            operator approves only while no customer security owner

            resolves for the deployment. The record names the fallback

            explicitly and never claims the customer''s approval; its

            `party` stays `necto`. Forward-only: a previous build

            refuses a record that states `true` rather than reading the

            operator as the customer''s approval.

            '
        reasonCode:
          type: string
          enum:
          - not-needed
          - wrong-scope
          - test-failure
          - citation-gone
          - unsafe-change
          - other
          description: 'A rejection''s structured reason code, from the closed set.

            An approval carries neither a code nor a reason text. The

            text beside it is stored and never parsed for ids or

            commands. Forward-only: a previous build refuses a record

            that states one rather than reading a rejection as an

            approval.

            '
        reason:
          type: string
          maxLength: 200
          description: 'The rejection''s free text, stored verbatim beside its code

            and escaped on render. It is never parsed for ids or

            commands. Forward-only: a previous build refuses a record

            that states one rather than reading an unreviewed word as

            the reason code.

            '
    CollaboratorGrant:
      type: object
      description: 'One recorded collaboration grant: the application, the granted

        subject''s issuer and subject, the role, the grant''s content-hash

        version, the actor whose act it was and the time. Every field is

        an identifier — never a display name, an address or a claim.

        `customerId` is the deployment''s own tenant key, recorded with

        the grant and never selected by the request.

        '
      required:
      - customerId
      - applicationId
      - issuer
      - subject
      - role
      - version
      - actor
      - recordedAt
      properties:
        customerId:
          type: string
        applicationId:
          type: string
        issuer:
          type: string
        subject:
          type: string
        role:
          type: string
          enum:
          - viewing
          - editing
          - deploying
          - approving
          - stopping
        version:
          type: string
          description: 'The content-hash identity of the grant state the act

            produced or removed

            '
        actor:
          type: string
        recordedAt:
          type: string
          format: date-time
    ApplicationClone:
      type: object
      description: 'One clone act''s result, as identifiers only: the clone''s new

        stable application id, its new plan version hash and the source

        version hash the clone is parented to. Never a plan, a secret or

        any copied content.

        '
      required:
      - applicationId
      - versionHash
      - parent
      properties:
        applicationId:
          type: string
        versionHash:
          type: string
          description: sha256:<hex> of the clone's new plan version
        parent:
          type: string
          description: The source version hash the clone is parented to
    CommentAnchor:
      type: object
      description: 'What a comment is attached to, by identifier: the anchor

        kind and the anchor''s own id. `kind` is a closed set — an

        unknown kind is refused rather than filed. The id is an

        identifier, never the object it names.

        '
      required:
      - kind
      - id
      properties:
        kind:
          type: string
          enum:
          - plan_version
          - deployment
          - run
          - policy_version
        id:
          type: string
    Comment:
      type: object
      description: 'One comment''s newest row, as identifiers and a count.

        `redacted` is true when the newest row is a redaction;

        `revisionCount` is how many rows the store holds — a count,

        never a prior body. `body` is the newest revision''s content

        and is present only for a live comment: a redacted comment

        carries no body. `redacts` is the version of the revision a

        redaction hides and is present only when `redacted` is true; the

        hidden revision stays in the store, never mutated or deleted.

        The actor is a subject identifier, never a

        display name, and `at` is when the newest row was recorded.

        '
      required:
      - commentId
      - application
      - anchor
      - actor
      - at
      - redacted
      - revisionCount
      properties:
        commentId:
          type: string
        application:
          type: string
        anchor:
          $ref: '#/components/schemas/CommentAnchor'
        actor:
          type: string
        at:
          type: string
          format: date-time
        redacted:
          type: boolean
        revisionCount:
          type: integer
        body:
          type: string
          description: 'The newest revision''s content; absent for a redacted

            comment, because a redacted body is never returned.

            '
        redacts:
          type: string
          description: 'The version of the revision this row hides; absent

            unless `redacted` is true, because a live comment

            redacts nothing.

            '
    ApplicationBudget:
      type: object
      required:
      - applicationId
      - runLimit
      - windowSeconds
      - windowStart
      - windowEnd
      - used
      - remaining
      properties:
        applicationId:
          type: string
        runLimit:
          type: integer
          description: The cap on runs admitted in one window.
        windowSeconds:
          type: integer
          description: 'The fixed window''s length; a window is aligned to the Unix

            epoch, so the same instant maps to the same window everywhere.

            '
        windowStart:
          type: string
          format: date-time
        windowEnd:
          type: string
          format: date-time
        used:
          type: integer
          description: The runs claimed in this window.
        remaining:
          type: integer
          description: 'The runs this window still admits, derived from the cap and
            the

            window''s own count rather than carried beside them.

            '
    PromotionRequest:
      type: object
      description: 'One promotion hop, as metadata only. There is no field for a

        credential or persistent data, structurally: promotion carries an

        image digest and an accepted plan version, never either.

        '
      properties:
        promotionId:
          type: string
        application:
          type: string
        actor:
          type: string
          description: The requester's assertion-resolved subject
        fromEnvironment:
          type: string
          enum:
          - dev
          - stage
          - prod
        toEnvironment:
          type: string
          enum:
          - dev
          - stage
          - prod
        imageDigest:
          type: string
        acceptedPlanHash:
          type: string
        sourceRevision:
          type: string
        detectorVersion:
          type: string
        builderVersion:
          type: string
        profileHash:
          type: string
          description: 'The platform-derived content hash of the application''s

            registered governed profile, or the explicit value `none` when

            none governs it. Never a request field.

            '
        governedRuntime:
          type: string
          description: server-derived from the application's governed profile, never
            a request field; the private-e2b placement gate reads it
        artifactHashes:
          type: array
          description: 'The platform''s own compiled-artifact hashes for the

            registered governed profile in the target environment, or empty

            when none governs it. Never a request field.

            '
          items:
            type: object
            required:
            - name
            - hash
            properties:
              name:
                type: string
              hash:
                type: string
        requestedTier:
          type: string
          enum:
          - standard
          - governed
        placementTier:
          type: string
          enum:
          - standard
          - governed
          description: The platform-computed placement
        namespaceName:
          type: string
          description: 'The platform-derived namespace for the target environment;

            supplied by the derivation seam, never by a caller.

            '
        tests:
          type: array
          items:
            type: object
            required:
            - name
            - artifactUrl
            properties:
              name:
                type: string
              artifactUrl:
                type: string
        requestedAt:
          type: string
          format: date-time
        combinedHash:
          type: string
          description: 'What the two approvers bind: the combined deployment hash
            over

            the application, target environment, image digest, accepted

            plan and governed hashes.

            '
    PromotionApproval:
      type: object
      properties:
        promotionId:
          type: string
        combinedHash:
          type: string
        decision:
          type: string
          enum:
          - approve
          - reject
        approver:
          type: string
        targetHead:
          type:
          - string
          - 'null'
          description: 'The target environment''s head deployment record id at decision

            time — what the approval vouched for alongside the hash.

            '
        decidedAt:
          type: string
          format: date-time
    ApplicationSource:
      type: object
      description: 'Where the application''s code comes from — one of the four accepted

        sources. An identifier, never content: a repository location, a template

        identity, a Dockerfile location or a prebuilt OCI image addressed by

        immutable digest. No field here carries code, a credential or

        persistent data.

        '
      required:
      - kind
      - reference
      properties:
        kind:
          type: string
          enum:
          - repository
          - template
          - dockerfile
          - oci_digest
        reference:
          type: string
          description: 'The source''s identifier: a repository location, a template
            id, a

            Dockerfile location, or a `sha256:<hex>` image digest — the

            immutable identity a prebuilt image is addressed by, never a tag.

            '
    Application:
      type: object
      description: 'One application: the primary customer object, one stable identity

        across `dev`, `stage` and `prod`. Metadata only — the record names

        its source, never code or a credential. Every record carries the

        deployment''s tenant key; no request field selects it.

        '
      required:
      - applicationId
      - name
      - source
      - createdBy
      - createdAt
      properties:
        applicationId:
          type: string
        name:
          type: string
        source:
          $ref: '#/components/schemas/ApplicationSource'
        createdBy:
          type: string
          description: The assertion-resolved subject that registered it
        createdAt:
          type: string
          format: date-time
    ManagedService:
      type: object
      description: 'One managed service''s own state in one application environment:

        the logical resource name and the credential *reference* the

        platform derived. Identifiers only — no field carries a credential

        value and no field carries payload content. `state` is the

        platform''s own word: `provisioned`, or `failed` when the provider

        did not answer — never reported as success.

        '
      required:
      - environment
      - service
      - state
      - resourceName
      - credentialRef
      properties:
        environment:
          type: string
          enum:
          - dev
          - stage
          - prod
        service:
          type: string
          enum:
          - postgresql
          - object_storage
          - durable_queue
        state:
          type: string
          enum:
          - provisioned
          - failed
        resourceName:
          type: string
        credentialRef:
          type: string
        recordedAt:
          type: string
          format: date-time
    ApplicationServices:
      type: object
      description: 'The managed state one application environment owns, verbatim from

        the platform: the three services'' logical resource names and

        credential references, each with its recorded state. The

        environment is the deployment''s own selection; another customer''s

        application is not one this deployment holds.

        '
      required:
      - applicationId
      - environment
      - services
      properties:
        applicationId:
          type: string
        environment:
          type: string
          enum:
          - dev
          - stage
          - prod
        services:
          type: array
          items:
            $ref: '#/components/schemas/ManagedService'
    ApplicationServiceSetup:
      type: object
      description: 'The one field setup names: the environment to set up. The names

        and references are the platform''s derivation, never body fields.

        '
      required:
      - environment
      properties:
        environment:
          type: string
          enum:
          - dev
          - stage
          - prod
    ApplicationSecret:
      type: object
      description: 'The secret''s name and value, carried once. The value is held
        by

        the secret manager and never recorded, returned or echoed; the

        answer carries the reference instead.

        '
      required:
      - environment
      - name
      - value
      properties:
        environment:
          type: string
          enum:
          - dev
          - stage
          - prod
        name:
          type: string
        value:
          type: string
          writeOnly: true
          description: The value the secret manager holds, never recorded or echoed
    SecretReference:
      type: object
      description: 'What recording a secret answered: the secret''s name and the

        reference the platform derived and the secret manager holds. The

        value is never in the answer and never in any record.

        '
      required:
      - name
      - reference
      properties:
        name:
          type: string
        reference:
          type: string
    ApplicationPlan:
      type: object
      description: 'The visible, editable plan: topology only, never authority. No
        field

        grants a tool, source, destination or credential — `necto.yaml`

        remains the only authority source for governed components. The plan

        carries the deployment''s tenant key; no request field selects it.

        '
      required:
      - customerId
      - source
      - components
      - toolchain
      - commands
      - network
      - schedules
      - resources
      - dependencies
      - destinations
      - runtimeTier
      properties:
        customerId:
          type: string
          description: The deployment's tenant key
        source:
          $ref: '#/components/schemas/PlanSource'
        components:
          type: array
          items:
            $ref: '#/components/schemas/PlanComponent'
        toolchain:
          $ref: '#/components/schemas/PlanToolchain'
        commands:
          $ref: '#/components/schemas/PlanCommands'
        network:
          $ref: '#/components/schemas/PlanNetwork'
        schedules:
          type: array
          items:
            $ref: '#/components/schemas/PlanSchedule'
        resources:
          $ref: '#/components/schemas/PlanResources'
        dependencies:
          type: array
          items:
            type: string
        destinations:
          type: array
          items:
            $ref: '#/components/schemas/PlanDestination'
        runtimeTier:
          type: string
          enum:
          - standard
          - governed
          description: The requested runtime tier; placement is computed later
    PlanSource:
      type: object
      description: 'The source revision and build context, or the digest that is the

        source for a prebuilt image — topology, never authority.

        '
      required:
      - kind
      properties:
        kind:
          type: string
          enum:
          - repository
          - dockerfile
          - oci_digest
        revision:
          type: string
        buildContext:
          type: string
        imageDigest:
          type: string
          description: sha256:<hex>; the immutable identity a prebuilt image is addressed
            by
    PlanComponent:
      type: object
      description: 'A component''s name and kind, and the one profile identity a

        governed component carries — a name, never the compiled profile.

        '
      required:
      - name
      - kind
      properties:
        name:
          type: string
        kind:
          type: string
          enum:
          - web
          - api
          - worker
          - static
        profile:
          type: string
    PlanToolchain:
      type: object
      description: 'The detector, builder and base-image identifiers. Identifiers,

        never measurements: synthetic adapter input records

        `synthetic-adapter-input` and claims no version.

        '
      required:
      - detector
      - builder
      - baseImages
      properties:
        detector:
          type: string
        builder:
          type: string
        baseImages:
          type: array
          items:
            type: string
    PlanCommands:
      type: object
      required:
      - build
      properties:
        build:
          type: array
          items:
            type: string
        start:
          type: string
    PlanPort:
      type: object
      required:
      - port
      properties:
        port:
          type: integer
        protocol:
          type: string
    PlanHealthCheck:
      type: object
      required:
      - path
      - intervalSeconds
      properties:
        path:
          type: string
        intervalSeconds:
          type: integer
    PlanNetwork:
      type: object
      required:
      - ports
      properties:
        ports:
          type: array
          items:
            $ref: '#/components/schemas/PlanPort'
        healthCheck:
          $ref: '#/components/schemas/PlanHealthCheck'
    PlanSchedule:
      type: object
      description: A command with no schedule is a worker; one with a cron expression
        is a schedule
      required:
      - name
      - command
      properties:
        name:
          type: string
        command:
          type: string
        schedule:
          type: string
    PlanResources:
      type: object
      description: Resources and scaling bounds
      required:
      - cpuCores
      - memoryMib
      - minInstances
      - maxInstances
      properties:
        cpuCores:
          type: number
          format: double
        memoryMib:
          type: integer
        minInstances:
          type: integer
        maxInstances:
          type: integer
    PlanDestination:
      type: object
      description: 'A declared external destination for a standard component — a bare

        host name, topology only. It grants no reachability and routes no

        traffic.

        '
      required:
      - host
      properties:
        host:
          type: string
    ApplicationPlanVersion:
      type: object
      description: 'One immutable plan version: the plan, its content hash and parent,

        and the winning source of every field. The hash is minted from the

        tenant key, the plan and the parent, so a version is never

        re-labelled and a correction is a new version, not a change to this

        one.

        '
      required:
      - applicationId
      - versionHash
      - plan
      - provenance
      properties:
        applicationId:
          type: string
        versionHash:
          type: string
          description: sha256:<hex> over the tenant key, the plan and the parent
        parent:
          type: string
          description: The version this one corrects; absent for the first version
        plan:
          $ref: '#/components/schemas/ApplicationPlan'
        provenance:
          type: object
          description: 'Every plan field''s winning source, keyed by the plan''s field

            name — the plan''s own precedence display.

            '
          additionalProperties:
            $ref: '#/components/schemas/PlanFieldProvenance'
    PlanFieldProvenance:
      type: string
      description: 'Where a field''s winning value came from, verbatim from the

        plan''s own precedence: the explicit repository file, the saved

        project setting, or the detector default. Every displayed field

        carries one.

        '
      enum:
      - explicit repository file
      - saved project setting
      - detector default
    ApplicationPlanDetection:
      type: object
      description: 'The detection input. The application''s registered source selects

        the construction path: `adapterInput` is the detector''s output for a

        repository or template source, `dockerfile` is the file''s text for a

        Dockerfile source, and a prebuilt image has neither — its digest is

        the source and the build is skipped. The detector output is adapter

        input, never the public schema: it is read against a closed

        contract, and a key outside it is refused rather than ignored.

        `saved` is the saved project setting level, read against the closed

        `necto.app.yaml` schema.

        '
      properties:
        adapterInput:
          type: object
          description: 'The detector''s output for a repository or template source,

            read against the adapter''s closed contract — never the public

            schema and never persisted as such.

            '
          additionalProperties: true
        dockerfile:
          type: string
          description: 'The Dockerfile''s text for a Dockerfile source; at most the

            minimal read (FROM, EXPOSE, CMD) resolves a value, and

            anything ambiguous is refused.

            '
        saved:
          type: object
          description: 'The saved project setting level, read against the closed

            `necto.app.yaml` schema; an unknown key is refused.

            '
          additionalProperties: true
    ApplicationPlanCorrection:
      type: object
      description: 'A customer correction of the current plan version. `values` is
        the

        closed `necto.app.yaml` schema — the correctable plan fields. A key

        outside it is refused as unknown; `toolchain` and `source` are

        deliberately not correctable, and `customer_id` is refused like any

        other unknown key because the deployment supplies the tenant key.

        '
      required:
      - values
      properties:
        values:
          type: object
          description: The closed `necto.app.yaml` value set
          additionalProperties: true
    ApplicationGuardrails:
      type: object
      description: 'The read-only graph and the default guardrails for one

        application''s accepted plan version. Every field states the source

        it came from and the accepted plan version it was read at: a graph

        field''s source is the plan''s own per-field provenance, a guardrail

        field''s source is the governed profile (or `default` when none

        governs), and the placement''s source is `computed`. The plan is

        topology only and grants no authority — no graph field is compiled

        into a guardrail.

        '
      required:
      - applicationId
      - planVersion
      - fields
      properties:
        applicationId:
          type: string
        planVersion:
          type: string
          description: The accepted plan version hash every field was read at
        profileHash:
          type: string
          description: 'The governed profile''s content hash when one governs the

            application; absent when none does

            '
        fields:
          type: array
          items:
            $ref: '#/components/schemas/GuardrailField'
        refusals:
          type: array
          items:
            $ref: '#/components/schemas/GuardrailRefusal'
    GuardrailField:
      type: object
      description: 'One graph or guardrail field, its value, the source the value
        came

        from and the accepted plan version it was read at.

        '
      required:
      - name
      - value
      - source
      - version
      properties:
        name:
          type: string
          description: 'The plan field''s camelCase name for a graph field, or the

            guardrail''s own dotted name (`guardrails.actionAllowlist`,

            `guardrails.egressAllowlist`, `guardrails.credentialScopes`,

            `placement`)

            '
        value: {}
        source:
          type: string
          description: 'The value''s winning source: the plan''s per-field provenance

            level for a graph field, `governed profile` for a compiled

            guardrail, `default` when no profile governs, or `computed`

            for the placement. Never a plan field standing in for

            authority.

            '
        version:
          type: string
          description: The accepted plan version hash this field was read at
    GuardrailRefusal:
      type: object
      description: 'A refusal the read made visible: the field it concerns, the policy

        decision''s own reason, its safe metadata and the accepted plan

        version it was read at — never a generic application error.

        '
      required:
      - name
      - reason
      - detail
      - version
      properties:
        name:
          type: string
        reason:
          type: string
          description: The policy decision's own named reason
        field:
          type: string
          description: The profile field a compile refusal names
        detail:
          type: string
        version:
          type: string
    ApplicationDeployment:
      type: object
      description: 'One deployment of the application''s accepted plan. The plan

        version is named by its content hash and the image by immutable

        digest; the environment is the deployment''s dev environment, and the

        governed profile hash and compiled-artifact hashes on the record are

        the platform''s own derived values, never body fields.

        '
      required:
      - acceptedPlanHash
      - imageDigest
      properties:
        acceptedPlanHash:
          type: string
          description: The accepted plan version's content hash
        imageDigest:
          type: string
          description: sha256:<hex>; the built image this deployment runs
        tests:
          type: array
          description: Test-name and artifact-URL identifier pairs
          items:
            type: object
            required:
            - name
            - artifactUrl
            properties:
              name:
                type: string
              artifactUrl:
                type: string
    DeploymentRecord:
      type: object
      description: 'Every deployment''s automatic record: metadata only —

        never source, prompt, response, secret or payload content.

        '
      properties:
        deploymentId:
          type: string
        actor:
          type: string
        application:
          type: string
        environment:
          type: string
          enum:
          - dev
          - stage
          - prod
        sourceRevision:
          type: string
        detectorVersion:
          type: string
        builderVersion:
          type: string
        acceptedPlanHash:
          type: string
        imageDigest:
          type: string
        tests:
          type: array
          description: Test-name and artifact-URL identifier pairs
          items:
            type: object
            required:
            - name
            - artifactUrl
            properties:
              name:
                type: string
              artifactUrl:
                type: string
        recordedAt:
          type: string
          format: date-time
        outcome:
          type: string
        profileHash:
          type: string
        artifactHashes:
          type: array
          items:
            type: object
            required:
            - name
            - hash
            properties:
              name:
                type: string
              hash:
                type: string
    AgentCard:
      type: object
      description: The agent card's field set is fixed
      properties:
        agentId:
          type: string
        owner:
          type: string
        purpose:
          type: string
        dataSources:
          type: array
          items:
            type: string
        allowedTools:
          type: array
          items:
            type: string
        environment:
          type: string
        currentVersion:
          type: string
        currentPolicyHash:
          type: string
        recentRuns:
          type: array
          items:
            $ref: '#/components/schemas/RunSummary'
        deniedRequests:
          type: array
          description: Returned alongside runs, never behind a filter
          items:
            $ref: '#/components/schemas/DeniedRequest'
        approvals:
          type: array
          items:
            $ref: '#/components/schemas/ApprovalRecord'
    RunSummary:
      type: object
      properties:
        runId:
          type: string
        startedAt:
          type: string
          format: date-time
        outcome:
          type: string
          enum:
          - drafted
          - approved
          - sent
          - refused
          - failed
        citationCount:
          type: integer
        uncited:
          type: boolean
          description: An uncited draft cannot be promoted to a send
        trigger:
          $ref: '#/components/schemas/RunTrigger'
    RunTrigger:
      type: object
      description: 'The schedule tick a governed run was started from. Absent on a
        run

        that was started without a trigger, which reads as absent rather

        than as a fabricated tick.

        '
      required:
      - kind
      - caller
      - taskId
      - scheduledAt
      properties:
        kind:
          type: string
          enum:
          - schedule
        caller:
          type: string
          description: 'The verified client principal that started the run, never
            a

            value the run''s own request supplied.

            '
        taskId:
          type: string
        scheduledAt:
          type: string
          format: date-time
    ScheduleTriggerRequest:
      type: object
      description: 'A schedule tick a registered machine client starts a run from.

        '
      required:
      - kind
      - taskId
      - scheduledAt
      - key
      properties:
        kind:
          type: string
          enum:
          - schedule
        taskId:
          type: string
        scheduledAt:
          type: string
          format: date-time
          description: The tick's instant, timezone-aware
        key:
          type: string
          description: 'The tick''s canonical key, recomputed from the task id and
            the

            scheduled instant; a value that does not match is refused.

            '
    ScheduleEventRequest:
      type: object
      description: 'A tick a scheduler observed but did not fire, reported so it is

        recorded rather than silent.

        '
      required:
      - kind
      - reason
      - taskId
      - scheduledAt
      - key
      properties:
        kind:
          type: string
          enum:
          - skipped
        reason:
          type: string
          enum:
          - missed
        taskId:
          type: string
        scheduledAt:
          type: string
          format: date-time
          description: The tick's instant, timezone-aware
        key:
          type: string
          description: 'The tick''s canonical key, recomputed from the task id and
            the

            scheduled instant; a value that does not match is refused.

            '
    DeniedRequest:
      type: object
      properties:
        at:
          type: string
          format: date-time
        caller:
          type: string
        kind:
          type: string
          enum:
          - retrieval_denied
          - egress_blocked
          - credential_expired
          - scope_refused
        reason:
          type: string
    ReviewBundle:
      type: object
      description: 'One period of one agent''s evidence, as an auditor reads it. Built

        only from `TimelineEntry` and release identifiers: there is no field

        here a document body or a raw prompt could occupy. Plain JSON,

        readable without Necto tooling.

        '
      required:
      - bundleVersion
      - exportId
      - agentId
      - exporter
      - exportedAt
      - periodFrom
      - periodTo
      - complete
      properties:
        bundleVersion:
          type: string
          description: The bundle format, so a reader can tell versions apart
        exportId:
          type: string
        agentId:
          type: string
        exporter:
          type: string
          description: The verified subject that asked for the export
        exportedAt:
          type: string
          format: date-time
        periodFrom:
          type: string
          format: date-time
        periodTo:
          type: string
          format: date-time
        retentionCutoff:
          type: string
          format: date-time
          description: Nothing before this is guaranteed to still exist
        complete:
          type: boolean
          description: False when the period begins before the retention cutoff
        incompleteReason:
          type: string
        entryCount:
          type: integer
        releases:
          type: array
          items:
            type: object
            properties:
              releaseId:
                type: string
              version:
                type: string
              state:
                type: string
              via:
                type: string
              createdAt:
                type: string
                format: date-time
              profileHash:
                type: string
              diffHash:
                type: string
              approver:
                type: string
              approvedAt:
                type: string
                format: date-time
              requiredTests:
                type: array
                items:
                  type: array
                  items: {}
                  description: name, passed, blocksRelease
              artifactHashes:
                type: array
                items:
                  type: array
                  items: {}
                  description: name, hash
        runs:
          type: array
          items:
            type: object
            properties:
              runId:
                type: string
              entries:
                type: array
                items:
                  $ref: '#/components/schemas/TimelineEntry'
    TimelineEntry:
      type: object
      description: 'Immutable. Identifiers and decisions only: no document text, no
        raw

        prompts, no credential values.

        '
      required:
      - at
      - kind
      - customerId
      properties:
        at:
          type: string
          format: date-time
        kind:
          type: string
          description: '`sandbox_resumed` is reserved and never emitted: the runtime

            interface has no resume operation. Do not branch on it.

            '
          enum:
          - sandbox_created
          - sandbox_resumed
          - sandbox_timeout
          - sandbox_killed
          - sandbox_start_denied
          - sandbox_start_failed
          - sandbox_exec_failed
          - run_finished
          - sandbox_stop_failed
          - scope_checked
          - retrieval_decided
          - candidate_dropped
          - credential_injected
          - draft_produced
          - approval_decided
          - send_attempted
          - action_replay_refused
          - export_created
          - containment_triggered
          - workload_stopped
          - authority_revoked
          - service_restored
          - identity_asserted
          - authority_resolved
          - authority_granted
          - authority_withdrawn
          - release_decided
          - standing_approval_revoked
          - ttl_exception_granted
          - credential_withdrawn
          - api_request
          - caller_first_seen
          - promotion_requested
          - promotion_decided
          - promotion_executed
          - schedule_fired
          - schedule_skipped
          - schedule_refused
        evidenceClass:
          type: string
          description: 'The entry''s own class: `semantic` for a kind the four

            behavioural dimensions read, `infrastructure` for every other

            kind the record names. A word, never a number or a score. An

            entry presented as semantic agent evidence is checked before

            it is presented, and a refusal names the kind.

            '
          enum:
          - semantic
          - infrastructure
        issuer:
          type: string
          description: 'Which identity provider vouched for `caller`. A `sub` is unique

            only within its issuer, and a later dispute needs to know which

            provider asserted it. Under federation this is the origin, not

            the console, so a federated login''s entry names the provider

            that actually asserted it.

            '
        customerId:
          type: string
          description: 'The third part of the authority key. The same human may exist

            under more than one customer.

            '
        agentId:
          type: string
          description: 'The agent an entry is about, where that is not implied by
            the

            run it belongs to. A containment event is the case that needs

            it: stopping an agent is not part of a run, and "which agent

            was contained" is the first question an incident review asks.

            '
        workloadId:
          type: string
          description: 'The workload a lifecycle entry is about. An identifier the

            substrate issued, never a substrate handle.

            '
        profileHash:
          type: string
        releaseVersion:
          type: string
        grantId:
          type: string
          description: 'The standing approval a revocation entry ends. An identifier

            of the grant, never its channel, message or approver.

            '
        artifactDigest:
          type: string
          description: 'The prebuilt artifact the workload started from: an E2B

            template build id (a UUID), built outside the platform process,

            never a template name, alias or tag. The released identifier the

            sandbox was actually created from, so "what ran" is answerable

            from the record rather than from the release store''s current

            state.

            '
        owner:
          type: string
          description: 'Who owns the agent, carried from the compiled metadata.

            Distinct from `caller`, which is whoever made the request an

            entry records.

            '
        caller:
          type: string
        taskId:
          type: string
          description: 'The scheduled task a `schedule_fired`, `schedule_skipped`
            or

            `schedule_refused` entry is about. It names the task whose tick

            did or did not run.

            '
        scheduledAt:
          type: string
          format: date-time
          description: 'The tick''s scheduled instant, timezone-aware. A tick is an

            instant, not a period.

            '
        key:
          type: string
          description: 'The tick''s canonical key, recomputed from the task id and
            the

            scheduled instant. A repeated key never starts a second run.

            Absent on a tick refused before it could have run: no key names

            a tick that has not happened.

            '
        purpose:
          type: string
          description: 'The declared purpose the query ran under. A decision is not

            reconstructable without it.

            '
        environment:
          type: string
          description: 'The environment the query ran in. Staging never reaches a

            production source; the record proves which one asked.

            '
        application:
          type: string
          description: 'The application the record is about, from the deployment

            record''s `(customer, application, environment)` triple. An

            identifier, never the application''s source, configuration or

            content.

            '
        component:
          type: string
          description: 'The plan component within the application the record is about.

            A component name, never the component''s command, image or

            environment.

            '
        deploymentId:
          type: string
          description: 'The deployment record the entry is about: the id a promotion

            approval records as its target head and the deployment store

            compares. An identifier, never the deployment''s actor, plan or

            artifact.

            '
        connector:
          type: string
          description: 'Which connector made the source decision. An identifier, never

            a credential or an endpoint.

            '
        documentId:
          type: string
        documentVersion:
          type: string
        location:
          type: string
        decision:
          type: string
          enum:
          - allow
          - deny
        reason:
          type: string
          description: 'Required on a deny. A silent drop is indistinguishable from
            a

            retrieval miss and destroys the audit trail. On a tick refused

            before it could have run it is `future`.

            '
        tokenId:
          type: string
          description: Identifier only, never the token value
        envelopeId:
          type: string
          description: 'The action envelope an entry is about. An approval binds the

            envelope''s hash, so a reviewer asking whether an action was

            executed, and executed once, has it in hand. The id is what

            joins `send_attempted` to `action_replay_refused`.

            '
        queryHash:
          type: string
          description: Salted
        citations:
          type: array
          items:
            type: string
          description: 'The citation set, as `document@version#location` identifier

            triples: the passages a step relied on, never the passages

            themselves.

            '
        upstreamIdentityPath:
          type: array
          items:
            type: string
          description: 'An `identity_asserted` or `authority_resolved` entry''s whole

            upstream identity path, as `issuer#subject#verification_basis`

            identifier triples, at most two, origin first.

            `verification_basis` is one of `id_token_verified`,

            `broker_verified_origin` or `broker_authenticated`. `issuer`

            and `caller` above already name the first entry; this carries

            the rest of the chain. Absent for a non-federated login.

            '
        method:
          type: string
          description: 'HTTP method of the request an `api_request` entry records.
            An

            identifier of the call, never the call itself.

            '
        pathTemplate:
          type: string
          description: 'The matched route''s path template, such as

            `/agents/{agentId}/card`, never the concrete path. Absent on an

            unmatched route, where only the concrete path exists.

            '
        routeName:
          type: string
          description: 'The matched route''s name. `unmatched` when no route matched:

            an unmatched route is a refusal, and its record has to say

            which path could not.

            '
        status:
          type: integer
          description: The response status the request produced.
        outcome:
          type: string
          enum:
          - served
          - refused
          - failed
          description: 'The refusal/fault taxonomy, on `api_request` entries:

            `served` is a 2xx; `refused` is Necto considering a request and

            saying no (the 4xxs); `failed` is Necto unable to answer (the

            5xxs). Not `decision`''s retrieval enum.

            '
        latencyBucket:
          type: string
          enum:
          - under_100ms
          - 100ms_1s
          - 1s_10s
          - over_10s
          description: 'Coarse response latency, an ordered bucket, not the raw duration.

            '
        exitCode:
          type: integer
          description: 'The artifact entrypoint''s own exit result, recorded

            unchanged. Present only on a run that ended: `0` ran, and the

            entrypoint''s own non-zero code is the entrypoint''s own result,

            not a platform refusal.

            '
        stdoutBytes:
          type: integer
          description: 'The number of UTF-8 bytes the entrypoint wrote to stdout,

            never the output itself. Present only on a run that ended, so a

            silent refusal is visible without any output reaching the record.

            '
        stderrBytes:
          type: integer
          description: 'The number of UTF-8 bytes the entrypoint wrote to stderr,

            never the output itself. Present only on a run that ended, so a

            silent refusal is visible without any output reaching the record.

            '
    RegimeKey:
      type: object
      description: 'The release regime a baseline and an observation are keyed to:
        one

        agent, one compiled profile and one release. A release opens a new

        regime; a prior regime''s baseline is kept for comparison and never

        scores the next one.

        '
      required:
      - agentId
      - profileHash
      - releaseVersion
      properties:
        agentId:
          type: string
        profileHash:
          type: string
        releaseVersion:
          type: string
    BehaviourRange:
      type: object
      description: A closed range. Both ends are included.
      required:
      - minimum
      - maximum
      properties:
        minimum:
          type: number
        maximum:
          type: number
    BehaviourRefusal:
      type: object
      description: 'One refused attempt, carrying the rule that refused it. A

        refusal is an observation, never an absence: an attempt the

        platform refused is returned here rather than dropped from the

        comparison. Identifiers only, never content, passages, prompts or

        credentials.

        '
      required:
      - rule
      properties:
        identifier:
          type: string
          description: 'The document, destination or tool identifier the attempt

            named, when the record names one.

            '
        rule:
          type: string
          description: The rule that refused the attempt, as the record states it.
    BehaviourCollection:
      type: object
      description: One document a run read, or was refused.
      required:
      - name
      - result
      properties:
        name:
          type: string
        result:
          type: string
          enum:
          - read
          - refused
        rule:
          type: string
          description: The rule that refused the read; absent when it was read.
    BehaviourDestination:
      type: object
      description: One destination a run contacted, or was refused.
      required:
      - name
      - result
      properties:
        name:
          type: string
        result:
          type: string
          enum:
          - sent
          - refused
        attempts:
          type: integer
          minimum: 0
          description: 'The number of attempts the run''s own record carries with

            this name and result: the send attempts the platform

            allowed for a `sent` destination, and the attempts the

            named rule refused for a `refused` one.

            '
        rule:
          type: string
          description: The rule that refused the send; absent when it was sent.
    BehaviourComparison:
      type: object
      description: 'One dimension''s comparison verdict. `neutral` when the run''s

        values are inside the regime''s baseline; `review` when they are

        outside it or an attempt on the dimension was refused. Never a

        number.

        '
      required:
      - verdict
      properties:
        verdict:
          type: string
          enum:
          - neutral
          - review
        reason:
          type: string
          description: Plain words for a `review` verdict; absent when neutral.
    BehaviourComparisons:
      type: object
      description: The comparison verdict for each of the four dimensions.
      required:
      - information
      - destinations
      - tools
      - volume
      properties:
        information:
          $ref: '#/components/schemas/BehaviourComparison'
        destinations:
          $ref: '#/components/schemas/BehaviourComparison'
        tools:
          $ref: '#/components/schemas/BehaviourComparison'
        volume:
          $ref: '#/components/schemas/BehaviourComparison'
    RegimeTransition:
      type: object
      description: 'Where one regime''s baseline ended and the next began, derived

        from the agent''s own run records.

        '
      required:
      - endedAt
      - ended
      - beganAt
      - began
      properties:
        endedAt:
          type: string
          format: date-time
        ended:
          $ref: '#/components/schemas/RegimeKey'
        beganAt:
          type: string
          format: date-time
        began:
          $ref: '#/components/schemas/RegimeKey'
    BehaviourBaselineInformation:
      type: object
      description: The documents a usual run reads, and how many it opens.
      required:
      - documents
      properties:
        documents:
          type: array
          items:
            type: string
        recordsPerRun:
          $ref: '#/components/schemas/BehaviourRange'
          description: Absent while the regime has no observed run.
    BehaviourBaselineDestinations:
      type: object
      description: The destinations a usual run contacts.
      required:
      - destinations
      properties:
        destinations:
          type: array
          items:
            type: string
    BehaviourBaselineTools:
      type: object
      description: The tool order a usual run follows.
      required:
      - order
      properties:
        order:
          type: array
          items:
            type: string
    BehaviourBaselineVolume:
      type: object
      description: The records-per-run and run-seconds a usual run shows.
      properties:
        recordsPerRun:
          $ref: '#/components/schemas/BehaviourRange'
          description: Absent while the regime has no observed run.
        runSeconds:
          $ref: '#/components/schemas/BehaviourRange'
          description: Absent while the regime has no observed run.
    BehaviourBaseline:
      type: object
      description: 'What is usual for one agent in one release regime. `state` is

        `ready` when the regime has enough observed history to compare

        against, and `insufficient_volume` when it does not: the state

        scores nothing, and no verdict is returned rather than a low one.

        '
      required:
      - agentId
      - state
      - observedRuns
      - information
      - destinations
      - tools
      - volume
      properties:
        agentId:
          type: string
        regime:
          $ref: '#/components/schemas/RegimeKey'
          description: Absent when no run names a regime yet.
        state:
          type: string
          enum:
          - ready
          - insufficient_volume
        reason:
          type: string
          description: Why the state is `insufficient_volume`; absent when ready.
        observedRuns:
          type: integer
          minimum: 0
          description: 'The number of the regime''s finished runs this baseline

            summarises. The platform holds no separate review record, so

            this is an observation count and never a claim that a person

            reviewed each run.

            '
        minimumVolume:
          type: integer
          minimum: 1
          description: 'The configured minimum the state is read against; absent when

            the deployment configures none, and then the state is

            `insufficient_volume` rather than ready.

            '
        information:
          $ref: '#/components/schemas/BehaviourBaselineInformation'
        destinations:
          $ref: '#/components/schemas/BehaviourBaselineDestinations'
        tools:
          $ref: '#/components/schemas/BehaviourBaselineTools'
        volume:
          $ref: '#/components/schemas/BehaviourBaselineVolume'
        transitions:
          type: array
          items:
            $ref: '#/components/schemas/RegimeTransition'
    BehaviourInformation:
      type: object
      description: The documents one run read, or was refused.
      required:
      - collections
      properties:
        collections:
          type: array
          items:
            $ref: '#/components/schemas/BehaviourCollection'
        records:
          type: integer
          minimum: 0
          description: How many records the run opened.
    BehaviourDestinations:
      type: object
      description: The destinations one run contacted, or was refused.
      required:
      - destinations
      properties:
        destinations:
          type: array
          items:
            $ref: '#/components/schemas/BehaviourDestination'
    BehaviourTools:
      type: object
      description: The tools one run used, in order, and those refused.
      required:
      - order
      properties:
        order:
          type: array
          items:
            type: string
        refusals:
          type: array
          items:
            $ref: '#/components/schemas/BehaviourRefusal'
    BehaviourVolume:
      type: object
      description: The records one run opened and the seconds it ran.
      required:
      - records
      properties:
        records:
          type: integer
          minimum: 0
        seconds:
          type: number
          description: Absent when the run has no recorded end.
    BehaviourObservation:
      type: object
      description: 'One run''s observed behaviour on the four dimensions, compared

        with the baseline of the regime that run belongs to, as it stood

        before the run started. Only that regime''s runs that finished

        before this run started count. Refused attempts are observations

        carrying the rule that refused them, never dropped. `state` is

        `insufficient_volume` when the regime has too little observed

        history to compare against, and then `comparison` is absent: the

        run scores nothing rather than badly.

        '
      required:
      - runId
      - agentId
      - state
      - observedRuns
      - information
      - destinations
      - tools
      - volume
      properties:
        runId:
          type: string
        agentId:
          type: string
        regime:
          $ref: '#/components/schemas/RegimeKey'
          description: The regime this run was compared against.
        state:
          type: string
          enum:
          - ready
          - insufficient_volume
        reason:
          type: string
          description: Why the state is `insufficient_volume`; absent when ready.
        observedRuns:
          type: integer
          minimum: 0
          description: 'The number of the regime''s runs that finished before this

            run started, which the comparison baseline summarises. The

            platform holds no separate review record, so this is an

            observation count and never a claim that a person reviewed

            each run.

            '
        minimumVolume:
          type: integer
          minimum: 1
          description: The configured minimum the state is read against; absent when
            the deployment configures none.
        information:
          $ref: '#/components/schemas/BehaviourInformation'
        destinations:
          $ref: '#/components/schemas/BehaviourDestinations'
        tools:
          $ref: '#/components/schemas/BehaviourTools'
        volume:
          $ref: '#/components/schemas/BehaviourVolume'
        comparison:
          $ref: '#/components/schemas/BehaviourComparisons'
          description: Absent while the state is `insufficient_volume`.
    AuthorityCheck:
      type: object
      required:
      - tool
      - allowed
      properties:
        tool:
          type: string
        allowed:
          type: boolean
        authority:
          type: string
          enum:
          - read_only
          - draft_only
          - write_reversible
          - write_external
          description: Present when allowed is true
        reason:
          type: string
          description: Required when allowed is false
    TaskCompileResult:
      type: object
      required:
      - narrowed
      - agentId
      - data
      - tools
      - authority
      - droppedData
      - droppedTools
      - grantedAuthority
      properties:
        narrowed:
          type: boolean
          description: 'Always true: a 200 is returned only once every axis is

            confirmed a subset of the target agent''s compiled artifacts. A

            response that would carry `false` is a 422 `Problem` instead.

            '
        agentId:
          type: string
        agentVersion:
          type: string
          description: The agent's last approved release version, when one exists
        data:
          type: array
          items:
            type: string
          description: The task's own `narrow.data`, already confirmed a subset
        tools:
          type: array
          items:
            type: string
          description: The task's own `narrow.tools`, already confirmed a subset
        authority:
          type: string
          enum:
          - read_only
          - draft_only
          - write_reversible
          - write_external
          description: The task's own requested authority
        droppedData:
          type: array
          items:
            type: string
          description: The profile's `data` minus the task's `narrow.data`
        droppedTools:
          type: array
          items:
            type: string
          description: The profile's `tools` minus the task's `narrow.tools`
        grantedAuthority:
          type: string
          enum:
          - read_only
          - draft_only
          - write_reversible
          - write_external
          description: The profile's own authority ceiling
        scheduled:
          type: object
          description: 'Present only when the applied task carried a `schedule`. The

            recorded schedule carries its invocation beside the narrowing,

            so a scheduler can reconstruct the run without the task file.

            '
          required:
          - taskId
          - schedule
          - input
          - deliver
          properties:
            taskId:
              type: string
            schedule:
              type: string
              description: 'Five-field cron (minute, hour, day-of-month, month,

                day-of-week) in UTC, validated by the standard-tier

                trigger validator. A malformed schedule is refused by

                name at compile, never recorded.

                '
            input:
              type: string
            deliver:
              type: string
    TaskSummary:
      type: object
      description: 'One recorded `kind: Task` apply that carried a `schedule`. The

        record carries the compiled invocation (input, deliver,

        authority, narrowed data/tools) and the tenant key, so a

        scheduler can reconstruct the run.

        '
      required:
      - customerId
      - taskId
      - agentId
      - schedule
      - input
      - deliver
      - authority
      - data
      - tools
      properties:
        customerId:
          type: string
          description: 'The deployment''s own tenant key, recorded with the task.

            Deployment-supplied, never taken from a request, claim or header.

            '
        taskId:
          type: string
        agentId:
          type: string
        schedule:
          type: string
          description: 'Five-field cron in UTC, validated by the standard-tier

            trigger validator at compile. A malformed schedule is refused

            by name, never recorded.

            '
        input:
          type: string
          description: The task's own `input`, kept for the scheduler
        deliver:
          type: string
          description: The task's own `deliver`, kept for the scheduler
        authority:
          type: string
          enum:
          - read_only
          - draft_only
          - write_reversible
          - write_external
          description: The task's own requested authority
        data:
          type: array
          items:
            type: string
          description: The task's narrowed `narrow.data`
        tools:
          type: array
          items:
            type: string
          description: The task's narrowed `narrow.tools`
        createdAt:
          type: string
          format: date-time
    MutationRecord:
      type: object
      description: 'One collaboration mutation, as the append-only journal recorded

        it: the actor, the organisation, the application, the object kind

        and id, the action and the resulting version. Every field is an

        identifier — never a comment body, prompt or payload. A clone lands

        as a `plan_version`, never as a kind of its own.

        '
      required:
      - customerId
      - organisationId
      - actor
      - application
      - objectKind
      - objectId
      - action
      - resultingVersion
      - at
      properties:
        customerId:
          type: string
          description: 'The deployment''s own tenant key, recorded with the

            mutation. Deployment-supplied, never taken from a request,

            claim or header. The read is already tenant-keyed; the key

            is carried so the record says what it is keyed by.

            '
        organisationId:
          type: string
          description: 'The first-class organisation the mutation belongs to,

            recorded beside the tenant key. Strictly inside the

            customer, never a second tenant axis; the record carries it

            so a caller can tell which organisation a mutation belongs

            to.

            '
        actor:
          type: string
          description: The mutation's assertion-resolved subject
        application:
          type: string
        objectKind:
          type: string
          enum:
          - comment
          - plan_version
          - role_grant
          - membership
          - component
        objectId:
          type: string
        action:
          type: string
          enum:
          - created
          - cloned
          - granted
          - revoked
        resultingVersion:
          type: string
        at:
          type: string
          format: date-time
    HistoryEntry:
      type: object
      description: 'One derived version-history entry: the anchor kind, the

        identifier its store knows it by, the time the store recorded

        and the actor where the store has one. `at` and `actor` are

        absent where the owning store has neither — a plan version

        carries no clock and no actor, a run record carries no actor.

        `parent` is a plan version''s lineage by hash. Nothing here is

        invented to fill a gap.

        '
      required:
      - anchor
      - identifier
      properties:
        anchor:
          type: string
          enum:
          - plan_version
          - deployment
          - release
          - run
        identifier:
          type: string
        at:
          type: string
          format: date-time
        actor:
          type: string
        parent:
          type: string
    WhoAmI:
      type: object
      required:
      - caller
      - customer
      - role
      properties:
        caller:
          type: string
          description: 'The stable subject identifier this caller resolved to. Under

            federation this is the origin entry''s `sub` on the assertion,

            not the console''s.

            '
        customer:
          type: string
        role:
          type: string
        approvalAuthority:
          type: boolean
          description: Whether this caller may approve a release hash
    FederationProvider:
      type: object
      description: 'One record of the committed federation registry, rendered for

        the console server that reconciles its provider list against it.

        Identifiers and display fields only, never a secret: the upstream

        issuer, the record''s Necto-side customer id, the named endpoint

        hosts and the allowed verification bases. No client credential,

        domain or endpoint path is in the record or the answer.

        '
      required:
      - issuer
      - customerId
      - jwksHost
      - allowedVerificationBases
      properties:
        issuer:
          type: string
          format: uri
          description: 'The upstream identity provider''s issuer identifier, as the

            record holds it.

            '
        customerId:
          type: string
          description: 'The Necto-side customer the record is for; every record this

            deployment answers carries the deployment''s own.

            '
        jwksHost:
          type: string
          description: The named host serving the issuer's JWKS
        discoveryHost:
          type: string
          description: The named discovery endpoint host, when configured
        tokenHost:
          type: string
          description: The named token endpoint host, when configured
        userinfoHost:
          type: string
          description: The named user-info endpoint host, when configured
        allowedVerificationBases:
          type: array
          items:
            type: string
          description: 'The verification bases the record allows, e.g.

            `id_token_verified`; the console''s own basis must be among

            them.

            '
    ObservedDestination:
      type: object
      description: 'One refused destination, identifier-only. The destination''s own

        identifiers and the platform''s own instants, plus the bounded

        attempt counter; no payload, content or capture field exists. The

        tenant key, the run''s governed-component identity and the run come

        from the deployment and the verified workload claims, never from a

        request body. The attempt counter is a bounded count, never a

        score. A repeat observation advances `lastSeenAt` and never moves

        `observedAt`; `capReached` marks the run''s single cap-reached

        record, written once when a new destination arrives past the

        per-run cap.

        '
      required:
      - destinationId
      - customerId
      - applicationId
      - runId
      - host
      - port
      - protocol
      - observedAt
      - lastSeenAt
      - attempts
      - capReached
      properties:
        destinationId:
          type: string
          description: 'The stable identifier for this observed destination; the

            decision record and the customer-plane routes name it.

            '
        customerId:
          type: string
          description: The deployment's own customer id
        applicationId:
          type: string
          description: 'The run''s governed-component identity -- the profile name

            the run route resolved -- never a body field

            '
        runId:
          type: string
          description: The run whose egress refused the destination
        host:
          type: string
          description: 'The named host the run tried, normalized; never a wildcard,

            a CIDR, a scheme, a port or a path

            '
        port:
          type: integer
          minimum: 1
          maximum: 65535
        protocol:
          type: string
          enum:
          - tcp
          - tls
        observedAt:
          type: string
          format: date-time
          description: 'The platform''s own instant at the first observation; a

            repeat counts and never moves it

            '
        lastSeenAt:
          type: string
          format: date-time
          description: 'The platform''s own instant at the most recent

            observation; a repeat advances it

            '
        attempts:
          type: integer
          minimum: 1
          description: 'The bounded attempt count; a repeat observation coalesces

            into the row and increments it up to the platform''s cap

            '
        capReached:
          type: boolean
          description: 'The run''s one cap-reached record: true when a new

            destination arrived past the per-run cap, so the refusal

            list is bounded and the cap is recorded rather than sampled

            '
