valis / Reference / API reference

Apply - API reference

Exported surface for the apply subsystem. Part of the API reference.

Package valis/src/apply/conditions

Conditions

delivery-artifact-malformed

Signalled when a member is present and is not what the apply requires: an empty file, a file that is not an ELF where an ELF is required, or a member that must not be present at all.

MEMBER names the offending entry and DETAIL says what was wrong with it, so the two shapes that produce this class stay apart in the report.

The third case is the one that reads as tidiness and is not. A per-host launcher config inside the archive pins that delivery to the single node it was built against, so an artifact carrying one is refused for what it would do on the second node, not for being untidy.

delivery-error

Root of every way applying a built delivery can fail.

ARTIFACT is the artifact path the apply was driving, exactly as the caller gave it, so an operator reading a report sees the value they invoked with rather than a canonicalised form they have to translate back. BRANCH names the half that broke. CAUSE carries the underlying condition or errno, never a rendered string.

A caller that only needs to know the apply failed handles this one class; a caller that must decide whether the node changed handles the subclasses.

delivery-manifest-incomplete

Signalled when the artifact does not carry every member the apply was asked to place.

MISSING holds the member names, so the report says WHAT is absent rather than only that something is. A report naming the missing member is a rebuild instruction; a report saying the manifest was incomplete is an invitation to unpack the artifact by hand and find out.

This is the refusal where nothing has moved: it is raised before the first byte is placed, so the node is serving exactly what it was serving.

delivery-not-serving

Signalled when the unit came back and the node is not serving. This is a FAILED apply, never a warning beside a successful one.

UNIT names what was restarted and DETAIL carries what the probe actually saw, so a report says which reading produced the refusal rather than only that one did.

REASON is a keyword from a CLOSED set. The set is closed because a probe that can invent a new reason at its call site produces failures nothing downstream can count, alarm on, or tell apart; adding a seventh is an edit here, made by somebody who has read the other six:

:no-answer the probe asked and nothing came back at all. :not-authoritative the node answered without authority for the name asked, so it is up and it is not serving what it serves. :control-answered the control name, which this node must never answer for, was answered. A probe whose control comes back positive cannot go red, and a check that cannot go red is not a check: the positive reading beside it establishes nothing. :control-silent the control name returned nothing at all. That is the same loss from the other side: a silent server reads exactly like one that has lost authority, so the confirmation sitting beside the control says nothing either, and the pair has to be refused as a whole. :restart-flapped the unit came back and did not stay up, so any reading taken of it describes a process that is already gone. :probe-unavailable the probe itself could not run, so nothing was read. Reported as a failure rather than passed over, because an apply that could not confirm the node came back has not confirmed it.

delivery-restart-refused

Signalled when the privileged restart was refused or returned non-zero.

EXIT-CODE is carried separately from DETAIL because a refusal by policy and a unit that failed to start are different numbers wanting different responses, and a report that folded both into prose would lose the one part a handler can branch on.

Nothing about the placed file is undone by signalling this. Whether to roll back to the retained predecessor has consequences for a node that may still be serving the old image, so it is a decision for a handler that knows the operator's intent rather than for the verb that noticed.

delivery-target-unconfigured

Signalled when the node's own environment does not name every site fact the resident it is about to receive refuses to start without.

MISSING holds one (VARIABLE WHAT) pair per unnamed fact, so the report says both which variable to set and what it is for. SOURCES names where the environment was read from, which is how an operator tells a variable that is genuinely missing from one this verb failed to see.

This is the refusal that exists because the alternative was measured on a real node: the replacement went into place, the unit was restarted, the resident refused to boot on an unconfigured fact, and the service manager was left restarting a binary that could never come up. Every check here is free and the placement is the first act that is not, so the environment the target will be given is read before the first byte is moved.

delivery-target-unusable

Signalled when the destination cannot receive the placement: the directory does not exist, the invoking account cannot write it, or it does not hold the file the apply was asked to replace.

The last of those is a refusal rather than a create, on purpose. An apply that placed a binary where none had been would stand a second serving core up beside the running one instead of updating it, and the operator would learn that from the node rather than from this condition.

Generic functions

delivery-artifact-member

(delivery-artifact-member condition)

Undocumented: this exported symbol needs a docstring.

delivery-error-artifact

(delivery-error-artifact condition)

Undocumented: this exported symbol needs a docstring.

delivery-error-branch

(delivery-error-branch condition)

Undocumented: this exported symbol needs a docstring.

delivery-error-cause

(delivery-error-cause condition)

Undocumented: this exported symbol needs a docstring.

delivery-error-detail

(delivery-error-detail condition)

