carlos / platform Public

docs: the stamp comment and the rollout's switch and recovery-mode consequences (final review F17, bullets 1 and 3)

offlineLanded's comment no longer claims stamp uniqueness: two identical
operator requests on one observation can propose the same stamp, and the
merge is harmless. The design's rollout says the source boxes need
CARLOS_SELF_SERVE_MOVES=offline to publish pre-admission per-host facts,
and that any active.json reservation at console start (an operator move's
included) puts the console in recovery mode, where the instance-token
mutation family dispatches to the self-serve doors until the obligations
clear.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev f7d3fe3c98e1df4caf2a4f86b4896c9ef4f31c84 parent f1e0963
2 files changed, +9 −2
  • docs/superpowers/specs/2026-10-21-self-serve-offline-moves-design.md +3 −1
  • internal/console/apiinstancemove_offline.go +6 −1
diff --git a/docs/superpowers/specs/2026-10-21-self-serve-offline-moves-design.md b/docs/superpowers/specs/2026-10-21-self-serve-offline-moves-design.md
index 5b7e3a1f2..d1485fa5b 100644
--- a/docs/superpowers/specs/2026-10-21-self-serve-offline-moves-design.md
+++ b/docs/superpowers/specs/2026-10-21-self-serve-offline-moves-design.md
@@ -226,10 +226,12 @@ Tito books the time and calls prepare when it arrives (its sweep's `scheduled` s
Platform:
1. Build behind `CARLOS_SELF_SERVE_MOVES`, default off; copy rows pending until Paul's review; census overlay PROPOSED.
-2. Release and roll dev, then flagship, then Tito (roll order rule; boxes before consoles).
+2. Release and roll dev, then flagship, then Tito (roll order rule; boxes before consoles). Set `CARLOS_SELF_SERVE_MOVES=offline` on the source boxes as well as the console: a box publishes the per-host facts the request door, the estimate and the job's reports judge (the replica's size, the files) only with the switch on, or while it takes part in a queued or staging move. With the switch on the console alone, admission refuses every request as unmeasured. The operator's return reads the same facts from the instance's current box.
3. Rehearse on dev with a Tito-shaped app (key file, store binding, config, custom domain) through Tito's own residency flow against the dev console: forward; cancel in ready; abort by a failed pre-check, by a stop deadline and by a landing failure; a forced post-open failure (the app refuses to start on the target) and its paging, then the operator's return with the runbook; the holding page on a third edge and on a custom domain; two moves on disjoint boxes at once; two queued on one box; the cleanup 24 hours on.
4. Turn the switch on for Tito's console with Paul's go.
+Recovery mode, with the switch on or off: a console that starts with any reservation in `control/offline-moves/active.json`, an ordinary operator move's included, or with any open self-serve row, pending release, owed effect or undelivered event in its journal, starts in recovery mode (it also starts there when it cannot read that evidence within 30 seconds). While recovery mode lasts, the three move mutation doors (request, cut, abort) are the self-serve doors, not the legacy doors: any caller but a move-capable instance token gets the move family's 404, a plain instance token included, and a move-capable token cannot prepare a new move while admission is off. The operator's offline doors stay the recovery route. Recovery mode ends at the first tick that reads none of that evidence, so plan an operator move on a console with the switch off knowing that the legacy move mutations answer 404 until the move is finished and its reservations released.
+
Tito's side (titogo; from titogo-f6's notes of 2026-10-21):
1. An account owner mints a move-capable token; the router's console credential switches to it (one env var, env sync, a router restart).
2. The residency tab shows the estimate from `?estimate=1&region=` on the confirm page, refuses controld accounts before posting, sends `display_name` and `requested_by`, and reads `last.outcome` and `last.reason` when `state` returns to `none`.
diff --git a/internal/console/apiinstancemove_offline.go b/internal/console/apiinstancemove_offline.go
index 7b530199d..9355360e6 100644
--- a/internal/console/apiinstancemove_offline.go
+++ b/internal/console/apiinstancemove_offline.go
@@ -1071,7 +1071,12 @@ func (s *Server) offlineAdvanceBound(w http.ResponseWriter, r *http.Request, d o
// observed record into want: the same move, in want's state, at want's
// stamps (requested_at for a staged start, staged_at for a restage). The
// stamp is proposed from the observation and is after every earlier stamp
-// on the record (provision.ProposeStamp), so no other write carries it.
+// on the record (provision.ProposeStamp), so no write decided on an earlier
+// version carries it. It is not unique: two identical operator requests
+// decided on the same observation can propose the same stamp, and only one
+// bound write lands. The other's confirmation then takes the landed one for
+// its own, which is harmless: both asked for the same transition of the
+// same move from the same record.
func offlineLanded(cur, want *provision.Record, id string) bool {
if cur == nil || cur.Move == nil || want.Move == nil || cur.Move.ID != id || cur.Move.State != want.Move.State {
return false