org.projectfile.artifacts

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 “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 the thing and how to call it. Each entry carries a kind plus the address fields that kind is addressed by (ref for an image, command/path for a binary, registry/name for a package, module for a library, ports for a service, url for a site and for a protocol endpoint such as ssh, whose scheme carries the port). kind is an open vocabulary and stays ADVISORY for lowering — a resolver MUST NOT branch on it — while being load-bearing for documentation and for the kind selector. An address reachable at more than one deployment also carries an environment (dev, ci, production, …), the second selector dimension that lets a live auditor dial the address belonging to the run it is part of; an entry declaring no environment matches no environment selector, so the fall-through to a production address cannot happen by omission. Named keys let each consumer reference its OWN artifact independently, retiring the stringly-typed space-separated M6E_ARTIFACTS list that could not say WHICH artifact a tool wanted. Referenced from any string via the §3.8 sigil: by name (${org.projectfile.artifacts.<name>.path}), or by the MAP selector for every artifact of a kind (${org.projectfile.artifacts{kind=image}.ref}) — curly braces, because artifacts is a mapping of named keys and a list operator ([kind=image]) on it is a malformed address. The map selector yields every match, which is what lets a shared fragment document a project publishing one image per matrix cell without naming a cell, and what makes such a fragment self-guarding: a recipe addressing an artifact the consumer does not declare resolves to nothing and its output is dropped. Sibling of org.projectfile.ignores / .vulnerabilities. Has a dedicated standalone schema (schema/org.projectfile.artifacts.v1.json).

Illustrative fragment — not a complete projectfile document. Consumers MUST round-trip unknown namespaces even when no shape is published.

---# SPDX-FileCopyrightText: 2026 Damián Búho <damian.buho@proton.me>## SPDX-License-Identifier: CC-BY-4.0# SPDX-License-Identifier: MIT
# Shape: org.projectfile.artifacts extension namespace# Illustrative fragment — not a complete projectfile document.# See registry.yaml for ownership and status.## A map of NAMED artifacts a project PRODUCES — the images it publishes, the# packages it ships, the executables it installs, the services it exposes. One# neutral home for "what does this project give me", read by a CI tool that needs# pointing at an output AND by a documentation generator that needs to say how to# install the thing and how to call it.## Each entry declares a `kind` plus the address fields that kind is addressed by:##   image    ref              a pull reference#   binary   path             the built file#   package  registry, name   which index, and the name published there#   module   module           an importable path#   service  ports, secrets   what it listens on (each port: name, protocol, path); what it must be given#   website  url              a reachable address#   ssh      url              a reachable address, ssh:// scheme#   archive  path             a bundle on disk#   action   ref, path        a workflow step pin; its action.yaml## Any kind MAY export a `command` — the name a user types — and `{command}` selects whatever does.## `website` and `ssh` are the same PROTOCOL shape — one address, one scheme, one# live auditor selecting on `{kind=…}` — and every further protocol kind follows# it (`mysql` addressed by `mysql://host:3306`) instead of inventing a new one.## A protocol artifact is usually reachable at SEVERAL addresses at once — a# developer's local vhost, an ephemeral CI stack, the public origin — so it also# carries an `environment` naming which deployment its `url` is true in. That is# a second selector dimension, not a second kind: `{kind=website,environment=ci}`# picks the CI address and nothing else. An artifact with no `environment` is# environment-neutral and matches no environment selector at all, so an auditor# built for an ephemeral stack can never silently fall through to production.## Referenced from any string via the §3.8 interpolation sigil, two ways:##   By NAME — one artifact, when a tool wants a specific output:#     run: gsa ${org.projectfile.artifacts.cli.path}##   By KIND — every artifact of a kind, using the MAP selector (curly braces,#   because artifacts is a mapping of named keys, not a list):#     commands: ["docker pull ${org.projectfile.artifacts{kind=image}.ref}"]##   By SEVERAL fields — the selector takes a comma-separated predicate list and#   an entry must match every one, which is how two artifacts of the same kind#   are told apart (`registry` for packages, `environment` for addresses):#     ${org.projectfile.artifacts{kind=website,environment=ci}.url}## The map selector yields EVERY match, not the first, because a mapping has no# order in which "first" would mean anything (§3.8). A consumer whose output# repeats — one line of a code block — emits one copy per value. That is what# lets a project publishing one image per matrix cell be documented completely by# a fragment that names no cell.## The kind selector is ALSO what makes a shared fragment self-guarding: a recipe# addressing an artifact the consuming project does not declare resolves to# nothing, and a consumer that must not publish a half-substituted string drops# that output instead. One fragment can therefore carry an install command for# every ecosystem in a fleet, and each project renders only its own.
org:  projectfile:    artifacts:      # what a user types, and what a size analyzer measures in place      cli:        # kind is an OPEN vocabulary: a consumer MUST NOT reject an unknown kind,        # and MUST NOT change any lowering (a workflow, a build) based on it — for        # that purpose it is advisory, like a tool's `tags`. It IS load-bearing        # for documentation and for the `{kind=…}` selector, neither of which is        # a lowering.        kind: binary        command: pf-cli        path: dist/pf-cli        # OPTIONAL  arguments whose `<command> <args> --help` is captured into docs/usage.d; "" is top-level        help: ["", get, "cache purge"]        # OPTIONAL  build cells: overrides the project-level lists, minus the excluded cells        operating-system: [linux, darwin, windows]        architecture: [amd64, arm64, riscv64]        exclude: [{os: darwin, arch: riscv64}, {os: windows, arch: riscv64}]        # OPTIONAL  published file per cell; {arch} is what `uname -m` prints there, {ext} is .exe on windows        asset: "{command}-{os}-{arch}{ext}"        # OPTIONAL  what the toolchain builds and where it stamps the version; defaults `.` and `main.version`        package: ./cmd/pf-cli        version-symbol: projectfile.org/projectfile/cli/internal/cmd.version        # OPTIONAL  link with no shared-library dependency; default true, false links dynamically        static: true
      # the published image; `${image.basename}` is the derived namespace/name      # path every lowering of this fleet already agrees on      image:        kind: image        ref: kiota.ch/${image.basename}:latest
      # a package, distinguished from a sibling package by its registry      npm-package:        kind: package        registry: npm        name: "@projectfile/reader"        # OPTIONAL  runs in a web page too, so a README shows its bundle size        browser: true        # OPTIONAL  a package exporting a command is installed for the machine, not added to a project        command: pf-reader        # OPTIONAL  platforms this artifact runs on; overrides org.projectfile.architecture / operating-system for it alone        architecture: [amd64, arm64]        operating-system: [linux, darwin]
      # a service is addressed by what it LISTENS on — the one thing no generic      # description of a service could supply      database:        kind: service        summary: MySQL-compatible database        ports:          - 3306          - port: 9104            name: metrics            # an HTTP port names the path a reader probes it at            path: /metrics        # OPTIONAL  dotted secret names it refuses to start without; `${… | env}` spells each as its variable        secrets: [database.root.password]
      # what lighthouse / linkinator audit — one entry per deployment, because      # the address a page auditor dials depends on where the run is happening      frontpage:        kind: website        environment: production        url: https://projectfile.org
      frontpage-ci:        kind: website        environment: ci        url: https://projectfile.test:8443
      # what ssh-audit audits — the scheme carries the port, so one field is the      # whole address; in CI the host is the compose SERVICE name the tool joins      sshd-ci:        kind: ssh        environment: ci        url: ssh://projectfile-sshd:22

.onion.i2p