carlos / platform Public

provision: the queue's receipt is the request's nonce alone

OfflineQueue kept a last_request the caller left in ss when the receipt
was empty. It now sets self_serve.last_request to the receipt's nonce
whatever ss carried, so an empty receipt leaves none and an operator-shaped
call can never carry a stale confirmation. Pinned in
TestOfflineQueueThenStaging. Census: the internal/provision envelope
re-pinned (PROPOSED-self-serve-moves-2).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev d0c0e345bab6175e41f36081150282659ca0c908 parent 7a28bf1
3 files changed, +20 −6
  • internal/provision/offline_move.go +5 −5
  • internal/provision/offline_queue_test.go +14 −0
  • internal/provision/testdata/plan14-root-reads-self-serve-moves-2.tsv +1 −1
diff --git a/internal/provision/offline_move.go b/internal/provision/offline_move.go
index 5cc11fa99..9530e1e72 100644
--- a/internal/provision/offline_move.go
+++ b/internal/provision/offline_move.go
@@ -330,8 +330,8 @@ func offlineBegin(id, from, to, by string, now time.Time, state, returnOf string
// st's bucket and region, and carries p and ss. requested_at, queued_at,
// staged_at and precheck_at are all `at`, the caller's proposed stamp, so
// StartedOffline is false until the start; move.seq is the record's
-// move_seq, which the queue does not advance. A non-empty receipt is ss's
-// last_request.
+// move_seq, which the queue does not advance. ss's last_request is the
+// receipt's nonce, "" under an empty receipt, whatever ss carried.
func OfflineQueue(id, from, to, by string, st StageTarget, p Participants, ss SelfServeMove, at time.Time, req Receipt) func(*Record) error {
begin := offlineBegin(id, from, to, by, at, OfflineQueued, "")
return func(rec *Record) error {
@@ -350,9 +350,9 @@ func OfflineQueue(id, from, to, by string, st StageTarget, p Participants, ss Se
t := *ss.OfflineAt
self.OfflineAt = &t
}
- if req.Nonce != "" {
- self.LastRequest = req.Nonce
- }
+ // The receipt is the request's nonce alone: whatever ss carried is
+ // replaced, and an empty receipt leaves none.
+ self.LastRequest = req.Nonce
m := rec.Move
m.Stager, m.StageBucket, m.StageRegion = p.Source[0].Label, st.Bucket, st.Region
m.Participants, m.SelfServe = &pin, &self
diff --git a/internal/provision/offline_queue_test.go b/internal/provision/offline_queue_test.go
index 936751573..c9df9e79d 100644
--- a/internal/provision/offline_queue_test.go
+++ b/internal/provision/offline_queue_test.go
@@ -121,6 +121,20 @@ func TestOfflineQueueThenStaging(t *testing.T) {
if got.MoveSeq != 5 || m.Seq != 5 {
t.Fatalf("staged start: move_seq %d move.seq %d, want 5", got.MoveSeq, m.Seq)
}
+
+ // The queue's receipt is the request's nonce and nothing else: a
+ // last_request the caller left in ss is replaced by the nonce, and
+ // cleared by an empty receipt.
+ for nonce, want := range map[string]string{"": "", "n-q": "n-q"} {
+ s := release.NewDirStore(t.TempDir())
+ rec := ssRecord(t, s)
+ ss := ssMove(2)
+ ss.LastRequest = "left-by-the-caller"
+ got := apply(t, s, rec, OfflineQueue(offID, "", "uk", "owner@example.com", ssStage, ssPin, ss, at, Receipt{Nonce: nonce}))
+ if got.Move.SelfServe.LastRequest != want || offlineGet(t, s, rec).Move.SelfServe.LastRequest != want {
+ t.Fatalf("receipt %q: last_request %q, want %q", nonce, got.Move.SelfServe.LastRequest, want)
+ }
+ }
}
func TestOfflineQueueRefusesARecreatedLifetime(t *testing.T) {
diff --git a/internal/provision/testdata/plan14-root-reads-self-serve-moves-2.tsv b/internal/provision/testdata/plan14-root-reads-self-serve-moves-2.tsv
index 4a5d1f8fc..c82a4e4ca 100644
--- a/internal/provision/testdata/plan14-root-reads-self-serve-moves-2.tsv
+++ b/internal/provision/testdata/plan14-root-reads-self-serve-moves-2.tsv
@@ -23,4 +23,4 @@ internal/console/templates/settings.html external legacy accepted-legacy accepte
internal/provision/offline_move.go offlineBegin legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=root-input (J4, as OfflineStart); the console (cmd/console serve under console.service, user carlos-console): self-serve moves 1 Task 7's body (OfflineStart's, the state a parameter; a direct start advances move_seq and stamps move.seq, refusing ErrLifetimeExhausted at MaxInt64 before any write), and it now also creates the self-serve move in offline-queued for OfflineQueue (queued_at, staged_at and precheck_at at the caller's stamp, move.seq set to the record's move_seq, no advance) and records return_of; a stage and a queue hold nothing; its one site is still rec.ActiveRun.Open(), a method on the record's run state (RunState.Open), not a file read; self-serve moves 2 Task 2 file:Open@208 0a9b432ddf2f39a977c0651e377258b35c1d05635e9979583eeac6253d6c0ec8
@coverage internal/console 29ae289dc256408f6debca9150987ba5ce7a2e80a0bbf5fc3064ead950d16bd1
@coverage internal/moves 51465341e311dc007d75ed4f68474fe4cec05d51c6ec07ee5c64653936faa410
-@coverage internal/provision 6e75b92e716fd61f0f70e3071e36dc2be240fb419b70845b3429e8c9990a018a
+@coverage internal/provision 177bbc7f94d9ba7e4d999f832920e8552a0af4315d7ce2445572efb21fada342