carlos / platform Public

claim census: the seven offline move doors are refused fixtures (MV-O20)

CI failed four claim tests in internal/console (TestClaimConsoleRouteCensus,
TestClaimConsoleMutationInvariant, TestClaimMovesConsoleRouteCensus,
TestClaimRegionHAConsoleRouteCensus): the offline operator move's doors
(GET and POST .../move/offline; POST .../abort, finish, flip, open, seal)
had no claim fixture.

Every offline door answers MV-O20 on a console with host claims or Phase 1
moves on (offlineGate), the posture of the legacy move doors'
claimMoveUnsupportedMsg. They are classified the same way: the routes join
claim-routes.txt, the sentence joins claimUnsupported with its reason, and
each door is a refused fixture in the base table that the moves and
region-HA tables merge. Each request is valid. The start names a movable
instance and another region, and each verb finds an offline move walked
through the record's own transitions to the state it acts on. The runner
asserts the exact 409 and no write of any kind. No invariant changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev d65c2c65359619c18e198fda47bbdcadfb95c5c8 parent 3df4f7c
3 files changed, +104 −0
  • internal/console/claim_invariant_fixtures_test.go +91 −0
  • internal/console/claim_invariant_test.go +6 −0
  • internal/console/testdata/claim-routes.txt +7 −0

CI passed — 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
ok  	github.com/carlosframework/platform	317.159s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.889s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.285s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.710s
ok  	github.com/carlosframework/platform/internal/activator	252.526s
ok  	github.com/carlosframework/platform/internal/agentsock	0.019s
ok  	github.com/carlosframework/platform/internal/alerting	0.003s
ok  	github.com/carlosframework/platform/internal/analytics	0.019s
ok  	github.com/carlosframework/platform/internal/appconfig	2.484s
ok  	github.com/carlosframework/platform/internal/apperr	0.072s
ok  	github.com/carlosframework/platform/internal/applog	0.123s
ok  	github.com/carlosframework/platform/internal/awsinv	0.003s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.006s
ok  	github.com/carlosframework/platform/internal/boxid	0.733s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.008s
ok  	github.com/carlosframework/platform/internal/caps	0.014s
ok  	github.com/carlosframework/platform/internal/certwarm	0.002s
ok  	github.com/carlosframework/platform/internal/cgroup	0.708s
ok  	github.com/carlosframework/platform/internal/changelog	0.330s
ok  	github.com/carlosframework/platform/internal/claimactivation	0.772s
ok  	github.com/carlosframework/platform/internal/clamd	0.054s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	412.438s
?   	github.com/carlosframework/platform/internal/controlname	[no test files]
?   	github.com/carlosframework/platform/internal/controlsock	[no test files]
ok  	github.com/carlosframework/platform/internal/dbdoor	6.466s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.009s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.003s
ok  	github.com/carlosframework/platform/internal/docsite	0.005s
ok  	github.com/carlosframework/platform/internal/domains	2.410s
ok  	github.com/carlosframework/platform/internal/doorkey	0.009s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	44.869s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.008s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.133s
ok  	github.com/carlosframework/platform/internal/gate	1.656s
ok  	github.com/carlosframework/platform/internal/genselect	0.016s
ok  	github.com/carlosframework/platform/internal/geo	0.018s
ok  	github.com/carlosframework/platform/internal/geoip	0.030s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.153s
ok  	github.com/carlosframework/platform/internal/host	187.533s
ok  	github.com/carlosframework/platform/internal/hostactors	6.297s
ok  	github.com/carlosframework/platform/internal/hostactors/awsmembership	0.004s
ok  	github.com/carlosframework/platform/internal/hostbox	2.701s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.586s
ok  	github.com/carlosframework/platform/internal/hostenv	0.012s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.012s
ok  	github.com/carlosframework/platform/internal/hostexec	5.012s
ok  	github.com/carlosframework/platform/internal/hostfence	0.029s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.441s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.015s
ok  	github.com/carlosframework/platform/internal/hostguard	1.032s
ok  	github.com/carlosframework/platform/internal/hostops	1.607s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.012s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.012s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.481s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.153s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.012s
ok  	github.com/carlosframework/platform/internal/infraview	0.012s
ok  	github.com/carlosframework/platform/internal/instancectl	5.227s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.746s
ok  	github.com/carlosframework/platform/internal/instancefs	0.017s
ok  	github.com/carlosframework/platform/internal/instancekey	0.014s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.019s
ok  	github.com/carlosframework/platform/internal/layout	1.548s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.002s
ok  	github.com/carlosframework/platform/internal/lease	9.893s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.379s
ok  	github.com/carlosframework/platform/internal/ledger	0.011s
ok  	github.com/carlosframework/platform/internal/mail	0.178s
ok  	github.com/carlosframework/platform/internal/maintenance	0.086s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.533s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.083s
ok  	github.com/carlosframework/platform/internal/metrics	0.005s
ok  	github.com/carlosframework/platform/internal/moves	21.290s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.824s
ok  	github.com/carlosframework/platform/internal/objstore	0.198s
ok  	github.com/carlosframework/platform/internal/ops	0.176s
ok  	github.com/carlosframework/platform/internal/park	0.216s
ok  	github.com/carlosframework/platform/internal/privatefile	0.004s
ok  	github.com/carlosframework/platform/internal/provision	37.220s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.010s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.519s
ok  	github.com/carlosframework/platform/internal/relay	7.253s
ok  	github.com/carlosframework/platform/internal/release	23.963s
ok  	github.com/carlosframework/platform/internal/rollout	3.790s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.004s
ok  	github.com/carlosframework/platform/internal/rootaws	0.007s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.060s
ok  	github.com/carlosframework/platform/internal/rootstate	0.026s
ok  	github.com/carlosframework/platform/internal/runs	0.588s
ok  	github.com/carlosframework/platform/internal/s3compat	0.006s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.490s
ok  	github.com/carlosframework/platform/internal/seeds	0.012s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	9.676s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.445s
ok  	github.com/carlosframework/platform/internal/status	0.061s
ok  	github.com/carlosframework/platform/internal/steering	0.006s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.298s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.004s
ok  	github.com/carlosframework/platform/internal/termfence	0.004s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.004s
ok  	github.com/carlosframework/platform/internal/unixdial	0.002s
ok  	github.com/carlosframework/platform/internal/updatewords	0.004s
ok  	github.com/carlosframework/platform/internal/usage	0.004s
ok  	github.com/carlosframework/platform/internal/version	0.002s
ok  	github.com/carlosframework/platform/internal/vet	1.567s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
linux/arm64 carlos binary: 49807522 bytes (ceiling 52000000)
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
  minio: cached, digest verified
  mc: cached, digest verified
  minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
  buckets and scoped user ready
