Commit graph

619 commits

Author SHA1 Message Date
17edea299e Merge pull request 'Routing proof waits out nginx's reload handoff and names who answered' (#8) from hands/routing-proof-handoff into main 2026-11-14 04:28:18 +00:00
506df0a81f Merge pull request 'Liveness gates presence: route challenge and topology refresh only for a running incarnation' (#7) from hands/liveness-gates-presence into main 2026-11-14 04:28:06 +00:00
eace76870a Pin liveness cut: truthful past-due status, dead-topology test reaches its gate, report-once and empty-lease pins
Status says how overdue a route challenge is, not a cause it cannot see.
The dead-generation topology test engages the lifecycle brake so every tick
reaches the gate. New pins: observe/lease/demotion/stale-incarnation/projection
repair said once and cleared; a running target with an empty lease is not
route challenged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 03:38:14 +00:00
c30f89f0df Pin the route handoff window, cadence, signer label and unchanged-route guard
The handoff loop reads its clock through a test-held virtual clock, so the
window and poll cadence are asserted exactly and no host load moves them; the
fails-at-once tests assert through stable_hits and the absence of sleeps.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 03:19:57 +00:00
47a9c3d89e Liveness cut: say lasting observe/lease/demotion faults once, mark a past-due route as waiting on a running incarnation
Applies Soul findings to cut-liveness-gates-presence: report_once+clear_fault
for the remaining per-tick eprintln sites in supervise_one_admitted_generation,
status marks a past-due challenge, tests for the unreadable-fragment and
HostUnobservable paths, F8 marked resolved in route-continuity-cut.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 03:02:47 +00:00
cdf987730e Routing proof waits out nginx's reload handoff and names who answered
systemctl reload only signals nginx's master; old workers keep answering
the stable listener until the new config is parsed and they are told to
quit, so Idunn's first challenge could reach the incumbent and fail the
attempt, rolling the fragment back. An answer naming the preflight's
incumbent instance is now not-yet for 10 s (one challenge per 250 ms,
never admitting, never actuating). Every failed proof now carries a
RouteProofAnswer context: the runtime instance and activation signer ids
the answer claimed (unauthenticated, ids only, 96 bytes each), so the
journal line and abort last_error say who answered.

Tests drive Routing through the stub runtime with incumbent-first,
never-lets-go, foreign-activation and stranger replies.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 02:39:27 +00:00
8dd9f8d503 Liveness gates presence: route challenge and topology refresh run only for an observed-running incarnation
Cut the pre-liveness route and topology supervision and the pre-challenge lease verdict; re-insert both calls after the running-branch incident close, with report_once and clear_fault per target. Rewrite the dead-unit test, add four tests, point the routed fixture's lifecycle brake store into the world.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 02:23:49 +00:00
7eae1c4ee1 Say why the stable route proof failed (#6) 2026-11-14 01:34:34 +00:00
74c04bbcc7 Merge pull request 'Steward-owned second deployment stop named by the binding' (#5) from eureka-self/brake-steward-store into main 2026-11-13 18:11:46 +00:00
c13e175bbf Steward brake: refuse a shared steward directory, name the proven owner, steward-only remedy
Binding validation refuses a steward store whose directory is shared with,
inside or around any other Idunn authority directory, since the steward owns
its directory and could unlink a root-owned store. The wait note names a
steward stop by the binding's steward user, not the record's claimed by. The
Unreadable hint for a steward store says only the owner uid can repair it.
Root-run tests skip with a message. Docs gain a provisioning runbook and the
sealed-binding recovery caveat.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 18:09:41 +00:00
c5a0ce3d23 Document the steward stop in deployment-authority
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 17:47:48 +00:00
37622db430 Deployment brake: a steward-owned second stop per target, named by the binding
BrakeBinding.steward = { user, store }. read_stops reads the operator store
as before and the steward store with the steward user's uid as the expected
owner, so the file owner proves who wrote it. Deploys admit iff every stop
admits; the wait note names each holding stop. Binding is stored as text in
the plan, so no persisted record changes. deployment-brake-status gains
--owner-user for the operator to read a steward's store.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 17:46:36 +00:00
58e744b745 Merge pull request 'Deployment brake is a stop: DeploymentStop record, signed release path deleted, record v6' (#4) from eureka-self/brake-is-a-stop into main 2026-11-13 17:43:21 +00:00
b230703a5c Idunn: a stop halts before irreversible steps; fail-closed reader; break-glass clear
- Re-read the stop at the static flip intent and at entry into Fencing for a
  deploy (stop_note, the same path as Sealing). After the intent or fencing
  the transaction runs to its end; documented.
- Absent means NotFound in an existing directory; every other lookup error,
  a zero-length file and a missing parent fail closed. Unreadable names the
  idunn-provision deployment-brake-clear command.
- replace_stop is the operator's break-glass: it replaces v1, foreign,
  ambiguous, zero-length and caller-owned undecodable stores, never a symlink
  or another user's file.
- Mutation-proven tests: foreign-owner Clear, Unreadable admitting at
  Sealing, author from USER.
- docs: stop semantics, v6 downgrade hazard and backup; legacy script no
  longer requires the store and drops the signed-grant wording; two stale
  doc lines.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 17:36:57 +00:00
a20568ef70 Idunn: the deployment brake is a stop, not a per-deploy gate
Delete the signed release path (deployment_brake_release, the operator
enroll/export verbs, the anchor option and flag, the consumed-once scan,
the durable re-verification, DeploymentAuthorization::validate_shape).
src/brake.rs is the one reader and writer of the stop
(idunn.deployment_stop.v1: engaged|clear, by from the process uid, reason,
at, sequence). Absent or clear admits; a foreign, corrupt, symlinked,
other-runtime or foreign-owned store fails closed. advance_sealing stamps
deployment_admitted_at_unix_millis (key 38) for Deploy and Rollback alike,
so static releases and rollbacks use the same stop.

Control record v6: key 38 changes canonical bytes, so v5 gets a legacy
layout, a lift, boot migration and pinned bytes tests. Key 16 stays a
never-written legacy field so old terminal records decode.

idunn-provision: deployment-brake-engage|clear|status take --store,
--runtime-id, --reason only; status exits nonzero unless deployments admit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 17:24:34 +00:00
114803c63b Merge pull request 'idunn rollback: flip a static release back to the previous one' (#3) from eureka-self/static-rollback into main 2026-11-13 17:06:30 +00:00
7ba27c362e Idunn: rollback drift after freezing, cross-binding refusal, and docs
Tests: a previous release that drifts after the rollback was frozen ends
FailedBeforeFencing at the Starting-time verify with current untouched (fails if
that verify is removed); rollback_source refuses a previous release deployed
under another release root or current link. Docs: CommandKind::Rollback needs no
schema change but an Idunn at 9f15cee or earlier cannot decode it (forward-only);
flipped_at ordering and the observed-link check; a rollback cannot be cancelled.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 17:03:48 +00:00
01b6016193 Idunn: idunn rollback <target>, a brake-gated transaction flipping to the previous static release
CommandKind::Rollback (command ids rollback-*). The daemon freezes the command
into one transaction built like from_continuity: plan, sealed release and
installed release copied from the transaction that flipped the previous release;
no source, no build, source cleanup SkippedContinuity, no admitted generation.
It reaches the deployment brake and advance_static unchanged. The previous
release is derived from the newest Admitted static flip (control.cc + history.cc)
and the observed link, and refused as a rejected transaction when there is no
completed release, no previous release, current is not the newest flip, the
previous directory is gone, or an artifact digest drifted (new port method
verify_installed, also checked before any flip intent). Rollback counts as a
deploy for the busy and live-per-target checks. CLI: idunn rollback via
parse_up(kind); commit_deployment_command and submit take the kind.

Tests: control_plane::tests::static_rollback_ (7) and parse_rollback (1).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 16:55:43 +00:00
9f15cee11f Merge pull request 'Static release target: StaticRelease binding, carry, atomic flip, control record v5' (#2) from eureka-self/static-release into main 2026-11-13 16:50:20 +00:00
be98521120 Idunn: pin static redeploy and stuck-flip behavior; document both
Release ids are per transaction, so redeploying an unchanged tree with a changed
site file yields a new release (the test asserts distinct ids, the first release
keeping its carry, current carrying the new file); previous_release_id can never
equal release_id and no refusal is needed. A flip whose current link was replaced
by a directory after the intent retries visibly: last_error is shown by idunn
status as 'waiting on', cancel is refused, and the docs name the by-hand fix.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 16:48:16 +00:00
dc86c6326c Idunn: a static flip that moved current can no longer end before fencing
carry_and_flip returns FlipRefused only before the rename (the last fallible
step); the parent-directory sync after it is best-effort. Once a flip intent is
recorded, any error that is not FlipRefused leaves the transaction resumable
instead of taking the pre-fencing abort, and a resumed step that finds current
already naming the release records the carry already in it (new port method
carried_sha256) instead of flipping again. validate refuses a pre-fencing abort
beside a landed flip.

Tests: post-rename port fault and post-landing observe fault both end Admitted
with current on the new release; validation refuses FailedBeforeFencing with a
landed flip; a refused flip over a real directory writes no carry; a populated
v4 record lifts to the exact v5 field by field.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 16:44:04 +00:00
74d6ef31e1 Idunn: v5 control record, v4 legacy layout, lift and boot migration
d5ea779 named this work but did not contain it: the working-tree edits were never
staged. LegacyDeploymentTransactionV4, the v5 schema constant, the v4 lift in
read_transaction_record and history decode, the boot-migration case, and the
pinned v5 / legacy v4 tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 16:41:25 +00:00
d5ea779096 Idunn: static release transaction (flip in Starting) and the v5 control record
A static release is the same DeploymentTransaction in three phases: Sealing,
Starting (carry and flip, behind the deployment brake) and Complete. It has no
Expected, activation, workload, warming, isolation, fencing, leasing, ready or
routing, and creates no AdmittedGeneration.

DeploymentTransaction gains key 37 `flip` (FlipEvidence: release, previous
release, carry source, carried sha256, time). Adding a key changes the bytes of
every record, and the control store refuses a record that does not re-encode to
what is stored, so this is schema idunn.deployment_transaction.v5. The v4
layout is kept as LegacyDeploymentTransactionV4 and lifted by the existing
mechanism (read_transaction_record, history decode, and the boot migration,
which rewrites v4 records in control.cc to v5). Migration is forward-only: an
Idunn that predates v5 cannot read a migrated control.cc.

Also: is_static() and brake_subject() (the brake names the binding's runtime id
and the sealed release, read by deployment_authorization, durable-authority
validation and status); validate_static; advance_sealing skips readiness_class
and expected_projection for static; advance_starting delegates to
advance_static (intent with the previous release read from current_link,
carry_and_flip then record, observe link == release, then Complete with
Admitted{generation-<tx>} and post_commit_cleanup(SkippedNoIncumbent, source
Pending)); a failure in Starting takes the existing pre-fencing abort;
Engine.static_release Arc<dyn StaticReleasePort>. Docs: a Static release
section in deployment-authority.md and a scope line in
deploy-provenance-target.md.

Tests: control_plane::tests::static_release_ (3), a pinned v4 record lifts and
re-encodes as the pinned v5, a v4 control store copy in a temp dir migrates to
v5 and an already-v5 store is untouched byte for byte, and the pinned-bytes
test now pins v5 with the v4 bytes kept as a legacy fixture. The test runner is
a stand-in over the real seal_artifact behind a cfg(test) Engine seam, because
DockerRunnerDriver workspace ownership requires root.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 16:32:06 +00:00
6c8fd12335 Idunn: StaticReleasePort and the filesystem flip driver
install_release and validate_installed_release read the release root through
WorkloadBinding::release_root(), so a static release installs through the
same copy, digest and harden path as a systemd one. StaticReleasePort
{ observe_current, carry_and_flip } with FilesystemStaticRelease: reads the
operator's site file (regular file, not a link, 1 MiB cap) and hashes it
before anything moves; writes <release>/<destination> by temp + rename at mode
0444 outside every artifact digest; then renames a fresh symlink over
current_link (a stale flip link a crash left is replaced; a real directory in
the link's place is never replaced). observe_current refuses a link that names
anything but a release under the release root.

Tests on a tempdir stand-in site (fake vite dist with .br/.gz twins, fake site
config): cargo test -p idunn --lib drivers::tests::static_release_ (5 passed).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 16:28:07 +00:00
46ec8241a2 Idunn: StaticRelease target declaration and workload binding
A target may declare [static_release] artifact = "<id>" instead of
[service], and bind driver = "static-release" (release_root, current_link,
one carry source/destination). TargetDeclaration.service becomes Option with
a service() accessor; admission refuses both-or-neither, state/provides/
dependencies/conflicts/route/write lease on a static target, stop-then-start,
retain_releases < 2, a carry destination that is not a single leaf or collides
with an artifact destination, a carry source inside the release root, and a
current link overlapping the release root or an authority path.

Read-only audit of TransactionCompletion::Admitted / admitted_for readers
(cut spec step 2); a static Admitted transaction has no AdmittedGeneration:
- control_plane.rs:1781 (deadline_for_current_activity, Complete): reads the
  variant and drains_incumbent only. Tolerates.
- :1828 (carries_a_deadline): variant only. Tolerates.
- :2086 (is_terminal): variant plus post_commit_cleanup. Tolerates.
- :2550 (validate, Complete): requires Admitted and generation-<tx>; the
  generation record is never read. Tolerates; static gets its own branch.
- :5085 (derived_command_status): variant only. Tolerates.
- :6567 region (command admission; the admitted_for read is at :6587): passes
  an Option incumbent, None for a static target. Tolerates. Anchor drifted by
  ~20 lines; the code matches.
- :8371 (advance_committing): the only writer of Admitted with a generation;
  a static transaction never enters Committing. Unchanged.
Other admitted_for readers (3517 fenced-incumbent check, 4963 render, 5482
incident close, 9571 provider rehydrate, 9654/9712/9777 rebase, continuity,
take_target): all handle None; a static target has no fencing, no incident,
no capability to be a provider.

Fallout: runner_for/workload_for gain a StaticRelease arm (docker runner; the
systemd driver serves install only), and the service-only readers in host.rs,
drivers.rs and deployment_plan.rs go through service()?.

Tests: cargo test -p idunn --lib deployment::tests::static_ (6 passed).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 16:27:56 +00:00
4b6a58c692 Merge pull request 'docs: drop references to deleted legacy Windows Idunn scripts' (#1) from eureka-substrate/stale-legacy-idunn-script-refs into main 2026-11-11 08:57:53 +00:00
157610adb7 Soul fixes: grammar; cite the gamecult-ops merge
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-11 08:57:51 +00:00
1c23b6e217 docs: drop references to deleted legacy Windows Idunn scripts
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-11 08:56:46 +00:00
Meta Vens
64d63cb417 Merge steward orders 1 (r3): an order is one systemd job; refusal reads the named unit only
Soul cut-steward-orders-1.s5 holds at 77e2295. Not installed: steward-orders-2 installs it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:57:39 +01:00
Meta Vens
77e2295e97 Steward orders: refuse Upholds and ConsistsOf units, pin the rule's gaps, README names the residuals
A unit with Upholds= is refused for start and restart (systemd would start the
upheld unit in a full transaction). A wrapper with ConsistsOf= is refused for
stop, restart and reload with a typed Refused::Wrapper naming the first member,
so it cannot report ran while touching nothing. Tests pin empty BoundBy, a
single show per refusal, no show for a refused name and each posted reason.
README: the one-job rule's residual full transactions. Removes the unused
BTreeMap import and the never_loop in the host actuator test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:32:30 +01:00
Meta Vens
fb1e7b5dd9 Steward orders: README states the one-job rule; pin the end-to-end argv
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:45:08 +01:00
Meta Vens
8ebe0208a6 Steward orders: pin the one-job rule on the real unit set and a real user manager
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:41:56 +01:00
Meta Vens
a859914b0b Steward orders: run every unit verb as one job and check only the named unit
Delete the transaction model (Edge, EDGES, action_target, Transaction, the
early-boot rule). Every unit verb runs systemctl <verb>
--job-mode=ignore-requirements -- <unit>; refusal_for reads one systemctl show
of the ordered unit: its names, the host-ending names, what fires when it
ends, and who is bound to it. Targets are no longer orderable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:40:58 +01:00
Meta Vens
4894ad20df README: an order's transaction follows emergency actions and OnFailure/OnSuccess; stopped boot units are refused
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:52:41 +01:00
Meta Vens
879b76a842 Steward orders: pin every emergency action spelling to its target
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:50:23 +01:00
Meta Vens
631fb7836b Steward orders: pin the new show properties; the unknown-action probe counts runs instead of expecting none
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:29:13 +01:00
Meta Vens
d29bf93be6 Steward orders: refuse a start or stop whose unit ends by emergency action or isolates; boot and recovery units are never started from stopped
refusal_for now reads SuccessAction, FailureAction, StartLimitAction and
JobTimeoutAction (each mapped to the target it runs, so reboot, poweroff,
halt, kexec, soft-reboot and exit, with -force and -immediate) and OnFailure
and OnSuccess as units a touched unit fires, followed through what they pull
in and what those fire. An action it does not know fails the order closed.
A stopped DefaultDependencies=no unit in the transaction is refused (Via::
EarlyBoot): rescue.service ends by isolating default.target, which no edge
describes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:26:29 +01:00
Meta Vens
d9710c988a Steward orders: the ledger reads an order only under its own schema too
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 06:35:43 +01:00
Meta Vens
caacbb4bba Steward orders: the end-to-end test waits 10 s for its posts
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 06:35:31 +01:00
Meta Vens
9653d270e5 Steward orders: pin the ledger schema guards, invocation ids and post backoff; tests fail fast instead of waiting
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 06:16:04 +01:00
Meta Vens
8a1beb5c87 README: an order is gated by the steward bound at execution; the socket is the steward's; root reads
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 05:35:30 +01:00
Meta Vens
7e15bf1a7b Steward orders: the Synapse deadline test fails instead of hanging when the deadline is gone
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 05:33:23 +01:00
Meta Vens
572f9e6aa4 Steward orders: the end-to-end test keeps serve's umask until it has read control.cc
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 05:31:34 +01:00
Meta Vens
56edad95c1 Steward orders: one steward gate for fresh and recovered orders; the orders unit leaks no group or umask; root reads; kernel-attested issuer; end-to-end serve test
execute refuses, with a record and a post, an order whose principal or issuing
uid is not the steward bound now, before grammar, scope and brake; the brake
is asked for the bound principal only. idunn-orders.service drops Group= and
UMask=: bind_socket gives the socket to the steward uid at 0600 and serve sets
Idunn's own umask, so a deploy order writes control.cc 0640. Root may show and
list, never order. The issuer unit is the first unit under system.slice, never
a name the peer chose below it. Synapse waits for a reply within one deadline.
due_posts takes the deploy-state reader so a finished deploy's post is tested.
Tests: the gate both ways, root, per-order posts, deploy result, intake and
Synapse timeouts, parse_serve, user_uid, socket and store modes, and the whole
service as root with a fake systemctl, systemd-run and homeserver.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 05:03:17 +01:00
Meta Vens
ca439a29ff Pin the systemctl show property list and unquoted backslashes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 04:17:12 +01:00
Meta Vens
27ca875194 Steward orders tests: clear four clippy hits in the module
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 03:53:41 +01:00
Meta Vens
be3b95bc21 Import BTreeMap for the transaction facts cache
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 03:50:20 +01:00
Meta Vens
cea17c06a7 README: a unit order is refused by the systemd transaction; a package order installs only what it names
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 03:49:36 +01:00
Meta Vens
ec6222e2a1 Steward unit orders: refuse by the systemd transaction, install exactly the named packages
An order is refused when the closure systemd would build for it reaches an
always-refused unit: a start pulls Requires/Requisite/BindsTo/Wants/Upholds and
stops Conflicts/ConflictedBy, a stop takes down RequiredBy/BoundBy/RequisiteOf/
ConsistsOf/PropagatesStopTo/StopPropagatedFrom, a restart does both. A refused
unit already running is left alone by a start. Names are unquoted from
systemctl show; a unit systemd cannot show fails the order.

Package installs run apt-get --no-remove with ?exact-name(<name>) per package
and a final dpkg -s step that fails unless every named package is installed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 03:49:26 +01:00
Meta Vens
7d5cd85728 Reword a test comment so the no-brake-release tripwire reads clean
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 02:33:12 +01:00