Commit graph

591 commits

Author SHA1 Message Date
Meta Vens
74a700817f Research: restart budgets and rollout/restart handover prior art (idunn-reincarnation forks)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:22:33 +02:00
Meta Vens
f54c776417 Reincarnation map: body facts from the 2026-10-18 reboot, model page, cut sequence
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:17:41 +02:00
Meta Vens
c2477d43d4 Reincarnation target: a reboot is not a fault, one owner per target at every instant
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:51:31 +02:00
Meta Vens
6667f0f9a6 Watchdog reporting postmortem
First typed Eureka campaign (idunn-watchdog): 8 cuts, 17 Soul verdicts,
55 findings; landed about 2.5x its estimate. Idunn pages the operator on
Discord through Bifrost; closure DM still owed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:25:36 +02:00
Meta Vens
cb6096512b Merge watchdog idunn-topology-lock: no tick waits on another uid's lock
Eureka idunn-watchdog cut idunn-topology-lock, attempts 1-2. Soul verdict
idunn-watchdog:verdict:cut-idunn-topology-lock.s2 holds. Topology and
incident writes share one private-lock exchange that replaces a non-private
lock by rename; a contended publish stays in phase and retries. Measured
tick with a held lock: 0.09 s (was 5.07 s).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:59:15 +02:00
Meta Vens
424300d672 A publish that loses to a held lock is retried before the fence, not aborted
resume_candidate turned any failed pre-Fencing step into a pre-fencing
abort, so a topology publish that met a held lock (the nightly backup's
shared lock, an operator) ended the deploy or continuity restart on its
first attempt. LockContended is now a typed error (drivers::LockContended,
raised by mutate and IncidentStore::exchange with the same texts as before)
and resume_candidate's one classification keeps such a transaction in its
phase with last_error set; the backoff retries it and the phase deadline
bounds the wait. Other publish errors still abort.

Tests at the resume path: a contended Starting publish stays in Starting
across ticks and lands after release with one continuity restart; a
non-contention publish error still aborts. Both builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 03:31:48 +02:00
Meta Vens
827cfad0bb Topology lock: test the lock-creation error classes and the create race
Mutation of the first cut left make_lock_private's error classification and
lost-create-race branch unkilled. Two behavioural tests at the primitive:
an uninspectable lock (name one byte past NAME_MAX) reports an inspection
error, an uncreatable one (procfs) a creation error, and neither is retried
as a lost race; eight publishers released together to create the same lock
all find it private, so none fails for having lost.

Tests only; builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 02:05:05 +02:00
Meta Vens
102c129891 Topology publishes never wait: one private-lock exchange primitive
exchange_behind_private_lock (drivers.rs) is the single write primitive for
the projection directory: one nonblocking compare-exchange behind a lock only
Idunn can hold. An absent lock is created O_EXCL 0600; a lock that is not a
regular file, ours, mode 0600 is replaced by a fresh 0600 file renamed over it
(a chmod does not revoke a descriptor another uid already holds); a private
lock is opened as is. The topology driver's mutate and the incident store both
go through it; the blocking compare_exchange_snapshot call, publish_projection_mode
(which published the lock 0644 on a false premise that readers open it) and
incident.rs secure_lock are deleted.

mutate now retries only a lost race; a held lock is Err "the topology
projection is locked by another holder" and a no-op, so no caller records a
digest, evidence or phase for a publish that did not land.

Test helpers: the lock holder is a separate flock process, and run_while_held
counts opens of the lock (inotify), so ANY wait on a held lock fails, not only
one past the 30 s ceiling. Tests reproduce the live default ACL and use
setpriv for the other-uid half.

