# GENERATED FILE — do not edit by hand.
# Source: Zod schemas + registries in @arbiter/shared (openapi/).
# Regenerate: npm run generate:api-contracts
# Contract: Partner sync events (OpenAPI 3.1 webhooks + inbound path)
# Ticket: #531 / TKT-COV-2026-0043
openapi: "3.1.0"
info:
  title: "Arbiter Partner Sync Events"
  version: "arbiter.partner.events.v1"
  description: "Partner Sync Events (`arbiter.partner.events.v1`): signed push/ack webhooks. Arbiter delivers outbound events to your webhook URL (`webhooks`). You POST remediation lifecycle events to `/api/integrations/partner-sync/webhook/{tenantSlug}`. All payloads are `classification: metadata_only` — hashes and references only; fetch bodies via Partner Read. Verify `payload_hash` (SHA-256 over `arbiter.jcs.v1` canonical JSON of `payload` only). OSCAL is not the webhook body. Transport: https://docs.arbiter.blockskunk.com/webhooks#transport."
servers:
  - url: "https://{org}.arbiter.blockskunk.com"
    description: "Tenant API host for inbound webhooks"
tags:
  - name: "Partner Sync Inbound"
    description: "Remediation lifecycle events from partners"
paths:
  /api/integrations/partner-sync/webhook/{tenantSlug}:
    post:
      operationId: "partnerSyncInboundWebhook"
      summary: "Inbound partner sync webhook"
      tags:
        - "Partner Sync Inbound"
      parameters:
        - name: "tenantSlug"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "X-Arbiter-Timestamp"
          in: "header"
          required: true
          description: "Unix epoch seconds (±5m replay window)"
          schema:
            type: "string"
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema: &a1
                $ref: "#/components/schemas/PartnerSyncInboundAck"
        "201":
          description: "Created"
          content:
            application/json:
              schema: *a1
        "401":
          description: "Unauthorized"
        "403":
          description: "Forbidden"
      description: "Partner posts remediation lifecycle events (`remediation.acknowledged` → `remediation.completion_claimed`). Authenticated via HMAC (not Arbiter API key). Claims do not auto-close Arbiter remediation state. Headers and replay window: https://docs.arbiter.blockskunk.com/webhooks#transport."
      security:
        - PartnerHmac: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerEventEnvelope"
            example:
              event_id: "evt-inbound-001"
              schema_version: "arbiter.partner.events.v1"
              tenant_id: "acme"
              event_type: "remediation.acknowledged"
              aggregate_type: "remediation"
              aggregate_id: "rem-001"
              aggregate_version: 1
              occurred_at: "2026-07-22T18:05:00.000Z"
              correlation_id: "corr-inbound-001"
              idempotency_key: "idem-rem-001-ack"
              actor:
                type: "service"
                id: "partner-os"
              classification: "metadata_only"
              payload_hash:
                alg: "sha256"
                canonicalization: "arbiter.jcs.v1"
                value: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
              payload:
                remediation_id: "rem-001"
                acknowledged_at: "2026-07-22T18:05:00.000Z"
                partner_task_id: "task-99"
webhooks:
  policyPublished:
    post:
      operationId: "webhook_policyPublished"
      summary: "Outbound — policy.published"
      description: "Arbiter notifies the partner webhook URL when a policy is published. Payload includes `body_hash` only — never policy body text. Verify `payload_hash` over canonical JSON (`arbiter.jcs.v1`). Transport: HMAC-SHA256; see https://docs.arbiter.blockskunk.com/webhooks#transport."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerEventEnvelope"
            example:
              event_id: "evt-001"
              schema_version: "arbiter.partner.events.v1"
              tenant_id: "acme"
              event_type: "policy.published"
              aggregate_type: "policy"
              aggregate_id: "pol-001"
              aggregate_version: 3
              occurred_at: "2026-07-22T18:00:00.000Z"
              correlation_id: "corr-001"
              idempotency_key: "idem-pol-001-v3"
              actor:
                type: "user"
                id: "user-1"
              classification: "metadata_only"
              payload_hash:
                alg: "sha256"
                canonicalization: "arbiter.jcs.v1"
                value: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
              payload:
                policyId: "pol-001"
                version: 3
                body_hash: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
                status: "approved"
                retrieval_uri: "/api/v1/policies"
      responses:
        "200":
          description: "Partner acknowledged delivery"
  controlUpdated:
    post:
      operationId: "webhook_controlUpdated"
      summary: "Outbound — control.updated"
      description: "Control metadata change. Classification is always `metadata_only`."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerEventEnvelope"
      responses:
        "200":
          description: "Partner acknowledged delivery"
  riskStatusChanged:
    post:
      operationId: "webhook_riskStatusChanged"
      summary: "Outbound — risk.status_changed"
      description: "Risk status transition notification."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerEventEnvelope"
      responses:
        "200":
          description: "Partner acknowledged delivery"
  coverageUpdated:
    post:
      operationId: "webhook_coverageUpdated"
      summary: "Outbound — coverage.updated"
      description: "Estate coverage rollup change."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerEventEnvelope"
      responses:
        "200":
          description: "Partner acknowledged delivery"
  remediationVerificationResolved:
    post:
      operationId: "webhook_remediationVerificationResolved"
      summary: "Outbound — remediation.verification_resolved"
      description: "Collector re-check resolved a remediation packet to `verified` or `still_open`. Never emitted from a partner completion_claimed alone — Arbiter remains the source of truth."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerEventEnvelope"
      responses:
        "200":
          description: "Partner acknowledged delivery"
