{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://offline-lab.com/schemas/package-yaml/v1",
  "title": "Offline Lab package.yaml (the manifest / DDI metadata)",
  "description": "The app manifest, written by the app author. Embedded in the image at /usr/share/<name>/package.yaml and, since ADR-0038, the ONLY package metadata (the standalone metadata JSON is retired). Field semantics: docs/specs/app-contract.md. YAML only (package.json was dropped). The floor is four lines: name, version, arch, command (command may be omitted when the image ships its own service unit).",
  "$comment": "Deliberately ABSENT, pending the T101 rulings (do not add without a decision): expose/ports, devices, resources, systemd_profile, network, volumes. Also removed by ruling: tags (freeform labels rejected, session §21), category (killed 2026-07-06, session §24 — no added value), strip (died with the old buildctl).",
  "type": "object",
  "required": ["name", "version", "arch"],
  "additionalProperties": false,
  "properties": {

    "spec_version": {
      "type": "string",
      "pattern": "^[0-9]+\\.[0-9]+$",
      "description": "Manifest format version, major.minor (session §24): minor = compatible additions, major = breaking changes. Current: '1.0'. Optional; defaults to '1.0'."
    },

    "name": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]*$",
      "description": "App name: lowercase alphanumeric and hyphens, starts alphanumeric. Names the service unit, storage paths, and all lifecycle verbs."
    },

    "version": {
      "type": "string",
      "pattern": "^[A-Za-z0-9.~^-]+$",
      "description": "The DDI version the author assigns (UAPI.10 Version Format: '~' pre-release sorts lower, '^' post-release sorts higher; compared like systemd-analyze compare-versions). NOT semver, and NOT the version of the software inside (deliberately untracked, session §21). Underscore is forbidden — it separates the filename fields <name>_<version>_<arch>.raw."
    },

    "arch": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]*$",
      "description": "Target architecture. An OPEN set (anyone who can build for an arch may ship it); the officially supported set is documented separately. Must have DPS partition type UUIDs and, for the docker backend, a platform mapping."
    },

    "command": {
      "type": "string",
      "pattern": "^/",
      "description": "Absolute path to the service entrypoint. Used to generate a minimal <name>.service when the image ships no unit; a shipped unit always wins. If neither exists, the build fails — an app must be runnable."
    },

    "runtime": {
      "type": "string",
      "enum": ["portable", "nspawn"],
      "default": "portable",
      "description": "Developer-declared runtime mode, baked into the app and travels with the image (ADR-0030/0011). portable = light, host-integrated (default); nspawn = isolated."
    },

    "socket_activation": {
      "type": "boolean",
      "default": false,
      "description": "When true, `up` starts <name>.socket (which must exist in the image) instead of starting the service directly."
    },

    "backend": {
      "type": "string",
      "enum": ["docker", "mkosi", "shell"],
      "description": "Build backend (build-time only; ADR-0031). Never guessed: declared here or passed as --backend. shell subsumes make."
    },

    "base": {
      "type": "object",
      "description": "Lineage: the ONE base this app layers on (2 layers max, no language layer — session §20). Absent = fat app. Matching is UAPI.4 exact: the base's os-release ID + SYSEXT_LEVEL. ('stack:' is reserved for the future compose feature, T53.)",
      "required": ["name", "sysext_level"],
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]*$",
          "description": "Base name (e.g. debian-base, debian-python — flat, complete bases)."
        },
        "sysext_level": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9._-]*$",
          "description": "The base publisher's compatibility level (UAPI.4 SYSEXT_LEVEL; their ABI promise, e.g. '13' for Debian 13.x). Exact string match at attach."
        }
      }
    },

    "lifecycle": {
      "type": "object",
      "description": "Hook stages (ADR-0016 as amended, session §18): each stage is an ORDERED LIST of unit names that exist inside the image. Present only when hooks exist — no null/empty forms. Hooks fire every time their stage fires; one-time behavior via the unit's own Condition* guards. pre_upgrade/post_upgrade are reserved for the upgrade design (T103).",
      "minProperties": 1,
      "additionalProperties": false,
      "properties": {
        "pre_start":  { "$ref": "#/definitions/hook_list" },
        "post_start": { "$ref": "#/definitions/hook_list" },
        "pre_stop":   { "$ref": "#/definitions/hook_list" },
        "post_stop":  { "$ref": "#/definitions/hook_list" },
        "pre_drop":   { "$ref": "#/definitions/hook_list" }
      }
    },

    "description": {
      "type": "string",
      "minLength": 1,
      "description": "Single-line human-readable description. Projected into index catalogs as the offline-search text (session §21). Catalog hygiene: enforced at `index add`, not at build."
    },

    "homepage":         { "type": "string", "format": "uri", "description": "Upstream/project homepage." },
    "license":          { "type": "string", "description": "SPDX license identifier (e.g. 'Apache-2.0')." },
    "publisher":        { "type": "string", "description": "Publisher organisation. Catalog hygiene: index holders may require it at `index add`." },
    "publisher_url":    { "type": "string", "format": "uri", "description": "Publisher homepage." },
    "maintainer":       { "type": "string", "description": "Package maintainer, 'Name <email>'." },
    "source_url":       { "type": "string", "format": "uri", "description": "Source repository URL." },
    "security_contact": { "type": "string", "description": "Email or URL for vulnerability reports." },
    "sbom_url":         { "type": "string", "format": "uri", "description": "URL to a published SBOM. Related: T106 (inner-software-version visibility) — kept as an optional pointer meanwhile." }
  },

  "definitions": {
    "hook_list": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "string",
        "pattern": "^[A-Za-z0-9:_.\\\\@-]+\\.(service|socket|target|timer|path|mount|automount)$",
        "description": "A systemd unit name that exists inside the image. Units run in list order; the next starts only after the previous succeeded."
      }
    }
  }
}
