console: the move mutation dispatcher: Phase 1, else the self-serve doors in self-serve mode, else legacy
handleAPIInstanceMoveRequest, Cut and Abort send Phase 1 on as today, the self-serve body in self-serve mode, and the legacy body otherwise, byte for byte. The three self-serve bodies run selfServeGate and, until Task 5 writes them, answer the family's 404. The status door is unchanged. CARLOS_SELF_SERVE_MOVES reaches Config.SelfServeMoves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 files changed,
+45
−0
cmd/console/main.go+3 −0internal/console/apiinstancemove.go+18 −0internal/console/selfserve_gate.go+24 −0
diff --git a/cmd/console/main.go b/cmd/console/main.go| index 6de4364ba..a6ce8575a 100644 |
| --- a/cmd/console/main.go |
| +++ b/cmd/console/main.go |
| @@ -415,6 +415,9 @@ func serve() { |
| PlanCatalog: catalog, |
| TemporaryApps: os.Getenv("CARLOS_TEMPORARY_APPS") == "1", |
| LifetimesMigrate: os.Getenv("CARLOS_LIFETIMES_MIGRATE") == "1", |
| + // CARLOS_SELF_SERVE_MOVES=offline is the self-serve offline moves |
| + // switch (selfServeMode). Unset on every deployment today. |
| + SelfServeMoves: strings.TrimSpace(os.Getenv("CARLOS_SELF_SERVE_MOVES")), |
| // CARLOS_ACME_DELEGATION_ZONE — the same variable the edge reads |
| // (internal/edge/wildcard.go's newWildcardFromEnv) for the same |
| // delegated-ACME story; NewServer normalizes it (lowercase, trim). |
diff --git a/internal/console/apiinstancemove.go b/internal/console/apiinstancemove.go| index 2ac1ca6b4..16290e8bd 100644 |
| --- a/internal/console/apiinstancemove.go |
| +++ b/internal/console/apiinstancemove.go |
| @@ -556,6 +556,12 @@ func moveGuardOf(rec *provision.Record) provision.MoveGuard { |
| // pass of Plan 5 Task 7 releases it. |
| func (s *Server) handleAPIInstanceMoveRequest(w http.ResponseWriter, r *http.Request) { |
| if !s.cfg.MovesEnabled { |
| + // Self-serve mode makes this the self-serve door; outside it the |
| + // legacy door answers, byte for byte. |
| + if s.selfServeMode() { |
| + s.selfServeMoveRequest(w, r) |
| + return |
| + } |
| s.legacyHandleAPIInstanceMoveRequest(w, r) |
| return |
| } |
| @@ -1015,6 +1021,12 @@ func (s *Server) settleFailedMoveWrite(w http.ResponseWriter, r *http.Request, c |
| // only be aborted (MV-N7 says so). |
| func (s *Server) handleAPIInstanceMoveCut(w http.ResponseWriter, r *http.Request) { |
| if !s.cfg.MovesEnabled { |
| + // Self-serve mode makes this the self-serve door; outside it the |
| + // legacy door answers, byte for byte. |
| + if s.selfServeMode() { |
| + s.selfServeMoveCut(w, r) |
| + return |
| + } |
| s.legacyHandleAPIInstanceMoveCut(w, r) |
| return |
| } |
| @@ -1046,6 +1058,12 @@ func (s *Server) handleAPIInstanceMoveCut(w http.ResponseWriter, r *http.Request |
| // changed" (final review I5). A stuck move keeps the grant's own answer. |
| func (s *Server) handleAPIInstanceMoveAbort(w http.ResponseWriter, r *http.Request) { |
| if !s.cfg.MovesEnabled { |
| + // Self-serve mode makes this the self-serve door; outside it the |
| + // legacy door answers, byte for byte. |
| + if s.selfServeMode() { |
| + s.selfServeMoveAbort(w, r) |
| + return |
| + } |
| s.legacyHandleAPIInstanceMoveAbort(w, r) |
| return |
| } |
diff --git a/internal/console/selfserve_gate.go b/internal/console/selfserve_gate.go| index abbe666a8..f4da2636e 100644 |
| --- a/internal/console/selfserve_gate.go |
| +++ b/internal/console/selfserve_gate.go |
| @@ -91,3 +91,27 @@ func (s *Server) instanceRails(w http.ResponseWriter, r *http.Request, acct *Acc |
| } |
| return true |
| } |
| + |
| +// The three self-serve door bodies. Task 5 writes them; until then each |
| +// answers the family's 404 once the gate has passed. |
| + |
| +func (s *Server) selfServeMoveRequest(w http.ResponseWriter, r *http.Request) { |
| + if _, _, _, _, ok := s.selfServeGate(w, r); !ok { |
| + return |
| + } |
| + http.NotFound(w, r) |
| +} |
| + |
| +func (s *Server) selfServeMoveCut(w http.ResponseWriter, r *http.Request) { |
| + if _, _, _, _, ok := s.selfServeGate(w, r); !ok { |
| + return |
| + } |
| + http.NotFound(w, r) |
| +} |
| + |
| +func (s *Server) selfServeMoveAbort(w http.ResponseWriter, r *http.Request) { |
| + if _, _, _, _, ok := s.selfServeGate(w, r); !ok { |
| + return |
| + } |
| + http.NotFound(w, r) |
| +} |