{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/phone-signed-request.schema.json",
  "title": "Phone signed request",
  "description": "What a phone signs for every request under /v1/phone/ (features/phone-api.md). The device sends deviceId, timestamp, nonce and signature as the headers X-MA-Device, X-MA-Timestamp, X-MA-Nonce and X-MA-Signature; the server rebuilds the other fields from the request itself. The signed text is the seven fields `version`, `method`, `target`, `bodySha256`, `deviceId`, `timestamp`, `nonce`, in that order, joined by a single \\n with no trailing newline, UTF-8, signed with the device's Ed25519 private key. No field may contain a newline, which is what makes the join unambiguous. The server refuses a timestamp more than 60 seconds behind its clock, more than 5 seconds ahead of it or earlier than its own start, and a nonce it has seen inside the window (manifest.json phone.signature windowSeconds and futureSkewSeconds).",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "version",
    "method",
    "target",
    "bodySha256",
    "deviceId",
    "timestamp",
    "nonce",
    "signature"
  ],
  "properties": {
    "version": {
      "const": "MA-PHONE-SIG-1",
      "description": "The scheme; a different version is a different signed text.",
      "x-sensitivity": "none"
    },
    "method": {
      "type": "string",
      "pattern": "^[A-Z]{1,10}$",
      "description": "The HTTP method as sent.",
      "x-sensitivity": "none"
    },
    "target": {
      "type": "string",
      "pattern": "^/[\\x21-\\x7e]{0,4095}$",
      "description": "The request target exactly as sent: path plus query string, percent-encoding untouched. A forwarder must pass it unchanged.",
      "x-sensitivity": "none"
    },
    "bodySha256": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$",
      "description": "Lowercase hex SHA-256 of the raw body bytes; an empty body hashes to e3b0c442...b855.",
      "x-sensitivity": "none"
    },
    "deviceId": {
      "type": "string",
      "pattern": "^d-[0-9a-f]{16}$",
      "description": "The id phone-devices.mjs add printed at enrolment. Header X-MA-Device.",
      "x-sensitivity": "none"
    },
    "timestamp": {
      "type": "string",
      "pattern": "^[0-9]{1,12}$",
      "description": "Unix seconds, as decimal digits. Header X-MA-Timestamp.",
      "x-sensitivity": "none"
    },
    "nonce": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{16,64}$",
      "description": "Fresh random base64url per request. Header X-MA-Nonce.",
      "x-sensitivity": "none"
    },
    "signature": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{86}$",
      "description": "Unpadded base64url of the 64-byte Ed25519 signature over the signed text. Header X-MA-Signature. Never logged.",
      "x-sensitivity": "local"
    }
  }
}
