carlos / platform Public

self-serve job: every attempt of one request writes the same bytes (fix round 2, item 4)

provision.Expect gains Stamp, the request's proposed stamp: when set,
SetRecordMoveAt writes it as the move's updated_at instead of the
attempt's clock. The job's bound write passes the persisted request's
proposed stamp, so a replay after a failure or a restart sends the first
attempt's body byte for byte. The operator doors' bound write
(offlineApplyBound) passes no Stamp: it makes one attempt and never
replays, and its updated_at stays the clock, so its answers are unchanged.

Witness: TestQueueReplayIsByteIdentical (e2e; failures, a restart, the
landing). RED: "attempt 1 of request rq_… differs from the first".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev c038dcef7a9c2bcf5f4fe1b4da27aab6130e26cb parent 0d3763d
3 files changed, +63 −3
  • internal/console/selfserve_apply.go +1 −1
  • internal/e2e/selfserve/queue_replay_test.go +50 −0
  • internal/provision/provision.go +12 −2
diff --git a/internal/console/selfserve_apply.go b/internal/console/selfserve_apply.go
index 87efbf6bf..8a67743ba 100644
--- a/internal/console/selfserve_apply.go
+++ b/internal/console/selfserve_apply.go
@@ -319,7 +319,7 @@ func (s *Server) selfServeRun(ctx context.Context, t *leaseToken, row *SelfServe
TestHooks.point(pr.Kind + ":write")
wctx, cancel := s.selfServeWriteCtx(ctx, t)
_, werr := provision.SetRecordMoveAt(wctx, s.cfg.Releases, row.Account, row.App, row.Host,
- provision.Expect{Incarnation: pr.ExpectInc, Seq: pr.ExpectSeq}, mut(pr.Proposed, provision.Receipt{Nonce: pr.Nonce}))
+ provision.Expect{Incarnation: pr.ExpectInc, Seq: pr.ExpectSeq, Stamp: pr.Proposed}, mut(pr.Proposed, provision.Receipt{Nonce: pr.Nonce}))
cancel()
lastErr, refused = werr, selfServeMutationRefused(werr)
if werr != nil && !refused {
diff --git a/internal/e2e/selfserve/queue_replay_test.go b/internal/e2e/selfserve/queue_replay_test.go
index a36d1dce0..d9104f306 100644
--- a/internal/e2e/selfserve/queue_replay_test.go
+++ b/internal/e2e/selfserve/queue_replay_test.go
@@ -7,6 +7,7 @@ import (
"regexp"
"strings"
"testing"
+ "time"
"github.com/carlosframework/platform/internal/host"
)
@@ -97,3 +98,52 @@ func TestQueueReplayKeepsItsStagingCoordinates(t *testing.T) {
})
}
}
+
+// TestQueueReplayIsByteIdentical (fix round 2, item 4): every attempt of
+// one request writes the same bytes. The queue's writes fail, the console
+// restarts and replays, failing again, and then lands: every body sent
+// under the request's nonce, the landed one included, is byte for byte the
+// first (the move's updated_at is the request's proposed stamp, not the
+// attempt's clock).
+func TestQueueReplayIsByteIdentical(t *testing.T) {
+ w := newJobWorld(t)
+ nonceOf := regexp.MustCompile(`"last_request":"(rq_[0-9a-f]+)"`)
+ bodies := map[string][]string{}
+ failing := true
+ w.fs.setPut(func(key string, body []byte) putFault {
+ b := string(body)
+ if !strings.Contains(b, `"offline-queued"`) {
+ return putRun
+ }
+ if n := nonceOf.FindStringSubmatch(b); n != nil {
+ bodies[n[1]] = append(bodies[n[1]], b)
+ }
+ if failing {
+ return putFail
+ }
+ return putRun
+ })
+ row := w.admit()
+ w.watch = row.ID
+ w.until(20, "a queue PUT sent", func() bool { return len(bodies) > 0 })
+ nonce := w.row(row.ID).Request.Nonce
+ w.advance(2 * time.Second) // the replay's clock is later than the issue's
+ w.tick()
+ w.restart()
+ w.advance(2 * time.Second)
+ w.tick()
+ failing = false
+ w.consoleUntil(10, "queued", func() bool { return w.record().Move != nil })
+ got := bodies[nonce]
+ if len(got) < 3 {
+ t.Fatalf("%d attempts of the request captured, want the failures, the restart's replay and the landing", len(got))
+ }
+ for i, b := range got {
+ if b != got[0] {
+ t.Fatalf("attempt %d of request %s differs from the first:\n first %s\n this %s", i, nonce, got[0], b)
+ }
+ }
+ if len(bodies) != 1 {
+ t.Fatalf("%d request identities sent, want one", len(bodies))
+ }
+}
diff --git a/internal/provision/provision.go b/internal/provision/provision.go
index 17a6db058..b8b42a317 100644
--- a/internal/provision/provision.go
+++ b/internal/provision/provision.go
@@ -1433,8 +1433,15 @@ func SetRecordMove(ctx context.Context, s release.Store, acct, app, host string,
}
// Expect is the record a bound write was decided on: its incarnation and
-// its record_seq as the caller observed them.
-type Expect struct{ Incarnation, Seq uint64 }
+// its record_seq as the caller observed them. Stamp, when set, is the
+// request's proposed stamp (the self-serve job's persisted request): the
+// write's move updated_at is it rather than the attempt's clock, so every
+// attempt of one request writes the same bytes (self-serve moves plan 2,
+// final review F10). Zero keeps the clock, as every other writer does.
+type Expect struct {
+ Incarnation, Seq uint64
+ Stamp time.Time
+}
// SetRecordMoveAt is SetRecordMove bound to the record its caller observed:
// it reads the record (judged through JudgeFrom(ctx) when the context
@@ -1458,6 +1465,9 @@ func SetRecordMoveAt(ctx context.Context, s release.Store, acct, app, host strin
}
if rec.Move != nil {
rec.Move.UpdatedAt = time.Now().UTC()
+ if !expect.Stamp.IsZero() {
+ rec.Move.UpdatedAt = expect.Stamp.UTC()
+ }
}
return nil
}, false, &expect)