{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/satohub-ai/json-schema/satohub-ai-version-schema.json",
  "title": "Sealed agent version (sato.version/v1)",
  "description": "One immutable, sealed version of a private agent project: what was built, from which exact files, under which declared runtime, permissions and licences, and whether the platform could sign the manifest. This describes a RECORD, not a guarantee — a sealed version has not been executed, installed or reviewed by anything unless a version_evidence row says so, and evidence written by the creator is labelled as the creator's own statement. `signature: null` with `signing: \"unavailable\"` is a supported, honest state and means no signing key was configured; it is never a placeholder signature.",
  "x-generated": "2026-10-30",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/satohub-ai-openapi.json#/components/schemas/version",
  "type": "object",
  "additionalProperties": true,
  "required": [
    "id",
    "project_id",
    "schema_version",
    "artifact_digest",
    "artifact_bytes",
    "manifest",
    "io_schemas",
    "runtime",
    "declared_secret_names",
    "signing",
    "sealed_at"
  ],
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "project_id": {
      "type": "string",
      "format": "uuid"
    },
    "schema_version": {
      "type": "integer",
      "minimum": 1
    },
    "parent_version_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "The version this one was derived from. Always a version of the SAME project — a composite foreign key enforces it."
    },
    "artifact_digest": {
      "type": "string",
      "pattern": "^sha256:[0-9a-f]{64}$",
      "description": "sha256 over the sealed files: each (path, content) pair length-prefixed, sorted by path. Order-independent and byte-sensitive. Recomputable from the downloaded archive; that recomputation is the exit check."
    },
    "artifact_bytes": {
      "type": "integer",
      "minimum": 1
    },
    "manifest": {
      "type": "object",
      "description": "The composed manifest the version was sealed from — the object `meta.signature` covers, and the file GET /api/versions/{id}/manifest serves unchanged. Versions sealed from Release 1.2 onward also carry the sealed claims (capabilities, requested_permissions, declared_secret_names, runtime, model_policy, io_schemas, dependency_lock_sha256) so the signature binds what the package asks for, not only its files; a manifest sealed earlier lacks those keys and still verifies. Verify offline with verify-release.mjs.",
      "additionalProperties": true,
      "properties": {
        "schema": {
          "const": "sato.version/v1"
        },
        "artifact_digest": {
          "type": "string",
          "pattern": "^sha256:[0-9a-f]{64}$"
        },
        "artifact_bytes": {
          "type": "integer",
          "minimum": 1,
          "description": "The byte length of the stored zip archive (not of the files)."
        },
        "project_id": {
          "type": "string",
          "format": "uuid"
        },
        "capabilities": {
          "type": "array",
          "items": {}
        },
        "requested_permissions": {
          "type": "array",
          "items": {},
          "description": "Requested, not granted."
        },
        "declared_secret_names": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "NAMES ONLY."
        },
        "runtime": {
          "type": "object",
          "additionalProperties": true
        },
        "model_policy": {
          "type": [
            "object",
            "null"
          ],
          "additionalProperties": true
        },
        "io_schemas": {
          "type": "object",
          "additionalProperties": true
        },
        "dependency_lock_sha256": {
          "type": [
            "string",
            "null"
          ],
          "pattern": "^[0-9a-f]{64}$",
          "description": "sha256 (hex) of the dependency lock serialised as canonical JSON (object keys sorted by code point, arrays in order, no whitespace — the construction in lib/signing.ts). Null means no lock was sealed, not that dependencies are unpinned by policy."
        },
        "meta": {
          "type": "object",
          "additionalProperties": true,
          "properties": {
            "schema_url": {
              "type": "string"
            },
            "signature": {
              "type": [
                "object",
                "null"
              ],
              "additionalProperties": true,
              "description": "Ed25519 over utf8(signed_at) || 0x0A || canonicalJson(manifest with meta.signature removed). Null when no signing key was configured: unsigned, never invalid and never trusted."
            }
          }
        }
      }
    },
    "dependency_lock": {
      "type": [
        "object",
        "null"
      ],
      "additionalProperties": true,
      "description": "The exact resolved dependency tree, when the package carries one. Null means no lock was sealed, not that dependencies are unpinned by policy."
    },
    "io_schemas": {
      "type": "object",
      "additionalProperties": true
    },
    "runtime": {
      "type": "object",
      "additionalProperties": true,
      "description": "Declared runtime and entrypoint. Declared: nothing in A1 executes it."
    },
    "model_policy": {
      "type": [
        "object",
        "null"
      ],
      "additionalProperties": true,
      "description": "recommended / candidates / evaluated. CANDIDATES ARE CONFIGURED CHOICES, not evidence of compatibility. A model enters `evaluated` only through a recipe-specific evaluation run recorded as evidence.",
      "properties": {
        "recommended": {
          "type": "string"
        },
        "candidates": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "evaluated": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": true
          }
        }
      }
    },
    "capabilities": {
      "type": "array",
      "items": {}
    },
    "requested_permissions": {
      "type": "array",
      "items": {},
      "description": "What the package asks for. Requested, not granted."
    },
    "declared_secret_names": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "NAMES ONLY. A secret value never enters a version row, a manifest, a package, a log or a transcript."
    },
    "licenses": {
      "type": "array",
      "items": {}
    },
    "provenance": {
      "type": "object",
      "additionalProperties": true
    },
    "signature": {
      "type": [
        "object",
        "null"
      ],
      "additionalProperties": true,
      "description": "Ed25519 (EdDSA) over the canonical manifest, the same construction as every other signed Sato Hub surface (lib/signing.ts). Null when no signing key is configured.",
      "properties": {
        "alg": {
          "const": "EdDSA"
        },
        "kid": {
          "type": "string"
        },
        "sig": {
          "type": "string"
        },
        "signed_at": {
          "type": "string",
          "format": "date-time"
        },
        "jwks_url": {
          "type": "string",
          "format": "uri"
        },
        "canonicalization": {
          "type": "string"
        }
      }
    },
    "signing": {
      "enum": [
        "signed",
        "unavailable"
      ],
      "description": "\"unavailable\" is a documented, supported state — no signing key was configured when this version was sealed. A verifier must treat it as unsigned, never as invalid and never as trusted."
    },
    "sealed_at": {
      "type": "string",
      "format": "date-time"
    },
    "ops": {
      "type": "object",
      "description": "Mutable operational metadata, kept OUTSIDE the sealed row so that \"the version never changes\" can be absolute.",
      "additionalProperties": true,
      "properties": {
        "display_label": {
          "type": [
            "string",
            "null"
          ]
        },
        "last_exported_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time"
        },
        "export_count": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "evidence": {
      "type": "array",
      "description": "Observations about THIS exact digest. An empty array means nothing has been observed — which is not a pass and not a failure.",
      "items": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "id",
          "test_id",
          "result",
          "label",
          "source",
          "observed_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "test_id": {
            "type": "string"
          },
          "test_version": {
            "type": "string"
          },
          "environment_digest": {
            "type": [
              "string",
              "null"
            ]
          },
          "result": {
            "enum": [
              "pass",
              "fail",
              "unknown"
            ],
            "description": "`unknown` is a first-class outcome. With reason_code `isolation_unavailable` it means nothing ran, and it must never be rendered as a failure."
          },
          "reason_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "label": {
            "enum": [
              "generated",
              "install-tested",
              "integration-tested",
              "task-tested",
              "user-reported",
              "simulation",
              "observed-execution"
            ]
          },
          "source": {
            "enum": [
              "platform",
              "creator"
            ],
            "description": "Derived from the credential that wrote the row, never from the request body. A creator may only carry `user-reported` or `generated`."
          },
          "runner_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "observed_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "trace_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          }
        }
      }
    }
  }
}