Undocumented: this exported symbol needs a docstring.

delivery-error-unit

(delivery-error-unit condition)

Undocumented: this exported symbol needs a docstring.

delivery-manifest-missing

(delivery-manifest-missing condition)

Undocumented: this exported symbol needs a docstring.

delivery-not-serving-reason

(delivery-not-serving-reason condition)

Undocumented: this exported symbol needs a docstring.

delivery-restart-exit-code

(delivery-restart-exit-code condition)

Undocumented: this exported symbol needs a docstring.

delivery-target-path

(delivery-target-path condition)

Undocumented: this exported symbol needs a docstring.

delivery-unconfigured-missing

(delivery-unconfigured-missing condition)

Undocumented: this exported symbol needs a docstring.

delivery-unconfigured-sources

(delivery-unconfigured-sources condition)

Undocumented: this exported symbol needs a docstring.

Package valis/src/apply/delivery

Functions

apply-delivery

(apply-delivery artifact &key destination unit confirm-server confirm-zone resident steer dry-run (active-deadline-seconds 30))

Take the built delivery ARTIFACT to a running node and return the report naming exactly what changed. The composed verb, in the fixed order this file's header states: resolve, check the target's configuration, verify, stage, place, restart, confirm.

The configuration check comes first because it is the cheapest refusal there is and because the failure it prevents was measured on a real node: the binary went into place, the unit restarted, and the resident refused to start on a site fact nobody had named, leaving the service manager restarting something that could never come up. It runs on a dry run too, since a dry run that skipped it would report an apply that WOULD succeed and would not.

DESTINATION, UNIT, CONFIRM-SERVER and CONFIRM-ZONE each resolve by their own precedence chain, and all four resolve FIRST, before the artifact is opened. The last two have no fallback at all: an apply that cannot say where to confirm, or what to confirm, cannot keep the promise that a node which does not come back is a failed apply, so it refuses while the node is still exactly as it was.

RESIDENT and STEER widen the placement beyond the serving binary, which is the narrow default; see %SELECTED-MEMBERS for why the wider set has to be asked for. Neither widens what the archive is checked for: an incomplete delivery is refused however few of its members this apply meant to place.

DRY-RUN returns the report the apply WOULD produce and places nothing, restarts nothing and logs nothing.

Exactly one restart is issued however many members were placed. The single unit covers all three: its ExecStart is the resident being replaced, and it loads the steer object on start, so one restart re-execs the new resident with the new steer.

⛔ A node that does not come back SIGNALS. DELIVERY-NOT-SERVING carries the reason and names the retained predecessor; DELIVERY-RESTART-REFUSED is the distinct failure where the restart itself was refused. Neither is a field in a returned report, because a report is read as success by anyone who does not think to check.

⚠ Nothing here rolls back. The predecessor is retained and named in the failure, and putting it back is the operator's hand act.

assert-delivery-sound

(assert-delivery-sound artifact destination &key (placing *expected-delivery-members*))

The door an apply passes through before it stages anything: signal the first fault ARTIFACT has as a delivery for DESTINATION, or return T when it has none.

Signalling only the first is deliberate. The faults are ordered by what has to be fixed first, and a caller handed all of them at once would act on the last one it read.

delivery-faults

(delivery-faults artifact destination &key (placing *expected-delivery-members*))

Every reason ARTIFACT would be refused as a delivery for the DESTINATION directory, as UNSIGNALLED condition objects, and an EMPTY list when it is sound. PLACING names the members the apply intends to put in place, and narrows the shape checks to those. It does NOT narrow membership: the archive is asked for the whole delivery whatever subset the caller means to place.

This is the half a caller can ask WITHOUT acting, which is what a dry run and a command-line report both need: a verb that could only refuse by signalling would force every caller wanting to describe an artifact to provoke a failure and read the condition back out of it.

A failure inside the lister or the extractor is returned as a fault rather than allowed out, because from the caller's side an archive tar cannot read is one more reason the artifact is unusable. The handler names DELIVERY-ERROR and nothing wider: a handler that caught everything could not tell an unreadable archive from a bug in this file, and would report the bug as an unsound delivery.

Variables

*delivery-extractor*

The seam unpacking named members out of an artifact. Default: %tar-extract-members, which drives tar -xzf with an explicit member list and returns (member-name . pathname) pairs. Rebound in tests to a stub that writes real files into the scratch tree, so the shape checks read genuine bytes without a tarball having to be built for every case.

*delivery-lister*

The seam reading an artifact's member names. Default: %tar-list-members, which drives tar -tzf through a list argv and returns the names it printed. Rebound in tests to a stub returning a written-out member list, so a refusal is proven against the manifest the test chose rather than against whichever archive happens to be sitting in dist/.

