Skip to content

M14: the loose ends, and closing the roadmap - #18

Merged
youhide merged 1 commit into
mainfrom
feat/m14-loose-ends
Aug 9, 2026
Merged

M14: the loose ends, and closing the roadmap#18
youhide merged 1 commit into
mainfrom
feat/m14-loose-ends

Conversation

@youhide

@youhide youhide commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Closes M14, and closes the roadmap.

Two clocks

A backoff, a stop timeout and a start timeout are all "this long from
now" and must not move when the clock is corrected: CLOCK_MONOTONIC,
relative. A calendar schedule is "at 03:30" and must stay at 03:30
however the clock gets there: CLOCK_REALTIME, and absolute.

M8 collapsed the second into the first — computing "in fourteen hours"
once and arming a monotonic timer with it — so an NTP correction moved a
nightly job by however much the clock moved.

The fix is not to recompute on a step; it is to stop converting. An
absolute deadline needs no recomputation, because the kernel compares it
against the same clock that changed.

TFD_TIMER_CANCEL_ON_SET is set as well and is not what makes that
true — it only makes the step visible, which is worth one line in the
log. That is worth saying, because the roadmap entry that asked for this
named the flag as though the flag were the fix.

Schedule now returns which clock it means — Next::After(Duration) or
Next::At(Unix) — rather than a delay that has already lost the
distinction. That is the whole of the type change, and it makes the
routing impossible to get wrong.

Datagram sockets

A different shape: never listened on and never accepted, so oxinit
starts the service on a readable descriptor rather than a pending
connection, and the datagram that woke it is still queued for the service
to read. listen-probe grew --recv and --send; the stream path could
not have exercised any of that.

type = "seqpacket" stays uncovered on purpose: it is accepted like a
stream, so it exercises the same oxinit path the existing units do. A
fixture for it would test the fixture.

A correction

M13's deferral note said the backoff reset was uncovered and needed "a
different kind of test than this suite is". That was wrong.
oxinit-service has had backoff_resets_after_the_service_stays_up and
a_brief_active_period_does_not_reset_backoff all along — policy logic
in a crate with no OS dependency, tested by a host test, which is exactly
where it belongs.

Also drops two stale claims in UNIT_FORMAT.md: "nothing here is
implemented yet" (true until M1) and "one of three kinds" (true until
M8).

Not doing, and why

Both were on the list; both come off it with a reason rather than sitting
there forever.

postcard instead of JSON. ARCHITECTURE sets the precondition
itself — "the likely replacement once the message set stops changing" —
and it has not. UnitStatus gained a field in M8 and another in M14.
Being readable with socat while the protocol still moves is worth more
than bytes on a socket carrying a few messages a minute.

Local time. It means carrying a timezone database, which contradicts
a position the project has taken in writing twice. Reopening that is a
decision, not a loose end.

Verified

41 checks per architecture, 43 against the real Alpine userspace, 10 in
the container, 148 host tests.

Closes M14, and closes the roadmap.

Two clocks, because two kinds of deadline mean different things. A
backoff, a stop timeout and a start timeout are all "this long from now"
and must not move when the clock is corrected: CLOCK_MONOTONIC,
relative. A calendar schedule is "at 03:30" and must stay at 03:30
however the clock gets there: CLOCK_REALTIME, and absolute.

M8 collapsed the second into the first — it computed "in fourteen hours"
once and armed a monotonic timer with it, so an NTP correction moved a
nightly job by however much the clock moved. The fix is not to recompute
on a step, it is to stop converting: an absolute deadline needs no
recomputation because the kernel compares it against the same clock that
changed. TFD_TIMER_CANCEL_ON_SET is set too and is NOT what makes that
true; it only makes the step visible, worth a line in the log. Saying so
matters, because the roadmap entry asking for this named the flag as
though the flag were the fix.

Schedule now returns which clock it means — Next::After(Duration) or
Next::At(Unix) — rather than a delay that has already lost the
distinction. That is what makes the routing impossible to get wrong.

A datagram socket is a different shape: never listened on, never
accepted, so oxinit starts the service on a readable descriptor rather
than a pending connection, and the datagram that woke it is still queued
for the service to read. listen-probe grew --recv and --send; the stream
path could not have exercised any of it. seqpacket stays uncovered on
purpose — it is accepted like a stream, so a fixture for it would test
the fixture.

Corrects M13's deferral note, which was wrong: the backoff reset is and
was covered, by two host tests in oxinit-service, which is exactly where
policy logic with no OS dependency belongs.

Also drops two stale claims in UNIT_FORMAT.md: "nothing here is
implemented yet", true until M1, and "one of three kinds", true until
M8.

41 checks per architecture, 43 against the distribution image, 148 host
tests.
@youhide
youhide merged commit a181bc9 into main Aug 9, 2026
5 checks passed
@youhide
youhide deleted the feat/m14-loose-ends branch August 9, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant