{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://sproutpad.ai/spec/envelope.schema.json",
  "title": "Governed Agent Spend — response envelope",
  "description": "Machine-checkable definition of the normative wire format in spec §3 (https://sproutpad.ai/spec.md). A response body is conformant iff it validates against exactly one of the two branches: a success envelope or an error envelope. Field names are normative — no renaming, no restyling.",
  "oneOf": [
    { "$ref": "#/$defs/successEnvelope" },
    { "$ref": "#/$defs/errorEnvelope" }
  ],
  "$defs": {
    "successEnvelope": {
      "type": "object",
      "description": "Success. Read-only responses carry any shape under `data`; MUTATING successes must additionally satisfy #/$defs/mutatingSuccessData (§3.1). Whether an operation is mutating is not inferable from the body alone, so graders apply mutatingSuccessData to responses of known-mutating operations.",
      "required": ["data"],
      "properties": {
        "data": { "type": "object" },
        "nextActions": { "$ref": "#/$defs/nextActions" }
      },
      "not": { "required": ["blockedBy"] }
    },
    "mutatingSuccessData": {
      "type": "object",
      "description": "The §3.1 contract for the `data` object of a successful MUTATING response: receipt, undo, budgetRemainingUsd — and nextActions on the envelope or in data.",
      "required": ["receipt", "undo", "budgetRemainingUsd"],
      "properties": {
        "receipt": { "$ref": "#/$defs/receipt" },
        "undo": { "$ref": "#/$defs/undo" },
        "budgetRemainingUsd": { "type": "number", "minimum": 0 },
        "nextActions": { "$ref": "#/$defs/nextActions" }
      }
    },
    "receipt": {
      "type": "object",
      "description": "What happened: the action, its costs, and provider references for everything created (§3.1).",
      "required": ["action"],
      "properties": {
        "action": { "type": "string", "minLength": 1 },
        "oneTimeUsd": { "type": "number", "minimum": 0 },
        "monthlyDeltaUsd": { "type": "number" },
        "resources": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["kind", "provider", "externalId"],
            "properties": {
              "kind": { "type": "string" },
              "provider": { "type": "string" },
              "externalId": { "type": "string" }
            }
          }
        }
      }
    },
    "undo": {
      "description": "How to reverse the mutation: a concrete command/call, or null accompanied by an explicit irreversibility marker (§3.1).",
      "oneOf": [
        {
          "type": "object",
          "required": ["command"],
          "properties": {
            "command": { "type": "string", "minLength": 1 },
            "irreversible": { "const": false }
          }
        },
        {
          "type": "object",
          "required": ["irreversible"],
          "properties": {
            "command": { "type": "null" },
            "irreversible": { "const": true }
          }
        },
        { "type": "null" }
      ]
    },
    "nextActions": {
      "type": "array",
      "description": "The sensible next calls — every response teaches the next step (§3.1).",
      "items": { "type": "string" }
    },
    "errorEnvelope": {
      "type": "object",
      "description": "Error (§3.2). blockedBy names the specific gate in a namespaced vocabulary. resolution, when present, is the machine-readable legitimate path forward; when absent, no legitimate path exists.",
      "required": ["blockedBy"],
      "properties": {
        "blockedBy": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9]*:[a-z0-9_.]+$",
          "description": "Namespaced gate identifier, e.g. policy:max_domain_price, budget:exceeded, input:invalid_domain. Values (not field names) use lower snake_case."
        },
        "message": { "type": "string" },
        "limit": { "type": "number" },
        "requested": { "type": "number" },
        "retryable": { "type": "boolean" },
        "resolution": { "$ref": "#/$defs/resolution" }
      }
    },
    "resolution": {
      "type": "object",
      "description": "The legitimate path forward. `type` names the path; the remaining fields carry the artifact needed to walk it (a task id to poll, a URL to hand to the human, a hint for a retry).",
      "required": ["type"],
      "properties": {
        "type": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]*$",
          "description": "e.g. human_approval, retry, signup, authenticate, update_billing, upgrade, unfreeze, human_action, human_dashboard"
        },
        "taskId": { "type": "string" },
        "hint": { "type": "string" },
        "url": { "type": "string", "format": "uri" },
        "signupUrl": { "type": "string", "format": "uri" },
        "billingUrl": { "type": "string", "format": "uri" },
        "dashboardUrl": { "type": "string", "format": "uri" },
        "docs": { "type": "string", "format": "uri" },
        "path": { "type": "string" },
        "note": { "type": "string" }
      }
    }
  }
}
