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).
- Namespace:
org.projectfile.artifacts - Owner: projectfile.org (community-maintained)
- Status: proposed
- Schema: /schema/org.projectfile.artifacts.v1.json
Used by
Section titled “Used by”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[org.projectfile.artifacts.cli]kind = "binary"command = "pf-cli"path = "dist/pf-cli"help = [ "", "get", "cache purge" ]operating-system = [ "linux", "darwin", "windows" ]architecture = [ "amd64", "arm64", "riscv64" ]asset = "{command}-{os}-{arch}{ext}"package = "./cmd/pf-cli"version-symbol = "projectfile.org/projectfile/cli/internal/cmd.version"static = true
[[org.projectfile.artifacts.cli.exclude]]os = "darwin"arch = "riscv64"
[[org.projectfile.artifacts.cli.exclude]]os = "windows"arch = "riscv64"
[org.projectfile.artifacts.image]kind = "image"ref = "kiota.ch/${image.basename}:latest"
[org.projectfile.artifacts.npm-package]kind = "package"registry = "npm"name = "@projectfile/reader"browser = truecommand = "pf-reader"architecture = [ "amd64", "arm64" ]operating-system = [ "linux", "darwin" ]
[org.projectfile.artifacts.database]kind = "service"summary = "MySQL-compatible database"ports = [ 3306, { port = 9104, name = "metrics", path = "/metrics" } ]secrets = [ "database.root.password" ]
[org.projectfile.artifacts.frontpage]kind = "website"environment = "production"url = "https://projectfile.org"
[org.projectfile.artifacts.frontpage-ci]kind = "website"environment = "ci"url = "https://projectfile.test:8443"
[org.projectfile.artifacts.sshd-ci]kind = "ssh"environment = "ci"url = "ssh://projectfile-sshd:22"{ "org": { "projectfile": { "artifacts": { "cli": { "kind": "binary", "command": "pf-cli", "path": "dist/pf-cli", "help": [ "", "get", "cache purge" ], "operating-system": [ "linux", "darwin", "windows" ], "architecture": [ "amd64", "arm64", "riscv64" ], "exclude": [ { "os": "darwin", "arch": "riscv64" }, { "os": "windows", "arch": "riscv64" } ], "asset": "{command}-{os}-{arch}{ext}", "package": "./cmd/pf-cli", "version-symbol": "projectfile.org/projectfile/cli/internal/cmd.version", "static": true }, "image": { "kind": "image", "ref": "kiota.ch/${image.basename}:latest" }, "npm-package": { "kind": "package", "registry": "npm", "name": "@projectfile/reader", "browser": true, "command": "pf-reader", "architecture": [ "amd64", "arm64" ], "operating-system": [ "linux", "darwin" ] }, "database": { "kind": "service", "summary": "MySQL-compatible database", "ports": [ 3306, { "port": 9104, "name": "metrics", "path": "/metrics" } ], "secrets": [ "database.root.password" ] }, "frontpage": { "kind": "website", "environment": "production", "url": "https://projectfile.org" }, "frontpage-ci": { "kind": "website", "environment": "ci", "url": "https://projectfile.test:8443" }, "sshd-ci": { "kind": "ssh", "environment": "ci", "url": "ssh://projectfile-sshd:22" } } } }}