*elf-delivery-members*

The members whose first four bytes must be the ELF magic: the serving core, the privileged resident, and the sklookup steer object. The two remaining members are a systemd unit and a tmpfiles rule, which are text.

Written out as data and kept beside the manifest expectation so both grow in one place. A chain of conditionals over member names would put the same knowledge in a second spot, and the second spot is the one nobody edits.

*expected-delivery-members*

The five members a complete delivery carries: the serving core, the privileged resident, the sklookup steer object, and the two systemd and tmpfiles units.

Taken from the MEMBERS array in scripts/verify-dist-tarball.sh, which is the shipped expectation the build host already enforces. The two lists say the same thing and are edited together: a delivery satisfying one and not the other passes on the build host and refuses on the node, or, worse, the reverse.

Growth here is a decision somebody makes on purpose, never a name that arrived because a build target quietly started emitting it.

*forbidden-delivery-members*

Member-name fragments no delivery may carry.

A per-host launcher config names a catchall-object-path that is true of exactly one node. An archive carrying one silently pins that delivery to the host it was built against, and the second node it reaches boots against a path belonging to the first. That is why this is a validation rule and not hygiene.

Matched as a fragment rather than a whole name, because the directory the config leaks in under varies while its file name does not. Growth here is a decision somebody makes on purpose.

*readiness-control-name*

The name the confirmation's CONTROL query asks for, which the node must never be able to answer for.

Under .invalid, which RFC 6761 reserves as guaranteed not to exist in the public namespace, so no delegation can ever make this name legitimately answerable and the control cannot go green by accident.

The control is what makes the confirmation beside it mean anything: a server answering everything would satisfy the confirmation on its own, and a check with nothing that can go red is not a check.

*readiness-outcomes*

The CLOSED set a readiness probe may report. Exactly one of these is :SERVING; the other five are failed applies that differ in what the operator does next.

Closed because a probe free to invent a reason at its call site produces failures nothing downstream can count, alarm on, or tell apart. A seventh is added here by somebody who has read the other six, and the same set is written out in the DELIVERY-NOT-SERVING docstring, which is where a caller handling the condition reads it.

*readiness-probe*

The seam the post-restart confirmation reads the node through. Default: %WIRE-ANSWER-PROBE, which drives the query tool at the resolved server with a list argv and reports one of READINESS-OUTCOMES.

Rebound in tests to a stub returning a chosen outcome, so each of the six readings is proven to produce the failure it should without a node to point at. It returns a keyword and never a boolean: a probe answering true or false cannot say WHY it said no, and every caller downstream would have to guess.

*resident-member*

The archive member holding the privileged resident, which owns the public port and the network namespace. Placed only when a caller asks for it by name.

*retained-predecessors*

How many predecessors of a placed file are kept beside it. The single place this bound is written.

Two is the number the live node already holds, so the bound describes the shape that directory is in rather than imposing a new one on it.

⚠ It does NOT mean nothing is deleted, and the docstring says so because this is what an operator reads before authorising an apply. The first real apply retains a third predecessor, the pruning pass then removes the oldest, and roughly 30 MB goes for good. That is the intent and it is the whole point of bounding the set, but a serving binary is not in git and nothing can bring the deleted one back. Raise this before an apply, never after one.

*serving-core-member*

The archive member holding the serving core: the one binary a routine apply replaces, and the only one the default selection places.

*serving-unit*

The service-manager unit supervising the serving core, used only when nothing else named one.

⛔ Like the conventional destination, this is the TARGET HOST's own layout and not a contract, so it sits at the END of a precedence chain rather than being written into the middle of a restart. A node whose unit is named differently is told, and a caller who knows its own unit passes it.

⚠ The unit's ExecStart is the resident launcher and NOT the serving binary this verb replaces. Restarting this unit after replacing that binary is correct, because the launcher execs it, but "the binary the unit points at" names a different file: an operator reading the unit sees the launcher, not the file an apply moved.

*steer-member*

The archive member holding the sklookup steer object, which decides which socket an inbound connection reaches. Placed only when a caller asks for it by name. It is the one placeable member that does not live beside the binaries.

*unit-controller*

The seam every service-manager reading and the one elevated restart go through. Default: %SYSTEMCTL-CONTROLLER, which drives systemctl with a list argv and reads its exit status.

Rebound in tests to a RECORDING stub: it answers each action from a script the test chose and keeps the call record. That record is what proves an apply issued no restart, or issued exactly one for three placed members, which is the half a returned outcome keyword cannot show. The outcome is what the verb says; the call record is what it did.