plan: offline move manifest has Owner and Source; transition names OfflineOpenTransition and OfflineFinishingTransition
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 file changed,
+5
−3
docs/superpowers/plans/2026-10-20-offline-operator-move.md+5 −3
diff --git a/docs/superpowers/plans/2026-10-20-offline-operator-move.md b/docs/superpowers/plans/2026-10-20-offline-operator-move.md| index 2d98a7c1b..ce49ded67 100644 |
| --- a/docs/superpowers/plans/2026-10-20-offline-operator-move.md |
| +++ b/docs/superpowers/plans/2026-10-20-offline-operator-move.md |
| @@ -67,6 +67,8 @@ Three layers, in this order, each checked by evidence before the next step. Fabl |
| | `receipts` | `move-receipts/<host>.json` in the source bucket | cleared at `finish` (`clearMoveReceipts`) | none needed (self-checking) | |
| | `class` | kind, addr, fleet, pool, hibernate, replica, replicas, steering, unit, seed, open run | refused at the door (`moveGate` plus the transition) | door tests | |
| +Each row has two fields. `Owner` is the step that proves the item where it must end up, and exactly one step owns each row; the `open` door requires every `landing`-owned item OK in the target's report. `Source` is the step that reports the item's expected value on the source side; the `flip` door requires every `Source: seal` item OK in the sealer's report (`SourceItemsOK`). The carried outage items (`db`, `lease`, `files`, `env`, `timers`, `next-due`, `replication`) are `Owner: landing, Source: seal`: the sealer records what it copied and the target proves it arrived equal, so `open` cannot pass on a target that lacks them. `db-tail`, `build-state` and `sidecar` are `Owner: seal` (only the source can say). `TestOfflineManifestIsCovered` pins each id's pair. |
| + |
| ## States, failures, and what the operator runs |
| `record.move` is `{kind: "offline", id, from, to, state, sealer, boxes, source{...}, requested_by, requested_at, updated_at}`. All fields are additive. |
| @@ -289,13 +291,13 @@ func OfflineSeal(id, sealer string, boxes []string) func(*Record) error |
| func OfflineSealedBy(id, sealer string, src MoveSource) func(*Record) error |
| func OfflineFlip(id string) func(*Record) error // Region = To |
| func OfflineLandedBy(id, to string) func(*Record) error // guarded by record region == to |
| -func OfflineOpen(id string) func(*Record) error |
| -func OfflineFinishing(id string) func(*Record) error // from open |
| +func OfflineOpenTransition(id string, now time.Time) func(*Record) error // the state constant owns the name OfflineOpen |
| +func OfflineFinishingTransition(id string) func(*Record) error // from open; the state constant owns OfflineFinishing |
| func OfflineAbort(id string) func(*Record) error // landing/landed: Region = From |
| func OfflineFinish(id string, now time.Time) func(*Record) error // from finishing (done) or reverting (aborted); LastMove set |
| // moves |
| -type OfflineItem struct{ ID, Item, Disposition, Owner string } // Owner: door|seal|landing|none |
| +type OfflineItem struct{ ID, Item, Disposition, Owner, Source string } // Owner: door|seal|landing|none; Source: seal or "" |
| var OfflineManifest []OfflineItem |
| type OfflineReport struct { |
| V int; Op, Host, Box, Role, Phase string // role: source|sealer|target; phase: stopped|sealed|landed|opened|parked|gone|lifted|reverted|failed |