{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://schema.tensr.fitness/swap-map/v1",
  "title": "tensr swap-map manifest (v1)",
  "description": "The install manifest for a layered swap map (s32). `workoutmd maps install <url>` fetches this JSON, then the versioned, content-hashed map asset it points at, and runs a four-stage validation gate — this schema (structure), sha256 (integrity), parse (CSV/TSV/PSV), and referential integrity (every from/to name resolves to a real catalog movement) — before the asset lands in the installed tier the swap resolver reads. The manifest is the rolling pointer that names the current version; the asset is immutable per version (content-addressed by sha256), so `maps.lock` can pin an exact version/hash. The trust gate is sha256 + HTTPS + this validation, NOT domain trust; cryptographic signing is a separate, later contract. v1 is frozen; incompatible changes ship as swap-map/v2.",
  "type": "object",
  "required": ["id", "version", "from", "to", "url", "sha256"],
  "additionalProperties": true,
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
      "description": "Stable identity for this map across versions — path-safe (a sanitized slug, never a raw remote string that could traverse the filesystem). Lowercase alphanumeric + hyphen, 1–64 chars, leading alphanumeric."
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "Monotonic version. `maps update` compares this + the sha to detect an available update; install refuses a version below the installed one (rollback protection) unless --force."
    },
    "from": {
      "type": "string",
      "minLength": 1,
      "description": "Source equipment the map rewrites from (e.g. \"dumbbell\") — informational, and the natural key of a reverse map."
    },
    "to": {
      "type": "string",
      "enum": ["cable", "dumbbell", "suspension"],
      "description": "Destination equipment → the swap resolver's target. Only the three destinations the resolver knows are valid; the asset lands under installed/<target>/."
    },
    "format": {
      "type": "string",
      "enum": ["csv", "tsv", "psv"],
      "default": "csv",
      "description": "Delimiter flavor of the map asset — comma, tab, or pipe. TSV/PSV free authors from comma-escaping in movement notes. One reader parses all three."
    },
    "url": {
      "type": "string",
      "format": "uri",
      "description": "The map asset — a versioned, content-hashed CSV/TSV/PSV. Fetched HTTPS-only (payload/timeout/redirect capped, private-address refused); its sha256 must match the field below."
    },
    "sha256": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$",
      "description": "Lowercase-hex sha256 of the asset bytes. The integrity gate: the downloaded asset is rejected if it does not match, so a compromised host cannot swap the asset under a trusted manifest."
    },
    "published_at": {
      "type": "string",
      "format": "date-time",
      "description": "When this version was published (RFC 3339). Recorded in the install provenance; informational."
    }
  }
}
