Map: B2 rework plan, D1/T1/T2/W1/R1 cuts, and the 2026-09-30 rulings

Splice the B2 rework map (owner map, per-file cut, verification, live
relevance, subtraction estimate) under B2; add cuts D1, T1, T2, W1 and R1;
record Q-B2-1 (a), Q-B2-2 (fail at once), Q-R1 (a) and Q-B4 (routed by
Idunn's route proof, unrouted by admission plus workload; the Odin non-Ready
veto clause held open). B4 is blocked on the Odin-lifecycle authority audit.
B2, D1, R1 ready for Hands; W1, T1, T2 in progress on hands/idunn-w1-t1-t2.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-29 23:44:25 +02:00
commit 97fb2b8871

View file

@ -193,7 +193,7 @@ it for merge.
- B2 also does not compile on top of B3, and after a one-line fix two tests fail, because the fixture target
is now route-proof.
The rework runs after B3 merges and is rebased onto it.
The rework is a fresh branch from `583b2a7`, not a rebase of `1786ddf`. Its map is under B2 in section 4.
**Rulings, 2026-09-30.**
- **The ceiling never refuses a rollback.** Restoring the admitted or incumbent route is survival, not
@ -203,6 +203,27 @@ The rework runs after B3 merges and is rebased onto it.
admitted generation, so a target with no admitted generation is charged too. That schema change is folded
into the store-version bump that F1 needs: a v4 generation, with a typed lift from v3.
**Rulings, 2026-09-30 (operator, on the B2 rework map).**
- **Q-B2-1 (a):** the continuity restart log moves into `TargetSupervision`; the restart ceiling reads the
target's own log; the carry compensator is deleted.
- **Q-B2-2: fail at once.** A deploy refused by the route/actuation ceiling before the fence fails immediately,
with the reopen time in the error.
- **Q-R1 (a):** the route declaration is the challenge declaration. Every routed target answers the
stable-route challenge regardless of readiness class. R1 checks it before the fence; H1 gives Heimdall a
responder. Operator note: the question's first framing was confusing; the cause is that Heimdall has no
responder, not its class.
- **Q-B4: Idunn's own proof.** Routed providers by current route proof and not degraded; unrouted providers by
admission plus a live workload observation. One clause is **held open**: "a current Odin non-Ready word
still excludes a provider" is not ruled, pending the Odin-lifecycle authority audit (a separate Imagination
pass). Operator: "the last few rulings on Odin are making my authority sense tingle, it feels like Odin is
doing lifecycle work when that's very much Idunn's wheelhouse." B4 is blocked on that audit.
**Cut status, 2026-09-30.**
- **Ready for Hands:** B2 (rework map in section 4); D1 (after B2); R1 (after B2).
- **In progress** on `hands/idunn-w1-t1-t2`: W1, T1, T2.
- **Blocked:** B4, on the Odin-lifecycle authority audit; H1 (Heimdall repo), on CultLib C1.
Heads read for this map:
| Repo | Ref | Notes |
@ -571,6 +592,97 @@ it. This is a CultLib foundation merge, so the operator should rule on it.
Depends on it: C1 and the StreamPixels gitlink in C2.
**Q-B2-1. Does the continuity restart log move into `TargetSupervision` too?**
**RULED (a) by the operator, 2026-09-30.**
Options:
- (a) Move it. `ContinuityBackoff`'s own doc (`:907-908`) already says it "belongs to the target, not the
generation". On the generation it survives only through the copy in `from_transaction` (`:2074-2076`).
Moving it deletes that carry and key 20. The mint CAS pins the target record instead of racing the
generation, which is the F3 shape. One record holds both of a target's meters.
- (b) Keep it on the generation, reshaped to the sliding log in place. The route meter still needs
`TargetSupervision` for first deploys, so the meters split across two records and the carry stays.
Outcome: the continuity restart log lives in `TargetSupervision`; the restart ceiling reads the target's own
log; the carry compensator in `from_transaction` is deleted.
Depends on it: B2 (see the B2 rework map in section 4).
**Q-B2-2. When the route ceiling refuses a first or forward deploy before the fence, does `idunn up` fail or
wait?**
**RULED: fail at once, by the operator, 2026-09-30.**
Options:
- (a) Fail. The deploy is aborted pre-fence with "route actuation ceiling reached for <target>; reopens at
<time>", and the command reports failed. The target is free at once.
- (b) Wait. A gate wait until `reopens_at`. The transaction owns the target for up to an hour, and nothing ends
that wait before B5's deadlines.
Outcome: a deploy refused by the route/actuation ceiling before the fence fails immediately, with the reopen
time in the error.
Depends on it: B2.
**Q-R1. Does a routed target answer the stable-route challenge whatever its readiness class?**
**RULED (a) by the operator, 2026-09-30.** The question's first framing was confusing. The cause is that
Heimdall has *no responder*, not its readiness class.
Mechanism (read at `583b2a7`): readiness class decides Warming, Ready and whether Routing reads Odin. It never
decides whether the stable route is challenged. Routing installs and then requires `prove_stable_route` for
every target with `expected.route` (`:6147-6190`), Commit proves it again (`:6272-6290`), and supervision
challenges every routed admitted generation (`:5171-5270`). Heimdall's recipe (`d5acc94`) declares
`odin.verse-rendezvous` (Odin-correlated) and is routed, but never answers a route challenge. Heimdall is
stateful, so a failed proof leaves the candidate route installed for fail-closed retry (`:6171-6175`), which
past the fence is resumable forever (F17) until B5. Its first `idunn up` would hold the target with its lease
granted, and the failure would surface at the most expensive point.
Options:
- (a) Yes. The route declaration is the challenge declaration. That is today's rule, made legible and checked
before the fence (cut R1). Heimdall gains a responder in owning-repo cut **H1**, the same shape as
StreamPixels C2: bump `vendor/CultLib` to at least C1 (`30ee8b9`), answer `POST /cultnet/snapshot` through
the signer's `answerRouteObservation`, and make `reportHealth` its single health owner. Idunn gains no
schema change, and every routed provider keeps a signed route proof, which Q2(b)/B4 currency needs.
- (b) No, the class decides. An Odin-correlated routed target is admitted by membership only. That needs a new
`RoutingEvidence` variant (a transaction and generation schema bump), class branches at Routing, Commit
and supervision, and loses the proof that the admitted process answers through the stable listener. It also
applies to streampixels-service, whose provider currency would then come from Odin, which is the C2 run 2
failure made permanent and contradicts Q2(b).
Outcome: every routed target answers the stable-route challenge regardless of readiness class. R1 checks it
before the fence. H1 (Heimdall repo, needs CultLib C1) gives Heimdall a responder.
Depends on it: R1, H1, and Heimdall's first deploy.
**Q-B4. For an admitted, running provider, what makes it current enough to satisfy a dependent's deploy?**
**RULED in part by the operator, 2026-09-30: Idunn's own proof. One clause is held open.**
Options:
- (a) A live readiness proof from the provider's readiness authority. Odin-correlated providers need a current
Ready correlation from Odin, so with Odin down or confused no dependent deploy is possible and C2 run 2
fails by design.
- (b) Idunn's own current observation, whatever the class.
- (c) Admission plus workload alive, for every provider, with no current readiness evidence.
Ruled, from (b):
- A **routed** provider is current by Idunn's route proof, and not degraded (B2's Q5(a) state). This is Q2(b)
as ruled, confirmed to override class.
- An **unrouted** provider is current by its admission plus a live workload observation of the admitted
incarnation.
- Capabilities come from the signed presence in the route proof (routed) or the admitted Ready receipt
(unrouted).
**Held open, NOT ruled:** the recommendation's clause that "a current Odin non-Ready word still excludes a
provider" (a stale one would not). The operator: "the last few rulings on Odin are making my authority sense
tingle, it feels like Odin is doing lifecycle work when that's very much Idunn's wheelhouse." That clause stays
open pending the Odin-lifecycle authority audit, a separate Imagination pass. Until it is ruled, B4 carries
no Odin-veto rule either way.
Depends on it: B4, which is now **blocked on the Odin-lifecycle authority audit**, not on Q-B4.
---
## 4. Draft cut list
@ -702,11 +814,11 @@ terminal records.
- Errors are isolated per transaction, including `archive_terminal_transaction`.
- `serve`'s boot wiring is pinned by a test.
**Moved to B2:** the continuity restart ceiling reads only `ContinuityBackoff`, never
`history.cc`. Otherwise an unreadable history file stops crash recovery for every target.
Deferral and backoff show in `idunn status`.
**Moved to B2:** the continuity restart ceiling reads only the target's restart log in
`TargetSupervision`, never `history.cc`. Otherwise an unreadable history file stops crash recovery for every
target. Deferral and backoff show in `idunn status`.
**B2 — Bounded, backed-off route and unit repair (Idunn).**
**B2 — Bounded, backed-off route and unit repair (Idunn). READY FOR HANDS; rework map below.**
*Widened 2026-09-29 by Self, from Soul's S1 pass.* S1 closed the healthy-target storm, and
Soul found two storm paths S1 does not claim. Both are B2's:
@ -730,16 +842,559 @@ and `preflight_root` on `RuntimeOptions`, defaulting to today's paths and used a
marks the route degraded (Q5 a). Today nothing is written and the next tick re-challenges
with no backoff.
- Challenges back off exponentially per target on consecutive failures, with a
floor at max age and a cap.
- Route actuations (write, reload, ufw) go through a per-target rolling
ceiling kept in route supervision state, so the ceiling survives restarts.
- Continuity restarts go through a per-target backoff and ceiling that is not
reset by a new generation (F9).
- Proof failure changes observation state only (Q5a).
- Verification: a timeline probe on Yggdrasil counts reloads per hour per
target in both steady and failing states. It must show zero reloads when
steady and no more than the ceiling when failing.
**Rework map, 2026-09-30 (Imagination pass, Opus).** Every `file:line` in this block is against Idunn
`583b2a7` (the code under main `94c7691`, installed on Yggdrasil). The salvage source is
`origin/idunn/route-b2` (`97f1ad2`, +1243/-200; merged with B1 at `1786ddf`). The rework is a fresh
branch from `583b2a7`, not a rebase of `1786ddf`. Rulings Q-B2-1 (a) and Q-B2-2 (fail at once) are in
section 3.
#### B2 rework, A. What B3 superseded, and what survives from `97f1ad2`
**Delete; do not port. B3 or this rework owns each of these now:**
- `routed_generation`, `hanging_up_listener`, `RoutedFixture`, `routed_transaction`,
`store_seeded`, the `seeded_record` split, `route_proof_evidence()` hand evidence, and
`DeadWorkload`.
- B3's `RoutedWorld` (`control_plane.rs:14255-14660`) already builds a real route-proof
admission, with a `RuntimeStub` at both endpoints and a `hang_up` switch (`:14068`).
- `SwitchWorkload::kill` (`:11932-11945`) is the dead workload.
- These are why B2 "does not compile on B3, and two tests fail because the fixture target is
route-proof".
- `Engine::update_admitted_generation`, the re-read-then-CAS helper. It existed because B2
charged the ceiling to the generation, so every other writer had to re-read to avoid losing
the charge. The meter moves off the generation (section B), so writers CAS on the envelope
they read. Tests use the existing `edit_incumbent` (`:12065`).
- `ActuationWindow::charge`: a fixed window anchored at the first charge. That is Soul's F4.
- The first-deploy exemption in `charge_route_actuation` (`let Some(current) = admitted_for
else return Ok(())`). The 2026-09-30 ruling removes it.
- `ContinuityBackoff.deferral_reason` added as a v3 field. That is Soul's F1. The reason moves
to the new record (section B).
**Port, reshaped as sections B-E say:**
- In `drivers.rs`:
- `RouteActuators` and its `Default`;
- `NginxRouteDriver::with_actuators`, with `new` delegating to it;
- the gate trait threaded through every mutating method;
- the `Unmetered` test gate;
- `a_refusing_gate_stops_every_route_actuation_before_it_starts`, split in two (section E).
- In `control_plane.rs`:
- `RuntimeOptions.route_actuators`;
- `Engine::route_driver`, used at every construction site;
- `EngineRouteGate`;
- the `RouteStubs` fixture and `EngineFixture::routed`;
- `RouteSupervisionState::{is_waiting, record_failed_challenge, record_proved_challenge}`;
- the `supervise_admitted_route` restructure, where a failed repair or proof records state;
- `render_supervision` plus the status loop;
- the continuity ceiling replacing the history count;
- the restart counted in the minting CAS;
- the tests `continuity_restarts_are_backed_off_and_bounded_per_target`,
`status_renders_…`, `a_deployment_yields_to_a_restart_only_while_the_target_has_restarts_left`,
and the storm tests, rebuilt on `RoutedWorld`.
- Constants, kept from B2 because Soul did not dispute them:
- `CONTINUITY_RESTART_ATTEMPTS = 6` per `CONTINUITY_RESTART_WINDOW_MILLIS = 3_600_000`;
- `CONTINUITY_RESTART_BACKOFF_MILLIS = 5_000`, doubling;
- `ROUTE_ACTUATION_CEILING = 12` per `ROUTE_ACTUATION_WINDOW_MILLIS = 3_600_000`;
- `ROUTE_CHALLENGE_BACKOFF_CAP_MILLIS = 600_000`.
#### B2 rework, B. Owner map (ownership changes)
**Route actuation rate.**
- **Owner:** `Engine::charge_route_actuation(target, RouteActuation)`, over a new per-target record,
`TargetSupervision.route_actuations`.
- **Inputs:** the target's record from a fresh control snapshot (absent means empty), `now`, and
the kind: `Forward` or `Survival`.
- **Output:** `Ok(())`, or the typed `RouteActuationRefused { target, used, reopens_at }`.
- Only `Forward` can be refused.
- `Survival` is always recorded and always admitted (ruling: never refuse a rollback).
- **Demoted:**
- `RouteSupervisionState.actuations` is dead, deleted in v4.
- `RouteSupervisionState::for_new_incarnation` (`:885-893`) is dead. A new incarnation's
route state is `default()`.
- **Forbidden writers:**
- the driver, which only asks;
- `AdmittedGeneration::from_transaction`, which no longer carries a meter;
- the commit CAS.
- **Shared paths:**
- Every `NginxRouteDriver` is built by `Engine::route_driver`. The seven `::new` sites are
`:5211`, `:5701`, `:6151`, `:6280`, `:6985`, `:7323` and `:7652`.
- Every mutating driver method takes `&dyn RouteActuationGate`, so none can be called
without the meter. The compiler enforces it.
**Continuity restart pacing** (Q-B2-1 ruled (a)).
- **Owner:** the mint in `supervise_one_admitted_generation`, over
`TargetSupervision.continuity_restarts` and `continuity_deferred_until`.
- **Forbidden writers and inputs:**
- `history.cc`: the `history_for_decision` call at `:5003` goes;
- the generation id: the count is no longer per generation;
- `from_transaction`'s backoff carry at `:2074-2076`.
- **Demoted:** `AdmittedGeneration.continuity_backoff` (key 20) is dead, deleted in v4.
**Route challenge pacing** stays per incarnation, on the generation.
- **Owner:** `supervise_admitted_route`, over `RouteSupervisionState` without `actuations`.
- `route_repair_started_at_unix_millis` (key 18) is dead, deleted in v4.
- Its last readers are the validate block at `:2139-2145` and the unrouted guard at
`:5173-5176`.
- Its last writer is the clear at `:5263`.
**One generation write per supervision iteration** (structural fix for F3). Any write to a
target's generation or to its `TargetSupervision` ends that target's iteration:
`progressed = true; continue`.
- `refresh_admitted_topology` already follows this rule (`:4827-4835`).
- The route challenge is the violator. Today a failure propagates as `Err` after B2's state
write, and the restart path then CASes a stale envelope.
**New persistent type, `TargetSupervision`** (`idunn.target_supervision`, schema v1, in
`control.cc`, keyed by target).
- **Owner:** the meter primitives above.
- **Live consumers:**
- the route gate (every install, preflight, rollback, withdraw and repair);
- the continuity mint;
- `status`.
- **Invariant:** a target's meters outlive every generation and exist before the first one.
- **Why no existing owner can serve:**
- The generation does not exist for a first deploy (the ruling).
- The transaction dies with each attempt.
- Brakes are operator-signed and are not Idunn's to write.
- **What it replaces:** two carry compensators, `for_new_incarnation` and the backoff copy in
`from_transaction`, plus a history scan.
#### B2 rework, C. Per file
The branch is fresh from `583b2a7`.
**Deletes first.**
`control_plane.rs`:
- `:63`: `CONTINUITY_RESTART_ATTEMPTS: usize = 3`. It is replaced by the constants in section A.
- `:876-893`: `ActuationWindow` and `for_new_incarnation`.
- `:906-926`: `ContinuityBackoff`. Q-B2-1 (a) moves it into `TargetSupervision`.
- `:1986-1988`: key 18.
- `:1992-1993`: key 20.
- `:2043-2047` and `:2074-2076`: the carries.
- `:2139-2145`: the key-18 validation.
- `:4974-5020`: the history-counted `is_refused_restart` and the `history_for_decision` read.
- `:5062-5071`: the deferral check on the generation.
- `:5173-5176`: the key-18 guard. Keep the `SkippedUnrouted` half.
- `:5263`: the key-18 clear.
Tests:
- `:9755-9801`, `continuity_gives_up_on_a_release_that_will_not_start`. It tests a copy of the
production closure, so it proves nothing.
- `:10564`, `:11528-11542`: the F0 assertions on `ActuationWindow` and `attempts`. Rewrite them
against `TargetSupervision::validate`.
- `:14622-14645`, `RoutedWorld::promote_by_hand`, and its 9 callers (`:14651`, `:14691`,
`:14722`, `:14767`, `:14927`, `:14949`, `:15041`, `:15299`, `:15433`). They become
`routed.step()`: the stub actuators let Routing run for real, so route-proof Routing gets
tested for the first time.
**Schema: generation v4, one bump.**
- `ADMITTED_GENERATION_SCHEMA` (`:55`) becomes `"idunn.admitted_generation.v4"`. The macro at
`:1939` changes to match.
- `:56`: add `ADMITTED_GENERATION_SCHEMA_V3`.
- The v4 layout:
- keys 0-17 are unchanged;
- 18 is a gap;
- 19 is `Option<RouteSupervisionState>` without `actuations`;
- 20 is a gap;
- **21 is `last_error: Option<String>`**, the dead-hold ruling's field. It is written by D1,
and B2 carries the slot so D1 needs no second bump.
- The cultcache derive encodes slots positionally, so a gap is a nil and any added slot changes
the array. That is why F1's unbumped field broke the canonical re-encode (`decode_record`,
`:2811-2821`), and why every change here rides one bump.
- **`LegacyAdmittedGenerationV3`**: move today's `AdmittedGeneration` (`:1936-1995`),
`RouteSupervisionState` and `ActuationWindow`, and `ContinuityBackoff` verbatim under
`Legacy*` names beside `LegacyAdmittedGeneration` (`:2967-3049`).
- Its `into_current()` drops key 18, `actuations`, `window_started_at`, `attempts` and
`next_restart_at`.
- It **refuses** a v3 record whose `actuations.count != 0` or `attempts != 0`.
- Why that is lossless (read): no released binary ever wrote those fields. On `583b2a7` the
only non-test writes are the carry at `:890`, which carries a default, and the ≤30 s
deferral `next_restart_at` at `:5154`. B2 was never installed.
- So the lift creates no `TargetSupervision`. Every target starts with an absent record, and
the refusal makes any future contradiction loud instead of silently dropped.
- The v2 lift (`:3016-3048`) targets v4 directly: the same body without the retired fields.
Keep it; section H explains why this is not a question.
- `read_generation_record` (`:3102-3121`): add a v3 arm.
- The migration filter at `:3147-3148` adds `ADMITTED_GENERATION_SCHEMA_V3`.
**New record, `TargetSupervision`**, beside `AdmittedGeneration`:
```rust
struct TargetSupervision { // idunn.target_supervision / .v1, key = target
#[cultcache(key = 0)] schema_version: String,
#[cultcache(key = 1)] target: String,
/// Newest ≤ ROUTE_ACTUATION_CEILING actuation times, ascending.
#[cultcache(key = 2)] route_actuations: Vec<u64>,
/// Newest ≤ CONTINUITY_RESTART_ATTEMPTS restart times, ascending. (Q-B2-1 (a))
#[cultcache(key = 3)] continuity_restarts: Vec<u64>,
#[cultcache(key = 4)] continuity_deferred_until: Option<u64>,
#[cultcache(key = 5)] continuity_deferral_reason: Option<String>,
}
```
The record threads through several places:
- `ControlSnapshot` (`:2564-2568`) gains `targets: Vec<Stored<TargetSupervision>>`.
- The `read` match at `:2580-2610` gains its arm. Without it, the match's "foreign document"
bail at `:2609` refuses the store.
- `validate_relations` checks unique targets.
- `target_supervision_envelope` goes beside `admitted_envelope` (`:3198`).
**Meters.** A sliding log, not a window (fixes F4):
- `charge(log, now, window, limit, kind)`:
1. Clamp any entry greater than `now` to `now` (fixes F5; see below).
2. Count the entries within `window`.
3. For `Forward`, if the count is at least `limit`, refuse with
`reopens_at = log[len - limit] + window`.
4. Otherwise push `now` and keep only the newest `limit` entries.
- Keeping the newest `limit` entries is exact for "at most `limit` Forward in any `window`",
even after Survival pushes past the limit.
- The restart log uses the same primitive. The next restart is due at
`last + BACKOFF << (n - 1)`, where `n` is the number of entries in the window.
**Clock steps.** Fixes F5, and collapses one helper.
- `is_waiting(now, not_before)` (`:3351-3353`) becomes `is_waiting(now, not_before,
longest_wait) = now < not_before && not_before - now <= longest_wait`.
- A due time further ahead than any wait this owner can set means the clock stepped back,
so the attempt is due.
- Callers:
- resume backoff at `:4537`, with `RESUME_BACKOFF_CEILING_MILLIS`;
- the route challenge, with `ROUTE_CHALLENGE_BACKOFF_CAP_MILLIS.max(max_age)`;
- the restart wait, with `BACKOFF << (ATTEMPTS - 1)`;
- the deferral, with `CONTINUITY_DEFERRAL_MILLIS`.
- The log clamp bounds a backwards step to one window, never the length of the step.
**`drivers.rs`.**
- Port `RouteActuators` from `97f1ad2` at `:5049-5068`.
- The trait becomes `RouteActuationGate::admit(&self, kind: RouteActuation) -> Result<()>`,
with `pub enum RouteActuation { Forward, Survival }`.
- Gate the methods:
| Method | Kind |
|---|---|
| `preflight` (`:5293`), before `validate_candidate_in_private_mount` at `:5315` | **Forward** (F7) |
| `install` (`:5338`), before the write at `:5360` | **Forward** |
| `restore` (`:5268`) and `fail_after_rollback` (`:5279`) | **Survival** |
| `withdraw_candidate_membership` (`:5409`) | **Survival** |
| `rollback` (`:5555`) | **Survival** |
| `restore_admitted_membership` (`:5420`), after the exact-bytes return at `:5430` | **Survival** |
- `install`'s own rollback, through `fail_after_rollback`, therefore charges a Survival even
when it follows a Forward.
**`control_plane.rs` Engine.**
- `RuntimeOptions` (`:2160`, `:2202`): add `route_actuators`.
- Add `route_driver` and `route_gate` as in `97f1ad2`.
- `charge_route_actuation`:
- reads a fresh snapshot;
- CASes the target's record, with `current: None` when absent (the first deploy);
- is typed as described above.
- **A refused Forward is an error of its phase.**
- Warming's preflight (`:5690-5711`) comes before the fence, so the refusal becomes a pre-fence abort
(`:4587-4593`) whose error names `reopens_at` (Q-B2-2 ruled: fail at once).
- Routing's install comes after the fence, so it stays resumable. The resume backoff spaces
it, and no actuation runs.
- Until B5 lands, a post-fence candidate whose install keeps being refused waits for the
window. That wait is bounded by the window, and it runs no programs.
**`supervise_one_admitted_generation` (`:4787-5145`)**, in order:
1. Hold (`:4960-4973`, unchanged).
2. Blocker yield (`:5025-5043`): `refused_restarts < ATTEMPTS` becomes
`!restarts_exhausted(now)`.
3. Exhausted: `eprintln` once through a `ReportOnce` keyed `continuity:<target>`, then
continue.
4. Waiting or deferred: continue.
5. Demotion (`:5072-5098`). On failure, write `continuity_deferred_until` and the reason to
`TargetSupervision`, then `progressed = true; continue`.
6. Lifecycle brake (`:5105`, unchanged).
7. Mint. The CAS at `:5114-5142` gains the `TargetSupervision` expected envelope, `None` when
absent, and its next value with `now` charged to the restart log and the deferral cleared.
A brake-parked target spends no attempt.
**`supervise_admitted_route` (`:5171-5270`).**
- At the top: if `route_supervision.is_waiting(now, cap)`, return `Ok(false)`.
- Port B2's challenge closure: the Survival-gated restore, prove, and re-observe.
- On `Err`:
- write `record_failed_challenge` to the generation, CAS on `current.envelope`;
- print once through a `ReportOnce` keyed `route:<target>`;
- return `Ok(true)`. This is the F3 fix.
- On `Ok`: write the receipt and `record_proved_challenge`, return `Ok(true)`.
- If the Survival repair itself charged `TargetSupervision`, that is a second record, not the
generation, so the generation CAS still holds. The iteration ends either way.
**`status` (`:3668-3756`).**
- After the command loop, print one block per target: the union of admitted generations and
`TargetSupervision` records.
- Port `render_supervision`, reading the restart and route logs from the target record:
- `route actuations N/12 in window, reopens-at …`;
- `continuity restarts N/6 … next-restart-at … deferred: <reason>`.
- A metered target with no generation prints its meters alone. That is the first-deploy case.
#### B2 rework, D. B2 verification
Each ruling or finding is pinned by a test that fails under its own mutation. All tests are
Engine-level on `RoutedWorld` with `RouteStubs`, `#[cfg(unix)]`, except where marked unit.
| Ruling / finding | Test | Mutation it must kill |
|---|---|---|
| Ruling: never refuse a rollback | `a_full_ledger_still_restores_the_admitted_and_incumbent_route`. The ledger holds 12 entries inside the window. (i) A post-fence abort's withdraw runs `systemctl reload` once and pushes an entry. (ii) Supervision repairs a drifted admitted fragment the same way. (iii) A Routing proof failure's `rollback` does too. | Survival treated like Forward (refused), which drops the reload; Survival not recorded, which drops the entry |
| Ruling: always counted | the same test, asserting the ledger's newest entry is `now` after each Survival | skipping the push for Survival |
| Ruling: first deploys metered | `a_first_routed_deploy_is_metered_and_refused_at_the_ceiling`. `RoutedWorld` seeds with no incumbent (`:14352`). (i) `run_to_routing` then one step creates `TargetSupervision` with 1 entry. (ii) A world whose record is pre-filled to 12: install fails with `RouteActuationRefused`, and the stub log has no `ufw`, `nginx` or `systemctl`. | the B2 early return "no admitted generation means unmetered" |
| F7 | `a_refused_preflight_starts_no_private_mount_unit`, a full ledger: the Warming preflight runs no `systemd-run` and the transaction pre-fence aborts with the reopen time | preflight's gate call deleted |
| F4 rolling | unit, `the_actuation_log_slides`: 12 charges at t, t+1…t+11. At t+window only one slot reopens (the 13th charge succeeds, the 14th is refused until t+1+window). | fixed window anchored at the first charge |
| F5 | unit, `a_clock_stepped_back_stalls_nothing_past_one_window`: log entries and due times at T, with `now = T − 1 day`. The Forward reopen is ≤ now + window; the route challenge, restart and resume waits read as due. | the clamp removed; the `longest_wait` guard removed |
| F3 | `a_failed_challenge_and_a_dead_unit_in_one_tick_do_not_lose_a_cas`. A drifted fragment, a `hang_up` stub and a killed `SwitchWorkload`. Tick 1 returns `Ok` and records the failure; tick 2 mints the restart. | a route failure returns `Err`, or falls through without `continue`: the mint CAS loses and the tick errors |
| F6 | `a_proved_challenge_clears_the_degradation`: `hang_up` on, one tick (degraded, failures 1); `hang_up` off, wait made due, one tick (failures 0, next None, degraded None) | `record_proved_challenge` call deleted |
| F1 | `a_v3_generation_lifts_to_v4_once_and_reencodes_canonically`, over **new golden fixtures** `generation-v3-odin.hex` and `generation-v3-route-proof.hex`, cut with the `583b2a7` encoder (add them to the fixture README). The migration counts 1, then 0. After it, `decode_record` accepts the bytes, and the decisions are equal apart from the retired fields. A v3 fixture edited to `attempts = 2` is refused. | the v3 schema dropped from the migration filter; the refusal removed |
| Restart ceiling reads no history | rename `unreadable_history_stops_continuity…` (`:13516`) as B2 did: a corrupt history still restarts, and the target's own log exhausts it | re-adding `history_for_decision` |
| Backoff and window | port `continuity_restarts_are_backed_off_and_bounded_per_target` on `SwitchWorkload` | halving the wait; resetting the log on a new generation |
| Storm (a) | port `a_failing_reload_that_deletes_the_fragment_backs_the_route_off`: 200 ticks give one reload | the `is_waiting` gate deleted |
| Storm (b) | port `a_candidate_whose_route_proof_fails_reloads_at_most_the_ceiling`: Forward installs are capped at 12 | the install gate deleted |
| Driver gates | split B2's refusing-gate test in two: Forward refused runs no program; Survival under a gate that "refuses" Forward still runs | a gate call moved after the write |
- Run `cargo mutants --in-diff` on the cut. The target is 0 missed in the meters, the
migration and the supervision edits.
- `a_failing_step_is_retried_after_a_backoff_not_every_tick` is flaky under a full parallel run
on the base too. Soul should not charge it to this cut.
- The live timeline probe, read-only, before and after install:
- `journalctl -u nginx --since -1h | grep -c 'Reloaded nginx'` stays at the C2 baseline of
about 0 per hour while steady;
- `idunn status` shows every routed target with `route actuations 0/12` and a healthy route.
#### B2 rework, E. Live-deploy relevance (read from migration code, not from the store)
**What is live now (ship log plus code).**
- `control.cc` generations are v3: F0's lift (`:3131-3181`) ran at the B3 install and migrated
5 records.
- Transactions are v4.
- `history.cc` holds v3 and v4 transactions and is never migrated (`:3388-3391`).
- B2 changes no transaction schema.
**What the B2 install does.**
- At boot, the migration rewrites every v3 generation as v4, each by CAS against its exact
envelope. That is the same machinery F0 used.
- It writes no `TargetSupervision`: the lift is lossless for the reasons in section C.
- The first route actuation after install, a deploy or a repair, creates each target's record.
**It is irreversible.**
- The pre-B2 binary refuses a v4 generation (`:3120`).
- It also refuses a `TargetSupervision` envelope: `ControlSnapshot::read` bails "foreign
document" at `:2609`.
- Binary rollback therefore means restoring the backup.
- Ship steps: back up `control.cc` and `history.cc`, install, then check that `idunn status`
renders every target.
**A lift refusal fails boot for every target.** It cannot happen from any released binary. If it
does, the backup plus the pre-B2 binary is the recovery, and the error names the record.
**Raven.**
- `idunn-host` decodes plans, not generations, so B2 needs no actuator rebuild.
- W1 is still the check that the actuator builds at the sha that ships.
#### B2 rework, F. Subtraction estimate (B2)
Production code in `control_plane.rs` and `drivers.rs`:
- **Removed:**
- about 45 lines of history ceiling;
- about 40 lines of `ActuationWindow`, carry and backoff;
- about 20 lines of key-18 and key-20 plumbing.
- **Added:**
- about 90 lines for `TargetSupervision`, its snapshot arm and its validation;
- about 60 lines for the meter primitive and the clock guard;
- about 60 lines for the v3 legacy layout, mostly moved rather than written;
- about 60 lines for the gate plumbing and `RouteActuators`;
- about 50 lines for status.
- **Net production:** about +250.
Tests:
- **Removed:** about 46 lines of the tautology test and about 24 lines of `promote_by_hand`.
- **Net tests:** about +350. The 9 hand promotions become real Routing steps.
Compared with `97f1ad2`, which was +1243/-200, about 600 of B2's lines are not ported.
Structural delta:
- One record type added.
- Two carry compensators and one history dependency deleted.
- Two generation keys retired.
- No targets, crates or dependencies.
- The build and test matrix is unchanged, apart from W1's check.
**Not asked, decided.** The v2 generation lift stays, retargeted to v4. Its only consumer is a
restore of `/root/idunn-store-backup-…-pre-route-b3`, and deleting it would force rewriting
about 10 tests built on `FIXTURE_GENERATION` for a saving of about 80 lines. Retire the v2 and v3
generation layouts together in one later cut, once the operator retires the pre-B3 and pre-B2
backups.
**D1 — The dead-hold report lives on the generation (Idunn; operator ruling, 2026-09-30). READY FOR HANDS, after B2** (B2 carries key 21 and `render_supervision`).
**Owner:** `Engine::report_generation_hold(current, detail)`. It is the only writer of
`AdmittedGeneration.last_error`.
- It writes only when the text differs.
- It prints once through the existing `ReportOnce` map (`:3865`), keyed by target.
- It ends the iteration, per the rule in section B.
**Writers today, by site.**
- `note_fault("holds admitted generation", "generation:<t>")` at `:4793-4799` hands a
generation key to `record_last_error` (`:4472-4495`). That function looks it up among
*transactions*, finds nothing, and so the report is stderr only. It also never clears.
- The dead-hold note at `:4965-4969` behaves the same way.
- **Both go.** They are replaced by `report_generation_hold`, with the readiness disagreement,
or with "is not running; declare readiness in its recipe and redeploy" when dead.
**Clearing** (the ruling: replaced, or hold released).
- Replacement clears by construction. `from_transaction` sets `None`, so it needs no code.
- Release: at the hold check, `readiness().is_ok() && last_error.is_some()` writes `None`, calls
`clear_fault`, and continues.
- This covers an Idunn upgrade that reclassifies a generation.
- `last_error` has no other writer: the continuity deferral reason lives in
`TargetSupervision`, and route failures live in `RouteSupervisionState`.
**Status.** `render_supervision` prints ` held: <last_error>`.
**Tests.**
- Extend `a_held_generation_that_dies_mints_no_continuity_and_leaves_its_target_free`
(`:13842`). It currently reads the in-memory `fault_reports` at `:13858-13859`; it must read
`last_error` from the stored generation instead: first the held text, then, after
`SwitchWorkload::kill`, the dead text.
- Add `a_released_hold_clears_its_report`: seed `last_error` on a generation whose readiness is
OK; one tick clears it, and a second tick writes nothing, with the envelope unchanged.
- Mutations it must kill:
- dropping the clear;
- writing on every tick;
- routing back to `record_last_error`.
**Size.** About +40 production lines and about +60 test lines. It deletes the two
`generation:`-keyed `note_fault` calls.
**Live.** No schema change. On install, the first tick writes `last_error` on any held generation
(none per the 2026-09-30 decode, now that raven-muninn is admitted).
**T1 — Pin the candidate-cleanup rule by removing its free arguments (Idunn; Soul follow-up on B3). IN PROGRESS on `hands/idunn-w1-t1-t2`.**
**The defect.**
- `pre_fencing_abort_intent` (`:7991-8005`) and `post_fencing_abort_intent` (`:8007-8039`)
each call `candidate_cleanup_requirement(activation.is_some(), workload.is_some())`
(`:8041-8050`).
- The unit test (`:9806-9820`) never covers `(false, true)`, and no test drives either intent
with exactly one of the two set.
- So swapping or constant-folding either argument at either call site survives.
**The cut.**
- Delete the helper.
- Add `DeploymentTransaction::candidate_cleanup_owed(&self) -> CleanupEvidence`, used by both
intents.
- Rewrite the unit test as a four-row table over the method.
- Add one test that both intents take the method's answer for `(Some, None)` and `(None, Some)`.
**Mutations it must kill:**
- `||` becoming `&&`;
- either operand becoming `false`;
- either intent hardcoding `Skipped`.
**Size.** About −10 production lines and about +8 test lines. Not live-relevant.
**T2 — The chunk-size line is exactly RFC 9112 (Idunn; Soul follow-up on B3). IN PROGRESS on `hands/idunn-w1-t1-t2`.**
**Probe** (Yggdrasil, throwaway commit `ce98e9b` on `583b2a7`): the HTTP reader accepts `" 3"`,
`"3 "`, `"3 ;x"` and `"3"` alike. The cause is `size.trim_matches([' ', '\t'])` in
`drivers.rs:5816-5821`.
**What RFC 9112 7.1 allows.** `chunk-size [ chunk-ext ] CRLF`, where
`chunk-ext = *( BWS ";" … )`. So whitespace is allowed only before a `;`, and never before the
digits.
**The cut.**
- Split at the first `;`.
- Trim trailing SP and HTAB only when an extension follows.
- Never trim leading whitespace.
- `parse_digits` (`:5846-5851`) stays the digit authority.
**Tests.** Add rows to `an_http_answer_that_frames_its_body_wrongly_is_refused` (`:8611`):
`" 3"` and `"3 "` are refused. Add to
`a_chunked_answer_must_end_its_trailers_and_terminate_each_chunk` (`:8799`) that `"3 ;x"` and
`"3\t;x"` are taken.
**Mutations it must kill:**
- trim both ends, which lets `" 3"` pass;
- no trim, which refuses `"3 ;x"`.
**Size.** About +4 production lines and about +8 test lines. It is live-relevant only as
stricter parsing of StreamPixels and Odin answers. Node's `http` emits bare hex, so there is no
expected behaviour change.
**Note.** Neither T1's nor T2's text is recorded in the map at `94c7691`. Both were taken from
the brief, and the defects were confirmed from code and the probe.
**W1 — Every verification checks the Windows actuator compiles (Idunn map and the eureka stopgap image). IN PROGRESS on `hands/idunn-w1-t1-t2`.**
**Probe** (Yggdrasil, rust image):
- `rustup target add x86_64-pc-windows-gnu; cargo check --locked --target
x86_64-pc-windows-gnu --bin idunn-host`.
- At `d32395a` (B3 merge, before the fix) it **fails** with four `E0425` errors, "cannot find
value `FROZEN_SOURCE_SYMLINK_TARGET_LIMIT`" (`drivers.rs:1461-1477`). That is exactly the
Raven break.
- At `583b2a7` it **passes** in 26 s. No mingw linker is needed, because `check` does not link,
and no dependency compiles C for the target.
**The cut.** No Idunn code changes.
1. `~/.claude/skills/eureka/tools/stopgap/rust.Dockerfile` gains
`RUN rustup target add x86_64-pc-windows-gnu`, so no job downloads `rust-std`. Rename the
file over itself; the image is re-tagged by the file's hash.
2. The map's "Build and verification path" makes the check a required step for every Idunn
cut, beside `cargo test`.
- It must not use `-D warnings`: the Windows target currently emits 9 warnings (unused
cfg-split code).
3. When the Idunn verify recipe exists (verify campaign), the same step moves into it, and
this stopgap line dies with `ygg-verify.sh`.
4. **The release build stays a documented step, not a verification.**
- `gamecult-ops/runbooks/idunn-host-raven.md:23` already names Starfire for
`cargo build --release --bin idunn-host`. Add: "at the exact Idunn sha being installed on
Yggdrasil; record the exe sha256 in the ship log".
- That is Windows-only work. It fits the load budget: one job, no burners.
**Honesty limit.**
- W1 catches compile rot, the class that broke Raven.
- It does not catch link failures or runtime behaviour on Windows. Q-V5 rules out a Windows
verify host.
- It does not catch version skew, where an old actuator cannot decode new plan types. That was
the other half of the Raven incident. Only the runbook's "rebuild at the installed sha" step
covers it. A version handshake at hub attach would be the structural fix; it is noted and not
mapped here.
**Size.** One Dockerfile line and one map line. It adds no targets.
**R1 — A routed target proves it answers the route challenge before its fence (Idunn; Q-R1 ruled (a)). READY FOR HANDS, after B2. Must land before Heimdall's first deploy.**
- **Owner:** Warming's last step, the transition to Fencing (`:5742`).
- **Inputs:**
- the candidate endpoint;
- `ReadinessClass::of(expected)`;
- `warming`: `RouteProofDirect` and `FirstOdinDirect` are already a direct answer.
- **Rule.** If `expected.route.is_some()` and `warming` is `OdinTopology`, then
`challenge_candidate(&current.value, &["warming", "active"], None)` (`:7313`) must answer
before `transition(Fencing)`:
- `Silent` becomes `record_gate_wait`, since the candidate may still be binding;
- `Refused` becomes `Err`, which resume turns into a **pre-fence abort** (`:4587-4593`) with
"routed target <t> does not answer the stable-route challenge; a routed recipe must serve
CultNet snapshot challenges";
- an `Answered` result is not persisted. It is not readiness, and passing Warming is the
record.
- **Deletes:** none. This is a check moved earlier, not a second authority. The post-fence
proofs at Routing and Commit stay the route's admission.
- **Size:** about +20 production lines, no schema change.
- **Tests** (Engine, B3 `RoutedWorld` with `provides_odin = false` and an Odin-correlated
recipe):
1. The stub's `reply` answers 404: the transaction pre-fence aborts from Warming, no lease is
granted, and nothing is fenced.
2. `hang_up` on: a gate wait, not an abort. Then `hang_up` off: it reaches Fencing.
3. An honest stub reaches Fencing exactly as today.
- Mutations it must kill: the class guard widened to `RouteProof` only (test 1 then reaches
Fencing); `Silent` treated as `Refused` (test 2 aborts).
- **Live:** nothing changes for ghostlight or streampixels-service, which answer today. Heimdall
(not admitted) now fails in Warming instead of wedging post-fence.
**B3 — Route-proof readiness (Idunn).** For a route-proof target:
- Warming is a direct candidate-endpoint challenge. This generalizes the
@ -754,7 +1409,7 @@ with no backoff.
decisions.
**B4 — Provider currency from Idunn's own observation; `9f00e7a` reverted
(Idunn).**
(Idunn). BLOCKED on the Odin-lifecycle authority audit.**
- Per Q2(b), `validate_selected_providers_current` and plan compile read the
provider's current route observation within max age, and read capabilities
from its signed presence.
@ -762,6 +1417,60 @@ with no backoff.
- Revert the admission-time authentication.
- `ManagedReady` carries a class-tagged evidence digest.
**B4 status, 2026-09-30: BLOCKED on the Odin-lifecycle authority audit** (a separate Imagination pass), not on
Q-B4. Q-B4 is ruled in part (section 3): routed providers are current by Idunn's route proof and not
degraded; unrouted providers by admission plus a live workload observation. The clause "a current Odin
non-Ready word still excludes a provider" is held open. B4 also takes B2's degraded state as input.
**C2 run 2 failure mechanism (Eyes, 2026-09-30)**
The code was read at `583b2a7`. The Idunn journal on Yggdrasil was read read-only; no state store
was read.
**Journal evidence.**
- 21:15:18: "excluded non-current provider streampixels-service: admitted provider's latest
receipt is not Ready at its admission time".
- 20:44 and 21:15: "…streampixels-web: admitted generation of streampixels-web is not
Odin-correlated".
- ghostlight and raven-muninn are excluded with the "latest … not Ready" text on every compile
since 19:45, with Odin up.
**The path.**
1. Plan compile (`advance_sealing`, `:5375-5385`) builds its provider list only from
`current_ready_provider_tokens` (`:7398-7415`), which calls `rehydrate_admitted_ready`
(`:7417-7470`).
2. `rehydrate_admitted_ready`:
- requires Odin receipts (`odin_receipts()`, `:7422`). A route-proof provider fails here with
`:2019`, so **no route-proof target can be a provider today**;
- requires the admitted `latest_odin_observation` to be semantically Ready (`:7453-7456`).
3. `refresh_admitted_topology` writes *every* fresh authenticated correlation as `latest`, Ready
or not (`:5320-5325`). Only a Ready one also moves `ready`.
- So one non-Ready word from Odin removes the provider until Odin says Ready again.
- Such a word can come from the provider's presence ageing out in Odin, including through
Odin's own faults, for example its 64-slot RUDP session table.
- With Odin stopped, that last word is frozen.
4. `managed_ready_provider_refs` (`deployment_plan.rs:264-316`) takes capabilities from Odin's
record. `select_dependencies` then bails "no expected provider satisfies
streampixels.service.api http.v1 v1" (`deployment_plan.rs:447-455`).
5. The same rehydration gates currency again at Starting, Routing and Committing, through
`validate_selected_providers_current` (`:7472-7534`). So a deploy that compiled can still
fail later on Odin's word.
**This is the unlanded B4.** Q2 was ruled (b): "Idunn's own current route observation of the
admitted provider … Unrouted providers fall back to their Q1 class evidence".
- streampixels-service is routed, so under B4 its currency is Idunn's route proof. C2 run 2
would admit.
- "Both runs must admit" is therefore **blocked on B4**, not wrong.
**One B4 brief item found here: challenge cadence against max age.**
- S1 challenges a healthy route only once its observation is older than
`topology_maximum_age_millis` (30 s) (`:5213-5221`).
- So a healthy route's observation ranges from 0 to about 30.5 s old.
- A B4 check of "within max age", repeated at compile, Starting, Routing and twice at Commit,
would sometimes refuse a healthy provider.
- B4 must define currency as "the route is not degraded, and the last proof is within 2 × max
age", or challenge on demand. That is a brief decision, not an operator one.
**B5 — Every post-fencing phase ends (Idunn).**
- A deadline resolver runs before `advance_transaction`.
- For stateless targets, expiry aborts to the incumbent through the existing
@ -836,6 +1545,8 @@ after C1, S3, B3).**
2. Both runs must admit.
3. Both must show zero steady-state reloads over one hour.
4. Both must show repeated successful signed challenges.
Run 2 (Odin stopped) is blocked on B4. The failure on 2026-09-29 21:15 is the unlanded B4,
not a C2 defect.
- Public cutover is out of scope and resumes afterwards through the ordinary
path.
@ -850,6 +1561,8 @@ after C1, S3, B3).**
commands on Yggdrasil, taken before and after each deploy.
- Each Idunn cut names its focused tests. The whole suite runs once per cut on
Yggdrasil, not per edit.
- Every Idunn cut also runs `cargo check --locked --target x86_64-pc-windows-gnu --bin idunn-host` (W1),
without `-D warnings`.
### Ops follow-ups, out of scope