=== RUN   TestDeliveredContractMinio
=== RUN   TestDeliveredContractMinio/put
=== RUN   TestDeliveredContractMinio/get_round-trips_the_body
=== RUN   TestDeliveredContractMinio/list_shows_the_key
=== RUN   TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN   TestDeliveredContractMinio/delete_removes_it
=== RUN   TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
    --- PASS: TestDeliveredContractMinio/put (0.00s)
    --- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
    --- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
    --- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
    --- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
    --- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/objstore	0.023s
=== RUN   TestT4StorageProbeMinio
2026/10/20 09:23:18 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18E032850A171564, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.01s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.021s
minio contract: PASS
diff --git a/internal/console/claim_invariant_fixtures_test.go b/internal/console/claim_invariant_fixtures_test.go
index 215960ec9..392667d42 100644
--- a/internal/console/claim_invariant_fixtures_test.go
+++ b/internal/console/claim_invariant_fixtures_test.go
@@ -528,6 +528,43 @@ func (cx *claimCase) stagedMove(t *testing.T, more ...func(*provision.Record) er
}
}
+// claimOfflineMoveID is the offline move offlineMove stages.
+const claimOfflineMoveID = "mv_zyxwvutsrqponmlkjihgfedcba"
+
+// offlineMove is movableInstance with an offline move from ie to uk walked
+// through the record's own transitions until it reaches state: the state a
+// door acts on, so its request is valid.
+func (cx *claimCase) offlineMove(t *testing.T, state string) {
+ t.Helper()
+ cx.movableInstance(t)
+ now := time.Now()
+ steps := []func(*provision.Record) error{provision.OfflineStart(claimOfflineMoveID, "ie", "uk", claimFxOwner, now)}
+ switch state {
+ case provision.OfflineStopping:
+ case provision.OfflineSealed, provision.OfflineLanded, provision.OfflineOpen:
+ steps = append(steps,
+ provision.OfflineSeal(claimOfflineMoveID, "ie-box", []string{"ie-box"}),
+ provision.OfflineSealedBy(claimOfflineMoveID, "ie-box", provision.MoveSource{}))
+ if state != provision.OfflineSealed {
+ steps = append(steps, provision.OfflineFlip(claimOfflineMoveID), provision.OfflineLandedBy(claimOfflineMoveID, "uk"))
+ }
+ if state == provision.OfflineOpen {
+ steps = append(steps, provision.OfflineOpenTransition(claimOfflineMoveID, now))
+ }
+ default:
+ t.Fatalf("offlineMove: no staging for %s", state)
+ }
+ for _, mut := range steps {
+ if _, err := provision.SetRecordMove(context.Background(), cx.rel, "bab", "titogo", "acme.go.tito.io", mut); err != nil {
+ t.Fatal(err)
+ }
+ }
+ rec, err := provision.GetRecord(context.Background(), cx.rel, "bab", "titogo", "acme.go.tito.io")
+ if err != nil || rec.Move == nil || rec.Move.State != state {
+ t.Fatalf("offlineMove: staged %+v (%v), want %s", rec, err, state)
+ }
+}
+
// claimSecondSqid is the second account the account-move door moves into.
var claimSecondSqid = domains.Sqid(2)
@@ -1375,6 +1412,60 @@ var claimFixtures = map[string]claimFixture{
status: http.StatusConflict,
msg: claimMoveUnsupportedMsg,
}},
+ // The offline operator move's doors are intentionally unsupported on a
+ // claim console (claimUnsupported, MV-O20), as the legacy move doors
+ // are. Each request is valid: the start names a movable instance and
+ // another region, and each verb finds the offline move in the state it
+ // acts on. The status view refuses the same way.
+ "route GET /api/cli/apps/{account}/{app}/instances/{host}/move/offline": {refused: &claimRefusalFixture{
+ apps: claimRefs("bab/titogo"),
+ setup: func(t *testing.T, cx *claimCase) { cx.offlineMove(t, provision.OfflineStopping) },
+ do: claimRefusedAPI(http.MethodGet, "/api/cli/apps/bab/titogo/instances/acme.go.tito.io/move/offline", ""),
+ status: http.StatusConflict,
+ msg: claimOfflineMoveUnsupportedMsg,
+ }},
+ "route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline": {refused: &claimRefusalFixture{
+ apps: claimRefs("bab/titogo"),
+ setup: func(t *testing.T, cx *claimCase) { cx.movableInstance(t) },
+ do: claimRefusedAPI(http.MethodPost, "/api/cli/apps/bab/titogo/instances/acme.go.tito.io/move/offline", `{"region":"uk"}`),
+ status: http.StatusConflict,
+ msg: claimOfflineMoveUnsupportedMsg,
+ }},
+ "route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/abort": {refused: &claimRefusalFixture{
+ apps: claimRefs("bab/titogo"),
+ setup: func(t *testing.T, cx *claimCase) { cx.offlineMove(t, provision.OfflineStopping) },
+ do: claimRefusedAPI(http.MethodPost, "/api/cli/apps/bab/titogo/instances/acme.go.tito.io/move/offline/abort", ""),
+ status: http.StatusConflict,
+ msg: claimOfflineMoveUnsupportedMsg,
+ }},
+ "route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/finish": {refused: &claimRefusalFixture{
+ apps: claimRefs("bab/titogo"),
+ setup: func(t *testing.T, cx *claimCase) { cx.offlineMove(t, provision.OfflineOpen) },
+ do: claimRefusedAPI(http.MethodPost, "/api/cli/apps/bab/titogo/instances/acme.go.tito.io/move/offline/finish", ""),
+ status: http.StatusConflict,
+ msg: claimOfflineMoveUnsupportedMsg,
+ }},
+ "route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/flip": {refused: &claimRefusalFixture{
+ apps: claimRefs("bab/titogo"),
+ setup: func(t *testing.T, cx *claimCase) { cx.offlineMove(t, provision.OfflineSealed) },
+ do: claimRefusedAPI(http.MethodPost, "/api/cli/apps/bab/titogo/instances/acme.go.tito.io/move/offline/flip", ""),
+ status: http.StatusConflict,
+ msg: claimOfflineMoveUnsupportedMsg,
+ }},
+ "route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/open": {refused: &claimRefusalFixture{
+ apps: claimRefs("bab/titogo"),
+ setup: func(t *testing.T, cx *claimCase) { cx.offlineMove(t, provision.OfflineLanded) },
+ do: claimRefusedAPI(http.MethodPost, "/api/cli/apps/bab/titogo/instances/acme.go.tito.io/move/offline/open", ""),
+ status: http.StatusConflict,
+ msg: claimOfflineMoveUnsupportedMsg,
+ }},
+ "route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/seal": {refused: &claimRefusalFixture{
+ apps: claimRefs("bab/titogo"),
+ setup: func(t *testing.T, cx *claimCase) { cx.offlineMove(t, provision.OfflineStopping) },
+ do: claimRefusedAPI(http.MethodPost, "/api/cli/apps/bab/titogo/instances/acme.go.tito.io/move/offline/seal", ""),
+ status: http.StatusConflict,
+ msg: claimOfflineMoveUnsupportedMsg,
+ }},
"route POST /api/cli/apps/{account}/{app}/instances/{host}/retry": {door: &claimDoor{
apps: claimRefs("bab/titogo"),
setup: func(t *testing.T, cx *claimCase) { cx.heldInstance(t, "web.go.tito.io") },
diff --git a/internal/console/claim_invariant_test.go b/internal/console/claim_invariant_test.go
index b28069f71..6f674b7eb 100644
--- a/internal/console/claim_invariant_test.go
+++ b/internal/console/claim_invariant_test.go
@@ -87,6 +87,10 @@ type claimRefusalFixture struct {
msg string
}
+// claimOfflineMoveUnsupportedMsg is MV-O20, what every offline move door
+// answers on a console with host claims or Phase 1 moves on (offlineGate).
+const claimOfflineMoveUnsupportedMsg = "offline moves are not available while host claims or Phase 1 moves are on"
+
// claimUnsupported is the closed list of operations an enabled console
// refuses on purpose, keyed by the exact message production answers. Only
// these may be a refused fixture; every other app mutator is a door that
@@ -97,6 +101,8 @@ var claimUnsupported = map[string]string{
claimHostResolveUnsupportedMsg: "host-loss declaration/readmission changes claim roots; the existing service has no scoped lifecycle grant in this slice",
claimMoveUnsupportedMsg: "instance move request, cut and abort rewrite the record's move journal, " +
"which only the unbuilt host-claim Moves protocol may do under its own operation",
+ claimOfflineMoveUnsupportedMsg: "the offline operator move (MV-O20) is only for consoles with host claims and Phase 1 moves off; " +
+ "every offline door, the status view included, refuses after its operator gate and before it reads evidence or writes the record",
claimRefusalText(errClaimAliasDetachUnsupported): "removing an alias-only marker needs its own conditional effect operation; " +
"a first-route detach retires its record and is a door",
claimRefusalText(errClaimAliasCapabilitiesUnsupported): "wildcard and catch-all claims are written only by the legacy capability path",
diff --git a/internal/console/testdata/claim-routes.txt b/internal/console/testdata/claim-routes.txt
index fc667b937..a29e86d85 100644
--- a/internal/console/testdata/claim-routes.txt
+++ b/internal/console/testdata/claim-routes.txt
@@ -57,6 +57,7 @@ route GET /api/cli/apps/{account}/{app}/geo
route GET /api/cli/apps/{account}/{app}/instances
route GET /api/cli/apps/{account}/{app}/instances/{host}
route GET /api/cli/apps/{account}/{app}/instances/{host}/move
+route GET /api/cli/apps/{account}/{app}/instances/{host}/move/offline
route GET /api/cli/apps/{account}/{app}/instances/{host}/runs/{id}
route GET /api/cli/apps/{account}/{app}/ledgers
route GET /api/cli/apps/{account}/{app}/ledgers/{ledger}/head
@@ -185,6 +186,12 @@ route GET /api/cli/apps/{account}/{app}/instances/{host}/updates
route PUT /api/cli/apps/{account}/{app}/instances/{host}/updates
route POST /api/cli/apps/{account}/{app}/instances/{host}/move
route POST /api/cli/apps/{account}/{app}/instances/{host}/move/cut
+route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline
+route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/abort
+route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/finish
+route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/flip
+route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/open
+route POST /api/cli/apps/{account}/{app}/instances/{host}/move/offline/seal
route POST /api/cli/apps/{account}/{app}/instances/{host}/retry
route POST /api/cli/apps/{account}/{app}/instances/{host}/runs
route POST /api/cli/apps/{account}/{app}/instances/{host}/runs/{id}/ack