Manifest schema reference¶
Generated from src/hammunition/manifest/schema.py — the authority. This page cannot drift from it, because it is rendered from the model definitions themselves: field names, types, whether each is required, and the description= text the schema carries in place. The prose above each table is the model's own docstring, which is where the cross-field validators and the reasoning behind a refusal are explained — a table cannot render a validator, so those are treated as part of the reference and kept current in the source.
A manifest is strict: an unknown field is an error, not ignored. That is deliberate — a typo'd field name that silently did nothing is the bug this catches at load time (D-016).
PackageManifest¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
str |
yes | |
version |
str |
yes | |
summary |
str |
yes | |
categories |
list[str] |
yes | Flat tags, D-003. |
install |
list[InstallBlock] |
yes | |
depends |
list[str] |
no | |
provides |
list[str] |
no | |
conflicts_with_repo_package |
list[str] |
no | |
after |
list[str] |
no | Ordering, not dependency. |
requires_kernel |
list[Literal[ax25]] |
no | Kernel subsystems the software cannot work without, checked against the running kernel at plan time. A machine whose kernel lacks one defers the unit in a profile and refuses it by name. Linux 7.1 removed AX.25 (merge 64edfa65, 2026-04-24); hammunition.kernel reads the module tree. The vocabulary is what has been measured. |
requires_java |
int \| None |
no | The lowest Java major version the software runs on, checked at plan time with java -version (D-037, amended 2026-10-02). default-jre-headless is a metapackage whose version says nothing about the Java major, so the floor is read from the java on this machine, never from the archive. A machine below it (or with no java, when this unit's depends would not install one that meets it) defers the unit in a profile and refuses it by name; nothing is fetched to meet it. Measured from the upstream's build file or its jar's class-file major, never copied from a README. |
desktops |
list[Desktop] \| None |
no | The desktops this unit is for, when it is for some and not others: kde, gnome, xfce, lxqt, lxde, mate, cinnamon. Omitted means any desktop, which is every unit that is not a panel applet or the like. Decided at plan time against the session files under /usr/share/xsessions and /usr/share/wayland-sessions (and the same under /usr/local/share), never XDG_CURRENT_DESKTOP (sudo drops it): a machine with no session for any listed desktop defers the unit in a profile and refuses it by name (D-060). The case it exists for is hammunition-tray, a Plasma applet whose .deb pulls plasma-workspace onto an Xfce machine. |
desktop_alternative |
str \| None |
no | The unit that does this job on the desktops this one is not for, named in the refusal and the deferral so the operator is told what to install instead. It must exist in the catalog, and its desktops must share none with this unit's (checked when the catalog loads). Requires desktops. |
menu_title |
str \| None |
no | What the desktop menu shows for the entry the engine generates when this unit ships none of its own: what it does, then the command in parentheses (Contest logger (tlf)). Defaults to the unit's name, which is fine for a name people know (gqrx) and not for one they do not (wwl). Ignored for a unit that ships its own desktop entries. |
menu_submenu |
str \| None |
no | Gather every desktop entry this unit ships into one submenu of this title, under the unit's first category only, instead of listing them beside everything else in every category it carries. For a unit that ships a toolkit: GNU Radio puts 21 entries into a submenu, and the SDR and Digital Modes menus were unreadable with them inline. |
binaries |
list[Binary] |
no | |
installed_files |
list[str] |
no | Files the build's own install rule puts under the prefix, as paths relative to it (lib/libacars-2.so.2, lib/pkgconfig/libacars-2.pc). Declared effects only: they are checked after the run (D-031) and are what lets a build with no executable be decided as already installed (D-051) or compared by update; the engine never copies or removes them. An executable belongs in binaries, not here. |
launchers |
list[Launcher] |
no | |
service_endpoints |
list[ServiceEndpoint] |
no | |
apt_repos |
list[AptRepo] |
no | |
system_modifications |
list[SystemModification] |
no | |
config_files |
list[ConfigFile] |
no | |
user_services |
list[UserService] |
no | systemd user services the engine renders from station values, enables, and reverses on uninstall (D-073 §6). Rendered into the operator's ~/.config/systemd/user/; deferred when a station value is missing, like config_files. |
debconf_selections |
list[str] |
no | debconf preseed lines applied BEFORE the apt install, so a package's postinst reads them instead of taking a default that needs an interactive answer. Each line is ' |
reconfigure_after |
list[str] |
no | Packages to dpkg-reconfigure non-interactively AFTER the apt install. Paired with debconf_selections for the case where a postinst action depends on another package in the same transaction: wireshark-common's setcap of dumpcap needs libcap2-bin, and apt does not guarantee it is configured first, so the reconfigure re-runs the action once the whole transaction is settled (measured on Debian 13, 2026-09-01). |
scope |
Literal[system, user] |
no (default system) |
|
status |
Status |
no (default supported) |
|
status_reason |
str \| None |
no | |
status_date |
date \| None |
no | |
status_verdict |
VerdictSource \| None |
no | |
retire_reason |
RetireReason \| None |
no | |
supersedes |
list[str] |
no | |
superseded_by |
str \| None |
no | |
recommended_default |
bool |
no (default True) |
|
toolkit_risk |
list[ToolkitRisk] |
no | |
update |
UpdateBlock |
yes | |
documentation |
Documentation |
yes |
Selector¶
Restricts an install block to some subset of targets.
An empty selector matches everything and acts as the default. Resolution is first-match-wins in list order, so defaults belong last.
| Field | Type | Required | Description |
|---|---|---|---|
distro |
list[str] \| None |
no | |
distro_version |
list[str] \| None |
no | |
arch |
list[Arch] \| None |
no |
InstallBlock¶
One (selector -> method) pair. The method itself varies, not just its argument — js8call is apt on Linux Mint 22.3 and a cmake build elsewhere.
| Field | Type | Required | Description |
|---|---|---|---|
when |
Selector |
no | |
install |
AptInstall \| SourceInstall \| GitInstall \| BinaryInstall \| VenvInstall \| NodeInstall \| PipxInstall \| DataInstall \| RegionalDataInstall \| DemTilesInstall \| TopoQuadsInstall \| RegisterInstall \| DerivedDataInstall \| KiwixBooksInstall \| MwmRegionsInstall |
yes | |
build_depends |
list[str] |
no | apt packages needed to BUILD only. Never reported as installed. |
binaries |
list[Binary] \| None |
no | This block's own build outputs, replacing the manifest's binaries wherever this block is the one that resolves. A prebuilt archive selected by arch can carry a different path per architecture -- rayhunter's zip has installer at the top level and one rayhunter-check under a per-platform directory -- and one manifest-level list cannot describe both. Omit the key to use the manifest's list; an empty list is refused, because it reads as an override to nothing. |
note |
str \| None |
no |
AptInstall¶
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[apt] |
no (default apt) |
|
packages |
list[str] |
yes | |
install_recommends |
bool |
no (default True) |
Whether apt installs this unit's Recommends. The global default stays what every target distribution does -- Recommends are not suppressed catalog-wide -- and this is a per-unit opt-out for a package whose Recommends conflict with the target's desktop stack. Debian's morse Recommends pulseaudio, which Conflicts pipewire-alsa, so on a PipeWire desktop apt satisfies the transaction by removing the machine's audio routing and the plan refuses it (D-022, issue #61, measured on Parrot 7.3 + KDE 2026-09-12). Set false and this unit's packages are installed by a second apt-get install --no-install-recommends, simulated separately and disclosed in the plan (D-052); everything else in the transaction keeps apt's defaults. |
SourceInstall¶
Build from a verified source archive.
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[source] |
no (default source) |
|
source |
RemoteArtifact |
yes | |
build_system |
Literal[autotools, cmake, qmake, qmake6, make, custom] |
yes | |
configure_args |
list[str] |
no | |
build_args |
list[str] |
no | |
compiler_flags |
list[str] |
no | e.g. -Wno-incompatible-pointer-types. Six AHRL units need these. |
project_file |
str \| None |
no | qmake .pro / cmake subdir. MSHV needs a different one per arch. |
patches |
list[Patch] |
no | |
build_dir |
str \| None |
no | |
autoreconf |
bool |
no (default False) |
Run autoreconf -fi before configure -- for autotools projects shipped without a generated configure (git checkouts, mostly). kalibrate-rtl proved the need (source-build-gaps #3); the planner injects the autotools toolchain when set. |
provides_install_target |
bool |
no (default True) |
False when the project's build system has no install rule. The backend then installs the manifest's binaries explicitly instead of running make install, which would fail. Requires binaries to be declared. |
install_tree |
bool |
no (default False) |
Install the whole built/extracted tree to |
tree_marker |
str \| None |
no | One file, relative to the installed tree, whose presence proves the tree is what the launcher expects: yaac's YAAC.jar, js8spotter's js8spotter.py. The effect check reads it back after the run; cp -aT exits 0 on any directory, so without it a tree unit ended verified: true with no check at all (issue #27). Required exactly when the block installs a tree. |
GitInstall¶
Build from a pinned git revision. ref must be immutable.
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[git] |
no (default git) |
|
repo |
str |
yes | |
ref |
str |
yes | Commit SHA or tag. Never a branch name. |
build_system |
Literal[autotools, cmake, qmake, qmake6, make, custom] |
yes | |
configure_args |
list[str] |
no | |
compiler_flags |
list[str] |
no | |
project_file |
str \| None |
no | qmake .pro / cmake subdir, as for a source build. |
build_args |
list[str] |
no | |
autoreconf |
bool |
no (default False) |
Run autoreconf -fi before configure -- for autotools projects shipped without a generated configure (git checkouts, mostly). kalibrate-rtl proved the need (source-build-gaps #3); the planner injects the autotools toolchain when set. |
provides_install_target |
bool |
no (default True) |
False when the project's build system has no install rule. See SourceInstall for the full note. |
install_tree |
bool |
no (default False) |
Install the whole built/extracted tree to |
patches |
list[Patch] |
no | Unified diffs applied after the checkout and before the build, in order, exactly as a source block's. linbpq's makefile runs sudo setcap inside the build (#96); the patch that removes it is the first use. |
tree_marker |
str \| None |
no | One file, relative to the installed tree, whose presence proves the tree is what the launcher expects: yaac's YAAC.jar, js8spotter's js8spotter.py. The effect check reads it back after the run; cp -aT exits 0 on any directory, so without it a tree unit ended verified: true with no check at all (issue #27). Required exactly when the block installs a tree. |
pin_review |
PinReview \| None |
no | Required when ref is a commit SHA rather than a tag. D-024. |
commit |
str \| None |
no | For a tag ref: the commit it must resolve to. The pin check then refuses a re-cut tag instead of only recording what it resolved to. CoMaps' tag is the one Flathub, nixpkgs and the AUR build, at this commit (D-024, D-069). |
submodules |
bool |
no (default False) |
Check out every submodule, recursively, at the superproject's gitlinks, shallow (git submodule update --init --recursive --depth 1, upstream CoMaps' own command), then refuse unless git submodule status --recursive shows each one at its gitlink. D-069. |
build_python |
list[str] |
no | Hash-pinned requirement lines for a Python the build needs, installed into a venv in the build directory with --require-hashes; prepare, configure and compile run with it first on the PATH. CoMaps' CMake refuses Debian's protobuf 4.x (D-069). Build-only: it is discarded with the build directory and never reaches the operator. |
prepare |
PrepareStep \| None |
no | An upstream script run in the tree before configure. D-069. |
extra_files |
list[ExtraFile] |
no | Files the install rule leaves out, installed after it. D-069. |
BinaryInstall¶
Vendor .deb, archive, or prebuilt executable.
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[binary] |
no (default binary) |
|
artifact |
RemoteArtifact |
yes | |
format |
Literal[deb, tarball, zip, executable, appimage] |
yes | |
placements |
list[Placement] |
no | Files of the unpacked archive installed at absolute paths, as a .deb's file list would, for an archive upstream publishes before it publishes a package (hammunition-tray 0.5.0). Root-owned, printed one by one in the plan, removed on uninstall on the log's attribution. |
placement_dirs |
list[str] |
no | Directories under PLACEMENT_ROOTS that hold nothing but this unit's placements; uninstall removes them whole, and only when the log attributes a placement inside them. |
devctl_helper |
DevctlHelper \| None |
no | The tray's privileged device helper, installed from this archive. |
deb_package |
str \| None |
no | The control-file Package name a deb artifact installs, read from the .deb itself (dpkg-deb -f file.deb Package), never assumed from the filename — wsjtx-improved's vendor deb installs as wsjtx, and GridTracker2's filename casing matches nothing. Required for format: deb; it is what uninstall hands to apt-get remove and what status probes. |
strip_components |
int |
no (default 0) |
|
install_tree |
bool |
no (default False) |
Install the whole built/extracted tree to |
tree_marker |
str \| None |
no | One file, relative to the installed tree, whose presence proves the tree is what the launcher expects: yaac's YAAC.jar, js8spotter's js8spotter.py. The effect check reads it back after the run; cp -aT exits 0 on any directory, so without it a tree unit ended verified: true with no check at all (issue #27). Required exactly when the block installs a tree. |
VenvInstall¶
A per-user Python virtualenv, hash-pinned end to end.
requirements lines are requirements-file syntax and every package
line must carry at least one --hash=sha256: — pip then runs with
--require-hashes, which extends the demand to the whole dependency
tree. That is CLAUDE.md's checksum rule applied to PyPI: apt packages are
distribution-signed, a bare pip install name is neither signed nor
pinned, and the difference is exactly what the rule exists for. Generate
the lines with uv pip compile --universal --generate-hashes.
Environment-marker lines (; python_version >= "3.10") and blank or
comment lines pass through untouched.
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[venv] |
no (default venv) |
|
requirements |
list[str] |
yes | |
python |
str |
no (default >=3.11) |
|
env |
dict[str, str] |
no | Build-time environment for pip. Exists for one measured case: a project using setuptools-scm, installed from a hashed release archive, has no .git to read its version from and needs SETUPTOOLS_SCM_PRETEND_VERSION_FOR_ |
payload |
RemoteArtifact \| None |
no | A verified archive whose extracted tree installs to |
payload_build_script |
str \| None |
no | A script inside the verified payload tree, run with sh before the tree installs -- radiosonde_auto_rx compiles its C demodulators via auto_rx/build.sh. Requires payload; declare its toolchain in the block's build_depends. |
tree_marker |
str \| None |
no | One file, relative to the installed tree, whose presence proves the tree is what the launcher expects: yaac's YAAC.jar, js8spotter's js8spotter.py. The effect check reads it back after the run; cp -aT exits 0 on any directory, so without it a tree unit ended verified: true with no check at all (issue #27). Required exactly when the block installs a tree. |
expose |
list[str] |
no | Console-script names from the venv's bin/ to wrap onto the operator's PATH (~/.local/bin). A venv nobody can invoke installs nothing while reporting success. |
licence |
str \| None |
no | The terms the installed software is under, when they are not a licence the operator would assume (SPDX where one exists, else the publisher's own words). Printed on the plan line that installs the venv, before the confirmation, and stated, never adjudicated (D-021, D-033). Requires licence_url. |
licence_url |
str \| None |
no | Where those terms are stated, on the publisher's site. Requires licence. |
NodeInstall¶
A Node.js application built from a verified source archive (D-037).
One measured user: openhamclock, a Node and Vite web application whose
release publishes no binary. The build is npm ci -> npm run
<build_script> -> npm prune --omit=dev, every step with lifecycle
scripts ignored, and the pruned tree installs per-user under
$XDG_DATA_HOME/hammunition/node/<name> with a wrapper on the
operator's PATH that runs node <entry> from it.
What it costs and how it is disclosed: Node comes only from the
distribution's nodejs/npm packages, never fetched, and the plan
refuses when they are absent or older than node_min_version. npm ci
fetches the dependency closure from registry.npmjs.org, each tarball
verified against the sha512 in package-lock.json — which lives inside
the sha256-verified archive, so the whole closure is transitively pinned
from one manifest hash. Both facts are printed in the plan.
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[node] |
no (default node) |
|
artifact |
RemoteArtifact |
yes | The sha256-pinned source archive. Must contain package-lock.json. |
node_min_version |
str |
yes | The lowest Node.js version, as MAJOR.MINOR, the application (its bundler included) runs on; the plan refuses below it. Measured by running it, never read from engines alone: openhamclock's dependencies satisfy Vite's ^18 floor and its server still dies on Node 18 at start, because require() of an ES module needs 20.19 -- a minor, which is why this is not a major. |
entry |
str |
yes | The script node runs from the tree root, e.g. server.js. |
build_script |
str \| None |
no (default build) |
The package.json script that produces the runtime tree (Vite's build). None for an application that runs from source unbuilt. |
build_output |
str \| None |
no (default dist) |
A directory build_script must produce, checked after the build (D-031: an npm run build exiting 0 is not evidence it built). None only when build_script is None. |
command |
str \| None |
no | Name of the wrapper put on the operator's PATH (~/.local/bin). Defaults to the manifest name. |
env |
dict[str, str] |
no | Runtime environment baked into the wrapper, e.g. PORT. HOST is refused: the engine always binds a node application to 127.0.0.1 (D-037), and a manifest cannot widen that. |
preserve |
list[str] |
no | Files inside the installed tree a reinstall keeps: an application that writes its configuration beside itself (openhamclock's .env) would otherwise lose it on every update. |
patches |
list[Patch] |
no | Unified diffs applied to the unpacked tree before npm runs, the source backend's mechanism. First user: openhamclock 26.7.0 reads HOST and then listens on 0.0.0.0 anyway, so the loopback bind the engine sets is one line of upstream away from meaning nothing. |
RemoteArtifact¶
A file fetched over the network. Verification is not optional.
| Field | Type | Required | Description |
|---|---|---|---|
url |
str |
yes | |
sha256 |
str |
yes | Mandatory. There is no unverified path. |
signature_url |
str \| None |
no | |
signing_key_fingerprint |
str \| None |
no |
Patch¶
An in-tree source edit. AHRL does these with sed; we declare them.
| Field | Type | Required | Description |
|---|---|---|---|
file |
str |
yes | |
description |
str |
yes | |
unified_diff |
str \| None |
no |
PinReview¶
When a commit pin was last looked at, and by whom. D-024.
A tag carries an upstream signal: someone decided that revision was worth naming. A commit SHA carries none — it is perfectly pinned and perfectly arbitrary. When a project stops tagging, pinning a commit is the right answer, but it moves a judgement upstream stopped making onto us, and an unreviewed commit pin from four years ago is the same failure as an abandoned tag pointed the other way.
So the judgement is recorded rather than implied. This is metadata about our decision, not about the software, which is why it lives beside the ref rather than in documentation.
| Field | Type | Required | Description |
|---|---|---|---|
last_reviewed |
date |
yes | |
reviewed_by |
str |
yes | Who looked. A name or handle, so the next reviewer knows who to ask. |
basis |
Literal[distribution_pin, own_choice] |
yes | Where the choice of commit came from. distribution_pin is strongly preferred and must name the distributions; own_choice requires saying what was checked and found nothing. |
distributions |
list[str] |
no | Distributions packaging this exact commit. Required for distribution_pin. |
rationale |
str |
yes | Why THIS commit rather than any other, and what was checked. 'HEAD at the time' is not a rationale; it is the absence of one. |
cadence_days |
int |
no (default 180) |
How long this pin may stand before it must be looked at again. |
Binary¶
Explicit build-output -> installed-name mapping.
This is what dissolves the wsjtx / wsjtx_improved rename dance: both builds
emit wsjtx, so AHRL renames around them. Declaring install_as makes the
collision impossible instead of choreographed.
| Field | Type | Required | Description |
|---|---|---|---|
produced |
str |
yes | Path the build emits, relative to the directory it builds in: cmake's out-of-tree build directory, the source tree for the rest. |
install_as |
str |
yes | Final name in the install prefix. |
Launcher¶
A generated wrapper script. 14 AHRL units need one.
| Field | Type | Required | Description |
|---|---|---|---|
name |
str |
yes | The wrapper's filename in ~/.local/bin, so the launcher is also what a shell finds by that name -- ahead of /usr/bin. It therefore never takes the name of the command it runs, of a binary the manifest installs, or of anything on the system PATH or in the unit's apt file list: name it for what it does (rigctl-dummy, hackrf_info-check; issue #174). |
exec |
str |
yes | Command template. May reference {endpoint:NAME}. A line starting with hammunition runs the engine, written into the wrapper as the absolute path of the hammunition that generated it (issue #145). |
title |
str \| None |
no | What the desktop menu shows for this launcher. Defaults to name, which a file name rarely says well (rigctl-dummy). The convention is what it does, then the command in parentheses (D-054). |
working_directory |
str \| None |
no | |
terminal |
bool |
no (default False) |
ServiceEndpoint¶
A remote service the software talks to.
Exists because AHRL hardcodes -b hamclock.com:80 into four generated
launchers, and that host was reported to stop serving in June 2026. A dead
upstream must be repointable by editing the catalog, not the launchers.
| Field | Type | Required | Description |
|---|---|---|---|
name |
str |
yes | |
default_url |
str |
yes | |
description |
str |
yes | |
user_configurable |
bool |
no (default True) |
|
note |
str \| None |
no |
SystemModification¶
| Field | Type | Required | Description |
|---|---|---|---|
kind |
Literal[udev_rule, modprobe_blacklist, group_create, group_membership, foreign_arch, package_purge, apt_pin, file_shadow, file_capability] |
yes | |
description |
str |
yes | |
detail |
str |
yes | |
reversible |
bool |
yes | |
reverse_hint |
str \| None |
no | |
group |
str \| None |
no | For group_membership: the group to add the operator to. Required there, and forbidden elsewhere. |
binary |
str \| None |
no | For file_capability: an installed binary's install_as name. |
capabilities |
list[Literal[CAP_NET_ADMIN, CAP_NET_RAW, CAP_NET_BIND_SERVICE]] |
no | For file_capability: the Linux capabilities set with permitted and effective flags, applied only after typed consent; --yes cannot satisfy it. |
ConfigFile¶
Templated configuration written on the operator's behalf.
AX.25 forces this into 1.0: its install appends
wl2k ${MYCALL} 1200 255 7 Winlink to /etc/ax25/axports.
| Field | Type | Required | Description |
|---|---|---|---|
path |
str |
yes | |
template |
str |
yes | May reference {station.callsign} etc. |
mode |
str |
no (default 0644) |
|
append |
bool |
no (default False) |
|
backup_existing |
bool |
no (default True) |
|
skip_if_present |
list[str] |
no | Append only: regular expressions, matched per line against the file as it is when the step runs. If any line matches any of them the append is skipped and the outcome says which -- the idempotence and the no-duplicate rule of a file like axports, where a second port with the same name or callsign is an error. May reference {station.*}; a value is matched literally (escaped), never as a pattern. |
AptRepo¶
Third-party apt source. Key pinning is mandatory. D-040.
name becomes two file names under /etc/apt and is restricted to
what one can safely be. key_fingerprint is the primary key's,
forty hex digits for a v4 key or sixty-four for a v6 one, spaces
permitted; the engine computes the same thing from the file it fetches
and refuses anything else. key_url is https: the fingerprint check
is what makes the key trustworthy, but the transport still decides who
can see the request.
when narrows the repository to some targets, with the same selector
an install block uses; unset, it applies everywhere. It exists because a
publisher may serve one tree per release under a different URI -- Kismet
serves .../release/trixie and .../release/noble, each its own
Release file -- and a manifest that declared both unconditionally
would add a noble repository to a Debian 13 machine. A target no
repository applies to gets no repository, and the unit falls to the
ordinary "the archive does not offer it" path (D-039), deferred by name.
| Field | Type | Required | Description |
|---|---|---|---|
name |
str |
yes | |
uri |
str |
yes | |
suites |
list[str] |
yes | |
components |
list[str] |
yes | |
key_url |
str |
yes | |
key_fingerprint |
str |
yes | |
rationale |
str |
yes | Shown to the user before the repo is added. |
when |
Selector |
no | The targets this repository applies to; unset means every target. |
ToolkitRisk¶
Standing exposure register. D-015.
The component list is derivable from build_depends; upstream port status and the date it was checked are not, which is the whole reason this exists.
| Field | Type | Required | Description |
|---|---|---|---|
framework |
Literal[qt5, qt6, gtk2, gtk3, gtk4, wx3.0, wx3.2, mono] |
yes | |
upstream_port_status |
Literal[ported, in_progress, no_path, unknown] |
yes | |
checked |
date |
yes | |
note |
str \| None |
no |
UpdateBlock¶
| Field | Type | Required | Description |
|---|---|---|---|
probe |
UpdateProbe |
yes | |
strategy |
Literal[reinstall, apt_upgrade, rebuild, manual] |
no (default reinstall) |
|
cadence_hint |
str \| None |
no |
UpdateProbe¶
How to learn the upstream version. D-010.
label_file is the one probe an upstream told us about directly: YAAC
publishes no tags and no releases, but its own Help > Check for Updates
fetches a one-line text file and compares it to the compiled-in build
label (issue #31, from the author). The catalog records that file as data
so an engine can make the same comparison; one measured user, like pypi.
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[apt_policy, github_release, github_tags, binary_version, label_file, pypi, kiwix, comaps_maps, none] |
yes | |
repo |
str \| None |
no | |
command |
str \| None |
no | |
pattern |
str \| None |
no | |
url |
str \| None |
no | For label_file: the plain-text file whose content is upstream's current version label, compared verbatim against version. |
package |
str \| None |
no | For pypi: the PyPI project name when it is not the unit's name. update --upstream asks pypi.org for it. |
Documentation¶
Required by CLAUDE.md. A manifest without these cannot ship.
| Field | Type | Required | Description |
|---|---|---|---|
what_it_does |
str |
yes | |
why_you_want_it |
str |
yes | |
prerequisites |
str \| None |
no | |
known_problems |
str \| None |
no | |
upstream_url |
str |
yes | |
upstream_support |
str \| None |
no |
ProfileManifest¶
A named bundle of packages. Flat tags with overlap, D-003.
| Field | Type | Required | Description |
|---|---|---|---|
name |
str |
yes | |
summary |
str |
yes | |
packages |
list[str] |
yes | |
stage |
Literal[1.0, post-1.0] |
no (default 1.0) |
|
consent |
ConsentGate \| None |
no | |
documentation |
ProfileDocumentation |
yes | |
suggests_one_of |
list[SuggestionGroup] |
no |
ProfileDocumentation¶
Required by CLAUDE.md for every profile.
| Field | Type | Required | Description |
|---|---|---|---|
what_it_installs |
str |
yes | |
why_together |
str |
yes | |
deliberately_excludes |
str |
yes | |
manual_configuration |
str |
yes | |
disk_footprint_hint |
str \| None |
no | |
who_for |
str \| None |
no | Who installs this, in a sentence or two. |
hardware_assumed |
str \| None |
no | What hardware the profile assumes, or says it needs none. |
footprint_short |
str \| None |
no | Disk footprint in a few words, for the index table. |
excludes_short |
str \| None |
no | What it leaves out, in a phrase, for the index table. |
goals |
list[str] |
no | Goals in an operator's words ('Make FT8 contacts'). The profiles index inverts these into its 'which profile do I want' table, so the same wording on two profiles puts both on one row. |
first_ten_minutes |
list[str] |
no | Ordered steps for the ten minutes after install, Markdown. |
SuggestionGroup¶
One-of-several optional companions, offered only when nothing serves.
Born from the claws-mail decision (Q-015 #1, resolved 2026-08-30): the
EMCOMM stack wants a local mail client, but choosing one for the
operator is desktop-distribution work — so the engine detects first
(any of detect_commands on PATH means the need is already met and
the system's own choice is respected), and only when nothing is found
does an interactive run offer options, every one an open-source
catalog manifest. --yes and non-interactive runs skip with a note,
never block — the station-prompt precedent (D-035).
| Field | Type | Required | Description |
|---|---|---|---|
name |
str |
yes | |
reason |
str |
yes | Why the profile wants one, shown at the prompt. |
detect_commands |
list[str] |
yes | Binaries whose presence means the need is already met. |
options |
list[str] |
yes | Catalog manifests to offer, in display order. |
recommended |
str \| None |
no | One of options, flagged at the prompt as the suggested pick. |
ConsentGate¶
An affirmative opt-in that --yes cannot supply. D-021.
The gate discloses a capability and asks the operator to affirm they have the authorization they need. It does not decide for them in either direction — neither granting permission nor refusing on their behalf.
| Field | Type | Required | Description |
|---|---|---|---|
risk_categories |
list[RiskCategory] |
yes | |
env_var |
str |
yes | Scripted path. Separate from --yes, and recorded when used. |
disclosure |
str |
yes | What the software can do. Capability, never legality. |
affirmation |
str |
yes | The question. Must ask about the operator's authorization. |
ConverterTool¶
A program a converter runs that no archive packages, pinned. D-067.
The catalog supplies where it is and what it hashes to; the engine owns
how it is run, exactly as it owns every converter's command line. It is
fetched, verified against artifact.sha256, checked against size, and
installed under <prefix>/share/hammunition/<unit>/ -- not under the
unit's data directory, which holds only files that are read, never run
(D-049). A signature_url is recorded and not verified; the fetch step
says so, in the words every declared-but-unverified signature gets.
| Field | Type | Required | Description |
|---|---|---|---|
artifact |
RemoteArtifact |
yes | |
size |
int |
yes | Bytes, measured; a download of another size is refused. |
licence |
str |
yes | SPDX identifier where one exists, else the publisher's own words. |
licence_url |
str |
yes | Where the licence is stated, on the publisher's site. |
DataArtifact¶
One file of an offline dataset: a map tileset, a Wikipedia ZIM, cty.dat.
size is declared so the plan can print it before the confirmation
(D-049): a 1.33 GB Geofabrik extract on a field connection is a decision,
and the number belongs in front of the operator, not in the download's
progress bar. The fetch verifies the declared size against the bytes it
received, so a wrong declaration is a refused manifest rather than a
surprise.
| Field | Type | Required | Description |
|---|---|---|---|
url |
str |
yes | |
sha256 |
str |
yes | Mandatory. There is no unverified path. |
signature_url |
str \| None |
no | |
signing_key_fingerprint |
str \| None |
no | |
size |
int |
yes | Bytes, as published. Printed in the plan, verified on fetch. |
format |
Literal[file, zip, tarball] |
no (default file) |
|
install_as |
str \| None |
no | For format: file, the name the file is installed under inside the unit's data directory. Archives extract their members and take none. |
members |
list[str] \| None |
no | For an archive: extract only these paths, as the archive names them (its top directory included); one ending in / takes everything below it. A member that matches nothing refuses the install. Without it the whole archive is extracted (D-071). |
into |
str \| None |
no | For an archive: the subdirectory of the unit's data directory it is extracted into, one plain name. Required on every archive of a unit with more than one archive, or with files beside an archive: an archive replaces the directory it is extracted into (D-071). |
DataInstall¶
Offline data whose payload is the point (D-049).
Not software: the engine never executes what it installs here. The files
land under <prefix>/share/hammunition/data/<name>/ and the reader --
kiwix, mbtileserver, a logger reading cty.dat -- names the data
unit in its own depends. Every artifact is pinned and hashed like any
other fetch; nothing is mirrored.
licence and licence_url are disclosed in the plan beside the
size, because a dataset under ODbL or CC BY-SA carries obligations the
engine states and does not adjudicate (D-021).
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[data] |
no (default data) |
|
artifacts |
list[DataArtifact] |
yes | |
licence |
str |
yes | SPDX identifier where one exists, else the publisher's own words. |
licence_url |
str |
yes | Where the licence is stated, on the publisher's site. |
DemTilesInstall¶
Elevation tiles for the squares the station's map regions cover (D-061).
Like RegionalDataInstall, nothing is pinned in the manifest: which
tiles are needed follows the operator's regions in station config, and
each tile is resolved at plan time and verified by a sha256 the catalog
carries (catalog/data/copernicus-glo30-pins.yaml) or by the MD5 in
the publisher's object metadata, the plan saying which, tile by tile.
provider is an enum so another source is a new member the engine
implements, never a URL in the catalog: usgs-3dep (D-068, amended
2026-10-01) is USGS 3DEP 1/3-arc-second bare earth, chosen by the
station's dem_source and checked by the S3 ETag its publisher lists.
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[dem-tiles] |
no (default dem-tiles) |
|
provider |
Literal[copernicus-glo30, usgs-3dep] |
no (default copernicus-glo30) |
|
licence |
str |
yes | SPDX identifier where one exists, else the publisher's own words. |
licence_url |
str |
yes | Where the licence is stated, on the publisher's site. |
DerivedDataInstall¶
Data produced by running a converter over another catalog unit's data.
converter names the transformation by enum, never a command line --
the catalog stays pure data (CLAUDE.md's founding invariant) and the
engine owns what each enum member means. source names the catalog
package whose data this is derived from; the manifest's own validator
requires it to also appear in depends, so the plan always installs the
source data before running the converter over it. Which install method
source must actually be is CONVERTER_SOURCE_METHOD, checked catalog-
wide because only the catalog knows what source resolves to (D-061).
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[derived] |
no (default derived) |
|
converter |
Literal[navit-maptool, mkgmap, routino-planetsplitter, gdal-dem, brouter-mapcreator, mapsforge-map, mapsforge-poi, ustopo-mosaic, tilemaker-pmtiles, graphhopper-import, splat-sdf] |
yes | The transformation to run. Each needs a source of one particular install method (CONVERTER_SOURCE_METHOD, checked catalog-wide, D-061): navit-maptool, mkgmap, routino-planetsplitter, brouter-mapcreator, mapsforge-map, mapsforge-poi, tilemaker-pmtiles and graphhopper-import need an osm-regions source; gdal-dem and splat-sdf (D-061, amended 2026-10-02) need a dem-tiles source; ustopo-mosaic needs a topo-quads source (D-068). |
source |
str |
yes | The catalog package name this is derived from: an osm-regions unit, for gdal-dem and splat-sdf a dem-tiles unit, for ustopo-mosaic a topo-quads unit. |
boundaries |
str \| None |
no | The catalog data unit holding country boundaries (one GeoJSON file) that navit-maptool merges into each region before conversion, so maptool files towns under a country and address search finds them (D-057 amendment, 2026-09-28). Must also be in depends. |
program |
str \| None |
no | brouter-mapcreator and graphhopper-import only, and required on both: the binary unit whose installed tree holds the jar the converter runs, BRouter's with its map creator (D-063) or GraphHopper's (D-076). Must also be in depends. |
profiles |
str \| None |
no | brouter-mapcreator only, and required there (D-063): the data unit holding all.brf and softaccess.brf, the map creator's filters, which BRouter's release zip does not carry. Must also be in depends. |
elevation |
str \| None |
no | brouter-mapcreator only, optional (D-063): the dem-tiles unit whose installed tiles are folded into the routing files as elevation. Without it the routes are flat. Must also be in depends. |
alternative |
str \| None |
no | gdal-dem and splat-sdf only, optional (D-068, amended 2026-10-01; D-061, amended 2026-10-02): the dem-tiles unit of provider usgs-3dep drawn from instead of source when the station's dem_source is 3dep. Must also be in depends. |
fstopo |
str \| None |
no | ustopo-mosaic only, optional (D-068, amended 2026-10-01): the topo-quads unit of provider usfs-fstopo whose sheets, when installed, are made a second QMapShack map, FSTopo.vrt, beside ustopo.vrt. Read, never depended on: the Forest Service publishes no checksum, so its sheets are installed only by name (CLAUDE.md's checksum rule), and US Topo's map works without them. |
kit |
str \| None |
no | tilemaker-pmtiles only, and required there (D-071): the data unit holding tilemaker's OpenMapTiles profile (config and Lua) and the Natural Earth shapefiles the profile names. Must also be in depends. |
licence |
str |
yes | SPDX identifier where one exists, else the publisher's own words. |
licence_url |
str |
yes | Where the licence is stated, on the publisher's site. |
tool |
ConverterTool \| None |
no | The pinned program the converter runs, for a converter in CONVERTERS_WITH_TOOL (mapsforge-poi: Maven Central's mapsforge-poi-writer, which no archive packages, D-067). Required for those converters and refused for every other. |
DevctlHelper¶
The privileged device helper that ships inside hammunition-tray's archive.
A block that names an interpreter nobody wrote down. The helper is root
code behind one polkit action (D-056); the engine copies it from the unpacked
archive to the paths the tray's contract fixes, bakes the engine's own venv
interpreter into the wrapper (the helper's time verbs import the
engine), and writes the tray's polkit action. None of those paths or that
interpreter is catalog data, so the block carries only what varies by pin:
where the code sits in the archive and which modules it has.
| Field | Type | Required | Description |
|---|---|---|---|
source |
str |
no (default devctl) |
The directory in the archive holding the hammunition-devctl entry script and the hammunition_devctl/ package. |
modules |
list[str] |
yes | Every module of the package, by file name. Explicit, so the plan lists exactly the files root will run, and a test compares it with the pinned archive. |
min_contract |
int |
no (default 1) |
The contract number (hammunition-devctl --version) an already-installed helper must answer for the engine to leave it alone. |
ExtraArtifact¶
A pinned file installed beside a build, with its size (D-069).
| Field | Type | Required | Description |
|---|---|---|---|
url |
str |
yes | |
sha256 |
str |
yes | Mandatory. There is no unverified path. |
signature_url |
str \| None |
no | |
signing_key_fingerprint |
str \| None |
no | |
size |
int |
yes | Bytes, as published. Printed in the plan, checked on fetch. |
ExtraFile¶
A file a build's install rule leaves out, installed after it (D-069).
Either a pinned artifact or a file from the built tree, installed at
<prefix>/<install_as> with mode 0644, after an rm -f so a symlink
at the destination is replaced and never written through. CoMaps'
install rule skips World.mwm and WorldCoasts.mwm when the tree has
none (they are downloaded, not built), and leaves out
categories_brands.txt; Flathub's manifest installs all three by hand.
| Field | Type | Required | Description |
|---|---|---|---|
artifact |
ExtraArtifact \| None |
no | |
from_tree |
str \| None |
no | A file in the checked-out tree, relative to it. |
install_as |
str |
yes | Relative to the prefix, under share/. |
KiwixBooksInstall¶
The Kiwix books the operator chose in station config (D-066).
Like DemTilesInstall, nothing is pinned in the manifest: which books
follows reference_books in station config, and each book resolves at
plan time to a pin in catalog/data/kiwix-pins.yaml, generated from
Kiwix's own .meta4 files. There is no licence here because the
books do not share one: each book's licence line is in the hand-written
catalog/data/kiwix-books.yaml, and the plan prints it beside the
book's size before the confirmation (D-049 rule 2). provider is an
enum, as dem-tiles' is, so another library is a new member the engine
implements, never a URL in the catalog.
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[kiwix-books] |
no (default kiwix-books) |
|
provider |
Literal[kiwix] |
no (default kiwix) |
MwmRegionsInstall¶
CoMaps' own map files for the station's map regions (D-069).
Like DemTilesInstall, nothing is pinned in the manifest: which maps
follows the operator's regions in station config, through the region
table in catalog/data/comaps-pins.yaml, generated from CoMaps' map
index at the commit the comaps unit pins. Each map is checked against
that index's SHA-1 and exact size, the publisher's own check, and the plan
says so. provider is an enum, so Organic Maps' CDN would be a new member
the engine implements, never a URL in the catalog.
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[mwm-regions] |
no (default mwm-regions) |
|
provider |
Literal[comaps] |
no (default comaps) |
|
licence |
str |
yes | SPDX identifier where one exists, else the publisher's own words. |
licence_url |
str |
yes | Where the licence is stated, on the publisher's site. |
PipxInstall¶
| Field | Type | Required | Description |
|---|---|---|---|
method |
Literal[pipx] |
no (default pipx) |
|
spec |
str |
yes | |
system_site_packages |
bool |
no (default False) |
Placement¶
One file of an unpacked archive, installed at an absolute path.
The same shape as a .deb's file list: a source inside the tree, a
destination, a mode. Never a command, never a glob -- every file is named,
so the plan prints each one and a file upstream adds later is not installed
by surprise (a test compares the list with the pinned archive).
| Field | Type | Required | Description |
|---|---|---|---|
source |
str |
yes | A file inside the unpacked tree, relative to its root. |
dest |
str |
yes | Where it is installed: an absolute path under one of PLACEMENT_ROOTS, with a path component named for this project. Under /usr/local/ it follows the engine's prefix. |
mode |
str |
no (default 0644) |
PrepareStep¶
An upstream script run in the checked-out tree before configure (D-069).
CoMaps' configure.sh generates the symbols, drawing rules and strings
the CMake build reads, and builds a helper tool to do it. The script is
upstream's, named by path, never a command line the catalog writes; the
engine owns how it runs. produces is what makes it checkable:
CoMaps' generate_symbols.sh exits 0 with no symbols when optipng is
missing, so a script's exit status is not evidence of anything (D-031).
| Field | Type | Required | Description |
|---|---|---|---|
script |
str |
yes | Path of the script, relative to the tree; run as ./
|