{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://actum.run/spec/preflight-failure.schema.json",
  "title": "Preflight Failure",
  "description": "0.10.0 (preflight+unwind-policy epic, plan D2). Returned when execution create (or resume) refuses a request before any execution resource exists, because a dependency the pinned composition needs is unavailable. Distinct from failure-contract.schema.json (whose error is the const compose_step_failed): that contract describes a composition that started and failed mid-flight, with completed steps to account for and an unwind plan to run. Preflight runs strictly before executor.create_execution, so its refusal guarantees \"nothing started, nothing to unwind\" -- there is no execution_id, no failed_step, no completed_steps, and never anything to compensate. HTTP mapping: 503 with a Retry-After header for a dependency that is simply unavailable (store round-trip failure, vault decrypt failure other than corruption, an unreachable Tier-2 provider probe); the one exception is an undecryptable vault entry from key mismatch/corruption, which is server misconfiguration and still maps to 503 kind: vault (not a 4xx) because the caller cannot fix it by retrying. Configuration failures that are the caller's/operator's to fix keep their own existing 4xx contracts and MUST NEVER use this schema: a missing connection grant stays the existing 403, a missing connection stays its existing error shape. Audit: a refusal emits action execution.preflight with outcome: \"denied\".",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "spec_version",
    "error",
    "dependency"
  ],
  "properties": {
    "spec_version": {
      "type": "string",
      "description": "Version of this contract schema (semver).",
      "examples": [
        "0.10.0"
      ]
    },
    "error": {
      "const": "preflight_failed"
    },
    "dependency": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "kind",
        "name"
      ],
      "description": "The dependency that was unavailable and caused the refusal.",
      "properties": {
        "kind": {
          "enum": [
            "store",
            "vault",
            "provider"
          ],
          "description": "store: the persistence backend round-trip (the same probe /v1/readyz performs) failed. vault: a connection binding the pinned composition references could not be decrypted (unavailable, or -- the one non-availability exception this schema still covers -- an undecryptable entry from key mismatch/corruption, which is server misconfiguration). provider: the named connection's remote API was unavailable -- at preflight, a Tier-2 probe (see composition.schema.json's preflight_probes) failed or could not be delivered. This is deliberately a closed three-value vocabulary shared with execution.schema.json's compensation.skipped[].dependency.kind (where provider likewise means a connect-phase failure against the binding connection's remote API), so a caller correlates a preflight 503 and a later unwind skip on identical kind/name values. Missing-connection/missing-grant configuration errors keep their existing 4xx contracts and never reach this schema."
        },
        "name": {
          "type": "string",
          "description": "Identifies the specific dependency instance: the connection name for vault/provider kinds; a store identifier for kind store (the field is always required — the reference implementation sends the literal \"store\").",
          "examples": [
            "tracker"
          ]
        }
      }
    },
    "retryable": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "retry_after"
      ],
      "description": "Optional machine-readable duplicate of the Retry-After response header. The header itself is REQUIRED on every preflight_failed 503 -- including the vault-corruption case, where it reflects when an operator-side fix is worth re-probing -- and when this body field is present its value MUST equal the header's. Absence of the body field does not imply immediate retry is safe; the header still governs.",
      "properties": {
        "retry_after": {
          "type": "integer",
          "minimum": 0,
          "description": "Seconds the caller should wait before retrying.",
          "examples": [
            30
          ]
        }
      }
    }
  }
}
