# Route continuity and admission: cut map 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. Ends are owned by route-continuity-target.md. **2026-09-29, Self (Eureka session "Codebase audit"; the campaign was handed over from the StreamPixels deployment session with the operator's confirmation).** The operator ruled all six questions in section 3, one at a time: **Q1 (b), Q2 (b), Q3 (b), Q4 (b), Q5 (a), and Q6 (a) with a Soul gate on the weekend branch before the merge.** Next: S1 in Hands, and the Q6 Soul gate on CultLib `codex/fix-node24-ajv-esm`. Behaviour cuts re-take their `file:line` anchors in their own briefs. The operator has asked for no live mitigation on Yggdrasil yet. **B3 status, 2026-09-30 (Self).** Hands pushed B3 to `idunn/route-b3` at `5ca7760`; it is based on B1's merge `a61540b`, not stacked on B2. The Opus Soul pass found: - Full suite green: 252 passed, 2 ignored. - `cargo-mutants --in-diff`: 31 caught, 4 missed. - Soul's probe P1 drove a stateful route-proof target through the Engine, so the stateful path works; it was only untested. Verdict: safe to merge for the StreamPixels web ship. The fix batch, in Hands, covers these findings: - the evidence tag is a second class authority, and a pre-B3 transaction wedges Routing; - the driver's rejections of a bad answer become gate waits instead of errors; - the shortfall is prose; - `OdinSelf` is decorative, and "is Odin" is still decided by name in five places; - Warming is never driven past one step; - seven unpinned checks. Before `idunn up`, one read-only decode of a *copy* of the live `control.cc` checks for non-terminal transactions of routed targets with no Odin declaration (operator approved, 2026-09-30). **B3 fix batch, 2026-09-30.** Pushed `6ea824b` and `e11b457`; 275 tests pass. - `ReadinessClass::of` is the only class authority. The evidence tag is demoted to `Voucher`. - `ChallengeFailure::{Silent, Refused}` separates silence from a refused answer. - `PresenceDisagrees` makes the capacity shortfall a typed value. - The recipes are on `route/b3-declare`: Heimdall `d5acc94`, Muninn `2260853`. - Still open, for a fresh Hands: three survivors (boot re-proof by tag, config error as silence, first-Odin check by name) and `cargo mutants --in-diff`. **Live store, read-only decode of a copy, 2026-09-30 14:56 (operator-approved).** - `control.cc` holds 5 envelopes, all `admitted_generation.v2` (pre-F0), and **no transactions**. - `history.cc` holds 957 transaction v3 envelopes and 641 command v2 envelopes. - Admitted generations, all on Odin receipts: - ghostlight: routed, declares Odin, stateful. - odin: routed rudp, provides the rendezvous. - raven-muninn: unrouted, no declaration, stateless. B3 class `Undeclared`, so **held**. - streampixels-service: routed, declares Odin, stateful. - streampixels-web: routed, declares Odin, needs `streampixels.service.api`. - **Heimdall is not admitted.** It drops out of the ship window. Its declaring recipe still merges. Its first deploy will hit Soul pass 2's F2: a routed, Odin-correlated target must pass the stable-route proof, and Heimdall answers no challenge. Record that for its own deploy. **B3 pass 2 (Soul, 86ee21d):** safe for the web ship. One same-window blocker: - **F1:** supervision mints a held continuity over a dead held generation, and that continuity owns the target, so the declaring redeploy can never freeze. The fix is in Hands. - F5, bad answers read as Silent over HTTP keep-alive and TCP, is in the same batch. **B3 fix batch (`86ee21d..e8b4487`), Soul pass 3, 2026-09-30: merge with follow-ups.** Nothing here hurts a live host on install. 294 passed, 2 ignored. `cargo mutants --in-diff`: 53 caught, 0 missed. - **F1 holds.** There is one continuity mint site, and the hold check comes before it. A declaring redeploy replaces a dead held incumbent end to end. - **F5 holds.** Zero bytes is Silent. Any byte then a stall or stop is Refused. Non-2xx responses and non-chunked Transfer-Encoding are Refused. - **Follow-ups, in Hands on the same branch:** - the P1b test never reaches the yield branch (mutant M7 survives); - the chunked trailer loop is unbounded; - `+N` lengths and whitespace before the colon are accepted; - exact 1 MiB and one-byte-then-stop are unpinned; - the `Silent` doc is stale; - the dead-hold report goes to stderr only and never clears. **Ruling, 2026-09-30 (operator): cancel accepts a held pre-fence Deploy.** Idunn still never aborts a held record on its own. An operator can discard a held Deploy that has not reached the fence with `idunn cancel`. Nothing is fenced, so the incumbent is untouched. **B3 follow-ups landed on `idunn/route-b3` at `5c49646`, 2026-09-30.** - Cancel accepts a held pre-fence Deploy. It shares `pre_fencing_abort_intent` with the scheduler. - The P1b test is now in direct-supervision form, with a positive control. - The reader is strict: trailers share the 32 KiB header budget; digits only; header names without whitespace. - The `Silent` doc is corrected. - Tests: 301 pass. `a_failing_step_is_retried_after_a_backoff_not_every_tick` is flaky under a full parallel run, on the base too. - `cargo mutants --in-diff`: 24 caught, 0 missed. - A narrow Soul pass gates the install. **Ship log, 2026-09-29 (host clock).** - B3 merged at `d32395a` and installed on Yggdrasil at 18:36 UTC, with sha `854aa351`. It migrated 5 control records. The store backup is `/root/idunn-store-backup-20260929T183604Z-pre-route-b3`. - Nginx reloads fell from 82 per 10 minutes to 0. Odin continuity timeouts fell from 92 in 6 hours to 0. - **Raven's actuator (built 2026-09-11) could not decode the new plan types.** A rebuild then found that the Windows `idunn-host` has not compiled since S1: `FROZEN_SOURCE_SYMLINK_TARGET_LIMIT` was defined for unix only. That is fixed at `d3db582`. - The new actuator (sha `f36bf1fc`) is swapped in on Raven. The old one is kept as `idunn-host.exe.prev-20260929T190006Z`. - Follow-up: nothing builds the Windows actuator in verification. - raven-muninn was admitted at 19:02 by Odin correlation, replacing the held generation. - **The streampixels-service deploy failed before fencing.** The S1 `bulk_fetch_objects` asks for bare blob ids with `git fetch --stdin`. In a `blob:none` clone, the connectivity check then fails with "bad revision ". The fix, with a test, is in Hands on `fix/bulk-blob-fetch`: a promisor-style fetch with `--filter=blob:none` and `fetch.negotiationAlgorithm=noop`. **Ship completed, 2026-09-29 (host clock).** - **Bulk-fetch fix.** Merged at `583b2a7` and installed at 19:17. The Idunn sha is `01bde676`. - **The first streampixels-service redeploy failed after fencing.** The incumbent was restored, and every restart was refused by Odin with "runtime presence publisher sequence was reordered". Cause: Odin compared publisher sequences across incarnations, and the TS publisher counts from 1 per process. - The operator ruled: fix Odin's rule. - Odin `db041ec` compares sequences only while the stored presence still authenticates under the current activation. Soul said deploy, with follow-ups; Odin was deployed at 19:4x. - **Admissions.** - streampixels-service was admitted at 19:50 on s3-c2. - The web binding dropped `STREAMPIXELS_ODIN_CULTMESH_RUDP`, and gamecult-ops `37b5ed9` does the same. - streampixels-web was admitted with Odin-free readiness. - **Public cutover at 19:54.** The vhost upstreams moved to `:8833` (web) and `:8832` (service). The backup is `/root/streampixels.gamecult.org.conf.bak-20260929-195415-pre-idunn-upstreams`. - `https://streampixels.gamecult.org/api/catalog` returns 200 (120,893 B), after being 502 since migration 013. - The overlay SSE stream answers `text/event-stream`, and the ops checker passes. - **Legacy units.** `streampixels-web.service` is disabled and stopped. `streampixels-service.service` is disabled, and was already stopped. - **Follow-ups.** - The C2 acceptance hour: sample the nginx reload counter at T0 and T0+60, with no rejected challenges. - Nothing builds the Windows `idunn-host` in verification. - Odin presence: pin the stored-at re-authentication time (Soul's M3 survived), and consider checking activation identity directly rather than any failure to authenticate. - The dead-hold report field (ruled). - B2 rework on B3. - Odin's CultLib pin bump, after the stray-packet merge. **C2 acceptance hour passed, 2026-09-29 19:55-20:55 UTC (21:55-22:55 CEST).** - 1 nginx reload, which was Odin's own route promotion during its `5c37860` deploy. - 0 rejected StreamPixels challenges, 0 Odin continuity timeouts, and Idunn `NRestarts=0`. - One running unit each for service, web and Odin. Public `/` and `/api/catalog` return 200. - Also deployed in the window: Odin `5c37860`, carrying CultLib `3bf1c0c` (RUDP stray-packet hardening in every runtime), Sleipnir removed, and presence ordering decided by activation identity. - Still open for Odin: it exits when its 64-slot RUDP session table fills, because its own heartbeat publish fails. Publishers that never Disconnect hold slots for 30 s. The fix is in Hands. - The second run, with Odin broken on purpose, remains the operator's to schedule. **Ruling, 2026-09-30 (operator): the dead-hold report is stored on the generation.** - Add an optional `last_error` to `AdmittedGeneration` (new key 21), and make `status` render admitted generations. - It is cleared when the generation is replaced or its hold is released. - The `generation:` readiness report gets the same home. - It is queued after the ship, with the B2 rework. **Odin diagnosis (Eyes, read-only, 2026-09-30).** - The 109 Odin route timeouts are self-inflicted. Live Idunn (`8ae00a1`, pre-S1) reloads nginx inside `restore_admitted_membership` before every Odin challenge. The reload moves the reuseport UDP flow, so the challenge misses. All 109 have a reload 3.05-3.12 s before them. - 2,977 reloads in 6 h. 479 shutting-down nginx workers, because the stream has no `proxy_timeout`. - The route timeouts do not block Ready correlation. - Odin has crashed twice right after a reload. A stray RUDP packet with a foreign connection id escapes `cultnet-rs` `rudp.rs:1217` (`require_connection`) and ends the process. Each crash costs about 75 s without a lease. The CultLib fix is in Hands on `hands/rudp-stray-packets`. Odin takes it at its next pin bump, after the ship. **Operator ruling: Idunn first.** Install the new Idunn (B3 carries S1), then watch for about 15 minutes that reloads stop and Odin stays up. Only then redeploy raven-muninn and StreamPixels. **Ship sequence:** 1. B3 fix, then narrow Soul, then merge B3. 2. Merge StreamPixels `route/s3-c2` and the Muninn and Heimdall `route/b3-declare` recipes. 3. Build the Idunn release on its deploy path. 4. Back up `control.cc` and `history.cc`. F0's lift is irreversible. 5. Install Idunn. 6. `idunn up` raven-muninn with the declaring recipe. 7. `idunn up` streampixels (service, then web). **Rulings, 2026-09-30:** - A stored record whose evidence disagrees with its declared class is **held and reported, never aborted** by Idunn. B5's deadlines or the operator resolve it. - **Heimdall and raven-muninn are redeployed with their declaring recipes in the same window as Idunn B3 and the StreamPixels ship.** **Ruling, 2026-09-30: the recipe declares readiness; Idunn infers nothing.** This settles the fork Hands raised against a literal Q1(b). An unrouted target whose recipe declares neither a route nor an Odin dependency is refused at admission with a typed error. It does not fall back to Odin correlation because it has no route. `raven-muninn` and Heimdall both publish to Odin, so their recipes declare it. The rejected alternative let Odin's availability gate Muninn's crash recovery through an undeclared, inferred dependency. **B2 status, 2026-09-30 (Self).** Hands pushed `idunn/route-b2` at `1786ddf`. The Opus Soul pass rejected it for merge. - **F1 (critical):** `ContinuityBackoff.deferral_reason` was added without a schema bump. B2 fails the canonical re-encode of every three-field v3 generation that F0, B1 or B3 wrote, so Idunn cannot read its own store. - **F2:** the ceiling refuses a rollback, which leaves an unproven candidate on the stable route. - **F3:** a failed challenge writes, and continuity's CAS then loses against a stale envelope. - **F4:** neither window rolls; both are anchored at the first charge. - **F5:** a clock stepped backwards stalls everything for the length of the step. - **F6:** `record_proved_challenge` is unpinned. - **F7:** the mount preflight is not gated. - 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 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 deployment. It is always allowed and always counted. Only installs and other forward changes can be refused. A rollback that keeps failing is spaced by backoff, never refused. - **First routed deployments are metered now.** The actuation window lives on the target, not on the 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).** - **3d A (2026-09-30, morning handoff):** separate budgets. The 12/h route actuation ceiling counts only Forward actuations (deploys). Survival actuations (continuity restarts, rollbacks, repairs) are still recorded for visibility but do not charge the ceiling; each stays bounded by its own mechanism (the 6/h per-target restart log, the challenge backoff). A crash-looping target can therefore always be rescued by a deploy. Option D (reset the restart log when a new release is admitted) was not chosen: a new release still inherits its predecessor's restart history. B2 is merged (`46d9c2f`) but not installed; this follow-up (B2-3d) lands before the install. - **B2-3d status (Self, 2026-09-30).** Built on `hands/b2-3d` (`e9c718e`, `057bdd5`). Soul re-ran `verify.sh` clean (341) and killed every one of its own mutants on the Forward/Survival split. **But the premise the operator ruled on is false:** "every Survival path has its own bound" does not hold. - **F1:** a continuity transaction whose route proof keeps failing retries inside one transaction under the 60 s resume cap, reloading nginx twice per retry. That is about 120 reloads/h per target, with no end until B5. - **F2:** a failing post-fence withdrawal reloads about 60/h. `restore` reloads even when the fragment is already correct. - **F3:** nothing bounds reloads across the host. - None of these is new in 3d: Survival was never refused before either. The live Idunn, which predates B2, has no reload bound at all. What 3d adds is the false claim, at `control_plane.rs:76-81`, plus the admitted Deploy doubling to 24/h. - The fix belongs to B5 ("every post-fencing phase ends"), plus a restore/install that skips the reload when the fragment is already correct. The operator was told on 2026-09-30. - **B2 + 3d installed (2026-09-30).** The operator accepted recommendation A: install now, with B5 next. - Installed at 15:22 CEST: `a96ad9d`, `idunn` sha256 `848c08e5...`; the replaced binary was `01bde676`. - The migration moved 5 control records to `deployment_transaction.v4` / `admitted_generation.v4`. - Pre-install backup, taken with Idunn stopped and byte-compared: `/root/idunn-store-backup-20260930T132204Z-pre-route-b2/` (control.cc `5f879f55...`, history.cc `bafa76ab...`, the other stores, and the old binaries). - Preflight: 690 transactions, all terminal. They were identical after the install. - No managed target's PID changed. Raven reattached after 15 s. - After 10 min: no odin route-continuity rejections (about 95 in the previous 3 h), 0 nginx reloads, and the Odin post-deploy check `fails=0`. - B5 and skip-unneeded-reloads are in Hands (`hands/b5`). - **B5 Soul pass 1 (2026-09-30, on `a96ad9d..c06ff2b`): merge after the batch-2 fixes; no install before them.** Soul reran verify: 352 passed. Batch 2, now in Hands, fixes: - **A (high).** A continuity aborted from `Prepared` leaves a lease on disk that `driver.grant` wrote before `Granted` was durable (`control_plane.rs:6916-6930`, `:9081`). The next fencing refuses, and the target is wedged. - **B (medium, a regression from `6ff7d5a`).** The reload skip hides a crash between the write and the reload on the `restore`/`install` transaction paths, leaving nginx proxying to a dead port. The skip goes back to supervision only. - **E.** The resolver's command filter is unpinned. - **F.** Odin-correlated adoption has no committed test; mutants M6 and M7 survive. - **J.** A flaky backoff test (`:14200`). Recorded: - **C** (the abort has no deadline) is closed by the Q3-ii cut. - **D.** There is no host-wide reload bound. 170/h is about a third of the F6 storm, and 4830af8 lowers the real worst case. - **G.** A newer Ready can rewrite `lease_adoption`. It names the same lease, so it is harmless. - **H.** AwaitingReady and Routing keep the F16 shape. It is unreachable at today's heartbeats. - **I.** An `idunn expire` request written while Idunn is stopped lingers, and there is no verb to withdraw it, which is a downgrade hazard. - **B5 MERGED and INSTALLED (2026-09-30).** Merged to main at `fcb8ba1` (Soul passes 1-3). Built on Yggdrasil: `/srv/build/idunn-fcb8ba1`, 360 passed. `idunn` sha256 `e96bcefa...`, `idunn-provision` `127902ef...`, unit unchanged. Installed at 22:35 CEST. - **Backup**, taken with Idunn stopped and byte-compared: `/root/idunn-store-backup-20260930T203515Z-pre-route-b5/`. It holds `control.cc` `fab5b66b...`, `history.cc` `a2627ea4...` and `idunn.running` `848c08e5...` (a96ad9d). - **Preflight** found one live transaction: the raven-muninn continuity `990dfc25` in AwaitingReady for about 40 min. Raven had rebooted, and its actuator timed out at 21:36 and 21:49 CEST. The operator ruled "Feel free to install". - **After install:** - No managed target's PID changed. Raven reattached after 15 s, and nginx made 0 reloads. - B5 aborted the stale continuity at its passed deadline, and supervision opened `continuity-26737b91`. - A single log line, "could not resume transaction tx-bcfbb693...: transaction disappeared while checking scheduler progress", appeared at 20:35:38 UTC. It looks like a same-tick ordering quirk between the resolver and resume. It goes to the next Soul pass. - **Rollback rule:** stop Idunn and check `control.cc` for any `idunn.expiry_request` envelope. If one exists, restore the backup instead of swapping the binary. - **Named tradeoff (Soul batch-3 F1):** a persistent `ufw delete` failure while aborting a first routed deploy now leaves the aborted candidate routed until ufw recovers. Before B5 the cost was reload churn. Q3-ii's abort deadline bounds it. - Test pins F2-F4 are in Hands (`hands/b5-pins`). - **B5 batch 2 (`c06ff2b..aef0da3`) and Soul pass 2 (2026-09-30): merge and install after batch 3.** - A, B, E, F and J hold. Verify: 356 passed. cargo-mutants caught 14 of 14 viable mutants, and Soul's hand mutants killed 16 of 20. - Batch 3, in Hands: test pins for MB2 (`changes_route` swapped for "no incumbent"), ME2/ME3 (the resolver filter by id length or first byte) and MJ2 (the computed backoff unpinned). `restore` withdraws the firewall rule before reloading when there is no prior route, so a failing `ufw delete` no longer reloads nginx each retry. - Hands corrected Self's brief: the continuity path's zero reloads came from `6ff7d5a`'s skip, not `4830af8`. The narrow no-change case (`changes_route() == false`) keeps it without a write, so there is no crash window. - Recorded, not in B5: - **P1:** a crash between `atomic_replace` and the reload in supervision's `restore_admitted_membership` (`drivers.rs:5543-5545`). Idunn has no SIGTERM handler, so a stop in that window leaves nginx on the drift until something else reloads. This also exists in live `a96ad9d`. Fix with Q3-ii or a durable "reload owed" marker. - **The abort revokes before it stops the candidate** (`control_plane.rs:8758` before `:8783`). Q3-ii owns the abort order. - **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. **Odin-lifecycle audit rulings, 2026-09-30 (operator).** The audit landed as section 5. Q-O1: Idunn proves (supersedes Q1 (b); readiness classes end at A1 + A3). Q-O2: a private challenge endpoint for unrouted targets (A2 + a Muninn responder). Q-O4: the Odin dependency is optional and does not gate a compile. Q-O5: restart on bad auth (amends Q5 (a)). The held Q-B4 clause is struck; B4 becomes B4′. Q-O3 (redeploy order odin -> streampixels-service -> ghostlight -> web with the operator present, vs a one-shot relabel) is **not yet asked** and is needed before A1 installs. Odin keeps three permanent self-publication failures fatal until A3 + Q-O5 land (temporary rule, section 4). **Cut status, 2026-09-30.** - **Ready for Hands:** B2 (rework map in section 4); D1 (after B2). **R1 is never to be written**, superseded by A1. - **In progress:** `hands/idunn-b2-rework` (B2 rework); `hands/idunn-w1-t1-t2`: W1, T1, T2; X1 on Odin `hands/odin-x1-residue`; the temporary fatal rule on Odin `hands/odin-session-table`. - **Next, in order:** A1 (after B2, and after Q-O3 is ruled), B4′, A2 with M1, A3 (irreversible store migration, back up first), O-R, L-R (section 4, Odin-authority cuts). - **Open for Soul:** the continuity-restart stall probe and the journal check of the ghostlight/raven-muninn exclusion (section 5.5). - **Unblocked:** B4 (now B4′). **Blocked:** H1 (Heimdall repo), on CultLib C1. A supervision change for Q-O5 has no cut yet. Heads read for this map: | Repo | Ref | Notes | |---|---|---| | `F:\Projects\Idunn` | `main` = `d719485` (code `8ae00a1`, trusted baseline `9ede4cd`) | `idunn/cut1-fix5` is 14 ahead / 25 behind `main` | | `F:\Projects\StreamPixels` | `main` = `a665bce` | `vendor/CultLib` gitlink = `542ddd3` | | `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) | | `F:\Projects\Odin` | `379b826` | read only, for lease pickup | | Yggdrasil | `/usr/local/bin/idunn` sha256 `5f257f2c…`, `idunn-yggdrasil.service` active | read-only probes, 2026-09-29 | "Read" means the claim comes from source at those heads. "Probe" means a read-only command on Yggdrasil on 2026-09-29. Nothing was written to the host. --- ## 1. Body facts ### 1.1 Admitted route supervision actuates on observation age - F1. The scheduler tick (`run_scheduler_tick`, `control_plane.rs:3075`) runs `resume_one_transaction`, then `supervise_one_admitted_generation` (`:3293`), then freezes commands. The loop sleeps `poll_millis` (default 500 ms, `:1473`) between ticks. Read. - F2. `supervise_one_admitted_generation` calls `supervise_admitted_route` (`:3606`) first for every admitted target that no transaction owns past Fencing. The call is made for every admitted target on every tick. Read. - F3. `supervise_admitted_route` returns early only when all three hold: the fragment on disk equals the admitted membership, the route observation is younger than `topology_maximum_age_millis` (default 30 000 ms, `DEFAULT_TOPOLOGY_MAXIMUM_AGE_MILLIS`, `:61`), and no repair intent exists. Otherwise it CASes `route_repair_started_at_unix_millis = now` into the admitted generation and returns (`:3654-3673`). On the next tick it calls `restore_admitted_membership` and then `prove_stable_route_against`. It clears the intent only after a successful proof (`:3707-3722`). Read. - F4. `restore_admitted_membership` (`drivers.rs:5455`) runs the following on every call, even when the fragment is byte-identical: 1. `validate_candidate_in_private_mount`, which launches a transient `systemd-run --wait` unit running `nginx -t`. 2. `admit_endpoint`, which runs `ufw allow`. 3. `reload()` (`:5254`), which runs `nginx -t` and then `systemctl reload nginx`. The write is skipped when the bytes match (`:5467`). The actuation is not. If the reload fails, it deletes the fragment. Read. - F5. Consequence: a healthy routed target reloads nginx about once every 30.5 s. A target whose proof fails keeps the repair intent and runs restore and reload on every tick, limited only by the 3 s challenge timeout. Nothing records, rate-limits, or backs off these actuations. Read. - F6. Probe: 551 `Reloaded nginx.service` in the last hour across four route fragments (`ghostlight`, `odin`, `streampixels-service`, `streampixels-web`). 460 processes named `nginx: worker process is shutting down`. The Idunn journal for the last 10 min shows route continuity rejections for `streampixels-service` (EAGAIN, 6), `streampixels-web` (EAGAIN, 4; HTTP 503, 1), and `odin` (RUDP connect timeout, 1). - F7. Probe: `/etc/nginx/idunn-stream-routes/odin.conf` is the Idunn-rendered UDP stream proxy `listen 10.77.0.1:17871 udp reuseport;` → `server 127.0.0.1:17973;`. `render` emits that shape for `NginxStreamUdp`+`rudp` (`drivers.rs:5210-5217`). Odin's own route proof goes through that listener as a RUDP catalog snapshot (`request_runtime_presence_at`, `drivers.rs:5566-5574`). Every reload of any target therefore moves Odin's UDP flows. Read + probe. - F8. An admitted generation is never withdrawn. The only writers of `AdmittedGeneration::TYPE` are commit (`:4749`), route supervision (`:3670`, `:3715`), and topology refresh (`:3780`). After three refused continuity restarts, supervision stops restarting (`:3522-3528`) but keeps supervising the route of a dead incarnation. Read. - F9. The continuity restart limit counts refused restarts per generation (`is_refused_restart`, `:3469-3488`). A restart that succeeds admits a new generation and resets the count. A unit that starts, is admitted, and then dies is restarted without bound and without backoff. Read. ### 1.2 Web route proof today - F10. Idunn's challenge is a CultNet `SnapshotRequest` sent to the stable endpoint. For `http` it is `POST /cultnet/snapshot` with MessagePack and an explicit `Content-Length`. There is a 3 s connect/read/write timeout, and `ConnectionRefused` is retried for 2 s (`29946bb`) (`drivers.rs:5524-5580`, `:5628-5720`). Read. - F11. `authenticate_routed_presence` (`control_plane.rs:5185`) verifies the following through `authenticate_runtime_presence_claim` and `correlate_runtime_presence_claim` (CultLib `61549aa`, `runtime_authority_contracts.rs:1163-1230`): - the provider signature; - the activation signature, made with the per-launch key whose public half is in Idunn's activation; - the Expected, activation, runtime and endpoint binding; - `observed_at >= challenged_at`; - age at most 30 s; - `state == "active"`; - `detail == "route-observation:{message_id}"`; - the exact current write-lease sha. **The Idunn verifier already satisfies invariant 4 over HTTP. It needs no Odin input.** Read. - F12. The app side couples that proof to Odin. `publishRouteObservation` (StreamPixels `vendor/CultLib/packages/cultnet-ts/src/idunn-runtime-authority.ts:286-298`) calls `publish("active", "route-observation:")`. That call signs the presence and then **publishes it to Odin over RUDP**, waiting up to 5 s for accept and 2 s for ack (`publishDocument`, `:319-356`), before it returns the document. It signs `active` regardless of application health. Read. - F13. The Next handler (`apps/web/app/cultnet/snapshot/route.ts`) maps any publisher failure to 503. The app-side RUDP wait (up to 7 s) exceeds Idunn's 3 s read timeout, which produces the EAGAIN (`os error 11`) in F6. Read + probe. - F14. The app requires Odin before it can prove anything: - `loadIdunnRuntimeAuthorityFromEnvironment` throws unless Expected carries a `shared-infrastructure odin.verse-rendezvous` dependency equal to `STREAMPIXELS_ODIN_CULTMESH_RUDP` (`idunn-runtime-authority.ts:114-118`). - `runIdunnRuntime` blocks startup for up to 30 s on Odin accepting the initial warming publication (`deployment/idunn/runtime-presence.mjs`, `publishInitialWarming`). - `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 | | Ready | `advance_awaiting_ready` `:4423` | Odin correlation `is_semantic_ready` | | 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`) | | Admitted refresh | `refresh_admitted_topology` `:3726` | Odin correlation updates the admitted receipts | | Graph gate | web recipe's `odin.verse-rendezvous` dependency | Odin must be admitted and Ready to compile the plan | - F16. **Commit livelock (source-read, supported by live history).** 1. `advance_committing` admits the topology, proves the stable route, then calls `admit_latest_topology(&ready_current, …)` again. 2. If a new Odin sequence arrived meanwhile, that call persists it, the envelope changes, and the function returns `Ok(())` without committing (`:4658-4666`). 3. The web's route proof itself publishes a fresh `active` presence to Odin (F12), and Odin re-stamps its correlation when the presence changes (comment `:5473-5478`). 4. Every commit attempt therefore tends to manufacture the evidence that aborts it. Probe: `up-efb85890…` (`tx-5fe84313…`) is now `Complete`/`Admitted` at Odin cursor 4008. It had been `Committing` for more than 12 h at cursor about 3680. A commit that eventually wins a race fits this mechanism. No reproducer has been written yet. ### 1.4 Post-fencing phases and leases - F17. When a transaction errors at or after Fencing, it is aborted only if `candidate_is_permanently_stopped`. Otherwise `record_resumable_error` runs (`:3107-3131`). There are no deadline fields on `DeploymentTransaction` (`:651-731`). Read. - F18. `advance_awaiting_ready` returns `Ok(())` for as long as no new Ready correlation exists. Only candidate death interrupts it (`observe_candidate_before_waiting`). Routing and Committing wait the same way. Read. - F19. Lease pickup windows are owned by the consumer, not by Idunn: - Odin accepts a lease only if its `warming_presence_sha256` names a warming proof Odin issued within `WARMING_PROOF_LIFETIME_MILLIS = 60_000` (`odin-daemon/src/main.rs:73`, `acquire_process_write_lease` `:886-911`, `try_activate` `:225-263`). - The TS publisher accepts any of its last 64 proofs with no time bound (`assertLeaseMatches`, `idunn-runtime-authority.ts:687-706`). - 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/.conf`) + **ufw allow** | path is injective per binding (`validate_route_binding_set` `:2386-2393`); first line `# Idunn Expected ` | 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 `streampixels-service`.** **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.** Options: - (a) Every non-Odin target proves Warming and Ready directly through Idunn's challenge. Odin only observes. - (b) A target's class follows its own declaration. A target that declares a `shared-infrastructure odin.verse-rendezvous` dependency is Odin-correlated, as today. A target that declares none is route-proof. - (c) A new explicit binding field chooses the class. Recommendation: **(b)**. It reads the ruling literally: GC infra that advertises into the Verse is correlated by Odin, and a web app that declares no Odin dependency cannot be gated on one. It needs no new field. A repository choosing route proof gains no privilege, because both classes are signed by the same Idunn-issued launch key. Depends on it: the F0 enum shape, the B3 scope, whether the service recipe changes (under b it does not), and whether the F16 livelock matters for Odin-correlated targets (B5 covers that). **Q2. Where a dependent's provider currency comes from** (the `9f00e7a` replacement). **RULED (b) by the operator, 2026-09-29.** Options: - (a) The provider's Odin receipts, authenticated now. This is a strict revert, but the web then depends on Odin transitively through `streampixels-service`, which breaks invariant 3. - (b) Idunn's own current route observation of the admitted provider: a successful challenge within max age, with capabilities taken from the signed presence in that proof. Unrouted providers fall back to their Q1 class evidence. - (c) Always the provider's own class evidence. Recommendation: **(b)**. Idunn already challenges every routed target, and under invariant 1 that challenge becomes the steady-state heartbeat. It is current, it is signed by the provider's launch key, and it needs no Odin. Depends on it: B4, the `ManagedReady` shape (the Odin sha and sequence become a class-tagged evidence digest), and plan compatibility with existing admitted dependents. **Q3. How a stateful post-fencing deadline resolves once the candidate may have written.** **RULED (b) by the operator, 2026-09-29.** Options: - (a) Always a terminal `OperatorRequired` record. - (b) Split on lease adoption (F0 evidence): - Not adopted: revoke, stop, and resolve like stateless. A deploy restores the incumbent through the fence. A continuity counts one refused restart. - Adopted: a deploy gets a terminal `OperatorRequired{adopted-lease}` record. A continuity gets a terminal record and the admitted release restarts under the continuity backoff, because it is the same release and state contract. - (c) Automatic incumbent restore whenever the state cut is declared reversible. Recommendation: **(b)**. Adoption is signed evidence that the candidate may have written. (c) trusts a declaration over evidence. Depends on it: B5, the recovery variants of the terminal record, and the replacement for `idunn cancel`. **Q3 follow-ups from B5 Hands (`hands/b5` `c06ff2b`), 2026-09-30:** - **Q3-i: does an issued lease with no adoption evidence count as possibly written?** **RULED yes** by the operator, 2026-09-30. A candidate cannot prove it holds the lease while warming, and streampixels-service migrates in exactly that window. So any issued lease on a deploy is treated as possibly adopted. - **Q3-ii: after an adopted-lease deploy expires, what stops continuity from restarting the incumbent on state the candidate may have migrated?** **RULED 2026-09-30: D before Ready, E after.** Operator, verbatim: "D before Ready, E after; this actually closes the hole, and is a nice feature for Idunn to have". - **D, before the candidate reports Ready:** after the fence and before the write lease is issued, Idunn snapshots the target's state through a per-target snapshot/restore declared in its binding. If the deadline expires before Ready, Idunn restores the snapshot and brings back the incumbent. A failed restore is the only terminal `OperatorRequired`. - **E, after Ready:** the candidate is known-good and may have served traffic, so an expiry rolls forward. The candidate stays the release supervision keeps alive, and Idunn completes or retries routing and commit. - Assumption Soul must falsify: the incumbent cannot write between the fence and the lease, so the snapshot is exact. - Mapped: section 4, "Q3-ii cut: snapshot before Ready, roll forward after" (Imagination, 2026-09-30); it establishes the assumption for Idunn-managed writers and names what breaks it (Q3-ii.2). History, the question as first put: The operator on the offered hold-until-next-`idunn up` option, verbatim: "I don't like any of these options, unless a stateful deploy running out of time is an extreme edge case, in which case A". It is not extreme: the default `awaiting_ready` deadline is 300 s (`deployment_plan.rs:474`), so any stateful release that migrates and then fails to reach Ready lands here. Alternatives are being put to the operator. **Q4. The operator's recovery verb after `8ae00a1` is deleted.** **RULED (b) by the operator, 2026-09-29.** Options: - (a) Delete it outright and let deadlines resolve everything. - (b) Replace it with `idunn expire `. The command writes a typed expiry request that the daemon consumes, so the phase resolves through the same deadline resolver. The CLI never writes transaction fields. Recommendation: **(b)**. It keeps a human escape for long deadlines and removes the second writer. Depends on it: S2 scope and the CLI surface. **Q5. Whether a sustained route-proof failure on an admitted target restarts its unit.** **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.** Options: - (a) No. The route is marked degraded, dependents stop selecting it (Q2b), and challenges back off. Restart happens only on workload death, as today. - (b) Yes, after N consecutive failures, under the continuity backoff. Recommendation: **(a)** for this campaign. A process that answers 503 while alive is a health-policy question, and making it restart authority mixes observation back into actuation. Depends on it: B2. **Q6. Landing the CultLib Idunn TS runtime on `main`** (F15). **RULED (a), with one Soul pass on the ten weekend commits before the merge by the operator, 2026-09-29.** Options: - (a) Merge `codex/fix-node24-ajv-esm` into CultLib `main` as-is, then cut C1 on `main`. - (b) Cut C1 on the branch and merge once. - (c) Rewrite the package on `main`. Recommendation: **(a)**. It makes the shared substrate honest before changing 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 ; reopens at