This commit has not been built; the verify run follows.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 01:28:05 +02:00
Meta Vens
a909f931ea Merge watchdog idunn-incident: continuity exhaustion is a durable incident
Eureka idunn-watchdog cut idunn-incident, attempts 1-3. Soul verdict
idunn-watchdog:verdict:cut-idunn-incident.s3 holds. Incidents live in
their own incidents.cc (0644, lock private 0600), written with non-blocking
snapshot exchanges; the tick never waits on them. Close rule: running and
no longer exhausted (operator ruling exhaustion-close-rule-op).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:15:08 +02:00
Meta Vens
7e4163645d Watchdog target: tick-lock-private is uid-scoped
Operator ruled tick-lock-scope = uid-scoped (target r4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:54:55 +02:00
Meta Vens
8d0786dcaf Watchdog map: topology lock probes C29-C40
Only the daemon writes topology.cc; Odin and Idunn read it lock-free; the
root backup's shared lock survives a private 0600 lock. A chmod does not
revoke an existing holder, so the shared primitive renames a fresh 0600
lock over a non-private one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:30:44 +02:00
Meta Vens
9549d00fb9 Incident lock created 0600 before CultCache opens it; fault keys per decision site; no wall-clock bounds in lock tests
lock-acl-world-openable: CultCache creates a lock with mode 0666, so under
Yggdrasil's default ACL (u::rw g::r o::r) the incident lock landed 0644 and
any uid could hold it and keep every incident from being recorded. Every
incident write (incidents.cc and incident-history.cc) now goes through
exchange(), which first creates the lock with O_CREAT mode 0600 (the create
mode is intersected with the ACL, so 0600 holds and no wider file ever
exists) and fchmods an earlier, wider lock to 0600 on the descriptor it just
opened. The test sets the live default ACL on its directory, asserts a
control file made the way CultCache does reads 0644 there, asserts both
locks are 0600 after open, close and retire, and, as root with setpriv,
that uid 65534 cannot open them.

close-fault-repeats: record_incident keys its fault by operation and subject
(open/close per target, * for the reconcile and retire passes), so one
site's Ok no longer clears another site's standing fault.

mutation-count-poisoned: a_held_incident_lock_changes_nothing_continuity_decides
failed under parallel load because run_while_held asserted the work took
under 2 s of wall clock, and the work is a whole continuity sequence. The
holder now lets go when the work returns, or after a 30 s stuck ceiling;
the test fails only if the work was freed by the ceiling. eventually() and
the control-plane retry use the same ceiling.

Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 23:13:52 +02:00
Meta Vens
4ac96e959d Route connect test: the listener never accepts, so the test cannot hang
route_connect_retries_connection_refused_until_listener_is_ready blocked in
accept() with no deadline. If the client connected to another listener on the
reused port, accept() waited forever and the whole verify job held its slot
for 39 minutes. The kernel completes a handshake from a listener's backlog,
so the connect succeeds without an accept: the listener now only outlives the
connect, waiting on a channel with a 30 s ceiling. A lost port race fails
the test instead of hanging it.

Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 23:13:52 +02:00
Meta Vens
bca3d85394 Watchdog target: no tick waits on a lock someone else can hold
Operator ruled the topology-lock stall into this campaign (target r3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:09:34 +02:00
Meta Vens
b69a8e81ba Secrets map: Heimdall secrets merged at ca2366c after Soul pass 7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:49:57 +02:00
Meta Vens
ddc031a363 Merge B5 test pins: no firewall withdrawal over an incumbent, exact expiry filter, backoff growth
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:37:51 +02:00
Meta Vens
f1b45666c6 Route map: B5 merged (fcb8ba1) and installed on Yggdrasil
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:36:02 +02:00
Meta Vens
1cfbeff083 B5 pins: firewall kept for a live incumbent, whole-id request match, backoff growth
Tests only, from Soul's last B5 check:
- F2: a rollback or withdrawal back to an incumbent route deletes no
  firewall rule (S-R2 survived).
- F3: expiry requests whose ids are a strict prefix of the transaction's
  command id, or extend it, leave it untouched (S-ME1, S-ME2 survived).
- F4: the second and third failures wait two and four polls, as back_off
  writes them (S-MJ1, S-MJ2 and a clamp survived).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:30:18 +02:00
Meta Vens
fcb8ba1c29 Merge B5: every post-fencing phase ends; reload skip confined to supervision
Phase deadlines are resolved before every post-fence step; `idunn expire` requests an
expiry through the same resolver; lease adoption is recorded from Odin's Ready; an abort
withdraws any issued lease (Prepared or Granted); install/rollback/withdrawal inside a
transaction always write and reload; a first-deploy withdrawal closes the firewall rule
before reloading. The operator-required arm for an adopted-lease deploy stays until the
Q3-ii snapshot cuts (Q-S5: B5 first).

Soul passes 1-3. Merged tree verified on Yggdrasil: 360 passed, 2 ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:28:52 +02:00
Meta Vens
7793b5bb89 Merge: retire the legacy bifrost-persona-feedback deploy route
The route ran the gamecult-ops compose lane, which would start a container beside the native
unit. Operator: "delete the Bifrost lane". Grant, dispatch and the Bifrost-authority check that
served only it are removed; bash -n and visudo -cf pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 21:50:03 +02:00
Meta Vens
889e23629c Retire the legacy bifrost-persona-feedback deploy route
The operator ruled "delete the Bifrost lane". This route was that lane's live
trigger: /srv/odin/deploy-manifests/bifrost-persona-feedback runs
`docker compose up persona-feedback`, and the provider actually running on
Yggdrasil is the native bifrost-persona-feedback.service. Firing the route
would start a container next to it.

Following deploy/legacy's own order, the grant goes first, then the dispatch:
- sudoers: drop `deploy bifrost-persona-feedback *`.
- idunn-yggdrasil: drop the target's policy case and its dispatch token, plus
  the Bifrost release-authority check that only it used (requires_bifrost_authority
  and the `idunn validate-release-authority` call). A deploy request that claims
  a Bifrost authority is still refused. The manifest environment keeps
  IDUNN_REQUIRES_BIFROST_AUTHORITY=false, because the installed Epiphany manifest
  checks for it.
- README: record the retirement and the counts. Also record that on 2026-09-30
  the installed actuator matched this file, but the sudoers grant was not
  installed: the idunn user cannot sudo.

Rebinding persona-feedback to a current Idunn binding is a separate follow-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 21:40:21 +02:00
Meta Vens
85964d0f00 Secrets map: Heimdall batch 6 and Soul pass 6 (hold; invalid index, collation takeover, predicate normaliser)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 20:35:30 +02:00
Meta Vens
f85e74377f B5 batch 3: firewall first on a withdrawal to nothing; four test pins
`restore` with no prior membership now withdraws the stable endpoint's
firewall allow before it writes the fragment and reloads. A failing `ufw
delete` retries without touching nginx, which bounds the withdrawal reload
loop at zero until the Q3-ii cut gives the abort a deadline. A crash between
the two steps is safe: a rule already gone reads as withdrawn, and the retry
removes the fragment and reloads.

Pins from Soul pass 2:
- MB2: a redeploy over an existing incumbent route installs, rolls back and
  withdraws with a reload each (`changes_route` as `is_none()` survived).
- ME2/ME3: the resolver's command filter is tested with an id of equal
  length and the same leading bytes.
- MJ2: the backoff test asserts the wait `back_off` writes, one poll after
  the failure.
- Soul's F2 probe returns as a test: a withdrawal whose `ufw delete` keeps
  failing reloads nginx not at all over 20 retries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 20:08:58 +02:00
Meta Vens
1f7c8e8f42 Route map: B5 Soul pass 2 (merge and install after batch 3 pins); P1 reload-owed window recorded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 20:07:49 +02:00
Meta Vens
3ed5f262ba Incident lock tests retry after release; a forked child can hold a released flock
A test that held incidents.cc.lock and then released it failed once in a full
parallel run: another test's forked child briefly shared the open file
description. The follow-up calls are exactly the "retried on a later pass" the
store promises, so the tests now retry for up to two seconds instead of
asserting the first call after release lands.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 19:59:41 +02:00
Meta Vens
9ad7e4209b Incident retirement test: the archive-first crash state is the designed one
A refused delete after a successful archive leaves the record in both files by
design; the test asserted the history file was absent. It now asserts the repeat
archives nothing twice.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 19:01:30 +02:00
Meta Vens
32cf1f7bf4 Incident writes never wait; exhaustion closes on health and the window; one retention predicate
Every incident write is one nonblocking try_compare_exchange_snapshot of the
whole file against the snapshot it was decided on (as drivers.rs does for the
write lease). A held lock or a lost race is an error and a no-op for the tick,
retried on a later pass; it is also what makes one open incident per
(condition, subject) real. The incident .lock is no longer published: readers
of incidents.cc take no lock, so only Idunn can hold it. History is written the
same way.

An exhaustion incident closes as recovered only when the workload is observed
running and restarts_exhausted is false (ruling exhaustion-close-rule), so a
release that survives one pass after each restart keeps one incident.

Reconcile closes only incidents whose condition ends with admission and whose
subject is not admitted; its dead accumulator is gone. IncidentRecord owns the
retention predicate that retirement and status both use. status writes to a
writer and serve's loop is serve_while, so tests reach both.

Not verified yet: this commit has not been built.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 18:56:33 +02:00
Meta Vens
aef0da34af B5: the Odin adoption test steps through sequence admission
AwaitingReady admits a new Odin sequence in one step and records Ready from
it in the next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:51:17 +02:00
Meta Vens
dcd0aa3f2b Secrets map: Heimdall batch 5 status and Soul pass 5 (hold; batch 6 in Hands)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:50:45 +02:00
Meta Vens
8b9e8dfe11 Route map: Q-S5 ruled (a), B5 first
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:42:51 +02:00
Meta Vens
224f006983 Route map: Q-S1..Q-S4 ruled (a); Q-S5 open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:42:42 +02:00
Meta Vens
64da79402a Deterministic backoff test (B5 Soul J)
`a_failing_step_is_retried_after_a_backoff_not_every_tick` raced the
wall-clock wait against ten ticks and lost on a loaded host (9 of 15
mutant runs). The test now owns the wait: held open, ten ticks retry
nothing; once due, the next tick retries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:35:48 +02:00
Meta Vens
c04f24f518 B5: pin lease adoption from Odin's Ready word (B5 Soul F)
A stateful candidate Odin reports on is warming until it holds the lease;
the world's fixture now says so, and a post-fence warming word lets the
grant happen. Odin's Ready carrying the granted lease records the adoption,
sourced `OdinTopology` and naming the presence Odin observed. Kills Soul's
M6 (the source labelled `Direct`) and M7 (no adoption from Odin).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:35:48 +02:00
Meta Vens
e0b31eee76 B5: pin the resolver's command filter (B5 Soul E)
With two live transactions, an expiry request for one leaves the other
running. Soul's mutant "any request expires every live transaction"
survived the suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:34:48 +02:00
Meta Vens
47067cce08 B5: an abort withdraws a lease issued as Prepared (B5 Soul A)
The physical grant lands before `Granted` is durable. A continuity that
died in that window and passed its deadline was aborted from `Prepared`,
its abort withdrew only a `Granted` lease, and the lease left on disk made
every later fencing of the target refuse "unexpected process write lease".
The abort now withdraws any issued lease; revoking an absent one is a no-op.

Test: a granted lease on disk behind a `Prepared` record, deadline passed:
the abort removes it and the next fencing's revoke succeeds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:34:30 +02:00
Meta Vens
4ee70f90ba Route driver: transaction paths always write and reload (B5 Soul B)
`6ff7d5a` skipped the write and reload in `install` and `restore` when the
fragment on disk already equalled what they would write. On a transaction
path that hides a crash between a write and its reload: an abort that
retries after the crash reloads nothing and then stops the candidate, so
nginx keeps proxying to a dead port, and a fenced install skips every retry
(and a first deploy's `ufw allow`) until its deadline. The skip on equal
bytes goes back to supervision only.

What stays: a transaction whose candidate membership is the preflight's
baseline (a continuity over its admitted route) has no route change to make
or undo, so its install, rollback and withdrawal run nothing. No write of
its own can have been cut short. `RoutePreflightReceipt::changes_route`
names it.

`restore_admitted_membership` now puts back the bytes it replaced when its
reload fails, instead of deleting the fragment, so the disk still shows
what nginx serves.

Tests: a crash between a write and its reload is reloaded on retry (install
past the fence and withdrawal, driver level; the abort, engine level); a
transaction that does not change the route runs nothing. Soul's F2 probe
test is gone with the rule it pinned: a withdrawal whose `ufw delete` keeps
failing reloads on every retry again, until the Q3-ii cut gives the abort a
deadline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:33:57 +02:00
Meta Vens
6854b0f825 Route map: B5 Soul pass 1 (merge after batch 2: lease wedge, reload-skip crash gap)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:29:56 +02:00
Meta Vens
5f232a6120 Route map: Q3-ii cut map (snapshot before Ready, roll forward after); Q-S1..S5 open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:16:06 +02:00
Meta Vens
3fad441917 Route map: Q3-ii ruled: snapshot/restore before Ready, roll forward after
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:56:54 +02:00
Meta Vens
e00ff5ec40 Route map: Q3-i ruled (issued lease counts as possibly written); Q3-ii open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:54:06 +02:00
Meta Vens
448f554af5 Watchdog map: Bifrost suite probes C21-C28
Only cultcache-ts was renamed in CultLib; Bifrost's tests and three tools
still use the bare name. Production Persona delivery is unaffected: its
release bundles CultLib f67f5122.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:15:38 +02:00
Meta Vens
c06ff2b84c B5: the expire test asks beside another target's live deployment
A terminal record leaves the live set, so it could not tell the verb's own
command filter from a looser one; another target's deployment in flight can.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:56:02 +02:00
Meta Vens
855ccf8247 Pin the incident retention and the status usage; keep transport names out of src
The retention constant and the usage line survived mutation untested. The
incident module doc named the delivery service, which the no-transport grep
over src/ must not find. Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 16:53:01 +02:00
Meta Vens
6394100a93 B5: pin the idunn expire verb as the CLI parses and runs it
Kills three cargo-mutants survivors: the --requested-by arm, run's
dispatch, and expire's own-live-transaction filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:51:06 +02:00
Meta Vens
7f7528b33e Add the idunn.operator_incident.v1 wire fixture
One open and one closed record, written by IncidentStore on Yggdrasil under
IDUNN_WRITE_FIXTURES=1 and read back by the_incident_fixture_is_what_idunn_reads.
Bifrost's reader tests copy this file. Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 16:23:57 +02:00
Meta Vens
202e5a2e10 Idunn records continuity exhaustion as a durable incident in incidents.cc
Idunn's daemon, at the continuity decision site, writes one
idunn.operator_incident.v1 record per exhaustion into its own published
incidents.cc (--incident-store), never control.cc. The record opens once per
(target, condition), closes on an observed running workload (recovered) or on
the target leaving the admitted set, and retires to incident-history.cc seven
days after closing. A store failure is traced once and never returned into the
tick. idunn status prints each target's incidents. ReportOnce is demoted to a
stderr trace.

Does not build the fixture test until tests/fixtures/idunn.operator_incident.v1.cc
lands in the next commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 16:21:02 +02:00
Meta Vens
436d6445fc Watchdog map: cut probes C1-C20; incidents live in their own incidents.cc
Imagination's cut-mapping probes and rationale. Self collapses the model
page's record and projection rows: control.cc refuses foreign types, so
incidents.cc is both the record and what Bifrost reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:12:18 +02:00
Meta Vens
487f4f14b8 Watchdog target: a closure notice follows an opening notice
Operator ruled closure-notice = opening-and-closure (target r2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:58:14 +02:00
Meta Vens
153af6ac40 Watchdog reporting map: body facts, model page, crossing rationale
Imagination step 0b for campaign idunn-watchdog. Forks are admitted in the
mind as questions delivery-crossing, paging-conditions, closure-notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:57:34 +02:00
Meta Vens
4830af8eb1 B5: a continuity over its admitted route reloads nothing while its proof fails
The live shape of Soul's F1: a continuity reuses the admitted Expected, so
its install and rollback find the admitted fragment already on disk and run
no host program, before or after its Routing deadline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:50:55 +02:00