{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://miniapp.sollar.com/schemas/manifest.schema.json",
  "title": "Sollar Mini App Manifest",
  "description": "The manifest is the central artifact of a Sollar Mini App. It is simultaneously the identity declaration, the permission contract enforced by native code, the network allowlist, and the tool registry an AI agent reads. It lives inside the signed package.",
  "type": "object",
  "required": ["manifest_version", "id", "name", "version", "description", "icons", "vendor", "age_rating", "entry", "routes", "permissions", "network", "csp", "auth"],
  "additionalProperties": false,
  "properties": {
    "manifest_version": { "type": "integer", "const": 1 },
    "id": {
      "type": "string", "pattern": "^[0-9a-f]{16}$",
      "description": "Derived from the developer public key. Not chosen by hand."
    },
    "name": { "type": "string", "minLength": 1, "maxLength": 60 },
    "short_name": { "type": "string", "maxLength": 20 },
    "version": {
      "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$",
      "description": "SemVer. Monotonic: the store rejects a version lower than or equal to the published one."
    },
    "description": { "type": "string", "minLength": 10, "maxLength": 300 },
    "icons": {
      "type": "array", "minItems": 2,
      "items": {
        "type": "object",
        "required": ["src", "sizes", "type"],
        "additionalProperties": false,
        "properties": {
          "src": { "type": "string" },
          "sizes": { "type": "string", "pattern": "^\\d+x\\d+$" },
          "type": { "type": "string", "enum": ["image/png", "image/webp"] }
        }
      }
    },
    "vendor": {
      "type": "object",
      "required": ["id", "name", "contact"],
      "additionalProperties": false,
      "properties": {
        "id": { "type": "string" },
        "name": { "type": "string" },
        "contact": {
          "type": "string", "format": "email",
          "description": "Required: this is the channel for the Apple 4.7.1 report-and-respond obligation."
        },
        "support_url": { "type": "string", "format": "uri" }
      }
    },
    "distribution": {
      "type": "object",
      "description": "A package cannot be both channels. Declaring both is a validation error, not a warning.",
      "oneOf": [
        {
          "required": ["channel", "tenants"],
          "additionalProperties": false,
          "properties": {
            "channel": { "const": "tenant-private" },
            "tenants": { "type": "array", "minItems": 1, "items": { "type": "string" } }
          }
        },
        {
          "required": ["channel"],
          "additionalProperties": false,
          "properties": { "channel": { "const": "store" } }
        }
      ]
    },
    "age_rating": {
      "type": "string", "enum": ["4+", "9+", "12+", "17+"],
      "description": "Required by Apple Guideline 4.7.5."
    },
    "locales": { "type": "array", "items": { "type": "string" } },
    "default_locale": { "type": "string" },
    "entry": { "type": "string", "description": "Entry document inside the package, e.g. index.html" },
    "routes": {
      "type": "array", "minItems": 1,
      "description": "Routes are declared, not discovered. Universal links for Apple 4.7.4 derive from here, and an undeclared route cannot be opened by an external link.",
      "items": {
        "type": "object",
        "required": ["path", "title"],
        "additionalProperties": false,
        "properties": {
          "path": { "type": "string", "pattern": "^/" },
          "title": { "type": "string" }
        }
      }
    },
    "permissions": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["scope", "purpose"],
        "additionalProperties": false,
        "properties": {
          "scope": {
            "type": "string",
            "enum": [
              "sollar.identity.basic",
              "sollar.room.context",
              "sollar.message.send",
              "sollar.files.read",
              "sollar.files.write",
              "sollar.storage",
              "sollar.directory.search",
              "sollar.notifications"
            ]
          },
          "purpose": {
            "type": "string", "minLength": 10, "maxLength": 200,
            "description": "Shown to the user at consent time. A reviewable claim, not a technical checkbox."
          },
          "optional": { "type": "boolean", "default": false }
        }
      }
    },
    "network": {
      "type": "object",
      "additionalProperties": false,
      "description": "Host lists, not URLs. HTTPS/WSS only, no bare IPs outside development mode. Enforced natively in the request interceptor, not only by CSP.",
      "properties": {
        "connect": { "type": "array", "items": { "$ref": "#/$defs/host" } },
        "socket": { "type": "array", "items": { "$ref": "#/$defs/host" } }
      }
    },
    "csp": {
      "type": "string",
      "description": "Must include object-src 'none' and a script-src of 'self' with no remote host and no 'unsafe-eval'. All executable code comes from the signed package: this is what keeps Sollar inside the Google Play interpreter carve-out."
    },
    "auth": {
      "type": "object",
      "oneOf": [
        {
          "required": ["type", "audience"],
          "additionalProperties": false,
          "properties": {
            "type": { "const": "sollar-exchange" },
            "audience": { "type": "string" },
            "scopes": { "type": "array", "items": { "type": "string" } }
          }
        },
        {
          "required": ["type", "issuer", "client_id"],
          "additionalProperties": false,
          "properties": {
            "type": { "const": "external-oauth" },
            "issuer": { "type": "string", "format": "uri" },
            "client_id": { "type": "string" },
            "scopes": { "type": "array", "items": { "type": "string" } }
          }
        },
        {
          "required": ["type"],
          "additionalProperties": false,
          "properties": { "type": { "const": "none" } }
        }
      ]
    },
    "actions": {
      "type": "array",
      "description": "The agent tool registry. Each entry renders as a UI affordance for a human and as a tool declaration for an agent.",
      "items": { "$ref": "#/$defs/action" }
    }
  },
  "$defs": {
    "host": {
      "type": "string",
      "pattern": "^(?!\\d+\\.\\d+\\.\\d+\\.\\d+$)[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$",
      "description": "Bare host. No scheme, no path, no bare IP."
    },
    "action": {
      "type": "object",
      "required": ["name", "title", "description", "input_schema", "annotations", "confirm"],
      "additionalProperties": false,
      "properties": {
        "name": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" },
        "title": { "type": "string", "maxLength": 60 },
        "description": {
          "type": "string", "minLength": 30,
          "description": "Make implicit context explicit; state units and formats; say what is irreversible. This field decides whether the agent gets it right."
        },
        "route": { "type": "string" },
        "input_schema": {
          "type": "object",
          "description": "JSON Schema 2020-12. additionalProperties:false is required."
        },
        "output_schema": { "type": "object" },
        "annotations": {
          "type": "object",
          "required": ["read_only", "destructive", "idempotent", "open_world"],
          "additionalProperties": false,
          "description": "Declarations, not guarantees. The runtime cannot verify them. What it can guarantee is that they live in the signed, reviewed package and cannot be changed silently after the fact.",
          "properties": {
            "read_only": { "type": "boolean" },
            "destructive": { "type": "boolean" },
            "idempotent": { "type": "boolean" },
            "open_world": { "type": "boolean" }
          }
        },
        "confirm": {
          "type": "string", "enum": ["always", "never", "if_destructive"],
          "description": "A floor, not a ceiling. Tenant and user policy may harden it, never loosen it below the declared value."
        }
      }
    }
  }
}
