carlos / platform Public

plans: shared contract types with claim Plan 6 Task 6 (feat/hostclaim-box bf63e250): moves.InstallManifest, moves.ErrAttemptOver and provision.MoveInputs are imported, not redefined, when their contract file is on main; shapes verified identical; index table

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev b78b3278c19a98a896776385a973460cab3b2278 parent 80647ee
4 files changed, +15 −2
  • docs/superpowers/plans/2026-10-06-moves-phase1-0-index.md +12 −0
  • docs/superpowers/plans/2026-10-06-moves-phase1-1-copy-and-staging.md +1 −0
  • docs/superpowers/plans/2026-10-06-moves-phase1-2-cut-flip-landing.md +1 −1
  • docs/superpowers/plans/2026-10-06-moves-phase1-3-probe-commit-open.md +1 −1

CI cancelled — superseded by a newer push — run details

diff --git a/docs/superpowers/plans/2026-10-06-moves-phase1-0-index.md b/docs/superpowers/plans/2026-10-06-moves-phase1-0-index.md
index 007fad73e..f7e5bec6b 100644
--- a/docs/superpowers/plans/2026-10-06-moves-phase1-0-index.md
+++ b/docs/superpowers/plans/2026-10-06-moves-phase1-0-index.md
@@ -138,6 +138,18 @@ source conversion, L13, the one-box scope).
| C7 | **Settled by claim rev 6.28 §7.3 (spec rev 7.20 §7.10):** `op.journal["fence.<box>"] = {"enrollment","key","sha256"}` names `control/box-fences/<enrollment>.json` and the digest of the bytes verified; a `loss.<id>` intent cites the same reference; consumers accept a fence only through it, re-verifying the record; an automatic declaration needs no quarantine revision and no operator. Still owed by Plans 8/8b: the fence record's writer and the verification helper (`hostclaim.VerifyFence(ctx, s, ref) error` or its real name) and the one-CAS conversion of a pre-flip `moving` op to `relocating` at `pinning` (`hostclaim.ConvertMovingToRelocating(opID, fence FenceRef) func(*Claim) error` or its real name: exec `"none"`, stamp and generation preserved, obligations untouched, `relocating`/`pinning` in the source region; refused once the record is `landing`). Moves' `ReturnToSource` (Plan 4 Task 2) and the console pass (Plan 5 Task 7) consume both. | claim Plans 8 and 8b | Plan 4 Task 2's fence form; Plan 5 Task 7's conversion call; Plan 6 Task 2's `waiting for a box in <region>` state |
| C8 | Region HA's files publisher API in two forms (spec rev 7.21 §7.6, L17, R18-1; claim rev 6.30 73b1b9dc). The **prepared-manifest build**, `files.BuildPrepared(ctx, store lease.VersionedStore, location, generation, opID string, stamp uint64, dir, envPath string, wake WakeState) (manifestVersion string, err error)` or its real name: before any session exists, under `op.exec`'s installation authority, create-only content objects plus one create-only manifest bound to `{location, generation, op id, installation stamp}`, exact versions, sequence 0, never replaced, retired with its generation. The **session-bound publisher**, `files.Publish(...)` or its real name (claim C3), which starts after the first admission, takes the prepared manifest by exact version as its baseline and publishes from sequence 1, writing origin-qualified references (P6-D3); and the reader a cold admission uses to establish its files manifest from the admitted predecessor chain by exact version. | region HA plan 1a | Plan 2 Task 10 `local`'s manifest step (6) (`.files-gen` alone until then); Plan 3 Task 5's admitted start (files from the manifest once C8 exists) |
+### Shared contract types (coordination with claim Plan 6 Task 6)
+
+Claim Plan 6 Task 6 on `feat/hostclaim-box` (bf63e250) implements C1 to C4 and defines two files our plans also name. The rule: whichever lands on `main` first owns the type, the other imports it and does not redefine it. Shapes verified identical against bf63e250 on 2026-10-06.
+
+| type | claim Plan 6 file | our definition | shape check |
+|---|---|---|---|
+| `moves.InstallManifest` | `internal/moves/install_contract.go` | Plan 2 Task 8 | identical: `V int; Op, Host string; N uint64; SnapshotKey, SnapshotVersion string; SnapshotCRC64 uint64; AnchorKey, AnchorVersion string; AnchorCRC64 uint64; IntegrityCheck string; DBSHA256 string; BuiltAt time.Time`, no json tags on either side |
+| `moves.ErrAttemptOver` | `internal/moves/install_contract.go` | Plan 3 Task 1 | identity only (`errors.Is`); Plan 3 now uses claim Plan 6's message text |
+| `provision.MoveInputs` | `internal/provision/moveinputs_contract.go` | **Plan 1 Task 2** (the record journal; it lands with the first task of the series, so our side may define it first) | identical fields and json tags: `bundle_etags`, `overlay_etag`, `release`, `files_plain`, `env_report`, all `omitempty` |
+
+Plans 1, 2 and 3 each carry the import rule at the definition site.
+
## Decisions locked across the series
- **D1 Journals.** The record's `move` field carries the move's phase
diff --git a/docs/superpowers/plans/2026-10-06-moves-phase1-1-copy-and-staging.md b/docs/superpowers/plans/2026-10-06-moves-phase1-1-copy-and-staging.md
index 3eb1d4e89..4b1bcb60a 100644
--- a/docs/superpowers/plans/2026-10-06-moves-phase1-1-copy-and-staging.md
+++ b/docs/superpowers/plans/2026-10-06-moves-phase1-1-copy-and-staging.md
@@ -268,6 +268,7 @@ Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>"
**Interfaces:**
- Produces: the extended `provision.MoveState` below; `Record.LastMove *MoveOutcome \`json:"last_move,omitempty"\`` beside `Record.Move` (`provision.go:58`); `MoveGuard` and the guarded transitions `MoveGuardedPromoteReady`, `MoveGuardedCut`, `MoveGuardedAbort`, `MoveGuardedAbortComplete` (below); the legacy `MoveRequest`, `MovePromoteReady`, `MoveMarkCutting`, `MoveAbort`, `MoveAbortComplete`, `MoveComplete` unchanged; state constants `MovePreparing, MoveReady, MoveCutting, MoveReverting, MoveAborting, MoveLanding, MoveCommitted, MoveRollingBack, MoveStuck`; `provision.MoveStart{ID, Incarnation, From, To, Cut, By string; Now time.Time}` and `provision.MoveStartTransition(p MoveStart) func(*Record) error`; `(m *MoveState) Legacy() bool`; `Manifest.Moves *MovesManifest{ReadyPath string}`; `(m Manifest) ReadyPathOrDefault() string`.
+- **Shared contract type (coordination with claim Plan 6 Task 6, `feat/hostclaim-box` bf63e250):** `provision.MoveInputs` is defined by this task below AND by claim Plan 6 in `internal/provision/moveinputs_contract.go`, with exactly the same shape (`BundleETags map[string]string \`json:"bundle_etags,omitempty"\`; OverlayETag string \`json:"overlay_etag,omitempty"\`; Release string \`json:"release,omitempty"\`; FilesPlain string \`json:"files_plain,omitempty"\`; EnvReport string \`json:"env_report,omitempty"\``, verified field for field and tag for tag against bf63e250). Whichever lands on `main` first owns the type and the other imports it (the coordinator's rule). **If `internal/provision/moveinputs_contract.go` is on `main` when this task is built, do not redefine `MoveInputs`: delete it from the code block below and use the existing type.** If this task lands first, it owns the type and claim Plan 6 imports it.
- Later plans add the phase transitions (Plan 2 Task 1 the cut phases, Plan 2 Task 8 the landing phases, Plan 4 Task 1 the rollback phases).
- [ ] **Step 1: Write the failing test**
diff --git a/docs/superpowers/plans/2026-10-06-moves-phase1-2-cut-flip-landing.md b/docs/superpowers/plans/2026-10-06-moves-phase1-2-cut-flip-landing.md
index 2136b37d2..ed6ae31e0 100644
--- a/docs/superpowers/plans/2026-10-06-moves-phase1-2-cut-flip-landing.md
+++ b/docs/superpowers/plans/2026-10-06-moves-phase1-2-cut-flip-landing.md
@@ -750,7 +750,7 @@ Tests: `RenderHeld` writes the file for a `landing` row that `SyncExec` would sk
**Interfaces:**
- `moves.ReadDBHeader(path string) (pageSize uint32, pageCount uint32, err error)` (SQLite header bytes 16-17 and 28-31).
- `moves.EncodeFull(db io.ReaderAt, pageSize, pageCount uint32, min, max ltx.TXID, timestamp int64, walOffset int64) (data []byte, crc64 uint64, err error)`: `ltx.NewEncoder` with `Header{Version: ltx.Version, Flags: ltx.HeaderFlagNoChecksum, PageSize, Commit: pageCount, MinTXID: min, MaxTXID: max, Timestamp: timestamp, WALOffset: walOffset}`, every page in order **except SQLite's lock page**, `ltx.LockPgno(pageSize)` (`ltx.go:494`; the encoder refuses it, `encoder.go:219`, and litestream skips it), while `Commit` keeps the full page count; `Close`; the CRC-64 is the trailer's `FileChecksum` (read it back with `ltx.NewDecoder(bytes.NewReader(data)).Verify()` then `Trailer().FileChecksum`).
-- `moves.BuildIntent{V int; Op, Host string; N uint64; PageSize, Commit uint32; Timestamp int64}`; `moves.InstallManifest{V int; Op, Host string; N uint64; SnapshotKey, SnapshotVersion string; SnapshotCRC64 uint64; AnchorKey, AnchorVersion string; AnchorCRC64 uint64; IntegrityCheck string; DBSHA256 string; BuiltAt time.Time}`; `moves.GenerationPrefix(location, op string) string` (`<location>g-<op>/`, location with its trailing slash); `moves.SnapshotKey(prefix string, n uint64)` = `prefix + "0009/" + ltx.FormatFilename(1, n)`; `moves.AnchorKey(prefix, n)` = `prefix + "0000/" + ltx.FormatFilename(n, n)`; `BuildIntentKey(prefix) = prefix+"build-intent.json"`, `InstallManifestKey(prefix) = prefix+"install.json"`.
+- `moves.BuildIntent{V int; Op, Host string; N uint64; PageSize, Commit uint32; Timestamp int64}`; `moves.InstallManifest{V int; Op, Host string; N uint64; SnapshotKey, SnapshotVersion string; SnapshotCRC64 uint64; AnchorKey, AnchorVersion string; AnchorCRC64 uint64; IntegrityCheck string; DBSHA256 string; BuiltAt time.Time}` (**shared contract type**, coordination with claim Plan 6 Task 6, `feat/hostclaim-box` bf63e250: claim Plan 6 defines this exact shape, no json tags, in `internal/moves/install_contract.go`, beside `moves.ErrAttemptOver`. Whichever lands on `main` first owns the type and the other imports it (the coordinator's rule). **If `internal/moves/install_contract.go` is on `main` when this task is built, import the existing `InstallManifest` and do not redefine it**; `install.json` is then encoded with Go's default field names, as the contract has no tags, and this task adds none); `moves.GenerationPrefix(location, op string) string` (`<location>g-<op>/`, location with its trailing slash); `moves.SnapshotKey(prefix string, n uint64)` = `prefix + "0009/" + ltx.FormatFilename(1, n)`; `moves.AnchorKey(prefix, n)` = `prefix + "0000/" + ltx.FormatFilename(n, n)`; `BuildIntentKey(prefix) = prefix+"build-intent.json"`, `InstallManifestKey(prefix) = prefix+"install.json"`.
- `moves.NewSealedClient(store lease.VersionedStore, bucket, replicaPrefix string, seal Seal) *SealedClient` implementing `litestream.ReplicaClient`: `Type() "sealed"`, `Init` no-op, `LTXFiles(ctx, level, seek, _)` returns `ltx.NewFileInfoSliceIterator` over the sealed objects at that level parsed from their keys (`ltx.ParseFilename`), `OpenLTXFile` opens exactly the sealed version by `OpenVersion(ctx, bucket, key, versionID)` (ranged through `VersionRangeOpener` when offset/size are set), `WriteLTXFile`, `DeleteLTXFiles`, `DeleteAll` return `ErrSealedReadOnly`, `SetLogger` stores it.
- `moves.BuildGeneration(ctx, in BuildInputs) (InstallManifest, error)` with `BuildInputs{Store lease.VersionedStore; Bucket string; Source []SealedObject (the pinned version set: for a move the seal's replica/ objects, for region HA's relocation the versions a `genselect` plan or a current listing pins, L16); N uint64; GenerationPrefix string; Op, Host string; WorkDir string; Now func() time.Time; Verify func(dbPath string) error}` (`SealedClient` is built over `Source`, so nothing in the routine names a staging lane): (1) read or write the intent (`PutIfAbsent`; on `lease.ErrExists` read it and use its timestamp, page size, commit); (2) restore into a fresh scratch file: remove `<WorkDir>/<host>.db`, `-wal`, `-shm` and `-txid` first (litestream's `Restore` refuses an existing output path, `replica.go:601`; the file is reproducible from the sealed versions, so a replay after any crash starts clean), then `litestream.NewReplicaWithClient(nil, sealedClient).Restore(ctx, opt)` with `opt.OutputPath = <WorkDir>/<host>.db`, `opt.TXID = ltx.TXID(seal.N)`; refuse unless the restored position is `N` (litestream writes `<db>-txid`? Only in follow mode; instead compare the plan's final `MaxTXID` through `litestream.CalcRestorePlan(ctx, client, ltx.TXID(N), time.Time{}, logger)` whose last file's `MaxTXID` must be `N`, and verify every planned file with `ltx.NewDecoder(...).Verify()` before the restore); `Verify(dbPath)` = `lease.CheckDB` (integrity_check); (3) `EncodeFull` snapshot `[1, N]` and `PutIfAbsent(SnapshotKey)`; on `ErrExists`, `OpenVersion` the current one and require equal bytes (identical by construction from the intent; differing bytes refuse with `ErrBuildDiverged`); (4) the anchor `[N, N]` with `WALOffset = litestream.WALHeaderSize`, same rule; (5) sha256 of the database file; (6) `InstallManifest` via `PutIfAbsent` (on exists, read and compare `N`, keys, versions).
diff --git a/docs/superpowers/plans/2026-10-06-moves-phase1-3-probe-commit-open.md b/docs/superpowers/plans/2026-10-06-moves-phase1-3-probe-commit-open.md
index ef896f89f..4457c8ad4 100644
--- a/docs/superpowers/plans/2026-10-06-moves-phase1-3-probe-commit-open.md
+++ b/docs/superpowers/plans/2026-10-06-moves-phase1-3-probe-commit-open.md
@@ -97,7 +97,7 @@ func TestProbeWindow(t *testing.T) {
}
```
-- [ ] **Step 2: Run**, **Step 3: implement** per the interface (straightforward arithmetic; `ErrAttemptOver = errors.New("moves: probe attempt over")`), **Step 4: run** `nice -n 10 go test ./internal/moves -run TestProbeWindow -count=1` → PASS.
+- [ ] **Step 2: Run**, **Step 3: implement** per the interface (straightforward arithmetic; `ErrAttemptOver = errors.New("moves: probe attempt window or deadline expired")`, the text claim Plan 6's `internal/moves/install_contract.go` uses on `feat/hostclaim-box` bf63e250; **if that file is on `main` when this task is built, import its `ErrAttemptOver` and do not redefine it** (whichever lands first owns it; every consumer uses `errors.Is`, so only the identity matters)), **Step 4: run** `nice -n 10 go test ./internal/moves -run TestProbeWindow -count=1` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "moves: the probe attempt policy, ported from the model (§7.7, R12-2)" ...`
---