7.5 Readiness Levels

network-online.target on systemd is famously meaningless, because "online" has no single definition. A service needing a default route and a service needing only a loopback address want different things, and one target cannot mean both.

peinit does not have an online target. A service says what it is waiting for, in the publisher's own vocabulary:

Requires = ["netd:routed"]
Wants    = ["timed:synchronised"]

netd:routed waits for netd to be active and to have published the level routed. netd on its own is unchanged — the ordinary "that service must be active" dependency.

7.5.1 The syntax #

<service>:<level>, split on the first colon. It rides on the existing Requires, Wants and BindsTo fields rather than a field of its own, because a level dependency is a service dependency with a stricter predicate: netd:routed subsumes netd. Each relationship keeps exactly the semantics it already has, applied to the stricter test.

It cannot collide with a service name. A service name may contain only letters, digits, ., _ and -, so a colon never appears in one, and no existing definition can accidentally become a level dependency.

A trailing colon — "netd:" — reads as a plain dependency on netd. It is a typo, and treating it as a request for the empty level would produce a condition nothing could ever satisfy.

7.5.2 Through a role #

The service side of the syntax may be a role instead of a name:

Requires = ["network:routed"]

network is filled by whichever service declares Provides = ["network"] — netd, on a shipped image — and peinit rewrites the entry to netd:routed before anything reads it, exactly as §7.6 resolves a role. The level rides across unchanged. Everything downstream — validation, the graph, svctl status — sees a level dependency on a service, and never learns a role was involved.

This is the form a definition should use for the network. The role is "the executor of Peios Network Policy's interface layer", and the levels are not the executor's own words but the specification's: PNP defines readiness as link, addressed and routed, and whichever service fills the role publishes those. A definition that says network:routed survives netd being replaced; one that says netd:routed does not.

Several services may fill a role; the entry then resolves to one level dependency per provider, and a Requires waits for all of them. A role no service fills is left as written and fails validation as the missing hard dependency it is, naming the role the definition wrote.

7.5.3 How a level gets there #

The service publishes it on the notification channel it already has, as LEVEL= beside READY=1 and STATUS=:

LEVEL=link
LEVEL=addressed
LEVEL=routed

All LEVEL lines in one datagram form one set: peinit atomically replaces the sender's previous set with the distinct nonempty values, before releasing any dependents, including when the same datagram carries READY=1. A datagram with no LEVEL lines leaves the set unchanged.

LEVEL= on its own clears the set. Empty values mixed with nonempty ones add no member; they do not clear other lines in the same datagram. peinit records the set against the sending service, and holds any dependent whose requested level is absent.

There is no subscription. peinit does not connect to netd's socket or timed's, does not need to find them before they exist, and carries no knowledge of their wire formats — which matters, because PID 1 is the one process on the machine that cannot afford to fail to start.

7.5.4 Levels match set membership #

Requires = ["netd:addressed"] is satisfied when the active publisher's set contains addressed. Every member is an opaque, case-sensitive name; commas do not split a name into several levels.

peinit has no ordering over another daemon's vocabulary. A publisher that sends only LEVEL=routed does not satisfy an addressed dependent. netd knows which conditions imply others, so when routed it publishes link, addressed and routed together. A service should depend on the condition it needs, and the publisher reports every condition that holds.

7.5.5 A level is never stale #

peinit clears a service's entire level set whenever that service leaves a state that satisfies dependents. A level is a claim about a process that is currently making it, and one that outlived its process would hold a dependent open on a promise nobody was keeping — the exact failure the mechanism exists to prevent.

Because peinit is the process that notices a service exiting, this needs no timeout and no liveness check.

7.5.6 How the gate holds #

A level is settled at dispatch, not at planning. The plan decides what to start; whether a level is met is a live question — it can arrive after the target's own start completed (netd is active long before DHCP finishes) and be retracted while a dependent is still waiting — so the execution graph re-checks it against the service table every time it considers releasing the dependent, and holds the start until the answer is yes.

That check runs even when there is nothing to start. An already-active target is not part of the start plan, but the condition on it still is: the graph keeps a level edge to a service it is not starting, which is what makes Requires = ["netd:routed"] hold when netd is up and merely not routed yet.

Two events re-open the question. A level-set update arriving from the publisher releases every dependent whose requested level is now present — this is the only thing that can open a Requires or BindsTo gate, which take the exact level and nothing else. And the publisher leaving a state that satisfies dependents releases Wants waiters: a soft dependency waits only while someone is running who could still publish the level, and proceeds once nobody is — which keeps Wants failure-tolerant, the property that defines it.

A held start does not time out, and its StartTimeout runs from the moment the level arrives, not from when the start was asked for. Requires = ["netd:no-such-level"] holds the dependent indefinitely: the service stays inactive and the start operation stays pending, visible in svctl status as an operation that has not completed. That is the declared semantics, not a hang — the condition was never met, so the start never happened.

7.5.7 What happens on a drop #

Nothing, in this version. If a level falls away after a dependent has already started, the dependent keeps running and is not told.

That is a deliberate limit, not an oversight. The notification channel is one-way by specification (PSPU §4.16), so peinit has no way to tell a running service anything. A service that needs to react to a level changing while it runs should subscribe to the publisher directly — which is what resolvd does with netd today, and what any consumer that cares that much will want anyway, since it will need the detail rather than just the fact.

peinit's contribution is the start gate: the moment when the service does not exist yet and so cannot subscribe to anything at all.

7.5.8 Published levels #

PublisherLevels
netd, as the role networklink → addressed → routed, defined by Peios Network Policy
timedunsynchronised, settling, synchronised, spike

Any service may publish a level; nothing about the mechanism is specific to these two. The vocabulary is the publisher's own — or, where the publisher fills a role a specification defines, the specification's — and needs no registration.

Edit this page