Soul found B1's rule sound and its transition unsafe. This makes the rule's
old inputs impossible to reintroduce a stuck state from.
- The control store's legacy lift owns the transition. A pre-B1 continuity
abort that issued an activation lifts with topology_reconciliation Pending,
so the one resolution demotes its own activation. A record already terminal
under the old rule is reopened at Starting, since a terminal record cannot
owe work. History lifts records as written. Before this, one resident
record of that shape failed validation and ControlSnapshot::read refused the
whole store, so Idunn could not boot.
- Supervision owns the Expected-only precondition. When its pre-restart
demotion fails it mints no continuity and writes next_restart_at_unix_millis
on the generation's ContinuityBackoff (attempts and window untouched), so it
neither restarts over an undemotable projection nor retries every tick.
- Boot reconciles once: a projected activation that exactly matches the
activation a failed continuity in history (or a resident terminal record)
issued is demoted. Nothing is adopted; an activation no admitted generation,
live transaction or failed issuer accounts for is reported and left alone.
New read-only CultCacheTopologyDriver::projected_activation.
- Unreadable history.cc fails closed. The continuity restart ceiling and the
consumed-command check no longer read an empty history; they stop, and the
fault is reported once while it lasts (ReportOnce). The undecodable-record
report is a pure function, pinned.
- docs/deployment-authority.md no longer says the projection never drifts.
Tests: legacy fixtures written by the 9001b58 encoder for the three abort
shapes (a second throwaway generator, see the fixtures README), driven through
the Engine; the seeded-transaction helper now takes a command kind and an
incumbent, and a Deploy carries a frozen-source receipt and a signed brake
release, so Deploy commit, Deploy pre- and post-fence aborts and a continuity
abort over an incumbent run through the real phase machine.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A continuity candidate shares the admitted Expected key. Both abort intents
marked its topology reconciliation Skipped, so a failed continuity left its
candidate activation projected under the admitted key while the admitted
receipt named the older one, and a later deploy's post-fence demotion refused
as substituted.
DeploymentTransaction::abort_topology_reconciliation is now the one rule for
both intents and pre-fence validation: a deployment owes the projection when it
published its Expected, a continuity owes it when it issued an activation.
reconcile_failed_candidate_projection is the one resolution for both abort
paths: a deployment withdraws its own key, a continuity demotes the shared key
to Expected-only with its own exact activation and never withdraws the
Expected. The post-fence incumbent demotion stays deployment-only and exact.
Tests read the persisted topology store after the production
publish/demote sequence.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This reverts commit a148802cb6.
Rollback after fencing adopted whatever activation was projected under the
shared Expected key, so a continuity abort's leftover activation could be
demoted by a later deploy. The drift is closed at its source by the next
commit: a continuity abort demotes its own exact activation. Rollback demotes
the admitted generation's exact activation again.
The doc paragraph is dropped; the 9f00e7a paragraph above it stays until B4.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Deletes the two stale "Current implementation boundary" sections in
docs/deployment-authority.md and docs/authority-map.md; both described a
pre-drivers.rs era and were wrong (drivers.rs has driven Git, runners,
systemd, nginx, brakes, CultCache and CultMesh for weeks). The still-true
second paragraph of the deployment-authority.md section (private plan state
is never published as Odin topology) moves to the end of "Typed deployment
state", reworded present tense.
Fixes README.md's stale claims: the CLI has five commands (cancel and
validate were missing), and docs/guide.md plus deploy/legacy/ describe a
generation that no longer runs anywhere, not "still what runs on yggdrasil".
Documents present truth in "Candidate promotion": advance_sealing resolves,
freezes, runs every recipe step and installs before the deployment brake is
consulted at all. The brake only gates the move into Starting, the first
Verse-visible change.
Docs only, no build required.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The projection kept one Expected/activation/lease slot per target. A
deployment's Sealing overwrote that slot with the candidate's Expected, the
incumbent failed activation-expected-projection on its next heartbeat and
exited, the deployment was aborted for having killed its incumbent, and the
command -- left resident after its transaction was retired to history --
read as queued and was frozen again. Odin cycled on that every few minutes.
- Topology driver records are keyed {target}@{expected sha}; the incumbent and
the sealed candidate are projected side by side. One CAS mutation replaces
the six bespoke loops. withdraw_incarnation removes only one incarnation;
demote_to_expected_only replaces restore_admitted_expected_only and is used
only for an incarnation whose process is gone. Legacy target-keyed records
are retired on the first publish for that target.
- receive() takes the incarnation; the read-as-absence compensator for a
correlation about another incarnation is gone.
- Candidate Expected is published as Starting's first step, after the brake
admitted the transaction, not in Sealing before it.
- A pre-fencing abort withdraws only the candidate; a post-fencing abort also
demotes the stopped incumbent.
- A consumed command is archived with its last transaction; status still
answers for it from history.
- An engaged lifecycle brake stops continuity being minted instead of parking
a transaction on the target.
- idunn cancel <command-id> retires a queued command as an operator refusal.
Verified with cargo test --lib in rust:1.95-bookworm on yggdrasil: 114 passed,
2 ignored.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Brings across everything an operator needs to run Idunn without reference to
Odin. deploy/ carries the worked systemd installation -- unit, operator
wrapper, and the narrow sudoers grant. docs/ carries the four documents that
were scattered across Odin's tree.
The README is new and is the public entry point the repo lacked: what Idunn
decides, how to build and run it, the authority model in the form doctrine
asks for, how to install it as a service, and where the deeper documents are.
Two corrections made while moving:
Requires=docker.service becomes Wants=. Deployment runners use Docker;
continuity actuation does not. A hard requirement lets a Docker outage stop
Idunn itself and take daemon survival down with it, which is precisely the
invariant Idunn exists to hold. This is live on yggdrasil today and should be
changed there too.
The guide still told operators to run from E:\Projects\Odin -- a drive that no
longer exists -- via npm wrappers that belonged to Odin. Paths generalised,
commands moved to direct cargo invocations, and a note added that the document
predates the extraction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>