From 2eb11e1fdb17efd85257dd4e21676b1239047128 Mon Sep 17 00:00:00 2001 From: Relayflow Date: Sun, 4 Oct 2026 08:38:18 +0000 Subject: [PATCH 1/7] fix(mountsync): accept upstream checkpoint cursors --- cmd/relayfile-cli/checkpoint_lifecycle.go | 2 +- .../checkpoint_lifecycle_test.go | 15 ++++++ .../mountsync/realtime_collaboration_test.go | 7 +++ internal/mountsync/syncer.go | 2 +- internal/mountsync/syncer_test.go | 50 +++++++++++++++++++ internal/relayfile/checkpoint_seal.go | 2 +- internal/relayfile/checkpoint_seal_test.go | 15 ++++++ openapi/relayfile-v1.openapi.yaml | 6 +-- summary.md | 17 +++++++ 9 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 summary.md diff --git a/cmd/relayfile-cli/checkpoint_lifecycle.go b/cmd/relayfile-cli/checkpoint_lifecycle.go index 96690d14..d6343902 100644 --- a/cmd/relayfile-cli/checkpoint_lifecycle.go +++ b/cmd/relayfile-cli/checkpoint_lifecycle.go @@ -39,7 +39,7 @@ var ( checkpointLifecycleIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$`) checkpointDigestPattern = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`) checkpointRevisionPattern = regexp.MustCompile(`^(?:0|rev_[0-9]+)$`) - checkpointCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+)$`) + checkpointCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$`) ) type checkpointCLIError struct { diff --git a/cmd/relayfile-cli/checkpoint_lifecycle_test.go b/cmd/relayfile-cli/checkpoint_lifecycle_test.go index 7196dc1b..24148d91 100644 --- a/cmd/relayfile-cli/checkpoint_lifecycle_test.go +++ b/cmd/relayfile-cli/checkpoint_lifecycle_test.go @@ -24,6 +24,21 @@ type fakeCheckpointLease struct{ released bool } func (l *fakeCheckpointLease) Release() error { l.released = true; return nil } +func TestCheckpointCursorPattern(t *testing.T) { + valid := []string{"0", "evt_42", "upstream:v1:notion:page_123", "upstream:v1:notion:page_123:2", "upstream:v1:" + strings.Repeat("a", 400)} + invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:notion page_123", "upstream:v1:notion\npage_123", "upstream:v1:" + strings.Repeat("a", 401)} + for _, cursor := range valid { + if !checkpointCursorPattern.MatchString(cursor) { + t.Errorf("valid cursor rejected: %q", cursor) + } + } + for _, cursor := range invalid { + if checkpointCursorPattern.MatchString(cursor) { + t.Errorf("invalid cursor accepted: %q", cursor) + } + } +} + func installCheckpointLifecycleSeams(t *testing.T, active activeCheckpointMount, receipt mountsync.CheckpointSeal) (*fakeCheckpointLease, *int, *int) { t.Helper() originalResolve := checkpointResolveActive diff --git a/internal/mountsync/realtime_collaboration_test.go b/internal/mountsync/realtime_collaboration_test.go index 1e930b5e..e2ea4ecb 100644 --- a/internal/mountsync/realtime_collaboration_test.go +++ b/internal/mountsync/realtime_collaboration_test.go @@ -1150,6 +1150,13 @@ func TestAdvanceEventCursorNeverRegressesRelayfileOrdinal(t *testing.T) { if got := advanceEventCursor("evt_42", "evt_43"); got != "evt_43" { t.Fatalf("cursor did not advance: %q", got) } + upstream := "upstream:v1:linear:issue_123:2" + if got := advanceEventCursor("evt_43", upstream); got != upstream { + t.Fatalf("mixed feed cursor = %q, want %q", got, upstream) + } + if !checkpointEventCursorPattern.MatchString(upstream) { + t.Fatalf("latest mixed feed cursor is not checkpoint-safe: %q", upstream) + } } // TestWebSocketURLScopesToMountRoot guards the path-scoped dial. A mount whose diff --git a/internal/mountsync/syncer.go b/internal/mountsync/syncer.go index 68683508..ad2edaba 100644 --- a/internal/mountsync/syncer.go +++ b/internal/mountsync/syncer.go @@ -51,7 +51,7 @@ var ( ErrCheckpointNonConverged = errors.New("local and durable Relayfile state did not converge") checkpointSessionPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$`) checkpointRevisionPattern = regexp.MustCompile(`^(?:0|rev_[0-9]+)$`) - checkpointEventCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+)$`) + checkpointEventCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$`) mountCorrelationIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$`) ) diff --git a/internal/mountsync/syncer_test.go b/internal/mountsync/syncer_test.go index 41d098aa..975b2d20 100644 --- a/internal/mountsync/syncer_test.go +++ b/internal/mountsync/syncer_test.go @@ -10731,6 +10731,25 @@ func TestCheckpointOwnershipEndToEndHandbackPreservesDestinationTurn(t *testing. } func TestVerifyCheckpointRequiresLocalExactnessAndServerReattestation(t *testing.T) { + t.Run("upstream cursor is opaque and server-attested", func(t *testing.T) { + client := &fakeClient{} + syncer, _ := newManagedCheckpointSyncer(t, client) + receipt := consumedCheckpointReceiptForTest(t, syncer) + receipt.EventCursor = "upstream:v1:linear:issue_123:2" + client.checkpointVerifySeal = receipt + syncer.state.EventsCursor = receipt.EventCursor + if err := syncer.saveStateWithoutLocalScan(); err != nil { + t.Fatal(err) + } + verification, err := syncer.VerifyCheckpoint(context.Background(), receipt) + if err != nil { + t.Fatalf("verify upstream cursor: %v", err) + } + if verification.Observed.EventCursor != receipt.EventCursor || client.checkpointVerifyCalls != 1 { + t.Fatalf("verification=%+v calls=%d", verification, client.checkpointVerifyCalls) + } + }) + t.Run("empty local cursor normalizes to canonical zero", func(t *testing.T) { client := &fakeClient{} syncer, _ := newManagedCheckpointSyncer(t, client) @@ -10885,6 +10904,37 @@ func TestVerifyCheckpointRequiresLocalExactnessAndServerReattestation(t *testing }) } +func TestCheckpointEventCursorPattern(t *testing.T) { + valid := []string{ + "0", + "evt_42", + "upstream:v1:linear:issue_123", + "upstream:v1:linear:issue_123:2", + "upstream:v1:" + strings.Repeat("a", 400), + } + invalid := []string{ + "", + " ", + "evt_", + "12", + "upstream:v1:", + "upstream:v1:linear issue_123", + "upstream:v1:linear/issue_123", + "upstream:v1:linear\nissue_123", + "upstream:v1:" + strings.Repeat("a", 401), + } + for _, cursor := range valid { + if !checkpointEventCursorPattern.MatchString(cursor) { + t.Errorf("valid cursor rejected: %q", cursor) + } + } + for _, cursor := range invalid { + if checkpointEventCursorPattern.MatchString(cursor) { + t.Errorf("invalid cursor accepted: %q", cursor) + } + } +} + func consumedCheckpointReceiptForTest(t *testing.T, syncer *Syncer) CheckpointSeal { t.Helper() digest, err := syncer.checkpointLocalDigestLocked() diff --git a/internal/relayfile/checkpoint_seal.go b/internal/relayfile/checkpoint_seal.go index 95058969..1f0e1252 100644 --- a/internal/relayfile/checkpoint_seal.go +++ b/internal/relayfile/checkpoint_seal.go @@ -37,7 +37,7 @@ var ( ErrCheckpointAdminConflict = errors.New("checkpoint administrative reconciliation identity conflicts with durable state") checkpointSessionPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$`) checkpointRevisionPattern = regexp.MustCompile(`^(?:0|rev_[0-9]+)$`) - checkpointEventCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+)$`) + checkpointEventCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$`) ) const ( diff --git a/internal/relayfile/checkpoint_seal_test.go b/internal/relayfile/checkpoint_seal_test.go index 64b2f3d6..7c998ccc 100644 --- a/internal/relayfile/checkpoint_seal_test.go +++ b/internal/relayfile/checkpoint_seal_test.go @@ -12,6 +12,21 @@ import ( const checkpointTestConsumerPrincipal = "cloud-dashboard-observer" +func TestCheckpointEventCursorPattern(t *testing.T) { + valid := []string{"0", "evt_42", "upstream:v1:github:pull_123", "upstream:v1:github:pull_123:2", "upstream:v1:" + strings.Repeat("a", 400)} + invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:github pull_123", "upstream:v1:github\tpull_123", "upstream:v1:" + strings.Repeat("a", 401)} + for _, cursor := range valid { + if !checkpointEventCursorPattern.MatchString(cursor) { + t.Errorf("valid cursor rejected: %q", cursor) + } + } + for _, cursor := range invalid { + if checkpointEventCursorPattern.MatchString(cursor) { + t.Errorf("invalid cursor accepted: %q", cursor) + } + } +} + func TestCheckpointIssuanceResponseLossRotatesBearerAcrossRestart(t *testing.T) { stateFile := filepath.Join(t.TempDir(), "relayfile-state.json") now := time.Date(2026, 8, 23, 12, 0, 0, 0, time.UTC) diff --git a/openapi/relayfile-v1.openapi.yaml b/openapi/relayfile-v1.openapi.yaml index eadb4c6f..c47fa144 100644 --- a/openapi/relayfile-v1.openapi.yaml +++ b/openapi/relayfile-v1.openapi.yaml @@ -4121,7 +4121,7 @@ components: pattern: '^(0|rev_[0-9]+)$' eventCursor: type: string - pattern: '^(0|evt_[0-9]+)$' + pattern: '^(0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$' issuedAt: type: string format: date-time @@ -4219,7 +4219,7 @@ components: pattern: '^(0|rev_[0-9]+)$' eventCursor: type: string - pattern: '^(0|evt_[0-9]+)$' + pattern: '^(0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$' consumedAt: type: string format: date-time @@ -4262,7 +4262,7 @@ components: pattern: '^(0|rev_[0-9]+)$' eventCursor: type: string - pattern: '^(0|evt_[0-9]+)$' + pattern: '^(0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$' issuedAt: type: string format: date-time diff --git a/summary.md b/summary.md new file mode 100644 index 00000000..5d10dc99 --- /dev/null +++ b/summary.md @@ -0,0 +1,17 @@ +# PR summary + +## What changed + +- Accept bounded provider-namespaced `upstream:v1:...` event cursors in mountsync checkpoint validation while retaining support for `0` and `evt_` cursors. +- Keep the self-hosted checkpoint verifier, CLI lifecycle validation, and all three OpenAPI checkpoint cursor schemas aligned with the backend-neutral contract. +- Add regression coverage for legacy and upstream cursor forms, multi-event `:N` suffixes, the 400-character payload boundary, malformed/control/whitespace inputs, mixed `evt_`/`upstream:` feeds, and destination checkpoint verification with an upstream cursor. + +## Verification + +- `go test ./internal/mountsync ./internal/relayfile ./cmd/relayfile-cli` +- `scripts/check-contract-surface.sh` +- `git diff --check` + +## Rollout note + +This compatibility change must be released before AgentWorkforce/relayfile-cloud#293 begins emitting canonical upstream event IDs. If that ordering cannot be guaranteed, #293 should gate the new IDs behind a rollout flag until compatible Relayfile clients are deployed. From 0cf7d653deffd4e6bc37ee2b10346dc38efa5171 Mon Sep 17 00:00:00 2001 From: Relayflow Date: Sun, 4 Oct 2026 08:44:02 +0000 Subject: [PATCH 2/7] fix(checkpoint): reject padded event cursors --- .../completed/2026-10/traj_0qdrmrp6ijnu.json | 53 +++++++++++++++++++ .../completed/2026-10/traj_0qdrmrp6ijnu.md | 31 +++++++++++ .trajectories/index.json | 7 +++ .../checkpoint_lifecycle_test.go | 2 +- internal/mountsync/syncer.go | 8 +-- internal/mountsync/syncer_test.go | 2 + internal/relayfile/checkpoint_seal.go | 2 +- internal/relayfile/checkpoint_seal_test.go | 2 +- 8 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 .trajectories/completed/2026-10/traj_0qdrmrp6ijnu.json create mode 100644 .trajectories/completed/2026-10/traj_0qdrmrp6ijnu.md diff --git a/.trajectories/completed/2026-10/traj_0qdrmrp6ijnu.json b/.trajectories/completed/2026-10/traj_0qdrmrp6ijnu.json new file mode 100644 index 00000000..690a91ef --- /dev/null +++ b/.trajectories/completed/2026-10/traj_0qdrmrp6ijnu.json @@ -0,0 +1,53 @@ +{ + "id": "traj_0qdrmrp6ijnu", + "version": 1, + "task": { + "title": "Repair upstream event cursor checkpoint validation" + }, + "status": "completed", + "startedAt": "2026-10-04T08:43:18.694Z", + "completedAt": "2026-10-04T08:43:23.917Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-10-04T08:43:18.916Z" + } + ], + "chapters": [ + { + "id": "chap_leubiq4kl800", + "title": "Work", + "agentName": "default", + "startedAt": "2026-10-04T08:43:18.916Z", + "endedAt": "2026-10-04T08:43:23.917Z", + "events": [ + { + "ts": 1791103398917, + "type": "decision", + "content": "Validate event cursors without trimming: Validate event cursors without trimming", + "raw": { + "question": "Validate event cursors without trimming", + "chosen": "Validate event cursors without trimming", + "alternatives": [], + "reasoning": "The cursor contract requires whitespace and controls to be rejected; trimming before the bounded regex silently accepted padded malformed cursors." + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Tightened checkpoint event-cursor validation to reject padded whitespace, added regression cases, and verified all Go packages plus contract checks.", + "approach": "Standard approach", + "confidence": 0.9 + }, + "commits": [], + "filesChanged": [], + "projectId": "AgentWorkforce/relayfile", + "tags": [], + "_trace": { + "startRef": "f2777d92d8cc70de11b23460f2eb0eb152ad0c06", + "endRef": "f2777d92d8cc70de11b23460f2eb0eb152ad0c06" + } +} \ No newline at end of file diff --git a/.trajectories/completed/2026-10/traj_0qdrmrp6ijnu.md b/.trajectories/completed/2026-10/traj_0qdrmrp6ijnu.md new file mode 100644 index 00000000..90a07220 --- /dev/null +++ b/.trajectories/completed/2026-10/traj_0qdrmrp6ijnu.md @@ -0,0 +1,31 @@ +# Trajectory: Repair upstream event cursor checkpoint validation + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** October 4, 2026 at 08:43 AM +> **Completed:** October 4, 2026 at 08:43 AM + +--- + +## Summary + +Tightened checkpoint event-cursor validation to reject padded whitespace, added regression cases, and verified all Go packages plus contract checks. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Validate event cursors without trimming +- **Chose:** Validate event cursors without trimming +- **Reasoning:** The cursor contract requires whitespace and controls to be rejected; trimming before the bounded regex silently accepted padded malformed cursors. + +--- + +## Chapters + +### 1. Work +*Agent: default* + +- Validate event cursors without trimming: Validate event cursors without trimming diff --git a/.trajectories/index.json b/.trajectories/index.json index 7228ae75..a634f8ad 100644 --- a/.trajectories/index.json +++ b/.trajectories/index.json @@ -463,6 +463,13 @@ "startedAt": "2026-10-04T15:50:26.132Z", "completedAt": "2026-10-04T16:07:54.908Z", "path": ".trajectories/completed/2026-10/traj_t8ngcsdcvvc7.json" + }, + "traj_0qdrmrp6ijnu": { + "title": "Repair upstream event cursor checkpoint validation", + "status": "completed", + "startedAt": "2026-10-04T08:43:18.694Z", + "completedAt": "2026-10-04T08:43:23.917Z", + "path": "/home/daytona/.relayflow-v2-supervisor/durable/repository/.trajectories/completed/2026-10/traj_0qdrmrp6ijnu.json" } } } diff --git a/cmd/relayfile-cli/checkpoint_lifecycle_test.go b/cmd/relayfile-cli/checkpoint_lifecycle_test.go index 24148d91..e35a3123 100644 --- a/cmd/relayfile-cli/checkpoint_lifecycle_test.go +++ b/cmd/relayfile-cli/checkpoint_lifecycle_test.go @@ -26,7 +26,7 @@ func (l *fakeCheckpointLease) Release() error { l.released = true; return nil } func TestCheckpointCursorPattern(t *testing.T) { valid := []string{"0", "evt_42", "upstream:v1:notion:page_123", "upstream:v1:notion:page_123:2", "upstream:v1:" + strings.Repeat("a", 400)} - invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:notion page_123", "upstream:v1:notion\npage_123", "upstream:v1:" + strings.Repeat("a", 401)} + invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:notion page_123", " upstream:v1:notion:page_123 ", "upstream:v1:notion\npage_123", "upstream:v1:" + strings.Repeat("a", 401)} for _, cursor := range valid { if !checkpointCursorPattern.MatchString(cursor) { t.Errorf("valid cursor rejected: %q", cursor) diff --git a/internal/mountsync/syncer.go b/internal/mountsync/syncer.go index ad2edaba..005e58d8 100644 --- a/internal/mountsync/syncer.go +++ b/internal/mountsync/syncer.go @@ -3244,7 +3244,7 @@ func (s *Syncer) HandbackCheckpoint(ctx context.Context, consumed CheckpointSeal // requiring a second (impossible) transition. if prepared.Status == "released" { if prepared.SealID != consumed.SealID || prepared.WorkspaceID != s.workspace || prepared.Root != "/" || prepared.SessionID != consumed.SessionID || prepared.Generation != consumed.Generation || - prepared.Digest != secondDigest || !checkpointEventCursorPattern.MatchString(strings.TrimSpace(prepared.EventCursor)) || !checkpointRevisionPattern.MatchString(strings.TrimSpace(prepared.WorkspaceRevision)) || prepared.ConsumedAt != consumed.ConsumedAt || strings.TrimSpace(prepared.PreparedAt) == "" || strings.TrimSpace(prepared.ReleasedAt) == "" || prepared.SourceResumedAt != "" { + prepared.Digest != secondDigest || !checkpointEventCursorPattern.MatchString(prepared.EventCursor) || !checkpointRevisionPattern.MatchString(strings.TrimSpace(prepared.WorkspaceRevision)) || prepared.ConsumedAt != consumed.ConsumedAt || strings.TrimSpace(prepared.PreparedAt) == "" || strings.TrimSpace(prepared.ReleasedAt) == "" || prepared.SourceResumedAt != "" { return CheckpointSealOwnership{}, health, fmt.Errorf("%w: server returned a changed released handback replay", ErrCheckpointNonConverged) } for _, raw := range []string{prepared.PreparedAt, prepared.ReleasedAt} { @@ -3263,7 +3263,7 @@ func (s *Syncer) HandbackCheckpoint(ctx context.Context, consumed CheckpointSeal return prepared, health, nil } if prepared.Status != "prepared" || prepared.SealID != consumed.SealID || prepared.WorkspaceID != s.workspace || prepared.Root != "/" || prepared.SessionID != consumed.SessionID || prepared.Generation != consumed.Generation || - prepared.Digest != secondDigest || !checkpointEventCursorPattern.MatchString(strings.TrimSpace(prepared.EventCursor)) || !checkpointRevisionPattern.MatchString(strings.TrimSpace(prepared.WorkspaceRevision)) || prepared.ConsumedAt != consumed.ConsumedAt || strings.TrimSpace(prepared.PreparedAt) == "" || prepared.ReleasedAt != "" || prepared.SourceResumedAt != "" { + prepared.Digest != secondDigest || !checkpointEventCursorPattern.MatchString(prepared.EventCursor) || !checkpointRevisionPattern.MatchString(strings.TrimSpace(prepared.WorkspaceRevision)) || prepared.ConsumedAt != consumed.ConsumedAt || strings.TrimSpace(prepared.PreparedAt) == "" || prepared.ReleasedAt != "" || prepared.SourceResumedAt != "" { return CheckpointSealOwnership{}, health, fmt.Errorf("%w: server returned a changed handback preparation", ErrCheckpointNonConverged) } if _, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(prepared.PreparedAt)); err != nil { @@ -3434,7 +3434,7 @@ func (s *Syncer) VerifyCheckpointOwnership(ctx context.Context, proof Checkpoint } if proof.Status != "source-resumed" || strings.TrimSpace(proof.SealID) == "" || proof.WorkspaceID != s.workspace || proof.Root != "/" || s.remoteRoot != "/" || !checkpointSessionPattern.MatchString(strings.TrimSpace(proof.SessionID)) || proof.Generation == 0 || !validCheckpointDigestString(proof.Digest) || - !checkpointRevisionPattern.MatchString(strings.TrimSpace(proof.WorkspaceRevision)) || !checkpointEventCursorPattern.MatchString(strings.TrimSpace(proof.EventCursor)) || + !checkpointRevisionPattern.MatchString(strings.TrimSpace(proof.WorkspaceRevision)) || !checkpointEventCursorPattern.MatchString(proof.EventCursor) || strings.TrimSpace(proof.ReleasedAt) == "" || strings.TrimSpace(proof.SourceResumedAt) == "" { return verification, fmt.Errorf("%w: malformed or mismatched source-resume proof", ErrCheckpointNonConverged) } @@ -3497,7 +3497,7 @@ func (s *Syncer) VerifyCheckpointOwnership(ctx context.Context, proof Checkpoint func validateConsumedCheckpointReceipt(seal CheckpointSeal) error { if strings.TrimSpace(seal.SealID) == "" || strings.TrimSpace(seal.SealToken) != "" || strings.TrimSpace(seal.WorkspaceID) == "" || seal.Root != "/" || - !checkpointSessionPattern.MatchString(strings.TrimSpace(seal.SessionID)) || seal.Generation == 0 || !validCheckpointDigestString(seal.Digest) || !checkpointRevisionPattern.MatchString(strings.TrimSpace(seal.WorkspaceRevision)) || !checkpointEventCursorPattern.MatchString(strings.TrimSpace(seal.EventCursor)) || strings.TrimSpace(seal.ConsumedAt) == "" { + !checkpointSessionPattern.MatchString(strings.TrimSpace(seal.SessionID)) || seal.Generation == 0 || !validCheckpointDigestString(seal.Digest) || !checkpointRevisionPattern.MatchString(strings.TrimSpace(seal.WorkspaceRevision)) || !checkpointEventCursorPattern.MatchString(seal.EventCursor) || strings.TrimSpace(seal.ConsumedAt) == "" { return errors.New("receipt must be an exact consumed full-root seal without sealToken") } for _, raw := range []string{seal.IssuedAt, seal.ExpiresAt, seal.ConsumedAt} { diff --git a/internal/mountsync/syncer_test.go b/internal/mountsync/syncer_test.go index 975b2d20..d809ea81 100644 --- a/internal/mountsync/syncer_test.go +++ b/internal/mountsync/syncer_test.go @@ -10887,6 +10887,7 @@ func TestVerifyCheckpointRequiresLocalExactnessAndServerReattestation(t *testing for name, mutate := range map[string]func(*CheckpointSeal){ "bare revision": func(receipt *CheckpointSeal) { receipt.WorkspaceRevision = "12" }, "bare cursor": func(receipt *CheckpointSeal) { receipt.EventCursor = "12" }, + "padded cursor": func(receipt *CheckpointSeal) { receipt.EventCursor = " evt_2 " }, } { t.Run(name, func(t *testing.T) { client := &fakeClient{} @@ -10919,6 +10920,7 @@ func TestCheckpointEventCursorPattern(t *testing.T) { "12", "upstream:v1:", "upstream:v1:linear issue_123", + " upstream:v1:linear:issue_123 ", "upstream:v1:linear/issue_123", "upstream:v1:linear\nissue_123", "upstream:v1:" + strings.Repeat("a", 401), diff --git a/internal/relayfile/checkpoint_seal.go b/internal/relayfile/checkpoint_seal.go index 1f0e1252..cfd81138 100644 --- a/internal/relayfile/checkpoint_seal.go +++ b/internal/relayfile/checkpoint_seal.go @@ -528,7 +528,7 @@ func (s *Store) VerifyConsumedCheckpointSeal(workspaceID string, req CheckpointS if err != nil || workspaceID == "" || strings.TrimSpace(req.SealID) == "" || !checkpointSessionPattern.MatchString(strings.TrimSpace(req.SessionID)) || req.Generation == 0 || consumerPrincipal == "" { return CheckpointSeal{}, ErrInvalidInput } - if !validCheckpointDigest(req.Digest) || !checkpointRevisionPattern.MatchString(strings.TrimSpace(req.WorkspaceRevision)) || !checkpointEventCursorPattern.MatchString(strings.TrimSpace(req.EventCursor)) || strings.TrimSpace(req.ConsumedAt) == "" { + if !validCheckpointDigest(req.Digest) || !checkpointRevisionPattern.MatchString(strings.TrimSpace(req.WorkspaceRevision)) || !checkpointEventCursorPattern.MatchString(req.EventCursor) || strings.TrimSpace(req.ConsumedAt) == "" { return CheckpointSeal{}, ErrInvalidInput } for _, raw := range []string{req.IssuedAt, req.ExpiresAt, req.ConsumedAt} { diff --git a/internal/relayfile/checkpoint_seal_test.go b/internal/relayfile/checkpoint_seal_test.go index 7c998ccc..0b37cc31 100644 --- a/internal/relayfile/checkpoint_seal_test.go +++ b/internal/relayfile/checkpoint_seal_test.go @@ -14,7 +14,7 @@ const checkpointTestConsumerPrincipal = "cloud-dashboard-observer" func TestCheckpointEventCursorPattern(t *testing.T) { valid := []string{"0", "evt_42", "upstream:v1:github:pull_123", "upstream:v1:github:pull_123:2", "upstream:v1:" + strings.Repeat("a", 400)} - invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:github pull_123", "upstream:v1:github\tpull_123", "upstream:v1:" + strings.Repeat("a", 401)} + invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:github pull_123", " upstream:v1:github:pull_123 ", "upstream:v1:github\tpull_123", "upstream:v1:" + strings.Repeat("a", 401)} for _, cursor := range valid { if !checkpointEventCursorPattern.MatchString(cursor) { t.Errorf("valid cursor rejected: %q", cursor) From a8af56dda7c28514491ff07978f0178b7e1f2f71 Mon Sep 17 00:00:00 2001 From: Relayflow Date: Sun, 4 Oct 2026 08:50:37 +0000 Subject: [PATCH 3/7] chore(trail): record checkpoint cursor verification --- .../completed/2026-10/traj_36h89bsoig5x.json | 53 +++++++++++++++++++ .../completed/2026-10/traj_36h89bsoig5x.md | 23 ++++++++ .trajectories/index.json | 7 +++ 3 files changed, 83 insertions(+) create mode 100644 .trajectories/completed/2026-10/traj_36h89bsoig5x.json create mode 100644 .trajectories/completed/2026-10/traj_36h89bsoig5x.md diff --git a/.trajectories/completed/2026-10/traj_36h89bsoig5x.json b/.trajectories/completed/2026-10/traj_36h89bsoig5x.json new file mode 100644 index 00000000..855ecab1 --- /dev/null +++ b/.trajectories/completed/2026-10/traj_36h89bsoig5x.json @@ -0,0 +1,53 @@ +{ + "id": "traj_36h89bsoig5x", + "version": 1, + "task": { + "title": "Accept upstream event cursors in mountsync checkpoints" + }, + "status": "completed", + "startedAt": "2026-10-04T08:46:27.242Z", + "completedAt": "2026-10-04T08:50:31.407Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-10-04T08:50:27.813Z" + } + ], + "chapters": [ + { + "id": "chap_ar50dfcveu54", + "title": "Work", + "agentName": "default", + "startedAt": "2026-10-04T08:50:27.813Z", + "endedAt": "2026-10-04T08:50:31.407Z", + "events": [ + { + "ts": 1791103827813, + "type": "decision", + "content": "Treat SDK ErrorEvent failure as toolchain setup mismatch: Treat SDK ErrorEvent failure as toolchain setup mismatch", + "raw": { + "question": "Treat SDK ErrorEvent failure as toolchain setup mismatch", + "chosen": "Treat SDK ErrorEvent failure as toolchain setup mismatch", + "alternatives": [], + "reasoning": "" + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Verified upstream checkpoint cursor support and repaired the local check environment to match CI Node 22; full repository checks pass.", + "approach": "Standard approach", + "confidence": 0.98 + }, + "commits": [], + "filesChanged": [], + "projectId": "AgentWorkforce/relayfile", + "tags": [], + "_trace": { + "startRef": "767ae7c90dee98904b36099f5d398bee5a1a4816", + "endRef": "767ae7c90dee98904b36099f5d398bee5a1a4816" + } +} \ No newline at end of file diff --git a/.trajectories/completed/2026-10/traj_36h89bsoig5x.md b/.trajectories/completed/2026-10/traj_36h89bsoig5x.md new file mode 100644 index 00000000..5836cc97 --- /dev/null +++ b/.trajectories/completed/2026-10/traj_36h89bsoig5x.md @@ -0,0 +1,23 @@ +# Trajectory: Accept upstream event cursors in mountsync checkpoints + +> **Status:** ✅ Completed +> **Confidence:** 98% +> **Started:** October 4, 2026 at 08:46 AM +> **Completed:** October 4, 2026 at 08:50 AM + +--- + +## Summary + +Verified upstream checkpoint cursor support and repaired the local check environment to match CI Node 22; full repository checks pass. + +**Approach:** Standard approach + +--- + +## Chapters + +### 1. Work +*Agent: default* + +- Treat SDK ErrorEvent failure as toolchain setup mismatch: Treat SDK ErrorEvent failure as toolchain setup mismatch diff --git a/.trajectories/index.json b/.trajectories/index.json index a634f8ad..b92634d6 100644 --- a/.trajectories/index.json +++ b/.trajectories/index.json @@ -470,6 +470,13 @@ "startedAt": "2026-10-04T08:43:18.694Z", "completedAt": "2026-10-04T08:43:23.917Z", "path": "/home/daytona/.relayflow-v2-supervisor/durable/repository/.trajectories/completed/2026-10/traj_0qdrmrp6ijnu.json" + }, + "traj_36h89bsoig5x": { + "title": "Accept upstream event cursors in mountsync checkpoints", + "status": "completed", + "startedAt": "2026-10-04T08:46:27.242Z", + "completedAt": "2026-10-04T08:50:31.407Z", + "path": "/home/daytona/.relayflow-v2-supervisor/durable/repository/.trajectories/completed/2026-10/traj_36h89bsoig5x.json" } } } From 47d901a5005af135b66623cc9b2fa08d3760e597 Mon Sep 17 00:00:00 2001 From: Relayflow Date: Sun, 4 Oct 2026 08:53:43 +0000 Subject: [PATCH 4/7] Keep relayflow working files out of the change --- summary.md | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 summary.md diff --git a/summary.md b/summary.md deleted file mode 100644 index 5d10dc99..00000000 --- a/summary.md +++ /dev/null @@ -1,17 +0,0 @@ -# PR summary - -## What changed - -- Accept bounded provider-namespaced `upstream:v1:...` event cursors in mountsync checkpoint validation while retaining support for `0` and `evt_` cursors. -- Keep the self-hosted checkpoint verifier, CLI lifecycle validation, and all three OpenAPI checkpoint cursor schemas aligned with the backend-neutral contract. -- Add regression coverage for legacy and upstream cursor forms, multi-event `:N` suffixes, the 400-character payload boundary, malformed/control/whitespace inputs, mixed `evt_`/`upstream:` feeds, and destination checkpoint verification with an upstream cursor. - -## Verification - -- `go test ./internal/mountsync ./internal/relayfile ./cmd/relayfile-cli` -- `scripts/check-contract-surface.sh` -- `git diff --check` - -## Rollout note - -This compatibility change must be released before AgentWorkforce/relayfile-cloud#293 begins emitting canonical upstream event IDs. If that ordering cannot be guaranteed, #293 should gate the new IDs behind a rollout flag until compatible Relayfile clients are deployed. From 5e13b7fc35ae61972bc63a2b488aaa1d1a159a2f Mon Sep 17 00:00:00 2001 From: Miya Date: Thu, 8 Oct 2026 03:36:30 +0200 Subject: [PATCH 5/7] fix(checkpoint): cover bounded upstream cursor handback and resume --- .trajectories/index.json | 4 +- cmd/relayfile-cli/checkpoint_lifecycle.go | 2 +- .../checkpoint_lifecycle_test.go | 4 +- internal/mountsync/syncer.go | 2 +- internal/mountsync/syncer_test.go | 40 ++++++++++++++++++- internal/relayfile/checkpoint_seal.go | 2 +- internal/relayfile/checkpoint_seal_test.go | 4 +- openapi/relayfile-v1.openapi.yaml | 6 +-- 8 files changed, 50 insertions(+), 14 deletions(-) diff --git a/.trajectories/index.json b/.trajectories/index.json index b92634d6..a13f683d 100644 --- a/.trajectories/index.json +++ b/.trajectories/index.json @@ -469,14 +469,14 @@ "status": "completed", "startedAt": "2026-10-04T08:43:18.694Z", "completedAt": "2026-10-04T08:43:23.917Z", - "path": "/home/daytona/.relayflow-v2-supervisor/durable/repository/.trajectories/completed/2026-10/traj_0qdrmrp6ijnu.json" + "path": ".trajectories/completed/2026-10/traj_0qdrmrp6ijnu.json" }, "traj_36h89bsoig5x": { "title": "Accept upstream event cursors in mountsync checkpoints", "status": "completed", "startedAt": "2026-10-04T08:46:27.242Z", "completedAt": "2026-10-04T08:50:31.407Z", - "path": "/home/daytona/.relayflow-v2-supervisor/durable/repository/.trajectories/completed/2026-10/traj_36h89bsoig5x.json" + "path": ".trajectories/completed/2026-10/traj_36h89bsoig5x.json" } } } diff --git a/cmd/relayfile-cli/checkpoint_lifecycle.go b/cmd/relayfile-cli/checkpoint_lifecycle.go index d6343902..113c5c80 100644 --- a/cmd/relayfile-cli/checkpoint_lifecycle.go +++ b/cmd/relayfile-cli/checkpoint_lifecycle.go @@ -39,7 +39,7 @@ var ( checkpointLifecycleIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$`) checkpointDigestPattern = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`) checkpointRevisionPattern = regexp.MustCompile(`^(?:0|rev_[0-9]+)$`) - checkpointCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$`) + checkpointCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,501})$`) ) type checkpointCLIError struct { diff --git a/cmd/relayfile-cli/checkpoint_lifecycle_test.go b/cmd/relayfile-cli/checkpoint_lifecycle_test.go index e35a3123..37ccd3c2 100644 --- a/cmd/relayfile-cli/checkpoint_lifecycle_test.go +++ b/cmd/relayfile-cli/checkpoint_lifecycle_test.go @@ -25,8 +25,8 @@ type fakeCheckpointLease struct{ released bool } func (l *fakeCheckpointLease) Release() error { l.released = true; return nil } func TestCheckpointCursorPattern(t *testing.T) { - valid := []string{"0", "evt_42", "upstream:v1:notion:page_123", "upstream:v1:notion:page_123:2", "upstream:v1:" + strings.Repeat("a", 400)} - invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:notion page_123", " upstream:v1:notion:page_123 ", "upstream:v1:notion\npage_123", "upstream:v1:" + strings.Repeat("a", 401)} + valid := []string{"0", "evt_42", "upstream:v1:notion:page_123", "upstream:v1:notion:page_123:2", "upstream:v1:" + strings.Repeat("a", 501)} + invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:notion page_123", " upstream:v1:notion:page_123 ", "upstream:v1:notion\npage_123", "upstream:v1:" + strings.Repeat("a", 502)} for _, cursor := range valid { if !checkpointCursorPattern.MatchString(cursor) { t.Errorf("valid cursor rejected: %q", cursor) diff --git a/internal/mountsync/syncer.go b/internal/mountsync/syncer.go index 005e58d8..698a2a72 100644 --- a/internal/mountsync/syncer.go +++ b/internal/mountsync/syncer.go @@ -51,7 +51,7 @@ var ( ErrCheckpointNonConverged = errors.New("local and durable Relayfile state did not converge") checkpointSessionPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$`) checkpointRevisionPattern = regexp.MustCompile(`^(?:0|rev_[0-9]+)$`) - checkpointEventCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$`) + checkpointEventCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,501})$`) mountCorrelationIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$`) ) diff --git a/internal/mountsync/syncer_test.go b/internal/mountsync/syncer_test.go index d809ea81..9cd50144 100644 --- a/internal/mountsync/syncer_test.go +++ b/internal/mountsync/syncer_test.go @@ -10905,13 +10905,49 @@ func TestVerifyCheckpointRequiresLocalExactnessAndServerReattestation(t *testing }) } +func TestUpstreamCheckpointHandbackAndResume(t *testing.T) { + client := &fakeClient{} + syncer, _ := newManagedCheckpointSyncer(t, client) + receipt := consumedCheckpointReceiptForTest(t, syncer) + cursor := "upstream:v1:" + strings.Repeat("p", 64) + ":" + strings.Repeat("i", 384) + ":2" + receipt.EventCursor = cursor + syncer.state.EventsCursor = cursor + if err := syncer.saveStateWithoutLocalScan(); err != nil { + t.Fatal(err) + } + client.checkpointHandbackFunc = func(_ context.Context, workspaceID string, request CheckpointSealHandbackRequest) (CheckpointSealOwnership, error) { + now := time.Now().UTC().Format(time.RFC3339Nano) + proof := CheckpointSealOwnership{SealID: request.SealID, WorkspaceID: workspaceID, Root: request.Root, + SessionID: request.SessionID, Generation: request.Generation, Digest: request.ExpectedDigest, + WorkspaceRevision: "rev_1", EventCursor: cursor, ConsumedAt: request.ConsumedAt, PreparedAt: now, Status: "prepared"} + if request.Phase == CheckpointHandbackPhaseCommit { + proof.Status = "released" + proof.ReleasedAt = now + } + return proof, nil + } + proof, _, err := syncer.HandbackCheckpoint(context.Background(), receipt, "cutover-upstream", "handback-upstream") + if err != nil { + t.Fatalf("upstream prepare/commit: %v", err) + } + if client.checkpointHandbackCalls != 2 || proof.EventCursor != cursor { + t.Fatalf("proof=%+v calls=%d", proof, client.checkpointHandbackCalls) + } + proof.Status = "source-resumed" + proof.SourceResumedAt = time.Now().UTC().Format(time.RFC3339Nano) + verification, err := syncer.VerifyCheckpointOwnership(context.Background(), proof) + if err != nil || verification.Observed.EventCursor != cursor { + t.Fatalf("upstream resume: %+v, %v", verification, err) + } +} + func TestCheckpointEventCursorPattern(t *testing.T) { valid := []string{ "0", "evt_42", "upstream:v1:linear:issue_123", "upstream:v1:linear:issue_123:2", - "upstream:v1:" + strings.Repeat("a", 400), + "upstream:v1:" + strings.Repeat("a", 501), } invalid := []string{ "", @@ -10923,7 +10959,7 @@ func TestCheckpointEventCursorPattern(t *testing.T) { " upstream:v1:linear:issue_123 ", "upstream:v1:linear/issue_123", "upstream:v1:linear\nissue_123", - "upstream:v1:" + strings.Repeat("a", 401), + "upstream:v1:" + strings.Repeat("a", 502), } for _, cursor := range valid { if !checkpointEventCursorPattern.MatchString(cursor) { diff --git a/internal/relayfile/checkpoint_seal.go b/internal/relayfile/checkpoint_seal.go index cfd81138..91a0863c 100644 --- a/internal/relayfile/checkpoint_seal.go +++ b/internal/relayfile/checkpoint_seal.go @@ -37,7 +37,7 @@ var ( ErrCheckpointAdminConflict = errors.New("checkpoint administrative reconciliation identity conflicts with durable state") checkpointSessionPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$`) checkpointRevisionPattern = regexp.MustCompile(`^(?:0|rev_[0-9]+)$`) - checkpointEventCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$`) + checkpointEventCursorPattern = regexp.MustCompile(`^(?:0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,501})$`) ) const ( diff --git a/internal/relayfile/checkpoint_seal_test.go b/internal/relayfile/checkpoint_seal_test.go index 0b37cc31..3e6ac4ef 100644 --- a/internal/relayfile/checkpoint_seal_test.go +++ b/internal/relayfile/checkpoint_seal_test.go @@ -13,8 +13,8 @@ import ( const checkpointTestConsumerPrincipal = "cloud-dashboard-observer" func TestCheckpointEventCursorPattern(t *testing.T) { - valid := []string{"0", "evt_42", "upstream:v1:github:pull_123", "upstream:v1:github:pull_123:2", "upstream:v1:" + strings.Repeat("a", 400)} - invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:github pull_123", " upstream:v1:github:pull_123 ", "upstream:v1:github\tpull_123", "upstream:v1:" + strings.Repeat("a", 401)} + valid := []string{"0", "evt_42", "upstream:v1:github:pull_123", "upstream:v1:github:pull_123:2", "upstream:v1:" + strings.Repeat("a", 501)} + invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:github pull_123", " upstream:v1:github:pull_123 ", "upstream:v1:github\tpull_123", "upstream:v1:" + strings.Repeat("a", 502)} for _, cursor := range valid { if !checkpointEventCursorPattern.MatchString(cursor) { t.Errorf("valid cursor rejected: %q", cursor) diff --git a/openapi/relayfile-v1.openapi.yaml b/openapi/relayfile-v1.openapi.yaml index c47fa144..f83843e6 100644 --- a/openapi/relayfile-v1.openapi.yaml +++ b/openapi/relayfile-v1.openapi.yaml @@ -4121,7 +4121,7 @@ components: pattern: '^(0|rev_[0-9]+)$' eventCursor: type: string - pattern: '^(0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$' + pattern: '^(0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,501})$' issuedAt: type: string format: date-time @@ -4219,7 +4219,7 @@ components: pattern: '^(0|rev_[0-9]+)$' eventCursor: type: string - pattern: '^(0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$' + pattern: '^(0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,501})$' consumedAt: type: string format: date-time @@ -4262,7 +4262,7 @@ components: pattern: '^(0|rev_[0-9]+)$' eventCursor: type: string - pattern: '^(0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,400})$' + pattern: '^(0|evt_[0-9]+|upstream:v1:[A-Za-z0-9._:\-]{1,501})$' issuedAt: type: string format: date-time From d4acf8b62e73e3a02fada101774d9500e9575ab9 Mon Sep 17 00:00:00 2001 From: Miya Date: Thu, 8 Oct 2026 03:37:49 +0200 Subject: [PATCH 6/7] fix(checkpoint): reject backslashes in upstream cursor tokens --- cmd/relayfile-cli/checkpoint_lifecycle_test.go | 2 +- internal/mountsync/syncer_test.go | 2 +- internal/relayfile/checkpoint_seal_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/relayfile-cli/checkpoint_lifecycle_test.go b/cmd/relayfile-cli/checkpoint_lifecycle_test.go index 37ccd3c2..540da9ac 100644 --- a/cmd/relayfile-cli/checkpoint_lifecycle_test.go +++ b/cmd/relayfile-cli/checkpoint_lifecycle_test.go @@ -26,7 +26,7 @@ func (l *fakeCheckpointLease) Release() error { l.released = true; return nil } func TestCheckpointCursorPattern(t *testing.T) { valid := []string{"0", "evt_42", "upstream:v1:notion:page_123", "upstream:v1:notion:page_123:2", "upstream:v1:" + strings.Repeat("a", 501)} - invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:notion page_123", " upstream:v1:notion:page_123 ", "upstream:v1:notion\npage_123", "upstream:v1:" + strings.Repeat("a", 502)} + invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:notion page_123", " upstream:v1:notion:page_123 ", "upstream:v1:notion\npage_123", "upstream:v1:slack:bad\\id", "upstream:v1:slack:bad\x7fid", "upstream:v1:" + strings.Repeat("a", 502)} for _, cursor := range valid { if !checkpointCursorPattern.MatchString(cursor) { t.Errorf("valid cursor rejected: %q", cursor) diff --git a/internal/mountsync/syncer_test.go b/internal/mountsync/syncer_test.go index 9cd50144..ccba1019 100644 --- a/internal/mountsync/syncer_test.go +++ b/internal/mountsync/syncer_test.go @@ -10959,7 +10959,7 @@ func TestCheckpointEventCursorPattern(t *testing.T) { " upstream:v1:linear:issue_123 ", "upstream:v1:linear/issue_123", "upstream:v1:linear\nissue_123", - "upstream:v1:" + strings.Repeat("a", 502), + "upstream:v1:slack:bad\\id", "upstream:v1:slack:bad\x7fid", "upstream:v1:" + strings.Repeat("a", 502), } for _, cursor := range valid { if !checkpointEventCursorPattern.MatchString(cursor) { diff --git a/internal/relayfile/checkpoint_seal_test.go b/internal/relayfile/checkpoint_seal_test.go index 3e6ac4ef..7ca25175 100644 --- a/internal/relayfile/checkpoint_seal_test.go +++ b/internal/relayfile/checkpoint_seal_test.go @@ -14,7 +14,7 @@ const checkpointTestConsumerPrincipal = "cloud-dashboard-observer" func TestCheckpointEventCursorPattern(t *testing.T) { valid := []string{"0", "evt_42", "upstream:v1:github:pull_123", "upstream:v1:github:pull_123:2", "upstream:v1:" + strings.Repeat("a", 501)} - invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:github pull_123", " upstream:v1:github:pull_123 ", "upstream:v1:github\tpull_123", "upstream:v1:" + strings.Repeat("a", 502)} + invalid := []string{"", " ", "evt_", "upstream:v1:", "upstream:v1:github pull_123", " upstream:v1:github:pull_123 ", "upstream:v1:github\tpull_123", "upstream:v1:slack:bad\\id", "upstream:v1:slack:bad\x7fid", "upstream:v1:" + strings.Repeat("a", 502)} for _, cursor := range valid { if !checkpointEventCursorPattern.MatchString(cursor) { t.Errorf("valid cursor rejected: %q", cursor) From 1d4a02b2c0c9baf64fce39f6c9b3e742fd8fd53b Mon Sep 17 00:00:00 2001 From: Miya Date: Thu, 8 Oct 2026 03:40:10 +0200 Subject: [PATCH 7/7] test(checkpoint): keep handback preparation timestamp stable --- internal/mountsync/syncer_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/mountsync/syncer_test.go b/internal/mountsync/syncer_test.go index ccba1019..d6895c76 100644 --- a/internal/mountsync/syncer_test.go +++ b/internal/mountsync/syncer_test.go @@ -10915,8 +10915,9 @@ func TestUpstreamCheckpointHandbackAndResume(t *testing.T) { if err := syncer.saveStateWithoutLocalScan(); err != nil { t.Fatal(err) } + preparedAt := time.Now().UTC().Format(time.RFC3339Nano) client.checkpointHandbackFunc = func(_ context.Context, workspaceID string, request CheckpointSealHandbackRequest) (CheckpointSealOwnership, error) { - now := time.Now().UTC().Format(time.RFC3339Nano) + now := preparedAt proof := CheckpointSealOwnership{SealID: request.SealID, WorkspaceID: workspaceID, Root: request.Root, SessionID: request.SessionID, Generation: request.Generation, Digest: request.ExpectedDigest, WorkspaceRevision: "rev_1", EventCursor: cursor, ConsumedAt: request.ConsumedAt, PreparedAt: now, Status: "prepared"}