components:
  securitySchemes:
    ArbiterApiKey:
      type: "http"
      scheme: "bearer"
      bearerFormat: "arb_"
      description: "Arbiter API key. Format: `arb_` + 43 url-safe base64 characters. Pass as `Authorization: Bearer arb_REDACTED_EXAMPLE_DO_NOT_USE` (redacted placeholder — never a live key). Raw keys are shown once at mint; examples in this document never contain live secrets."
    PartnerHmac:
      type: "apiKey"
      in: "header"
      name: "X-Arbiter-Signature"
      description: "HMAC-SHA256 of the raw request body using the partner sync shared secret. Pair with X-Arbiter-Timestamp (±5 minutes). Not a `arb_` API key."
  schemas:
    PartnerEventEnvelope:
      type: "object"
      properties:
        event_id:
          type: "string"
          minLength: 1
        schema_version:
          type: "string"
          const: "arbiter.partner.events.v1"
        tenant_id:
          type: "string"
          minLength: 1
        event_type:
          anyOf:
            - type: "string"
              enum:
                - "policy.published"
                - "control.updated"
                - "risk.status_changed"
                - "coverage.updated"
                - "remediation.verification_resolved"
            - type: "string"
              enum:
                - "remediation.acknowledged"
                - "remediation.progress_reported"
                - "remediation.evidence_submitted"
                - "remediation.completion_claimed"
        aggregate_type:
          type: "string"
          minLength: 1
        aggregate_id:
          type: "string"
          minLength: 1
        aggregate_version:
          type: "integer"
          exclusiveMinimum: 0
          maximum: 9007199254740991
        occurred_at:
          type: "string"
          format: "date-time"
          pattern: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        correlation_id:
          type: "string"
          minLength: 1
        idempotency_key:
          type: "string"
          minLength: 1
        actor:
          type: "object"
          properties:
            type:
              type: "string"
              enum:
                - "user"
                - "service"
                - "system"
            id:
              type: "string"
              minLength: 1
          required:
            - "type"
            - "id"
          additionalProperties: false
        classification:
          type: "string"
          const: "metadata_only"
        payload_hash:
          type: "object"
          properties:
            alg:
              type: "string"
              const: "sha256"
            canonicalization:
              type: "string"
              const: "arbiter.jcs.v1"
            value:
              type: "string"
              pattern: "^[a-f0-9]{64}$"
          required:
            - "alg"
            - "canonicalization"
            - "value"
          additionalProperties: false
        payload:
          type: "object"
          propertyNames:
            type: "string"
          additionalProperties: {}
      required:
        - "event_id"
        - "schema_version"
        - "tenant_id"
        - "event_type"
        - "aggregate_type"
        - "aggregate_id"
        - "aggregate_version"
        - "occurred_at"
        - "correlation_id"
        - "idempotency_key"
        - "actor"
        - "classification"
        - "payload_hash"
        - "payload"
      additionalProperties: false
    PartnerPolicyPublishedPayload:
      type: "object"
      properties:
        evidence_hash:
          type: "string"
          pattern: "^[a-f0-9]{64}$"
        snapshotId:
          anyOf:
            - type: "string"
              minLength: 1
            - type: "null"
        retrieval_uri:
          type: "string"
          minLength: 1
        regulation_map:
          type: "array"
          items:
            type: "string"
            minLength: 1
        policyId:
          type: "string"
          minLength: 1
        version:
          type: "integer"
          exclusiveMinimum: 0
          maximum: 9007199254740991
        body_hash:
          type: "string"
          pattern: "^[a-f0-9]{64}$"
        status:
          type: "string"
          minLength: 1
      required:
        - "policyId"
        - "version"
        - "body_hash"
      additionalProperties: false
    PartnerControlUpdatedPayload:
      type: "object"
      properties:
        evidence_hash:
          type: "string"
          pattern: "^[a-f0-9]{64}$"
        snapshotId:
          anyOf:
            - type: "string"
              minLength: 1
            - type: "null"
        retrieval_uri:
          type: "string"
          minLength: 1
        regulation_map:
          type: "array"
          items:
            type: "string"
            minLength: 1
        controlId:
          type: "string"
          minLength: 1
        version:
          type: "integer"
          exclusiveMinimum: 0
          maximum: 9007199254740991
        implementation_status:
          type: "string"
          minLength: 1
        status:
          type: "string"
          minLength: 1
      required:
        - "controlId"
      additionalProperties: false
    PartnerRiskStatusChangedPayload:
      type: "object"
      properties:
        evidence_hash:
          type: "string"
          pattern: "^[a-f0-9]{64}$"
        snapshotId:
          anyOf:
            - type: "string"
              minLength: 1
            - type: "null"
        retrieval_uri:
          type: "string"
          minLength: 1
        regulation_map:
          type: "array"
          items:
            type: "string"
            minLength: 1
        riskId:
          type: "string"
          minLength: 1
        status:
          type: "string"
          minLength: 1
        previous_status:
          type: "string"
          minLength: 1
      required:
        - "riskId"
        - "status"
      additionalProperties: false
    PartnerCoverageUpdatedPayload:
      type: "object"
      properties:
        evidence_hash:
          type: "string"
          pattern: "^[a-f0-9]{64}$"
        snapshotId:
          anyOf:
            - type: "string"
              minLength: 1
            - type: "null"
        retrieval_uri:
          type: "string"
          minLength: 1
        regulation_map:
          type: "array"
          items:
            type: "string"
            minLength: 1
        estate_summary:
          type: "object"
          properties:
            covered:
              type: "integer"
              minimum: 0
              maximum: 9007199254740991
            uncovered:
              type: "integer"
              minimum: 0
              maximum: 9007199254740991
            noncompliant:
              type: "integer"
              minimum: 0
              maximum: 9007199254740991
            unknown:
              type: "integer"
              minimum: 0
              maximum: 9007199254740991
          required:
            - "covered"
            - "uncovered"
            - "noncompliant"
            - "unknown"
          additionalProperties: false
        evaluated_at:
          type: "string"
          format: "date-time"
          pattern: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
      required:
        - "estate_summary"
        - "evaluated_at"
      additionalProperties: false
    PartnerRemediationAckPayload:
      type: "object"
      properties:
        remediation_id:
          type: "string"
          minLength: 1
        acknowledged_at:
          type: "string"
          format: "date-time"
          pattern: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        partner_task_id:
          type: "string"
          minLength: 1
        note:
          type: "string"
          maxLength: 2000
      required:
        - "remediation_id"
      additionalProperties: false
    PartnerRemediationProgressPayload:
      type: "object"
      properties:
        remediation_id:
          type: "string"
          minLength: 1
        progress_pct:
          type: "number"
          minimum: 0
          maximum: 100
        partner_state:
          type: "string"
          maxLength: 100
        note:
          type: "string"
          maxLength: 2000
      required:
        - "remediation_id"
      additionalProperties: false
    PartnerRemediationEvidencePayload:
      type: "object"
      properties:
        remediation_id:
          type: "string"
          minLength: 1
        evidence_hash:
          type: "string"
          pattern: "^[a-f0-9]{64}$"
        note:
          type: "string"
          maxLength: 2000
      required:
        - "remediation_id"
        - "evidence_hash"
      additionalProperties: false
    PartnerRemediationCompletionClaimedPayload:
      type: "object"
      properties:
        remediation_id:
          type: "string"
          minLength: 1
        claimed_at:
          type: "string"
          format: "date-time"
          pattern: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        evidence_hash:
          type: "string"
          pattern: "^[a-f0-9]{64}$"
        note:
          type: "string"
          maxLength: 2000
      required:
        - "remediation_id"
      additionalProperties: false
    PartnerRemediationVerificationResolvedPayload:
      type: "object"
      properties:
        evidence_hash:
          type: "string"
          pattern: "^[a-f0-9]{64}$"
        snapshotId:
          anyOf:
            - type: "string"
              minLength: 1
            - type: "null"
        retrieval_uri:
          type: "string"
          minLength: 1
        regulation_map:
          type: "array"
          items:
            type: "string"
            minLength: 1
        remediation_id:
          type: "string"
          minLength: 1
        status:
          type: "string"
          enum:
            - "verified"
            - "still_open"
      required:
        - "remediation_id"
        - "status"
      additionalProperties: false
    PartnerSyncInboundAck:
      type: "object"
      properties:
        accepted:
          type: "boolean"
          const: true
        event_id:
          type: "string"
          minLength: 1
      required:
        - "accepted"
        - "event_id"
      additionalProperties: false
