On this page
Claims
A package's provides (§4.1.4) lets several installed
packages satisfy the same virtual name. A claim extends
that idea to the filesystem: it lets several installed
packages contend for a single shared filesystem name, with
exactly one of them owning it at a time. The canonical
example is a role daemon — loregd and an alternative
registry source may both be installed, but only one may own
/usr/bin/registryd.
This section defines claims, the manifest fields that declare them, and how the package manager materialises a claim on the filesystem. The operations that grant, revoke, and withdraw a claim are defined in §7.7.
§4.4.1 Concepts
- Role: a virtual name (§4.1.4) that one or more packages
contend to own. A role is identified by the
nameof aprovidesor dependency entry that carries aclaimsfield.registrydis a role. - Slot: a named channel within a role. A role has one or
more slots; each slot materialises one filesystem name.
binaryis a slot of theregistrydrole. - Claim path: the absolute filesystem path a slot
materialises at (
/usr/bin/registryd). A slot MAY have more than one claim path. - Target: the file a claim path points at while a given
provider holds the slot (
/usr/bin/loregd). The target is a payload file of the holding package. - Holder: the single installed package that currently owns a role. The holder's targets fill every one of the role's claim paths. A role has at most one holder at a time.
/usr/bin/registryd and expects to find a registry daemon
there — declares the claim path. The provider — loregd
— declares the target, the file of its own that should
answer that path. The package manager joins the two by
(role, slot) and creates the symlink. Splitting the
declaration this way puts the path where the dependency on
it lives and the target where the implementation lives.§4.4.2 Declaration
A claim is declared by adding a claims field to a
dependency entry (§4.1.1), an optional-dependency entry, or a
provides entry (§4.1.4).
The claims field is an object mapping a slot name to a slot
descriptor:
"claims": {
"<slot name>": { "path": "<absolute path>",
"target": "<absolute path>" }
}
A slot name MUST conform to §2.1. A slot descriptor has two OPTIONAL fields:
| Field | Type | Description |
|---|---|---|
path |
string | An absolute claim path the slot materialises at — the location of the symlink. |
target |
string | An absolute path to a payload file of the declaring package — what the symlink points at while this package holds the role. |
Which fields are permitted depends on where the claims
field appears:
- On a dependency or optional-dependency entry (the
consumer side), each slot descriptor MUST contain
pathand MUST NOT containtarget. A consumer declares only where it expects the name; it supplies no implementation. - On a
providesentry (the provider side), each slot descriptor MUST containtargetand MAY containpath. A provider declares the file that answers the slot, and MAY additionally declare a default claim path (§4.4.4).
A target MUST name a path that the declaring package itself
installs as a payload entry (§3.5.1); it MUST therefore lie
within the permitted-install-path set of §3.4.1. A target
that does not correspond to one of the package's own payload
paths is INVALID and MUST cause the package to be rejected.
A path is the location of a manager-managed claim link, not
a payload entry, and is governed by its own location rule: it
MUST satisfy the payload path-syntax and safety constraints
of §3.2.7, and MUST lie within the §3.4.1 permitted-install-
path set or under /run/. The /run/ allowance accommodates
runtime handles — sockets and the like — that conventionally
live there.
/run/
for it — without permitting packages to ship payload there
— lets a role expose a runtime socket (/run/logsink.sock)
while keeping /run off-limits to package payloads.Example — peinit consumes the role, loregd provides it:
// peinit's manifest (consumer)
"dependencies": [
{ "name": "registryd",
"claims": { "binary": { "path": "/usr/bin/registryd" } } }
]
// loregd's manifest (provider)
"provides": [
{ "name": "registryd",
"claims": { "binary": { "target": "/usr/bin/loregd" } } }
]
The slot keys within a claims object are an unordered JSON
object and carry no ordering requirement. The enclosing
dependencies, optional_dependencies, and provides
arrays remain sorted and unique by name per §4.1.7.
§4.4.3 Eligibility
A package is an eligible provider of a role when it has a
provides entry whose name is the role and whose claims
field declares a target for at least one of the role's
slots. Only an eligible provider may become the holder of a
role (§7.7).
A package that depends on a role and declares a claim path
for it, but does not provide the role, is a consumer only:
it contributes claim paths but can never hold the role.
§4.4.4 Materialisation
When a package holds a role, the package manager materialises the role: for every slot of the role it creates a symbolic link at each of the slot's claim paths, pointing at the holder's target for that slot.
The set of claim paths for a slot is the union of:
- every
pathdeclared for that slot by an installed consumer (a dependency or optional-dependency entry, §4.4.2), and - the
pathdeclared for that slot by the holder's ownprovidesentry, if present.
path is a default: it lets a role
materialise at a conventional location even when no
installed package declares a dependency-side claim for it —
a daemon a human invokes by name at a shell, say, with
nothing in the package graph hard-coding the path. When at
least one consumer declares a path, the consumer's and the
provider's paths are both materialised (their union).The materialised links for a role are a function of current system state: the set of links MUST at all times equal the cross-product of the role's computed claim paths (above) with the holder's targets. The package manager MUST re-evaluate this set — creating newly-required links, removing orphaned links, and repointing changed links — within any transaction that changes its inputs: a change of holder (§7.7.5, §7.7.6), or the installation or removal of a package that declares a claim path or a target for the role (§7.7.1, §7.7.6). This re-evaluation is reconciliation; it is idempotent, so reconciling against unchanged state produces no filesystem change.
A held role whose computed claim-path set is empty materialises no links yet remains held: the holder is recorded in the package database (§7.7.4) independently of whether any link exists. If a package that declares a claim path for the role is installed later, reconciliation materialises the link retroactively, against the holder already on record — the holder is not re-decided.
loregd holds registryd, but
until some package declares a dependency-side path (or
loregd itself declares a provider default), there is no
/usr/bin/registryd to point anywhere. Installing peinit
— which declares the path — does not re-open the question
of who holds the role; it only contributes a path, and
reconciliation creates the link against the recorded
holder. This is why holder state is tracked in the database
independently of link existence.A claim link is owned and managed by the package manager,
not by any package. A claim link MUST NOT appear in any
package's payload (§3.5.1) and MUST NOT be recorded as a
package-owned path (§7.1.5). This is what lets two eligible
providers coexist: neither ships /usr/bin/registryd, so the
one-package-per-path rule (§3.4.10) is never engaged by the
providers themselves.
A claim path MUST NOT collide with a path owned by any installed package. Before materialising a claim link, the package manager MUST verify that no installed package owns the claim path (§3.4.10); if one does, materialisation MUST fail with an explicit error and the containing transaction MUST be rolled back (§7.5).
A claim link MUST be created using the same TOCTOU-safe path
resolution mandated for payload extraction (§7.1.2.3 step 1).
The holder's target MUST resolve to a path the holder owns
at the time of materialisation.
§4.4.5 One holder per role
A role has at most one holder at any time. The holder's targets fill all of the role's claim paths; a role is never partly held by one provider and partly by another.
Changing a role's holder — promoting a different eligible
provider — is a single operation: the package manager
repoints every one of the role's claim links from the old
holder's targets to the new holder's targets. Each repoint
MUST be atomic (create the new link at a temporary path, then
renameat2 over the old link, §7.1.2.3) so that no consumer
of a claim path ever observes the path absent. The repoint of
all of a role's links MUST occur within a single transaction
(§7.4).
/usr/bin/registryd
may be exec'd by a service supervisor at any moment.
Tearing the old link down and building the new one as two
steps would expose a window with no registryd; the
rename-over makes the swap instantaneous.A role MAY be unheld: zero installed providers hold it. An unheld role materialises no claim links, and its claim paths simply do not exist. A dependency on an unheld role is still satisfied at resolution time by any eligible provider that is installed (§4.4.6) — primacy governs the filesystem name, not dependency satisfaction.
§4.4.6 Relationship to resolution
Claims are orthogonal to dependency resolution (§4.2). Adding
a claims field to a dependency entry does NOT change
whether or how that dependency is satisfied: satisfaction is
determined solely by name, constraint, and architecture
(§4.2.3), exactly as for a dependency with no claims field.
In particular:
- A dependency on a role is satisfied by any installed eligible provider, whether or not that provider currently holds the role.
- Which provider holds the role has no effect on the resolver's candidate selection (§4.2.4).
- A package MAY depend on a role solely to declare a claim
path for it, relying on the resolver's ordinary
providesmatching to ensure some provider is present.
§4.4.7 Optional claim consumers
A claim path declared on an optional_dependencies entry
(§4.1) is an optional claim consumer. The dependency need
not be satisfied for the consuming package to install
(§4.2.6), and the claim path is materialised only if and when
an eligible provider holds the role.
peinit would use a log sink if one exists but
ships without one. It declares
{ "name": "logsink", "claims": { "sink": { "path": "/run/logsink.sock" } } } in optional_dependencies.
peinit installs with no provider present and
/run/logsink.sock is not created. Installing eventd —
which provides logsink — lets eventd claim the role
(§7.7), at which point /run/logsink.sock is materialised
against peinit's declared path. Removing eventd again
withdraws the claim with no effect on peinit.The distinction between a required and an optional claim consumer governs the safety of withdrawing a role's holder (§7.7.6): withdrawing a role on which only optional consumers depend cannot break a required dependency.
§4.4.8 Resource limits
The claims mechanism is bounded by the resource limits of
§3.2 — the number of slots per claims field and the number
of claim paths materialised per role. A manifest whose
claims field exceeds these limits is INVALID and MUST be
rejected.
§4.4.9 What claims are not
Claims are NOT:
- A general symlink mechanism. A package that needs a fixed symlink among its own files ships it as a payload symlink entry (§3.2.6), not as a claim. Claims exist for names contended by multiple packages.
- A service-registration or service-activation mechanism. A materialised claim link is a filesystem symlink and nothing more; it starts no process and registers nothing with peinit (§4.3, "What side effects are not").
- A dependency-resolution input (§4.4.6).
- A way to escape the one-package-per-path rule (§3.4.10) for ordinary payload files. Only package-manager-owned claim links are exempt, and only at claim paths that no package owns.