{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://commonsc.io/schemas/manifest.schema.json",
  "title": "CommonSense Algorithm Manifest",
  "description": "Authoritative description of a publishable CommonSense algorithm bundle. The manifest is the only artifact the host inspects to decide whether and how to run an algorithm. Hash + signatures cover the canonical-JSON encoding of every field except `checksum` and `signatures`.",
  "type": "object",
  "required": [
    "schemaVersion",
    "id",
    "version",
    "name",
    "blurb",
    "license",
    "tier",
    "entrypoint",
    "artifact",
    "lockfile",
    "capabilities",
    "input",
    "output",
    "supportedKinds",
    "references",
    "publisher",
    "checksum",
    "signatures"
  ],
  "additionalProperties": false,
  "properties": {
    "schemaVersion": {
      "const": "2",
      "description": "Manifest schema version. Bumped on breaking changes; the host refuses unknown majors."
    },
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]*\\/[a-z0-9][a-z0-9-]*$",
      "description": "Globally unique identifier in the form `publisher-handle/algorithm-handle`. Kebab-case, lowercase, ASCII."
    },
    "version": {
      "type": "string",
      "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-[0-9A-Za-z.-]+)?$",
      "description": "Semantic version. Immutable once published."
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 80,
      "description": "Human-readable title displayed in the catalog and run UI."
    },
    "blurb": {
      "type": "string",
      "minLength": 1,
      "maxLength": 240,
      "description": "One-line summary for catalog cards."
    },
    "license": {
      "type": "string",
      "description": "SPDX license expression for the algorithm source and bundled deps. Closed-source bundles use `LicenseRef-<id>`."
    },
    "category": {
      "type": "string",
      "enum": [
        "appearance", "senses", "fuel", "motion", "wellness",
        "quality", "ancestry", "trait", "risk", "research"
      ],
      "description": "Catalog grouping for the consumer 'atlas of self-discovery' UI. Primary regions: appearance, senses, fuel, motion, wellness. Quality is calibration (QC). Legacy: trait (kept for older manifests). Future: ancestry, risk, research."
    },
    "tier": {
      "type": "string",
      "enum": ["pyodide", "webr", "container"],
      "description": "Sandbox tier. v0 of CommonSense only loads `pyodide`."
    },
    "entrypoint": {
      "type": "object",
      "required": ["module", "function"],
      "additionalProperties": false,
      "properties": {
        "module": {
          "type": "string",
          "description": "Python module name resolvable inside the sandbox after the artifact is unpacked. Example: `prs.main`."
        },
        "function": {
          "type": "string",
          "description": "Callable inside `module`. Must accept the declared input and return the declared output."
        }
      },
      "description": "Where execution begins. Resolved by the sandbox shim — the algorithm never imports host APIs directly."
    },
    "artifact": {
      "type": "object",
      "required": ["mediaType", "size", "sha256"],
      "additionalProperties": false,
      "properties": {
        "mediaType": {
          "type": "string",
          "enum": ["application/vnd.commonsc.pyodide-bundle.tar+zstd"],
          "description": "Tier-specific bundle media type. Locked per tier so the loader can reject mismatches immediately."
        },
        "size": {
          "type": "integer",
          "minimum": 1,
          "description": "Bundle size in bytes. The host enforces this before download completes."
        },
        "sha256": {
          "type": "string",
          "pattern": "^[a-f0-9]{64}$",
          "description": "Lowercase hex digest of the on-disk artifact. Verified before unpack."
        }
      }
    },
    "lockfile": {
      "type": "object",
      "required": ["interpreter", "dependencies"],
      "additionalProperties": false,
      "properties": {
        "interpreter": {
          "type": "object",
          "required": ["name", "version"],
          "additionalProperties": false,
          "properties": {
            "name": {
              "type": "string",
              "enum": ["pyodide"],
              "description": "Interpreter identifier. Currently only Pyodide."
            },
            "version": {
              "type": "string",
              "description": "Exact interpreter release the bundle was built against. The host refuses runs against an incompatible engine."
            }
          }
        },
        "dependencies": {
          "type": "array",
          "description": "Fully pinned dependency closure. Every wheel/binary referenced by the bundle, with content hash. Reproducibility lives or dies here.",
          "items": {
            "type": "object",
            "required": ["name", "version", "sha256"],
            "additionalProperties": false,
            "properties": {
              "name": { "type": "string" },
              "version": { "type": "string" },
              "sha256": {
                "type": "string",
                "pattern": "^[a-f0-9]{64}$"
              }
            }
          }
        }
      }
    },
    "capabilities": {
      "type": "object",
      "additionalProperties": false,
      "description": "What the algorithm is permitted to do. Default-deny: anything not listed here is unavailable inside the sandbox. The host enforces — these are not advisory.",
      "properties": {
        "network": {
          "const": false,
          "description": "Hardcoded to false in v0. Privacy invariant: plugin code has no network. Reserved as a property so future tiers can request scoped network for non-data hosts (e.g. reference panels) under explicit user consent."
        },
        "fsRead": {
          "type": "array",
          "items": { "type": "string", "enum": ["/input"] },
          "description": "Read-only mount points the host exposes. Only `/input` is permitted in v0."
        },
        "fsWrite": {
          "type": "array",
          "items": { "type": "string", "enum": ["/output"] },
          "description": "Write-only mount points. Only `/output` (a host-owned scratch dir) is permitted in v0."
        },
        "progress": {
          "type": "boolean",
          "default": false,
          "description": "Whether the algorithm emits structured progress events via the host import. Rate-limited by the host regardless."
        }
      }
    },
    "input": {
      "type": "object",
      "required": ["schemaRef", "requiredRsids"],
      "additionalProperties": false,
      "properties": {
        "schemaRef": {
          "type": "string",
          "description": "URI of a JSON Schema in this repo (e.g. `genomic-io.schema.json#/$defs/VariantSet`) describing what the host streams in. The host validates every record against this schema before it crosses the sandbox boundary."
        },
        "referenceBuild": {
          "type": "string",
          "enum": ["GRCh37", "GRCh38"],
          "description": "Reference build the algorithm was authored against. A mismatch with the user's data is the one wrong-answer mode no sandbox catches — surfaced loudly in the run UI."
        },
        "requiredRsids": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "pattern": "^rs[0-9]+$" },
          "description": "Every rsID the algorithm reads from the VariantSet. The host extracts exactly these from the user's retained genome and passes only them — so the algorithm sees what it needs without the rest of the genome crossing the sandbox boundary. Also drives the honest pre-run compatibility check (which of these the sample actually called). Declare ALL of them; the devkit cross-checks against the code."
        }
      }
    },
    "output": {
      "type": "object",
      "required": ["schemaRef"],
      "additionalProperties": false,
      "properties": {
        "schemaRef": {
          "type": "string",
          "description": "URI of the result schema (e.g. `result.schema.json#/$defs/Result`). The host validates the algorithm's return value before showing it to the user."
        }
      }
    },
    "supportedKinds": {
      "type": "array",
      "minItems": 1,
      "uniqueItems": true,
      "items": {
        "$ref": "genomic-io.schema.json#/$defs/FileKind"
      },
      "description": "Which parsed input formats this algorithm can run against. Compatibility UI uses this to grey out cards that don't match the user's sample."
    },
    "references": {
      "type": "array",
      "minItems": 1,
      "description": "Evidence the rsID→trait association rests on. At least one is required: a test that can't cite its source is not publishable. The marketplace gate resolves every pubmed/doi citation and rejects any that don't exist (killing fabricated/AI-hallucinated references); a reviewer confirms relevance. Surfaced to users as 'Sources' on the result.",
      "items": {
        "type": "object",
        "required": ["type", "id"],
        "additionalProperties": false,
        "properties": {
          "type": {
            "type": "string",
            "enum": ["pubmed", "doi", "snpedia", "clinvar", "url"],
            "description": "Citation kind. `pubmed`/`doi` are resolved for existence at the gate; `snpedia`/`clinvar`/`url` are checked for reachability."
          },
          "id": {
            "type": "string",
            "minLength": 1,
            "description": "PMID (digits, e.g. `21162744`), DOI (`10.xxxx/...`), SNPedia page (e.g. `Rs1815739`), ClinVar id, or a full https URL."
          },
          "title": {
            "type": "string",
            "maxLength": 240,
            "description": "Optional human-readable label shown in the Sources list."
          },
          "rsids": {
            "type": "array",
            "items": { "type": "string", "pattern": "^rs[0-9]+$" },
            "description": "Optional: which of the declared requiredRsids this reference supports."
          }
        }
      }
    },
    "requirements": {
      "$ref": "genomic-io.schema.json#/$defs/Requirement",
      "description": "Resource budget the host will refuse to allocate. Profiled by the canonical gates at publish time; surfaced in the capability prompt."
    },
    "publisher": {
      "type": "object",
      "required": ["handle", "name", "keyId"],
      "additionalProperties": false,
      "properties": {
        "handle": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]*$"
        },
        "name": { "type": "string" },
        "url": { "type": "string", "format": "uri" },
        "keyId": {
          "type": "string",
          "description": "Identifier of the ed25519 public key on the curator key list. The keyset is fetched from commonsc.io/catalog/keys.json and verified against the platform root."
        }
      }
    },
    "sourceUrl": {
      "type": "string",
      "format": "uri",
      "description": "Public source repository if the algorithm is open. Required for the open-source library; optional for closed-source publishers."
    },
    "checksum": {
      "type": "string",
      "pattern": "^[a-f0-9]{64}$",
      "description": "SHA-256 over the canonical-JSON encoding of this manifest with `checksum` and `signatures` blanked. Detects post-publish tampering."
    },
    "signatures": {
      "type": "object",
      "required": ["publisher", "marketplace"],
      "additionalProperties": false,
      "description": "Both signatures must verify before the host will instantiate the artifact.",
      "properties": {
        "publisher": {
          "$ref": "#/$defs/Signature",
          "description": "Publisher's ed25519 signature. Asserts authorship and version intent."
        },
        "marketplace": {
          "$ref": "#/$defs/Signature",
          "description": "Marketplace's ed25519 co-sign. Asserts the bundle cleared the canonical gates and human review."
        }
      }
    }
  },
  "$defs": {
    "Signature": {
      "type": "object",
      "required": ["alg", "keyId", "value"],
      "additionalProperties": false,
      "properties": {
        "alg": { "const": "ed25519" },
        "keyId": {
          "type": "string",
          "description": "Identifier resolving to a public key in the curator/publisher key list."
        },
        "value": {
          "type": "string",
          "pattern": "^[A-Za-z0-9+/]+={0,2}$",
          "description": "Base64-encoded detached signature over the canonical manifest bytes (checksum + signatures blanked)."
        }
      }
    }
  }
}
