Status: cut map, Imagination pass 0b (Opus), 2026-09-29. Nothing has landed. 2026-09-30: the Odin-lifecycle authority audit is folded in (sections 3, 4 and 5); see the Cut status block.
| `GameCult/CultLib` | `origin/main` = `268e0ef`; `origin/codex/fix-node24-ajv-esm` = `542ddd3` | the Idunn TS runtime package exists only on that branch (see F-C1) |
-`deployment/idunn/web.toml` declares the Odin dependency and the env var.
Read.
- F15. The Idunn runtime TS package (`idunn-runtime-authority.ts` and its
test) does not exist on CultLib `origin/main`. It exists only on the
unmerged branch `codex/fix-node24-ajv-esm`, which is 10 commits ahead of
`origin/main` (`0bedcde`..`542ddd3`). StreamPixels pins that branch commit.
Read.
### 1.3 Which Idunn decisions consume Odin for a non-Odin target
All of these are read:
| Decision | Site | Odin input |
|---|---|---|
| Provider selection at plan compile | `advance_sealing``:3825` → `current_ready_provider_tokens` | admitted providers' Odin `ready` and `latest_odin_observation`; `ManagedReady` carries `odin_topology_correlation_sha256` and sequence (`deployment_plan.rs:136-145`) |
| Provider currency (Deploy only) | Sealing→Starting `:3880`, Starting `:3945`, Routing `:4527`, Committing `:4629`, `:4673` → `validate_selected_providers_current` | same receipts; since `9f00e7a`, authenticated at their admission time rather than now |
| Warming | `advance_warming``:4028-4103` | only Odin correlation, except the `FirstOdinDirect` exception for `target == "odin"`. For a non-Odin target with no correlation it records a gate wait forever |
| Lease grant (stateful) | `fresh_warming_for_lease``:4808` | new Odin sequence plus new signed presence |
| Route admission | `advance_routing``:4487` | admits the latest topology; Ready is refreshed and `require_current=true` |
| Commit | `advance_committing``:4599` | topology admitted twice, once before and once after the final stable-route proof; `AdmittedGeneration` requires `ready`, `latest_odin_observation`, `odin_authority` and the cursor as non-optional fields (`:1322-1331`) |
- Idunn neither knows the window nor observes adoption, other than through a
later Odin correlation.
Read.
- F20. `cancel` (`:1725`) is an operator CLI process that writes
`post_fencing_abort` directly into a live daemon-owned transaction by CAS
(`8ae00a1`). The only cases it accepts are Deploy, past Fencing,
`SkippedStateless`, no completion. Continuity and stateful transactions have
no recovery command. Read.
### 1.5 Continuity drift between the admitted receipt and the projection
- F21. `from_continuity` (`:783`) reuses the incumbent's Expected, so the
continuity candidate projects under the same key `{target}@{expected sha}`.
`publish_observed_activation` replaces any activation under that key
"without comparison" (`drivers.rs:4927-4955`). The admitted generation keeps
the old activation until commit. Commit replaces the admitted generation and
the transaction in one CAS (`:4745-4770`), so a **successful** continuity
leaves no drift. Read.
- F22. **Drift source.** Both abort intents set `topology_reconciliation =
Skipped` for `CommandKind::Continuity` (`begin_pre_fencing_abort`
`:5822-5828`, `post_fencing_abort_intent``:6224-6230`). A failed continuity
therefore leaves its own candidate activation projected under the shared
key while the admitted receipt names the older activation. Two things then
refuse, because `ProjectedIncarnation::read` rejects any projected activation
that differs from the one passed in (`drivers.rs:4293-4300`):
- supervision's pre-restart demotion (`:3538-3556`, logged and ignored);
- a later deploy's post-fencing `demote_to_expected_only`.
`a148802` made rollback demote whatever activation is projected, provided it
verifies against Idunn's anchor
(`demote_current_activation_to_expected_only`, `drivers.rs:4500-4543`). The
cause is the skipped reconciliation. The coherent cut is for the continuity
abort to demote its own exact activation to Expected-only. Read.
- F23. `9f00e7a` exists because admitted receipts only advance when Odin emits
a new sequence (`refresh_admitted_topology`). Provider publications to Odin
were failing (F7, F12), so a provider's receipts aged past 30 s and
dependents could not select it. The drift source is that dependency currency
is sourced from Odin receipts at all (see Q2). Read.
### 1.6 Weekend commits
| Commit | Read | Classification |
|---|---|---|
| `a148802` recover fenced rollback from current activation | diff | **Revert** in Cut B1, together with the F22 fix. Delete `demote_current_activation_to_expected_only` and its test. Restore the doc paragraph to the exact-activation rule |
| `9f00e7a` replay provider receipts at admission time | diff | **Revert** in Cut B4, together with its replacement currency source (Q2). A revert alone re-breaks dependency selection |
| `ae8ac45` process title as launch evidence | diff | **Keep.**`command_line_sha256` stays launch-time evidence; Node's `process.title` rewrite is not identity drift |
| `29946bb` wait for stable route listener | diff | **Keep.** After Cut S1 it covers only install-time listener handoff, which is its honest scope |
| `8ae00a1` cancel live stateless candidates | diff | **Delete** in Cut S2 once deadlines exist. It is a second writer into daemon-owned transaction state (F20) |
| `8651906` dynamic primary group | diff | **Keep.** Independent of this campaign |
| `55e5d55`/`7a61c77`, `4200330`/`e0f5147` | log | Self-reverted pairs; net zero |
---
## 2. Identity, lifecycle, authority
An empty or "none" cell is a finding. It is marked **(gap)**.
| Kind | Identity | Lifecycle | Authority |
|---|---|---|---|
| **Admitted generation** (`idunn.admitted_generation.v2`, `control.cc`) | key = target; injective per target. `generation_id = generation-{tx}` | **Created/superseded** by the commit CAS (transaction + generation atomic). **Revised** by route supervision (routing receipt, repair intent) and topology refresh (Odin receipts, cursor). **Withdrawn:** never **(gap, F8)**. **Replayed:** read on every tick; `validate_durable_authority` at boot | Owner: Engine commit. Three in-process writers (commit, `supervise_admitted_route`, `refresh_admitted_topology`), all CAS. Forbidden: CLI, Odin, drivers |
| **Route observation / receipt** (`RoutingEvidence::Promoted` inside transaction and generation) | `route_id` + `runtime_instance_id` + `membership_sha256` + `signed_presence_sha256`; injective per incarnation | Created at Routing. Re-created by each successful admitted challenge. Stale at 30 s, which today **triggers actuation** (F3). What is in force afterwards: the last successful receipt, even while proofs fail | Owner: route supervision (observe only, after S1). Forbidden: reload path, Odin |
| **Route repair intent** (`route_repair_started_at_unix_millis`, gen key 18) | per admitted target; a single timestamp | Set when stale or drifted. Cleared on proof. **Attempt count, backoff, ceiling: none (gap)** | Owner today: supervision. Replaced in F0 by *route supervision state* (below) |
| **Route fragment on disk** (`binding.config_path`, `/etc/nginx/idunn-stream-routes/<route>.conf`) + **ufw allow** | path is injective per binding (`validate_route_binding_set``:2386-2393`); first line `# Idunn Expected <sha>` | Written by `install` (Routing), `restore_admitted_membership` (supervision), and `restore` (abort, rollback). Removed on failed restore reload or last withdrawal. ufw is re-admitted on **every** restore (F4) | Owner should be the route driver, acting only on membership change. Forbidden (after S1/B2): observation freshness, proof failure |
| **nginx reload** (process actuation) | none; not a record **(gap: no durable actuation ledger, so the invariant 2 ceiling cannot survive an Idunn restart)** | Unbounded (F5, F6) | Owner: route driver. Rate authority: **none (gap)**. F0 adds it |
| **Deployment transaction** (`idunn.deployment_transaction.v3`) | `tx-{uuid}`; one live per target (`blocks_new_target_mutation`) | Phases Sealing…Complete. Aborts pre and post fencing. Archived to history when terminal. **Phase deadlines: none (gap, F17-F18)**. Post-fence error = resumable forever | Owner: Engine. **Forbidden writer present: `idunn cancel` CLI (F20)** |
| **Activation** (in transaction/generation + projection `idunn.runtime_activation` in `topology.cc`) | projection key `{target}@{expected sha}` is **not injective across continuity**: every restart of one Expected shares it (F21) | Published after workload observation. Replaced without comparison. Demoted on death or abort. **Continuity abort leaves its own activation projected (gap, F22)** | Owner: transaction for its own activation; generation for the admitted one. Forbidden: rollback adopting an activation it did not issue for this transaction (`a148802`) |
| **Write lease** (file `record_path` + `.lock`) **+ projection** | exact record binding Expected, activation, warming sha, epoch | Prepared → Granted → (adopted by process) → revoked at fence or abort. **Adoption is not observed by Idunn and has no pickup deadline (gap, F19)** | Owner: Idunn lease driver. Pickup-window contract: owned by consumers (Odin 60 s, TS 64 proofs). **No Idunn-side owner (gap)** |
| **Runtime presence doc** (`gamecult.runtime_presence_health.v2`) | target + `runtime_instance_id` + `publisher_sequence`; the challenge nonce rides in `detail` | Minted by the app per challenge and per heartbeat. Idunn persists only `signed_presence_sha256` (route receipt) or the full bytes (`FirstOdinDirect`). Odin persists its own copy | Owner: the service (provider key + Idunn-issued activation key). Forbidden: Idunn manufacturing it; publication to Odin being a precondition of the route answer (F12) |
| **Odin correlation topology** (`/var/lib/gamecult/odin/idunn-runtime-topology.cc`) | `{target}@{expected sha}`, publisher sequence per signer | Written by Odin whenever facts change. Idunn persists admitted sequences into the transaction/generation and advances a per-target cursor | Owner: Odin (discovery). Consumer authority today: warming, Ready, dependency currency for **every** non-Odin target (§1.3). After this campaign: observation for route-proof targets, never admission |
| **Continuity restart history** | derived by counting failed continuity transactions per generation (live + history) | Resets on each new generation (F9). **Per-target ceiling and backoff: none (gap)** | Owner: supervision |
| *New in F0:***readiness evidence** | enum on transaction and generation: `OdinCorrelated(TopologyEvidence)` or `RouteProof(RuntimePresenceEvidence)`; the class is derived from Expected (Q1) | Replaces the mandatory `ready`, `latest_odin_observation`, `odin_authority` and cursor for route-proof targets | Owner: Engine. Forbidden: a route-proof target's admission reading Odin |
| *New in F0:***route supervision state** | per admitted routed target: `last_challenge_at`, `consecutive_failures`, `next_challenge_at`, and a rolling `actuations` window (count + window start) | Revised only by supervision. Carried across continuity commits. Replaces `route_repair_started_at` | Owner: supervision. Forbidden: any reload path that does not consult it |
| *New in F0:***phase deadline** | per transaction: `phase_entered_at` + `deadline_at`, from binding-owned durations frozen into the compiled plan (Idunn defaults) | Set on every phase transition. Expiry is resolved by the deadline resolver (B5) | Owner: Engine transition primitive. Forbidden: resume paths extending it |
| *New in F0:***lease adoption evidence** | the candidate's signed presence carrying `write_lease_sha256 == granted sha`, obtained directly or through Odin | Recorded once in Leasing/AwaitingReady. Its absence at the deadline means the lease was not adopted | Owner: Engine |
| *New in F0:***stateful terminal record** | completion variant naming the recovery (`RestartAdmitted`, `RestoreIncumbent`, `OperatorRequired{reason}`) | Terminal, archived like other completions | Owner: deadline resolver |
| *New in F0:***continuity backoff** | per target: attempts in window, `next_restart_at` | Carried across generations. Replaces per-generation counting for the ceiling | Owner: supervision |
| *HTTP route proof* | **no new kind.** It reuses the presence v2 contract and `RouteObservation` (F11). `RuntimePresenceEvidence` gains a route-proof use beside `FirstOdinDirect` | — | — |
---
## 3. Operator questions
**Q1. Readiness authority for a CultMesh-aware service such as
**RULED (b) by the operator, 2026-09-29. HISTORY, not live design: superseded 2026-09-30 by Q-O1 (Idunn proves; readiness classes end at A1 + A3). The text below is kept as the record of the ruling.**
**RULED (a) by the operator, 2026-09-29. Amended 2026-09-30 by Q-O5: an answer that does not authenticate as the admitted incarnation restarts the process under the continuity meter; silent answers stay degraded-only.**
**Held clause, STRUCK 2026-09-30 (operator, in Q-O1): "a current Odin non-Ready word still excludes a provider" is rejected. B4 becomes B4′ (section 4, Odin-authority cuts). The original held text follows as history.** The recommendation's clause that "a current Odin non-Ready word still excludes a
- 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`: installs and their Survival rollbacks share the 12-per-hour route ledger, so a stateless candidate whose proof keeps failing gets 6 install+rollback pairs per hour; only Forward is refused at the ceiling, and Survival is always admitted (counted when the ledger can record it; ruled 2026-09-30 after B2 Soul) | 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>`.
**R1 — NEVER TO BE WRITTEN: superseded by A1 (2026-09-30, Q-O1).** Every routed target is proved by Idunn's own challenge whatever it declares, so the class guard below has nothing to guard. H1 (Heimdall responder) is still needed. The text is kept as history.
**R1 (superseded) — A routed target proves it answers the route challenge before its fence (Idunn; Q-R1 ruled (a)).**
(Idunn). Now B4′ (section 4, Odin-authority cuts), unblocked by the audit and Q-O1: its unrouted Odin-class branch and the Odin-veto clause are gone.**
**B5 — Every post-fencing phase ends (Idunn).** *2026-09-30: B5 loses its Odin arm (the Odin part of the F16 fix) once A3 lands; route-proof targets never entered that path.*
| Kind | What names it | Life over time | Who decides |
|---|---|---|---|
| **Snapshot declaration** (`[state_snapshot]` in the operator binding) | the binding's target; frozen into the plan through `binding_blob` | Changes only by an ops binding change and a new Deploy plan. An old plan without it keeps running continuity (S11) | gamecult-ops template; Idunn validates shape at parse and coverage at compile |
| **Snapshot evidence** (transaction key 37, `state_snapshot`) | the transaction; one per Deploy past Fencing | Written once, when every store is captured and digested. Read by the restore. Terminal with its transaction | Engine (Snapshotting). Forbidden: CLI, drivers writing the record |
| **Snapshot bytes** (`/var/lib/gamecult/idunn/snapshots/<target>/<transaction_id>/`, 0700 root) | path derived from the transaction id | Rewritten whole on a Snapshotting replay. Deleted by the transaction's own terminal cleanup step, except under a state hold, where they are deleted when the hold is released. No janitor | the transaction that owns the path |
| **State restoration** (`PostFencingAbort.state_restoration`) | the abort | `Skipped` (no lease issued) → `Pending{deadline_at}` → `Complete{proof}` or `Failed{store, kind}` | abort path. Forbidden: supervision, CLI |
| **State hold** (`TargetSupervision.state_hold`, supervision v2) | target | Set in the same CAS as a terminal `OperatorRequired{RestoreFailed}`. Released per Q-S4. Never set by anything else | Engine at the terminal write; release per Q-S4. Forbidden: continuity mint, which only reads it |
| **Roll-forward record** (transaction key 38 `roll_forward` intent; completion `Admitted{recovery: RollForward}`) | the transaction | Set by the resolver after Ready. Terminal at the forced commit | deadline resolver |
-`PhaseEnd::OperatorRequired` and its doc (`:943-945`);
- the `CommandKind::Deploy if issued` arm (`:1588`);
- the resolver's `OperatorRequired` branch (`:8610-8619`, the `deadline:<tx>` report);
- the "left to the operator" comment (`deployment_plan.rs:470-473`);
- the three `OperatorRequired` rows of `an_ended_phase_aborts_unless_a_deployment_candidate_was_issued_a_lease`
(`:12887-12889`); the test is rewritten to the Ready table;
- the string-reason test `an_operator_required_recovery_must_say_why` (`:12911-12935`), rewritten for the typed
reason.
- **Recommendation: merge B5 first, after its own Soul pass. This map is a follow-on cut** (Q-S5).
- B5 bounds the live continuity reload loop (B2-3d F1).
- Its left-running arm is no worse than today's resumable-forever post-fence.
- D and E carry two schema bumps and a new driver, and need their own falsification.
- Cost of the gap: a stateful deploy that expires after a lease stays live and reported, as today. `idunn expire`
re-enters that arm (watchdog I6). The StreamPixels-service redeploy in the ship sequence runs under that
behaviour.
#### Q3-ii.7 Cuts
Order: SR-1 → SR-2 → SR-3 → SR-4. The build and tests run on Yggdrasil (`ygg-verify.sh`) against the single `idunn`
crate. The build activates no new target.
- **SR-1. Snapshot drivers (Idunn `drivers.rs`, `deployment.rs`).** No phase-machine change.
- One port: `StateSnapshotPort { capture, check_no_writer, restore, prove }`.
- Two implementations:
-`SlotSnapshotDriver`: files, with `.lock` exclusivity;
-`PostgresSnapshotDriver`: `runuser -u postgres` for `pg_dump -Fc`, `pg_restore`, `psql`; side database,
fingerprint, swap. The program paths are injectable, as the route stubs are.
- The binding table parse and validation.
- The closed `SnapshotFault` kind and the stderr quarantine.
- **SR-2. D (Idunn `control_plane.rs`, `deployment_plan.rs`).**
- Deletes first: B5's arm (Q3-ii.6).
- Then:
- the Fencing change (Q3-ii.2 item 1);
- the Snapshotting phase;
- the restore step, abort deadline, reorder and `Abandoned`;
- the state hold;
- the compile-time coverage rule;
- all three schema bumps and lifts, including E's shapes, so the store moves once.
- **SR-3. E (Idunn `control_plane.rs`).**
- the `RollForward` end and the forced commit;
-`Unproven` in Routing and supervision;
-`Admitted.recovery`.
- **SR-4. Ops (gamecult-ops).**
-`[state_snapshot]` for `streampixels-service` (`state-slots` + `postgres streampixels`), `ghostlight`
(`state-slots`), `odin` (`state-slots`), and the Heimdall template (`state-slots` + `postgres heimdall`);
- the backup exclude;
- fixing the odin template's `state_root` drift (S6).
- **Owning-repo follow-ups (not in this campaign's cuts):**
- **H-S1** Heimdall opens Postgres and runs `ensureSchema` only after its lease.
- **H-S2** Heimdall moves `runtime-presence-sequence` out of its state root; it is runtime identity, not state.
- **G-S1** Ghostlight declares `service/play-turn-v1.cc` as a slot (under Q-S2 (a)).
- The legacy `heimdall.service` retires before Heimdall's first Idunn deploy (secret-files Cut 5).
#### Q3-ii.8 Authority map
- **Owner.**
- The deadline resolver owns how an ended phase resolves (restore, roll forward, or plain abort).
- The abort path owns the restore.
- The Engine's Snapshotting step owns the snapshot.
- **Inputs:**
- the plan's frozen binding (`[state_snapshot]`);
- the recipe's slots;
-`ready`, `leasing` and `fencing` evidence;
- the phase and abort deadlines;
- expiry requests;
- the stores themselves, through the port.
- **Outputs:**
- snapshot evidence;
-`state_restoration`;
- the terminal completion with its recovery;
-`state_hold`;
- an admitted generation with `Unproven` routing.
- **Derived state:**
- Q3-i's "possibly written" is derived from `leasing`;
- "stopped at fence" is derived from `incumbent_stopped` and no longer from the lease digest;
- the snapshot directory is cache-only, and its truth is the evidence digests;
-`report_once` text is notification-only.
- **Forbidden writers:**
- supervision writing restore or snapshot state, or minting over a held target;
- the CLI writing any of it (`expire` writes only its request);
- drivers writing records;
- the backup producer restoring anything;
- a route or topology step deciding the recovery.
- **Shared paths:** deadline expiry and `idunn expire` go through one resolver. Direct and Odin-correlated Ready use
one boundary field. D aborts and plain aborts use one abort path with one deadline rule. The forced commit and the
normal commit use one `from_transaction`.
- **Deletion line:**
- B5's `OperatorRequired` left-running arm;
- lease-derived fence-stop inference;
- the unbounded abort step.
#### Q3-ii.9 Verification
Each test pins a rule. Each must be killed by the named mutation.
| Test (behaviour) | Rule pinned | Mutation that must kill it |
|---|---|---|
| a deploy that expires before Ready restores the snapshot, and the incumbent restarts on the pre-lease bytes | D | restore marks Complete without writing; or `PhaseEnd` returns `Abort{restore: false}` |
| continuity mints nothing while the restore is Pending | S8 | widen the supervision skip range's lower bound past the abort |
| a failed restore is terminal `OperatorRequired{RestoreFailed}`, and supervision mints no restart | only terminal operator case; hold | drop the hold check, or set the hold outside the completion CAS |
| a deploy that expires in Routing after Ready is admitted `Unproven`, restores nothing, and supervision later promotes it | E | Ready-side arm returns Abort |
| an expiry in Committing rolls forward from the promoted receipt | E | forced commit rewrites the routing receipt |
| the Ready table: (Deploy, Continuity) × ready × (no lease, Prepared, Granted) | boundary and Q3-i | treat Prepared as unissued |
| `idunn expire` before Ready restores; after Ready it rolls forward | shared path | a separate expire branch |
| the fence stops an unleased incumbent when the candidate is stateful | exactness 1 | revert the `:6719` condition |
| a held slot lock, or a live Postgres session (fake runner), refuses Snapshotting with no lease issued | exactness 2/3 | skip either check, or check only at start |
| crash replay at every snapshot and restore step converges (persist the prefix, rerun) | idempotence | the restore trusts a sub-step flag over observation |
| the restore removes a slot file the candidate created | exact restore | copy-over only |
| an abort whose `ufw delete` fails forever ends at its deadline: state restored first, route `Abandoned`, recovery `RestoreIncumbent` | every abort ends | route before state; or Abandoned allowed on the state step |
| a v4 store (every fixture) lifts to v5, and supervision v1 to v2; each re-encodes canonically | schema | drop a field default |
| a stateful Deploy with no `[state_snapshot]` is refused at compile, and continuity of an old admitted plan still runs | Q-S1 (a); S11 | put the rule in `admit()` |
| a canary in fake `pg_restore` stderr appears in no `last_error`, report or `status` line | secrets | copy stderr into the error |
| Postgres integration, `#[ignore]`, run on Yggdrasil against a scratch cluster or container: side-database restore drops candidate tables; fingerprint mismatch refuses the swap; the swap terminates a live session; a crash between swap and drop replays | S7 | in-place `--clean` restore |
Then run `cargo mutants --in-diff`.
**Only a live Yggdrasil rehearsal, with the operator present, can show:**
-`runuser`/`pg_dump` reach the socket under the real unit sandbox;
- the snapshot directory is written under the real `ReadWritePaths`;
- a streampixels-service Deploy from a scratch branch whose candidate migrates and never reports Ready restores
`streampixels`, after which the incumbent serves again. Use a shortened `awaiting_ready_seconds` in a scratch
binding;
- the measured write pause;
- E: a Ready candidate whose stable-route proof is blocked is admitted `Unproven`, and is promoted by supervision once
unblocked;
- the backup run skips `snapshots/`.
#### Q3-ii.10 Subtraction ledger (estimate)
| | Production | Tests |
|---|---|---|
| Deleted | about -40 (B5 arm, comment, lease-derived stop inference) | about -30 (rows, string-reason test) |
| SR-1 drivers and binding | about +450 | about +300 (fake runners), +120 (ignored Postgres integration) |
| SR-2 D, lifts, hold, abort deadline | about +450 | about +450 |
| SR-3 E | about +180 | about +250 |
| SR-4 ops | about +20 of binding TOML, 1 backup exclude line | none |
- **Net:** about +1,050 production and +1,100 test lines in Idunn. It adds two schema bumps plus one supervision
bump, installed as one store move, and no new target, service or dependency (it uses the host's `pg_dump`,
`pg_restore` and `runuser`).
- **What it buys:** the ruled capability, which closes Q3-ii's hole, and an end for every post-fence path, including
B5's abort livelock.
- **What it retires:**
- the operator-only exit for stateful deploys;
- the `b5-operator-required-no-exit` follow-up (watchdog map).
#### Q3-ii.11 Watchdog interface (question 5)
The interface is a single point. The `operator-required` incident opens at the terminal CAS that writes
`FailedAfterFencing{OperatorRequired{RestoreFailed}}` and sets `state_hold`; its subject is the transaction id, as
the watchdog map models it. It closes when that hold is released (Q-S4). Nothing else in this cut opens or closes
**Rulings, operator, 2026-09-30:** "S1: Refuse / S2: Declared state / S3: Detect and refuse / S4: deploy or release". That is **Q-S1 (a), Q-S2 (a), Q-S3 (a), Q-S4 (a)**. Q-S5: "B5 first", which is **(a)**: B5 merges after its Soul pass, and SR-1..SR-4 follow as their own cuts.
Source: the Odin-lifecycle authority audit (anchors Idunn `94c7691`/`583b2a7`, Odin `5c37860`), folded in as
section 5. It ends B4's "blocked on the audit" state. Sequenced against the B2 rework map. Line counts are
estimates from function spans.
| # | Cut | Repo | Blocked by | Subtraction / addition | Live relevance |
|---|---|---|---|---|---|
| **X1** | Delete the dead lifecycle residue: O12 (`idunn.rs` plus the `idunn.*` schemas in `documents.rs`), O13 (JS coordinator, its tests, `package.json start`, `start-/restart-odin.ps1`), O14 (the other services' deploy, restart and health scripts), O15 (doc sections rewritten to the live body); `gamecult-ops/compose/odin.yggdrasil.yaml`; the `idunn-deployment-targets.ps1` references to Odin scripts. **IN PROGRESS on Odin `hands/odin-x1-residue`** | Odin, ops | nothing | about **-7,000** lines (672 + 2,814 + 140 + 3,208 + docs), +~40 of docs | none. Muninn keeps compiling at its pin; its next bump must drop `IdunnDaemonHealthRecord` (follow-up **M2**) |
| **A1** | Every routed target is proved by Idunn's own challenge, whatever it declares. `ReadinessClass::declared`: `routed -> Direct`. OdinSelf and OdinCorrelated apply only to unrouted targets until A2. Odin itself is routed, so its FirstOdinDirect path generalizes; delete the "is Odin" arms in Warming (`:5614-5640`). The recipes' Odin dependency flips to `optional` (Q-O4) | Idunn (+ recipes) | Q-O3; after B2 | about -60 / +20 | admitted ghostlight, streampixels-service and odin carry Odin receipts and become `WrongVoucher`-held on install (Q-O3). Continuity of ghostlight and service no longer needs Odin (fixes I2 for them). **Replaces R1**, which is never written (its class guard has nothing to guard). H1 (Heimdall responder) is still needed |
| **B4′** | B4 as mapped, with its rule collapsed: provider currency is Idunn's own proof, not degraded, last within 2 x max age. `ManagedReady` carries a direct-evidence digest. Capabilities come from the challenged presence. **The held Q-B4 clause is struck** | Idunn | A1 (or with it) | B4's own estimate, minus the unrouted Odin branch | C2 run 2 admits |
| **A2** | Unrouted targets get a challenge endpoint. The binding declares a private challenge endpoint without a stable route; `request_runtime_presence_at` (`drivers.rs:5488`) is reused against it; Expected carries it digest-bound. **M1** (Muninn): raven-muninn answers `SnapshotRequest` for its presence on that endpoint (the StreamPixels C2 and Heimdall H1 shape) | Idunn, Muninn, ops binding | Q-O2 (ruled) | about +80 Idunn, +~60 Muninn | raven-muninn gains a proof path and leaves the held state after a redeploy |
| **A3** | Delete Odin consumption from Idunn entirely: every forbidden writer in section 5.3. Schema bump: transaction and generation lose `latest_odin_observation`, `odin_publisher_sequence_cursor` and `odin_authority`, plus the Odin enum variants. Boot stops requiring the Odin anchor. **B5 loses its Odin arm**: the Odin part of the F16 fix is deleted from B5's brief | Idunn | A1, A2, B4′; every admitted generation re-admitted Direct | about **-1,100** production and **-1,500 to -2,000** test lines; +~100 of negative tests | **store migration, irreversible; back up `control.cc` and `history.cc` first.** Fold the v2/v3 lift retirement in if the operator has retired those backups |
| **O-R** | Odin stops originating readiness. Delete `ready`, `dependency_evidence` and `dependency_permits_ready` (O1-O3), and O7's re-verification. If nothing reads the correlation after A3, retire its publication, its signer and dedupe (O8), and `OdinTopologyIdentity` enrolment in `idunn-provision` | Odin (+ Idunn provision) | A3 | about -150 (verdict only) to about -600 (the correlation retired) | none after A3 |
| **L-R** | Retire the `odin.runtime_topology_correlation.v2` contract in cultnet-rs (`runtime_authority_contracts.rs:455-~1100`: dependency evidence, record, authenticator), under CultLib's per-runtime rules. Only cultnet-rs implements it | CultLib | O-R | about -400 | none |
**Order.**
1. X1 lands any time (in progress).
2. Then B2 as mapped, then A1 (Q-O1, after Q-O3 is ruled), and the redeploys for Q-O3. H1 is still needed; R1 is
never written.
3. Then B4′, then A2 with M1, then A3, then O-R, then L-R.
4. Meanwhile S3 (the web drops Odin) proceeds unchanged. B5 loses its Odin arm. T1, T2, W1 and D1 are unaffected.
**Net estimate.** About -9,000 lines across Odin, Idunn and CultLib, of which about 7,000 is dead residue. Plus 1
Idunn schema bump (A3), about 200 lines added in A2 and M1, and about 100 lines of new negative tests.
**Verification gates (Soul, per cut).**
- **A1.** With Odin stopped, a continuity of a routed target that declares Odin reaches Complete. An Odin
correlation that says not-Ready about it changes nothing. A held Odin-voucher generation mints no continuity
(existing F1 rule).
- **A3.** `rg -i odin src/` in Idunn returns only strings: projection doc text, and the capability name inside test
recipes. Boot succeeds with no Odin anchor file. A store with a v4 Odin-voucher generation is refused or held,
never re-proved.
- **O-R.** No Odin code path computes a boolean named or used as Ready for another target.
**Temporary rule, Odin (deletion line: A3 plus Q-O5's supervision restart).** Until A3 and the Q-O5 restart land,
Odin keeps three permanent self-publication failures fatal, as `WriteLeaseLost` is: no runtime authority,
signer/anchor mismatch, and the fail-closed stored-presence branch (`lib.rs:945-954`). Without it `eeb6812`'s
`survive` would swallow them, Odin's self-correlation would freeze stale, and by O2/I8/I9/I2 every dependent would
be excluded and stall in Warming, deploys and continuity alike; before `eeb6812` the process exited and a restart
cleared it. It is not a second lifecycle owner: it is Odin reporting its own death through the only signal Idunn
acts on today, and `CONTINUITY_RESTART_ATTEMPTS` bounds the loop. Being added on Odin `hands/odin-session-table`.
| I1 | `ReadinessClass::declared/of``:632-670`; `CompiledDeploymentPlan::readiness_class``deployment_plan.rs:645-661` | A discovery dependency makes Odin the readiness voucher | 1 (root conflation) | chooses every row below |
| I2 | `advance_warming` Odin branch `:5597-5700`, gate text `:5653` | Warming waits for Odin's correlation. Only OdinSelf is exempt (`:5614-5618`); **Continuity is not exempt** for anyone else | 1 | Warming, deploy **and continuity** |
| I3 | `is_semantic_warming``:8098-8128`, `warming_disagreements_match_incumbent``:8130-8145` | Interprets Odin's `present`, state and disagreements as Warming | 2 | Warming |
| I4 | `fresh_warming_for_lease` OdinTopology arm `:6500-6544` | A newer post-fence Odin sequence is required before the write lease is granted | 1 | lease grant |
| I6 | `advance_routing``:6096-6135` | A non-Ready latest word is `Err` post-fence (`:6116-6119`); the deploy resumes forever until B5 | 1 | Routing |
| I7 | `advance_committing``:6220-6330` | Topology admitted twice around the final proof (F16 livelock). Commit copies `current_odin_authority` onto the generation (`:6343`) | 1 | Commit |
| I8 | `refresh_admitted_topology``:5272-5339` | Supervision stores Odin's latest word, Ready or not (`:5320-5325`); only Ready moves `ready` | 1 / 2 | provider exclusion |
| I9 | `current_ready_provider_tokens` / `rehydrate_admitted_ready``:7398-7470`; `managed_ready_provider_refs``deployment_plan.rs:264-316`; `ManagedReady` odin fields `:138-148` | Provider eligibility is Odin's latest word; capabilities are the provider's presence **as relayed by Odin** | 2 + 1 | plan compile (C2 run 2) |
| I10 | `validate_selected_providers_current``:7472-7534`, called at `:5466`, `:6140`, `:6253`, `:6327` | The same, re-checked at Starting, Routing and twice at Commit (Deploy only, `:8074-8080`) | 1 | deploy abort or resume |
| I11 | Graph gate: every non-optional dependency needs a provider (`deployment_plan.rs:447-455`); the Odin dependency is `shared-infrastructure` | Odin must be admitted and current to *compile* any Verse-advertising target | 3 in intent, gate in effect | compile |
| I12 | Boot: `Engine::open` requires the bootstrap Odin anchor file (`:3900-3902`); `validate_durable_authority` re-proves every Odin receipt against the admitted Odin's key (`:4110-4225`); `boot` refuses on any failure (`:4355`) | An Odin-owned key and anchor gate **Idunn's own startup** | 4-by-proxy (doctrine violation) | Idunn boot |
| I13 | `current_odin_authority``:4041-4054`; `AdmittedOdinAuthority``:1905-1935` | Bootstrap key until Odin is admitted, then the admitted Odin's key. Odin's own commit copies the *current* authority (test `:14920-14936`), so Odin's topology key can never rotate through a deploy | 3 (trust anchoring), exists only for I1 | all of the above |
| I14 | `odin_publisher_sequence_cursor`, `max_odin_sequence``:2762-2808`, `sequence_requires_admission``:8052-8072` | Idunn's anti-replay depends on Odin's persisted watermark. If Odin loses state, every Odin-correlated target gets "replayed or reordered" as `Err` | 3, exists only for I1 | Warming, refresh |
| I15 | Topology projection: `CultCacheTopologyDriver``drivers.rs:4238-4250`, `publish_*`, `demote_to_expected_only` | Idunn publishes Expected, activation and lease; Odin and the workloads read them | 3 (correct direction) | none. Its doc comment ("only Odin may correlate ... into Present/Ready", `drivers.rs:4239-4241`) is the misplaced doctrine in prose |
| I16 | `AdoptionSource::OdinTopology``:786-795` | No production writer (only tests, `:11502`, `:11692`). B5's adoption slot | dead variant | none |
| I17 | `validate_live_providers_for_deploy``:8074-8080` | Continuity skips provider currency. The only continuity exemption; I2 and I4-I7 have none | n/a | n/a |
### 5.2 Odin sites (`crates/odin-daemon/src/lib.rs` at `5c37860` unless named)
| # | Site | Mechanism | Class |
|---|---|---|---|
| O1 | `ready``:773-777` | `presence.state == "active"`, relayed, and no disagreements, and every dependency permits Ready. Odin originates the verdict Idunn acts on (I5-I10) | 2 + **4** |
| O2 | `dependency_evidence``:1002-1103` (`ready``:1098`, D `:1108`) | Authenticates the *provider's* stored correlation against **now**, 30 s window, plus capacity. A provider whose correlation is not re-stamped for 30 s flips every dependent to not-Ready: a transitive freshness verdict | **4** |
| O3 | `dependency_permits_ready``:1192` (D `:1202`) | Kind policy (`optional` and `external-operator-binding` never block) lives in Odin. It already bent recipes: Ghostlight `c5bf090` dropped its Heimdall dependency to dodge it (`gamecult-ops/runbooks/ghostlight-dungeon-yggdrasil.md:49-55`) | **4** |
| O4 | `admit_presence``:654-676`, `select_presence``:886-897` | A presence is admitted only for an incarnation Idunn projects. Odin consumes Idunn's truth | 3, keep |
| O5 | `same_publisher``:929-956` (D `:961`/`:964`) | Anti-replay within one activation. Surfaced as a lifecycle outage only because of O1 -> I8/I9 | 3, keep |
| O6 | Stored presence re-authenticated at its receipt time `:960-988`; 30 s max age and 5 s skew `:62-71` | Odin's admission window | 3 |
| O7 | `correlate_write_lease``:1126-1157`, `classify_runtime_authority``:1196-1324`, `classify_current_lease``:1326-1356` | Odin re-verifies Idunn's own artifacts. Redundant with Idunn | 1 |
| O8 | Correlation dedupe and watermark `:541-575`, `:776-792` | Publisher discipline toward I14. Dies with the correlation | 3 |
| O9 | `main.rs``stored_snapshot``:466-490`; `raw_snapshot``:290-310` (D `main.rs:298-316`) | Filtered relay of provider bytes. Odin answers challenges to itself with `signed_presence_document`, the same signer and authority its self-publication uses | 2/3, keep |
| O10 | `main.rs` activation waits for Idunn's lease, 300 s (`:67`, `:580-589`) | Odin as a stateful Idunn target | correct direction |
| O11 | `survive` (D `main.rs:671`, `eeb6812`) | Every error except `WriteLeaseLost` is transient, including permanent self-publication failures | see the temporary rule in section 4 |
**Dead residue** (class 4 in content; nothing deployed; cut by X1):
| # | Residue | Status |
|---|---|---|
| O12 | `crates/odin-core/src/idunn.rs` (672 lines), including `plan_keepalive``:14-173`; `documents.rs:28-59`, about 30 `idunn.*` lifecycle schemas | Uncalled. `odin-daemon` does not depend on `odin-core`. Muninn and Sleipnir (Muninn workspace) still emit `IdunnDaemonHealthRecord` via `odin-core` pinned at `3e96c6c`; no live Idunn reads it |
| O13 | JS coordinator `src/` (2,814 lines) plus tests (140 lines): `probes.cjs` docker and adb probes, `state.cjs` mints service states, `provider-ingress.cjs` 120 s TTL and hard-coded `active` | Not deployed (`state/map.yaml:30`). Still reachable by `package.json start`, `scripts/start-odin.ps1`, `scripts/restart-odin.ps1`, and `gamecult-ops/compose/odin.yggdrasil.yaml` |
| O14 | `scripts/` (3,208 lines): deploy, restart and health actuators for heimdall, streampixels, repixelizer, stonks, vili, weksa, nightwing and voidbot | Deploy scripts dead but armed (`IDUNN_ACTUATOR=1`, referenced by `gamecult-ops/scripts/idunn/idunn-deployment-targets.ps1:178-216`). Restart scripts target the retired Windows Idunn. Health scripts are stubs or orphans |
| O15 | `docs/architecture.md``:49-62`, `:97-109`, `:218-228`; `README.md``:71-105` | Claim process lifecycle, health and "Rust lifecycle logic" for Odin, and name `crates/idunn-daemon`, which does not exist |
**Recipes and ops.**
- G1: Muninn `2260853``raven-muninn.toml` declares the Odin dependency *to obtain a readiness class* (its own
comment says so). Unrouted. Held `Undeclared` on the live store, so it has no continuity restarts today.
- G2: Ghostlight `c5bf090` and the ops runbook `:49-55`: a real dependency was removed to escape O2 and O3. Odin's