Package Format¶
Status: rewritten 2026-07-05 against the App contract and ADR-0034/0037/0038 (T98). This page specifies the wire format: what the file is. What makes it an app (manifest fields, run intent, storage, conformance) is the app contract; how it is catalogued and distributed is the Index contract.
One file¶
A package is a single file:
It is a DDI (UAPI.3):
a GPT image containing the root filesystem, its dm-verity hash tree, and the
PKCS7 signature over the roothash — self-describing, self-verifying, signed.
There is no metadata file beside it and no transport wrapper (ADR-0038):
the package metadata is the manifest embedded inside the image, and the
payload is already compressed (zstd squashfs / erofs), so no .zip exists.
systemd discovers and verifies the image natively; no custom verification
code exists anywhere in the tools.
DDI layout¶
A GPT (with protective MBR — without it systemd-dissect cannot
identify the image) and exactly three partitions, per the
Discoverable Partitions Specification (UAPI.2 / DPS):
| # | Partition | Contents | Discovered as |
|---|---|---|---|
| 1 | Root | squashfs (or erofs) filesystem | root-<arch> |
| 2 | Verity | dm-verity superblock + hash tree over partition 1 | root-<arch>-verity |
| 3 | Signature | JSON binding the roothash to a PKCS7 signature | root-<arch>-verity-sig |
Discovery is by partition type UUID only; names/labels are informational.
Partition type UUIDs¶
Type UUIDs are arch-specific, defined by DPS. Live-verified values:
| Arch | Root | Verity | Verity-sig | Verified |
|---|---|---|---|---|
arm64 |
B921B045-1DF0-41C3-AF44-4C6F280D3FAE |
DF3300CE-D69F-4C92-978C-9BFB0F38D820 |
6DB69DE6-29F4-4758-A7A5-962190F00CE3 |
✓ live test, systemd 259 / Fedora 44 |
Other arches: take the values from DPS /
systemd-repartoutput on the target — do not copy them from secondary sources. A previous revision of this page listed an amd64 verity UUID that was actually DPS's home partition type; online sources get these wrong. Verify before use.
Partition GUIDs encode the roothash¶
Per DPS, the partition GUIDs (unique IDs, distinct from the type UUIDs) of the root and verity partitions are derived from the roothash:
- Root partition GUID = first 128 bits of the roothash
- Verity partition GUID = last 128 bits of the roothash
systemd enforces this: mismatched GUIDs make attach fail. The assembler therefore computes the roothash before writing the GPT. Live-verified example (systemd 259):
Roothash: ef87a379dc7560ab95325f7ef84d0d45a0e8d81ba68e64dfaa1ff69dba8c82cb
Root GUID: ef87a379-dc75-60ab-9532-5f7ef84d0d45 (first 128 bits)
Verity GUID: a0e8d81b-a68e-64df-aa1f-f69dba8c82cb (last 128 bits)
Signature partition¶
A single JSON object binding the roothash to the build signature:
The PKCS7 blob carries the signer's certificate, which enables
show-and-accept at import (ADR-0037); trust still comes only from the
consumer's accepted-cert store (/etc/verity.d/, VOA — see
Security model). systemd verifies the signature
natively (validate_signature_userspace()), then activates dm-verity;
the kernel enforces block integrity at runtime.
PKCS7 digest MUST be SHA-256. OpenSSL 3.x rejects SHA-1 digests ("invalid digest") under default security policies. Any signer used by the build pipeline must produce SHA-256 PKCS7.
Additional JSON fields in this partition are ignored by systemd; producers should emit the two standard fields.
Two package shapes¶
| Fat app | Layered app | |
|---|---|---|
| Carries | complete userland | only the app's own files |
| Identity file | /etc/os-release (ID=<name>, VERSION_ID=<version>) |
/usr/lib/extension-release.d/extension-release.<name> — no os-release |
| Runs | alone | on exactly one base (2 layers max, ADR-0034 §20) |
| Extra rules | — | UAPI.4 sysext: ID= equals the base's (never _any), SYSEXT_LEVEL= (the base's ABI promise), ARCHITECTURE=, SYSEXT_SCOPE=portable; purely additive over the base (file overlap = build error) |
Bases themselves are specified separately (T96); a base is a UAPI.4-matchable base DDI, independently signed — it vouches for itself.
DDI metadata (the embedded manifest)¶
The manifest (package.yaml) is embedded at
/usr/share/<name>/package.yaml (ADR-0017) and is the package
metadata — "DDI metadata," an essential part of the format. Field semantics
live in the App contract; machine-validatable shapes in
the JSON schemas (T95).
Reading it follows the ADR-0038 order — verification first, always:
- Probe the GPT (type UUIDs; no content read).
- Verify the PKCS7 signature against the accepted-cert store.
- Verity-verify the root partition.
- Only then extract the manifest (
systemd-dissect --copy-from, image policy enforced).
Filenames are untrusted; identity comes from the verified manifest. Indexes
project the searchable fields out of the manifest once, at index add —
nothing ever scans DDIs at search or resolution time.
Root partition content constraints¶
Summarized from the App contract (conformance §):
- All files owned
root:root(ADR-0014). - Identity file per the package shape (exactly one of the two).
/usr/lib/systemd/system/<name>.service(and<name>.socketiffsocket_activation); every declared lifecycle-hook unit exists.- No
User=/Group=in any unit (ADR-0015) and no host-path mounts — the runtime's generated drop-in owns placement (ADR-0036). - Empty mount-point dirs
/etc/<name>/and/var/lib/<name>/exist (bind targets; a mount point cannot be created on a read-only squashfs). - Optional pristine config defaults at
/usr/share/factory/etc/<name>/(seeded into the config volume on first provision). - The embedded manifest at
/usr/share/<name>/package.yaml. - Layered shape: additive over the base — no file overlap.
Naming¶
| Field | Rules | Example |
|---|---|---|
name |
lowercase, alphanumeric + hyphens, starts alphanumeric | mosquitto |
version |
UAPI.10 (~ pre-release, ^ post-release) — not semver. The version of the DDI, not of the software inside |
2.0.18 |
arch |
open, pattern-validated string; officially supported set documented separately | arm64 |
Joined with underscores (UAPI.3): mosquitto_2.0.18_arm64.raw.
This naming is systemd-vpick-native: a mosquitto.raw.v/ directory of
such files is resolved to the newest version by systemd itself
(UAPI.10 ordering, arch filtering) — relevant to the upgrade design (T103).
Host paths (consumer side)¶
| Purpose | Path |
|---|---|
| App storage (config/data) | <root>/apps/<index-hash>/<name>/ — relocatable <root>, default /var/lib/appctl (ADR-0035) |
| Runtime state | file-per-record under <root> (ADR-0026); source of truth for recover |
| Trust store (up-gate) | /etc/verity.d/ (systemd-consumed today) |
| Tool trust material | VOA hierarchy (UAPI.11), per ADR-0034 |
On Offline Lab OS the persistence of these paths is the OS's duty
(OS-CONFORMANCE.md); the format knows nothing about overlay wipes.
Standards references¶
| Spec | Relevance |
|---|---|
| UAPI.2 (DPS) | partition type UUIDs; roothash-derived partition GUIDs |
| UAPI.3 (DDI) | the image format; underscore naming |
| UAPI.4 (Extension Images) | layered-app shape (sysext, extension-release matching) |
| UAPI.10 (Version Format) | version syntax + ordering |
| UAPI.11 (VOA) | trust-material layout (tool-side now; up-gate when systemd consumes VOA — T99) |
| systemd | systemd-dissect, validate_signature_userspace(), systemd-vpick |