Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .trajectories/completed/2026-10/traj_0qdrmrp6ijnu.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
31 changes: 31 additions & 0 deletions .trajectories/completed/2026-10/traj_0qdrmrp6ijnu.md
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions .trajectories/completed/2026-10/traj_36h89bsoig5x.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
23 changes: 23 additions & 0 deletions .trajectories/completed/2026-10/traj_36h89bsoig5x.md
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions .trajectories/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,20 @@
"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": ".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": ".trajectories/completed/2026-10/traj_36h89bsoig5x.json"
}
}
}
2 changes: 1 addition & 1 deletion cmd/relayfile-cli/checkpoint_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,501})$`)
)

type checkpointCLIError struct {
Expand Down
15 changes: 15 additions & 0 deletions cmd/relayfile-cli/checkpoint_lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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", 501)}
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)
}
}
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
Expand Down
7 changes: 7 additions & 0 deletions internal/mountsync/realtime_collaboration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions internal/mountsync/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,501})$`)
mountCorrelationIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$`)
)

Expand Down Expand Up @@ -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} {
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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} {
Expand Down
89 changes: 89 additions & 0 deletions internal/mountsync/syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -10868,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{}
Expand All @@ -10885,6 +10905,75 @@ 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)
}
preparedAt := time.Now().UTC().Format(time.RFC3339Nano)
client.checkpointHandbackFunc = func(_ context.Context, workspaceID string, request CheckpointSealHandbackRequest) (CheckpointSealOwnership, error) {
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"}
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", 501),
}
invalid := []string{
"",
" ",
"evt_",
"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: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)
}
}
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()
Expand Down
Loading
Loading