{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://projectfile.org/schema/org.projectfile.artifacts.v1.json",
  "title": "org.projectfile.artifacts extension (v1)",
  "description": "Standalone contract for the value at the org.projectfile.artifacts subtree — a map of NAMED artifacts a project PRODUCES: the container images it publishes, the packages it ships to a registry, the executables it installs, the services it exposes, the sites it deploys. One neutral home for the question \"what does this project give me\", answering both a CI tool that needs pointing at an output and a documentation generator that needs to say how to install and call it. Retires the stringly-typed space-separated `M6E_ARTIFACTS` list (which could not say WHICH artifact a given tool wanted) with named keys, so a binary analyzer (gsa) references one artifact and a site auditor (lighthouse) another. Each entry carries a `kind` plus the address fields that kind is addressed by. Referenced from a string anywhere in the document via the §3.8 interpolation sigil: by name for one artifact (`${org.projectfile.artifacts.<name>.path}`), or by the map selector for every artifact of a kind (`${org.projectfile.artifacts{kind=image}.ref}`), which fans a repeating output out to one entry per match. The selector takes several predicates, so an artifact addressable at more than one deployment carries an `environment` and is picked with `{kind=website,environment=ci}`.",

  "type": "object",
  "additionalProperties": { "$ref": "#/$defs/artifact" },
  "propertyNames": { "pattern": "^[a-z0-9][a-z0-9-]*$" },

  "$defs": {
    "localizedString": {
      "description": "BCP 47 language-tagged string map. As a shorthand, a bare string is permitted and treated as { und: <string> } (§7). Inlined rather than referenced across documents so this standalone schema validates with no network fetch, like every other per-namespace schema here — keep it in step with v1.json's own $defs.localizedString.",
      "oneOf": [
        { "type": "string" },
        {
          "type": "object",
          "minProperties": 1,
          "patternProperties": {
            "^([a-zA-Z]{2,3}|und)(-[a-zA-Z0-9]+)*$": { "type": "string" }
          },
          "additionalProperties": false
        }
      ]
    },

    "artifact": {
      "type": "object",
      "additionalProperties": false,
      "description": "One thing the project ships. `kind` says what sort of thing it is; `environment` says where the address is true; the address fields say where to find it. At least one address field (or `ports`) MUST be present — an artifact nothing can be pointed at is meaningless. Which field carries meaning follows from the kind, and the split is deliberate: a consumer pointed at the wrong field resolves nothing rather than a plausible-looking wrong value.",
      "properties": {
        "kind": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]*$",
          "description": "What sort of thing this is. The vocabulary is OPEN — a consumer MUST NOT reject a document for an unknown kind — and it MUST NOT change how any lowering (a CI workflow, a build) is generated: for that purpose `kind` is advisory, exactly like a tool's `tags`. It IS load-bearing for documentation and for the `{kind=…}` selector, neither of which is a lowering. Registered kinds and the field each is addressed by: `image` → ref; `binary` → path; `package` → registry, name; `module` → module; `service` → ports; `website` → url; `ssh` → url; `archive` → path; `action` → ref (`owner/repo@version`), path (its `action.yaml`). `website` and `ssh` are both PROTOCOL kinds — one address field, one scheme, one live auditor selecting on `{kind=…}` — and the pair is the template every further protocol kind follows (`mysql` addressed by `mysql://host:3306`, and so on) rather than a shape anyone has to invent again. Absent => the artifact is unclassified and no kind selector matches it."
        },
        "environment": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]*$",
          "description": "WHERE this address is true — the deployment the artifact is reachable at. The vocabulary is OPEN (`dev`, `ci`, `production`, `staging`, …) and, exactly like `kind`, it is ADVISORY for lowering and load-bearing only for documentation and for the `{environment=…}` selector. It exists because one project ships one site at SEVERAL addresses at once — a developer's local vhost, an ephemeral CI stack, the public origin — and a live auditor must dial the one belonging to the run it is part of, not an arbitrary sibling. Pairs with `kind` to make a two-key selector (`{kind=website,environment=ci}`) that picks exactly those, which is what lets one shared CI preset audit every environment a project declares without the preset naming any address. Absent => the artifact is environment-neutral and NO environment selector matches it: a consumer selecting on an environment MUST NOT fall back to an entry that declares none. That refusal is deliberate — the failure it prevents is an auditor built for an ephemeral stack silently dialling production."
        },
        "summary": {
          "$ref": "#/$defs/localizedString",
          "description": "One line describing this artifact, for a generator listing what the project provides. Localizable — the sentence is prose, unlike the addresses around it."
        },
        "path": {
          "type": "string",
          "minLength": 1,
          "description": "Filesystem path (relative to the project root) of an on-disk output — a built binary a scanner analyzes in place, a release archive. Referenced as `${org.projectfile.artifacts.<name>.path}`."
        },
        "url": {
          "type": "string",
          "minLength": 1,
          "description": "A reachable address, SCHEME INCLUDED — what a live auditor connects to: `https://…` for a page auditor (lighthouse / linkinator), `ssh://host:port` for a protocol auditor (ssh-audit). The `kind` is what says which auditor the entry belongs to; the scheme is what makes the address readable on its own and carries the port, which is why a protocol kind needs no second field. Unlike a webhook secret (named by `${VAR}`), this is a public address committed in the document."
        },
        "ref": {
          "type": "string",
          "minLength": 1,
          "description": "A container-image pull reference, registry host included (`kiota.ch/b19/ubuntu/noble:latest`) — what `docker pull` and a Dockerfile `FROM` take. A project publishing one image per matrix cell writes ONE artifact whose ref resolves to several values (via a `{…}` matrix placeholder or a `[]` reference to the axis list), rather than one artifact per cell."
        },
        "command": {
          "type": "string",
          "minLength": 1,
          "description": "The command this artifact exports — what a user types. Any kind MAY export one (a binary, an npm or PyPI package, …), and `{command}` selects whatever does. Frequently NOT the project's own name (a project named `bridge` may ship `pf-bridge`), which is why it cannot be inferred and MUST be declared for a generator to document how to invoke the thing."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "The name this artifact is published under in its registry, which is not always the project's name — a scoped npm package (`@scope/thing`), a distribution renamed for PyPI. Distinct from the artifact's own map key, which is its identity WITHIN the document."
        },
        "registry": {
          "type": "string",
          "minLength": 1,
          "description": "Which package index `name` is published to: `npm`, `pypi`, `crates`, `maven`, `nuget`, `oci`, … Open vocabulary. It is what lets one selector distinguish two packages of the same kind — `{kind=package,registry=npm}` — so a project publishing to several indexes documents each install command separately."
        },
        "architecture": {
          "type": "array",
          "items": { "type": "string", "minLength": 1 },
          "uniqueItems": true,
          "description": "CPU architectures THIS artifact runs on, in the org.projectfile.architecture vocabulary (OCI image-spec: `amd64`, `arm64`, …). Overrides the project-level list for this artifact only, so a portable package can declare more than the image the project builds. Absent => the project-level org.projectfile.architecture applies."
        },
        "operating-system": {
          "type": "array",
          "items": { "type": "string", "minLength": 1 },
          "uniqueItems": true,
          "description": "Operating systems THIS artifact runs on, in the org.projectfile.operating-system vocabulary (`linux`, `darwin`, `windows`, …). Overrides the project-level list for this artifact only. Absent => the project-level org.projectfile.operating-system applies."
        },
        "exclude": {
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "description": "Cells of the `operating-system` × `architecture` product this artifact is NOT built for. A cell names an `os`, an `arch`, or both; one naming a single axis drops every cell with that value. Each value MUST appear in the effective list it filters (this artifact's, else the project-level one), so a typo fails validation instead of silently excluding nothing.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "minProperties": 1,
            "properties": {
              "os": { "type": "string", "minLength": 1 },
              "arch": { "type": "string", "minLength": 1 }
            }
          }
        },
        "asset": {
          "type": "string",
          "minLength": 1,
          "description": "File name one built cell is published under. Placeholders: `{command}`, `{os}` (lowercased), `{arch}` (the name `uname -m` prints on that cell: `x86_64`, `aarch64`, `arm64` on darwin), `{ext}` (`.exe` on windows, else empty). Absent => `{command}-{os}-{arch}{ext}`, so a reader's `$(uname -s)-$(uname -m)` names the file for their own machine."
        },
        "package": {
          "type": "string",
          "minLength": 1,
          "description": "The package the toolchain builds into this artifact, relative to the project root (`./cmd/pf-cli` for Go). Absent => `.`."
        },
        "version-symbol": {
          "type": "string",
          "minLength": 1,
          "description": "Fully qualified variable a Go build stamps the version into with `-ldflags -X`. Absent => `main.version`."
        },
        "version-env": {
          "type": "string",
          "pattern": "^[A-Z_][A-Z0-9_]*$",
          "description": "Environment variable a build reads the version from at compile time (Rust's `env!`). Absent => the upper-cased `command` with `-` as `_`, plus `_VERSION` (`PF_CLI_VERSION`)."
        },
        "static": {
          "type": "boolean",
          "description": "A binary linked with no shared-library dependency, so one file runs on any distribution of its operating system. `false` links against the build host's shared libraries. Absent => true."
        },
        "browser": {
          "type": "boolean",
          "description": "A package that also runs in a web page, so consumers MAY show its bundle size. Absent => false."
        },
        "module": {
          "type": "string",
          "minLength": 1,
          "description": "An importable / installable module path, in whatever form the language's toolchain takes (`kiota.ch/projectfile/core/v2` for Go). Kept separate from `name` because a module path is a resolvable location while a package name is a registry lookup key."
        },
        "version": {
          "type": "string",
          "minLength": 1,
          "description": "The version this artifact is published at, when the document pins one. Usually ABSENT: a released version belongs to the release, not to the source document, and a stale value here is worse than none."
        },
        "help": {
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "items": { "type": "string" },
          "description": "Arguments whose `<command> <args> --help` output a generator captures as usage documentation. The empty string is the top-level command."
        },
        "secrets": {
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "items": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]*(\\.[a-z0-9][a-z0-9_-]*)*$" },
          "description": "Dotted names of the secrets a service artifact refuses to start without (`o9s.postgresql.password`). A generator spells each as the environment variable that carries it with the §3.8 `env` filter, so the names are declared once."
        },
        "ports": {
          "type": "array",
          "minItems": 1,
          "description": "The ports a service artifact listens on once it is running. This is the answer to \"what do I connect to\", which no generic description of a service could supply.",
          "items": {
            "oneOf": [
              {
                "type": "integer",
                "minimum": 1,
                "maximum": 65535,
                "description": "A bare port number — the whole declaration for a single-port service."
              },
              {
                "type": "object",
                "additionalProperties": false,
                "required": ["port"],
                "description": "A named port, for a service exposing more than one.",
                "properties": {
                  "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "description": "What this port is for (`api`, `metrics`), so a reader can tell several apart."
                  },
                  "protocol": {
                    "type": "string",
                    "enum": ["tcp", "udp", "sctp"],
                    "description": "Transport protocol. Absent means unspecified rather than tcp — a consumer that needs a default picks its own."
                  },
                  "path": {
                    "type": "string",
                    "pattern": "^/",
                    "description": "HTTP path a reader probes this port at (`/metrics`, `/healthz`). Absent means the port speaks no HTTP a reader can try."
                  }
                }
              }
            ]
          }
        }
      },
      "anyOf": [
        { "required": ["path"] },
        { "required": ["url"] },
        { "required": ["ref"] },
        { "required": ["command"] },
        { "required": ["name"] },
        { "required": ["module"] },
        { "required": ["ports"] }
      ]
    }
  }
}
