openapi: 3.1.0
info:
  title: DVM-MPC Runtime Node API
  version: "1.0"
  description: |
    Low-level runtime node API for one DVM-MPC node.

    This API accepts canonical admission objects, materializes policy receipts,
    starts execution, gates publication, records external exports, and exposes
    lifecycle, evidence, ledger, journal, readiness, and metrics projections.
    Integrate product services against the operation gateway; use this API for
    node operators, cluster tooling, and audit exporters on a private control
    plane network.

    Conventions: every 32-byte value (operation ids, hashes, roots, result_ref)
    is a lowercase 64-character hex string. Timestamps are *_at_ms epoch
    milliseconds. Errors are stable text or JSON reason codes by endpoint.
servers:
  - url: http://localhost:19100
    description: Runtime node API (development compose)
tags:
  - name: node-operations
    description: Runtime node — operation protocol steps
  - name: node-reads
    description: Runtime node — lifecycle, evidence, ledger reads
  - name: node-operator
    description: Runtime node — health, readiness, metrics, interface metadata
security:
  - {}
paths:
  /v1/operations:
    post:
      tags: [node-operations]
      summary: "Node: submit the canonical operation admission record"
      operationId: nodeSubmitOperation
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/NodeOperationSubmitRequest" }
      responses:
        "200": { description: Replayed by idempotency key. }
        "201": { description: Created; body carries record, record_root, ledger_root, journal roots. }
        "400": { $ref: "#/components/responses/BadRequest" }
  /v1/operations/{operationId}:
    get:
      tags: [node-reads]
      summary: "Node: read the retained operation record"
      operationId: nodeGetOperation
      parameters: [{ $ref: "#/components/parameters/operationId" }]
      responses:
        "200": { description: Operation record, lifecycle phase, roots, and retained metadata. }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/operations/{operationId}/admission:
    post:
      tags: [node-operations]
      summary: "Node: apply the signed admission decision"
      operationId: nodeApplyAdmission
      parameters: [{ $ref: "#/components/parameters/operationId" }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/NodeAdmissionDecisionRequest" }
      responses:
        "200": { description: "Status Allowed | Denied | Replayed with admission_hash and roots." }
        "422": { description: Trust, profile, or decision verification rejected. }
  /v1/operations/{operationId}/policy-receipt:
    post:
      tags: [node-operations]
      summary: "Node: materialize the policy receipt (binds the kernel permit)"
      operationId: nodeMaterializePolicyReceipt
      parameters: [{ $ref: "#/components/parameters/operationId" }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [bind_context, materialized_at_ms]
              properties:
                bind_context: { $ref: "#/components/schemas/CallContext" }
                materialized_at_ms: { type: integer }
      responses:
        "200": { description: Receipt id, permit binding id, permit handle, roots. }
        "409": { description: missing_allowed_admission_decision. }
  /v1/operations/{operationId}/execute:
    post:
      tags: [node-operations]
      summary: "Node: start the admitted computation ({} is a valid body)"
      operationId: nodeExecuteOperation
      parameters: [{ $ref: "#/components/parameters/operationId" }]
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ExecuteOperationRequest" }
      responses:
        "200": { description: "result_ref, result_kind, phase: Executed." }
        "409": { description: missing_policy_receipt | execution_not_startable. }
  /v1/operations/{operationId}/publication-gate:
    post:
      tags: [node-operations]
      summary: "Node: authorize disclosure of the executed result"
      operationId: nodePublicationGate
      parameters: [{ $ref: "#/components/parameters/operationId" }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [bind_context, scope, published_at_ms]
              properties:
                bind_context: { $ref: "#/components/schemas/CallContext" }
                scope: { type: string, example: signature }
                published_at_ms: { type: integer }
      responses:
        "200": { description: Kernel gate receipt, publication receipt, result_ref, scope. }
        "409": { description: operation_not_allowed | missing_policy_receipt. }
  /v1/operations/{operationId}/external-export:
    post:
      tags: [node-operations]
      summary: "Node: record external rail state against the publication receipt"
      operationId: nodeExternalExport
      parameters: [{ $ref: "#/components/parameters/operationId" }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [bind_context, publication_receipt, rail, export_ref, state, exported_at_ms]
              properties:
                bind_context: { $ref: "#/components/schemas/CallContext" }
                publication_receipt: { type: string }
                rail: { type: string }
                export_ref: { $ref: "#/components/schemas/Hex32" }
                state: { type: string, enum: [Submitted, Accepted, Settled] }
                exported_at_ms: { type: integer }
      responses:
        "200": { description: Kernel export receipt and roots. }
  /v1/operations/{operationId}/node-recovery:
    post:
      tags: [node-operations]
      summary: "Node: record a crash-recovery effect acknowledgment"
      operationId: nodeRecoveryAck
      parameters: [{ $ref: "#/components/parameters/operationId" }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/NodeRecoveryRequest" }
      responses:
        "200": { description: Recovery effect record, recovery semantics, and updated roots. }
  /v1/operations/{operationId}/lifecycle:
    get:
      tags: [node-reads]
      summary: "Node: lifecycle projection (phase, next actions, result_ref once Executed)"
      operationId: nodeGetLifecycle
      parameters: [{ $ref: "#/components/parameters/operationId" }]
      responses:
        "200": { description: Lifecycle projection. }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/operations/{operationId}/evidence:
    get:
      tags: [node-reads]
      summary: "Node: operation evidence bundle"
      operationId: nodeGetEvidence
      parameters: [{ $ref: "#/components/parameters/operationId" }]
      responses:
        "200": { description: Record, roots, lifecycle, journal handles. }
  /v1/operations/{operationId}/audit-replay:
    get:
      tags: [node-reads]
      summary: "Node: redacted audit replay export"
      operationId: nodeGetAuditReplay
      parameters: [{ $ref: "#/components/parameters/operationId" }]
      responses:
        "200": { description: Redacted replay payload and redaction manifest. }
        "403": { $ref: "#/components/responses/Forbidden" }
  /v1/admission/ledger:
    get:
      tags: [node-reads]
      summary: "Node: admission ledger snapshot"
      operationId: nodeGetAdmissionLedger
      responses:
        "200": { description: Ledger snapshot with roots and retained records. }
  /v1/admission/journal:
    get:
      tags: [node-reads]
      summary: "Node: hash-linked admission journal export"
      operationId: nodeGetAdmissionJournal
      responses:
        "200": { description: Verified replay events with ledger and journal roots. }
        "404": { description: admission_journal_not_configured | admission_journal_missing. }
  /v1/policies/active:
    get:
      tags: [node-reads]
      summary: "Node: active policy projection"
      operationId: nodeGetActivePolicy
      responses:
        "200": { description: Active policy metadata and roots. }
  /v1/runtime/interface:
    get:
      tags: [node-operator]
      summary: "Node: interface and state schema versions"
      operationId: nodeGetRuntimeInterface
      responses:
        "200": { description: versions and state_codec. }
  /healthz:
    get:
      tags: [node-operator]
      summary: Liveness
      operationId: healthz
      responses:
        "200": { description: ok }
  /readyz:
    get:
      tags: [node-operator]
      summary: Readiness (secret kernel reachable)
      operationId: readyz
      responses:
        "200": { description: ready }
        "503": { description: secret_kernel_unreachable }
  /metrics:
    get:
      tags: [node-operator]
      summary: Prometheus exposition
      operationId: metrics
      responses:
        "200": { description: "text/plain; version=0.0.4" }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Gateway auth modes jwks/introspection. trusted_headers mode uses x-client-id/x-subject-id/x-roles/x-scopes/x-tenant instead.
  parameters:
    operationId:
      name: operationId
      in: path
      required: true
      schema: { $ref: "#/components/schemas/Hex32" }
    keyRef:
      name: keyRef
      in: path
      required: true
      schema: { type: string }
  responses:
    BadRequest:
      description: invalid_json | invalid_operation_id | invalid_execute_request
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Forbidden:
      description: audit_replay_forbidden | policy_rejected
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: operation_not_found | custody_key_not_found
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Conflict:
      description: operation_not_executed | result_ref_mismatch | publication_receipt_mismatch
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    RateLimited:
      description: policy_admission_rate_limited
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
  schemas:
    Hex32:
      type: string
      pattern: "^[0-9a-f]{64}$"
      description: 32-byte value as lowercase hex.
    Error:
      type: object
      properties:
        error: { type: string }
        message: { type: string }
    ClientBinding:
      type: object
      required: [client_id, subject_id, roles, scopes]
      properties:
        client_id: { type: string }
        subject_id: { type: string }
        roles: { type: array, items: { type: string } }
        scopes: { type: array, items: { type: string } }
        cert_sha256: { type: [string, "null"] }
        token_jti: { type: [string, "null"] }
        tenant: { type: [string, "null"] }
    PaymentIntent:
      type: object
      required: [from_account, to_account, amount_minor, currency]
      properties:
        from_account: { type: string }
        to_account: { type: string }
        amount_minor: { type: integer }
        currency: { type: string }
        reference: { type: string }
    SignOperationRequest:
      type: object
      required: [idempotency_key, payment, requested_participants, publication_mode, client]
      properties:
        idempotency_key: { type: string }
        key_ref: { type: [string, "null"] }
        payment: { $ref: "#/components/schemas/PaymentIntent" }
        bip32:
          type: [object, "null"]
          properties:
            network: { type: string }
            path: { type: string, example: "0/5" }
        requested_participants:
          type: array
          items: { type: integer }
          description: 0-based signer indices.
        threshold: { type: [integer, "null"] }
        epoch_fence_token_hex: { type: [string, "null"] }
        publication_mode: { type: string, enum: [manual, automatic] }
        client: { $ref: "#/components/schemas/ClientBinding" }
    SignOperationResponse:
      type: object
      properties:
        operation: { $ref: "#/components/schemas/OperationView" }
    OperationView:
      type: object
      properties:
        operation_id: { $ref: "#/components/schemas/Hex32" }
        state:
          type: string
          enum: [submitted, pending_approval, admitted, receipt_materialized, executing, executed, published, failed]
        intent: { type: object }
        approvals:
          type: object
          properties:
            approved: { type: integer }
            required: { type: integer }
            subjects: { type: array, items: { type: string } }
        policy: { $ref: "#/components/schemas/PolicyPreview" }
        runtime:
          type: object
          properties:
            phase: { type: string }
            record_root: { $ref: "#/components/schemas/Hex32" }
            ledger_root: { $ref: "#/components/schemas/Hex32" }
            admission_hash: { $ref: "#/components/schemas/Hex32" }
            policy_receipt_id: { type: string }
            permit_binding_id: { type: [string, "null"] }
            result_ref: { type: [string, "null"] }
            publication_receipt: { type: [string, "null"] }
        next_actions:
          type: array
          items: { type: string }
    ApproveOperationRequest:
      type: object
      required: [subject_id, roles, approved_at_ms]
      properties:
        subject_id: { type: string }
        roles: { type: array, items: { type: string } }
        approved_at_ms: { type: integer }
    ExecuteOperationRequest:
      type: object
      properties:
        requested_at_ms: { type: [integer, "null"] }
        signing_share_mul: { type: [object, "null"] }
        recovery_helper_shares: { type: [object, "null"] }
    PublishOperationRequest:
      type: object
      required: [scope, requested_at_ms]
      properties:
        scope: { type: string, example: signature }
        requested_at_ms: { type: integer }
        expected_result_ref: { type: [string, "null"] }
    NodeRecoveryRequest:
      type: object
      required: [effect_id, result_root]
      properties:
        effect_id: { type: string }
        result_root: { $ref: "#/components/schemas/Hex32" }
        secret_kernel_payload: { type: [object, "null"] }
    OperationNodeRecoveryResponse:
      type: object
      properties:
        recovery: { type: object }
        operation: { $ref: "#/components/schemas/OperationView" }
    PolicyPreviewRequest:
      type: object
      required: [intent]
      properties:
        intent: { $ref: "#/components/schemas/SignOperationRequest" }
        approvals:
          type: array
          items: { $ref: "#/components/schemas/ApproveOperationRequest" }
    PolicyPreview:
      type: object
      properties:
        decision: { type: string, enum: [ALLOW, DENY] }
        bundle_id: { type: string }
        bundle_hash: { type: string }
        reasons: { type: array, items: { type: string } }
        obligations: { type: array, items: { type: string } }
        facts: { type: array, items: { type: string } }
    CustodyKeyCreateRequest:
      type: object
      required: [key_ref, tenant_id, asset_id, purpose, threshold, participants, signature_profile, client]
      properties:
        key_ref: { type: string }
        tenant_id: { type: string }
        asset_id: { type: string }
        purpose: { type: string }
        threshold: { type: integer }
        participants: { type: array, items: { type: integer } }
        signature_profile: { type: string, example: ecdsa-secp256k1-dkls23-v1 }
        chains: { type: array, items: { type: string } }
        client: { $ref: "#/components/schemas/ClientBinding" }
    CustodyKeyView:
      type: object
      properties:
        key_ref: { type: string }
        state: { type: string }
        key_id: { $ref: "#/components/schemas/Hex32" }
        public_key_artifact_ref: { type: string }
        active_epoch: { type: string }
        threshold: { type: integer }
        participants: { type: array, items: { type: integer } }
        latest_operation_id: { $ref: "#/components/schemas/Hex32" }
    CustodySignRequest:
      type: object
      required: [idempotency_key, asset_id, action, chain_id, request_digest, typed_data_root, requested_participants, publication_mode, client]
      properties:
        idempotency_key: { type: string }
        asset_id: { type: string }
        action: { type: string, example: transfer }
        chain_id: { type: string, example: ethereum-mainnet }
        request_digest: { $ref: "#/components/schemas/Hex32" }
        typed_data_root: { $ref: "#/components/schemas/Hex32" }
        requested_participants: { type: array, items: { type: integer } }
        epoch_fence_token_hex: { type: [string, "null"] }
        publication_mode: { type: string, enum: [manual, automatic] }
        client: { $ref: "#/components/schemas/ClientBinding" }
        public_fields:
          type: object
          additionalProperties: { type: string }
    CustodyKeyRefreshRequest:
      type: object
      required: [target_epoch, reason, publication_mode, client]
      properties:
        idempotency_key: { type: [string, "null"] }
        target_epoch: { type: string }
        reason: { type: string }
        publication_mode: { type: string, enum: [manual, automatic] }
        client: { $ref: "#/components/schemas/ClientBinding" }
        metadata: { type: [object, "null"], additionalProperties: { type: string } }
    CustodyKeyReshareRequest:
      allOf:
        - $ref: "#/components/schemas/CustodyKeyRefreshRequest"
        - type: object
          required: [new_participants, threshold]
          properties:
            new_participants: { type: array, items: { type: integer } }
            threshold: { type: integer }
    CustodyKeyRotateRequest:
      allOf:
        - $ref: "#/components/schemas/CustodyKeyRefreshRequest"
        - type: object
          required: [new_key_ref, participants, threshold]
          properties:
            new_key_ref: { type: string }
            participants: { type: array, items: { type: integer } }
            threshold: { type: integer }
    CustodyKeyRecoverRequest:
      allOf:
        - $ref: "#/components/schemas/CustodyKeyRefreshRequest"
        - type: object
          required: [recovery_case_ref, recovering_participant, helper_participants, helper_share_cids]
          properties:
            recovery_case_ref: { type: string }
            recovering_participant: { type: integer }
            helper_participants: { type: array, items: { type: integer } }
            helper_share_cids: { type: array, items: { type: string } }
            expected_helper_admission_root: { type: [string, "null"] }
    CustodyOperationResponse:
      type: object
      properties:
        operation: { $ref: "#/components/schemas/CustodyOperationView" }
    CustodyOperationView:
      allOf:
        - $ref: "#/components/schemas/OperationView"
        - type: object
          properties:
            operation_type:
              type: string
              enum: [key_create, sign, refresh, reshare, rotate, recover]
            key_ref: { type: string }
            asset_id: { type: string }
            tenant_id: { type: string }
            created_at_ms: { type: integer }
            updated_at_ms: { type: integer }
            target_key_ref: { type: [string, "null"] }
            target_epoch_hex: { type: [string, "null"] }
            target_threshold: { type: [integer, "null"] }
            target_participants: { type: array, items: { type: integer } }
    CustodyExternalExportRequest:
      type: object
      required: [publication_receipt, rail, export_ref, state, external_id, exported_at_ms]
      properties:
        publication_receipt: { type: string }
        rail: { type: string }
        export_ref: { $ref: "#/components/schemas/Hex32" }
        state: { type: string, enum: [Submitted, Accepted, Settled] }
        external_id: { type: string }
        exported_at_ms: { type: integer }
    CallContext:
      type: object
      description: Bind context for node-level calls; see the Node API reference for field semantics.
      required: [execution_profile, step, operation_id, key_id, session_id, epoch_id, phase, slot, request_digest]
      properties:
        execution_profile:
          type: object
          properties:
            domain_id: { type: string }
            profile_id: { type: string }
            alg_id: { type: string }
            shape_id: { type: string }
            family_id: { type: string }
        step:
          type: object
          properties:
            op_class: { type: string }
            step_seq: { type: integer }
            admission_hash: { $ref: "#/components/schemas/Hex32" }
            intent_hash: { $ref: "#/components/schemas/Hex32" }
            prev_transition_hash: { $ref: "#/components/schemas/Hex32" }
        operation_id: { $ref: "#/components/schemas/Hex32" }
        key_id: { $ref: "#/components/schemas/Hex32" }
        session_id: { $ref: "#/components/schemas/Hex32" }
        epoch_id: { $ref: "#/components/schemas/Hex32" }
        lane: { type: [integer, "null"] }
        phase: { type: string }
        slot: { type: string }
        request_digest: { $ref: "#/components/schemas/Hex32" }
    NodeOperationSubmitRequest:
      type: object
      required: [intent, client_binding_root, submitted_at_ms]
      properties:
        intent:
          type: object
          properties:
            version: { type: integer }
            operation_id: { $ref: "#/components/schemas/Hex32" }
            operation_kind: { type: string, example: sign_digest }
            key_id: { $ref: "#/components/schemas/Hex32" }
            tenant_root: { type: [string, "null"] }
            session_id: { $ref: "#/components/schemas/Hex32" }
            epoch_id: { $ref: "#/components/schemas/Hex32" }
            request_digest: { $ref: "#/components/schemas/Hex32" }
            business_intent_root: { $ref: "#/components/schemas/Hex32" }
            public_fields_root: { $ref: "#/components/schemas/Hex32" }
            requested_participants: { type: array, items: { type: integer } }
            publication_mode: { type: string }
            signature_profile: { type: string }
            hash_profile: { type: string }
            canonicalization_profile: { type: string }
        client_binding_root: { $ref: "#/components/schemas/Hex32" }
        subject_root: { type: [string, "null"] }
        submitted_at_ms: { type: integer }
    NodeAdmissionDecisionRequest:
      type: object
      required: [decision, domain_manifest, authority_set, policy_bundle, decided_at_ms]
      properties:
        decision: { type: object, description: Signed AdmissionDecision from the Policy Authority. }
        domain_manifest: { type: object }
        authority_set: { type: object }
        policy_bundle: { type: object }
        decided_at_ms: { type: integer }
        now_ms: { type: [integer, "null"] }
