carlos / platform Public

harness: Run 9 (B) rollback rehearsal PASS (23 s), Run 9 corrections R9-1..4, M1..M10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev d4eb286652a14d38d0a7830870ed6769c330f401 parent 72022e7
1 file changed, +85 −0
  • harness/RUNLOG.md +85 −0

CI cancelled — superseded by a newer push — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.011s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m

amadan: job cancelled — a newer push superseded it
diff --git a/harness/RUNLOG.md b/harness/RUNLOG.md
index b2dea7040..f0d211c5e 100644
--- a/harness/RUNLOG.md
+++ b/harness/RUNLOG.md
@@ -1813,3 +1813,88 @@ grace runs out (`internal/layout/pass.go:318-322`); an app with a 1-minute sched
`carlos status --layout`: `noor-marquetry (nested by default) moveapp.dev.harness.test flat` and the warning "runs
nested instances beside flat ... Nested isolation on this box is not in force until none of those remains".
+### B5. The rollback rehearsal on rollbackapp: abort after flip, before open: PASS (17:32:49-17:33:13Z)
+
+`blocks/r9-move.host.sh rollbackapp abort`. Before (woken on sophie): `bg` 2689 rows, `worker` 1351, instance key
+sha256 `eb5cb3433bc745c9`, files `instance.key` `2167c1dd...`, lease `sophie:230`.
+
+| step | at | took | result |
+|---|---|---|---|
+| start | 17:32:49.695Z | 2.1 s | as moveapp's; `instance exited (exit 0)` 17:32:50Z |
+| seal | 17:32:51.939Z | 6.1 s | `Copied and checked.`; data-ie lease `moved:mv_kai2mu6xs376umepnqhhvykfuu`; sophie's slice empty (0 processes) |
+| flip | 17:32:59.877Z | 6.1 s (try 1: inside the 2-minute window) | `Stopped in ie. Starting in uk.` / `uk holds everything ...`; dennis row `landing`, flat, env `165773df...` = sophie's, config.env 7 names, files `2167c1dd...` |
+| abort | 17:33:06.382Z | 4.1 s | `Moving rollbackapp.dev.harness.test back to ie.` / `Every box has put rollbackapp.dev.harness.test back in ie. Run carlos move offline finish ...` exit 3 (the CLI's "aborted" code); reports: sealer, both sources and the target `reverted` |
+| finish | 17:33:10.587Z | 0.1 s | **`rollbackapp.dev.harness.test is back in ie and serving. Nothing was lost.`** `Move mv_kai2mu6xs376umepnqhhvykfuu ... is aborted.` exit 3 |
+| first request on sophie | 17:33:12.342Z | 0.6 s | `/ready` 200 (0.44 s) at 17:33:12.979Z: **offline 23.3 s** from start (the console: `offline_s 20.9`) |
+
+Verified (17:33:32Z): sophie `/report` `bg` 1-2735 and `worker` 1-1375, contiguous and past the 2689/1351 read
+before start: **no write lost**; `platform_key_sha256 eb5cb343...` and the files unchanged; the store object
+`probe/a6f1b49d1c6b0487.txt` from the new boot. Sophie's row `ready` (nested, the local DB never purged); data-ie lease
+`sophie:230` generation 2 (the tombstone was lifted and the source took its own lease again); data-uk has no lease and
+no `moves/` object for the host (staging deleted); dennis has no row and nothing in `/data` for it, and parked what it
+had extracted under `/data/.offline-moves/rollbackapp.dev.harness.test/mv_kai2mu6xs376umepnqhhvykfuu/target/`
+(`<host>/instance.key`, `<host>.env`, `<host>.config.env`); the record is back in home (`region` absent), `move` null,
+`last_move {"outcome":"aborted","offline_s":20.9}`.
+
+## Run 9 corrections
+
+### To `claim-dev-cohort1-runbook` (the roll) and the README
+
+- **R9-1 (README "Checking dev", step 3).** On dev, leo (carlos-dev-ie-b) has had `CARLOS_POOLS=cohort1` since step
+ 4.2 and no wakecheck row since 4.3, so the pinned request is answered by backhaul from the console box. Replace
+ "A pass is a 200 whose `host=` is that box" with "A pass is a 200 whose `host=` is `carlos-dev-ie` (the request
+ reaches carlos-dev-ie-b's edge and is backhauled; while leo is in pool cohort1 it holds no wakecheck row)". And
+ step 4: wakecheck is `nested` on the console box only.
+- **R9-2 (G4).** Add sophie: "Sophie (canary, armed) takes v0.28.5 by the same seven steps with `--channel canary`;
+ its console keeps running the protected v0.28.4 console (a router apply does not touch it)." Add under "Why not
+ `system update`": "This holds for the v0.28.4 to v0.28.5 step only. Harness Run 9 showed a box on v0.28.5 taking the
+ next release through its own `system update` with no hand step."
+- **R9-3 (G4 precondition).** Fill "Harness run ID": Run 9, `cohort1-20261020T161315Z` (the identity session) on
+ harness-build; re-arm PASS on leo 16:19-16:23Z and sophie 16:30-16:33Z; normal road PASS 16:38-16:48Z.
+- **R9-4 (the console, NEW, blocking).** There is no block that moves sophie's guarded console to v0.28.5: `system
+ claims install-console` refuses once a console is installed (defect 5). Until it is ruled, dev's console stays on
+ v0.28.4, and the offline-move doors (only in the new console) do not exist on dev. E1 (move both slots aside, then
+ install-console) worked in the harness; it is an experiment, not a ruled block.
+- **C4 holds:** the roster revision moved 9 -> 10 -> 11 for leo's roll, 11 -> 12 -> 13 for sophie's, 13 -> 15 for the
+ normal road.
+
+### To the offline-move plan's "Morning runbook outline" (`2026-10-20-offline-operator-move.md` on main)
+
+- **M1 (P0.1/P0.2).** Add: "The console that serves the doors runs the release: `carlos move offline status` answers
+ (an older console has no offline doors). On a guarded console box this needs R9-4's ruling first."
+- **M2 (P2.1, P1.2; finding 7).** Replace "`status` until sealed; `flip`; `status` until landed; `open`" with: "run
+ `flip` within two minutes of the seal (and `open` within two minutes of the flip): the doors trust a box report for
+ two minutes, and boxes rewrite theirs only on the moves pass that a door's serial bump starts, or every 5 to 6
+ minutes. If `flip` answers `<box> still runs a process for <host>` and `status` shows `Its report is not current`,
+ that is a stale report, not a process: retry every 15 s until the next pass (at most 6 minutes). Only a current
+ report with a process count is a real MV-O31."
+- **M3 (P0.7).** Replace "Make the first request yourself straight after `open`, then let the layout pass nest it
+ inside the window" with: "Make the first request yourself straight after `open`. The row is flat. The layout pass
+ nests it only when it is asleep, or stops it after a 1-hour grace (`layout: deferred until asleep -> <host> (awake
+ (stopped for the move in 1h0m0s ...))`). An instance with a schedule shorter than the idle timeout never sleeps, so
+ plan a second short stop about an hour after `open`, and confirm `carlos status --layout` shows it nested after
+ that." (B4 below has the measured result.)
+- **M4 (P1.0).** In dev's current state (leo armed, claims off at the box, `CARLOS_POOLS=cohort1`), leo is not a
+ blocker: it publishes `offline-move-1` (an armed guard does not withhold it), and it took part as a source box
+ (held, fenced, reported, `gone` at finish) without holding the host. The gate applies only once leo's claims are on
+ (after the window's 8.2).
+- **M5 (P1.1).** Before the first wake, check the box rendered `<host>.config.env`
+ (`/data/instances/<host>/<host>.config.env` on a nested box). If it is missing, change any setting (`carlos env
+ set`) to make a new bundle generation (finding 6). Otherwise the instance can run without its settings for up to an
+ hour.
+- **M6 (P1.2/P2.3, defect 8).** After `finish`, on each source box: `ls -la /data/.instances/<host>/`. If a
+ `<host>.env` is there (written by the source's scheduler after `open`), move it into
+ `/data/.offline-moves/<host>/<id>/source/` and record it. Never delete it.
+- **M7 (Cleanup recipe).** A finished move does not delete the source's `instances/<host>/lock.json`: it rewrites it
+ as `owner moved:<id>`, `expires_at 1970-01-01T00:00:00Z`. That expired object is the normal end state, not an
+ abandoned move. The cleanup recipe is for a `moved:` owner whose `expires_at` is still in the future.
+- **M8 (P1.3, P2.4).** `abort` and the `finish` after it exit 3 (the CLI's "aborted" code); that is the expected
+ result, not a failure.
+- **M9 (P0.2, as commands).** Per box: `grep -c '^CARLOS_HOST_CLAIM=.' /etc/carlos/host.env` = 0, no
+ `CARLOS_MOVES_PHASE1`, `systemctl show carlos-edge -p User -p Delegate` = `User=carlos` and `Delegate=yes`,
+ `systemctl is-active carlos-stage.path` = active (the UK box at least), and in each box's
+ `control/edges/<box>.json`: `.capabilities` contains `offline-move-1` and every `.secrets_key_fp` is equal.
+- **M10 (P0.6 budget).** Measured: about 25 s of door and box work for a 4 MB nested instance (start 2 s, seal 6 s,
+ flip 4-6 s, open 4 s, first request 6 s). Abort after flip: back serving in 23 s. Add the operator's gaps and up to
+ 6 minutes per stale-report wait (M2). `finish` takes 4 to 5 minutes but the instance is serving by then.
+