A community based topic aggregation platform built on atproto

docs: add PutRecord implementation backlog items

Add two P3 technical debt items for future optimistic locking:

1. Implement PutRecord in PDS Client
- Add swapRecord/swapCommit for optimistic locking
- Handle conflict responses gracefully

2. Migrate UpdateComment to Use PutRecord
- Blocked by PutRecord implementation
- Will prevent concurrent update races

These items are not urgent since concurrent comment updates are rare,
but will improve robustness when implemented.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+124
docs
+124
docs/PRD_BACKLOG.md
···
## 🔵 P3: Technical Debt
+
### Implement PutRecord in PDS Client
+
**Added:** 2025-12-04 | **Effort:** 2-3 hours | **Priority:** Technical Debt
+
**Status:** 📋 TODO
+
+
**Problem:**
+
The PDS client (`internal/atproto/pds/client.go`) only has `CreateRecord` but lacks `PutRecord`. This means updates use `CreateRecord` with an existing rkey, which:
+
1. Loses optimistic locking (no CID swap check)
+
2. Is semantically incorrect (creates vs updates)
+
3. Could cause race conditions on concurrent updates
+
+
**atProto Best Practice:**
+
- `com.atproto.repo.putRecord` should be used for updates
+
- Accepts `swapRecord` (expected CID) for optimistic locking
+
- Returns conflict error if CID doesn't match (concurrent modification detected)
+
+
**Solution:**
+
Add `PutRecord` method to the PDS client interface:
+
+
```go
+
// Client interface addition
+
type Client interface {
+
// ... existing methods ...
+
+
// PutRecord creates or updates a record with optional optimistic locking.
+
// If swapRecord is provided, the operation fails if the current CID doesn't match.
+
PutRecord(ctx context.Context, collection string, rkey string, record any, swapRecord string) (uri string, cid string, err error)
+
}
+
+
// Implementation
+
func (c *client) PutRecord(ctx context.Context, collection string, rkey string, record any, swapRecord string) (string, string, error) {
+
payload := map[string]any{
+
"repo": c.did,
+
"collection": collection,
+
"rkey": rkey,
+
"record": record,
+
}
+
+
// Optional: optimistic locking via CID swap check
+
if swapRecord != "" {
+
payload["swapRecord"] = swapRecord
+
}
+
+
var result struct {
+
URI string `json:"uri"`
+
CID string `json:"cid"`
+
}
+
+
err := c.apiClient.Post(ctx, syntax.NSID("com.atproto.repo.putRecord"), payload, &result)
+
if err != nil {
+
return "", "", wrapAPIError(err, "putRecord")
+
}
+
+
return result.URI, result.CID, nil
+
}
+
```
+
+
**Error Handling:**
+
Add new error type for conflict detection:
+
```go
+
var ErrConflict = errors.New("record was modified by another operation")
+
```
+
+
Map HTTP 409 in `wrapAPIError`:
+
```go
+
case 409:
+
return fmt.Errorf("%s: %w: %s", operation, ErrConflict, apiErr.Message)
+
```
+
+
**Files to Modify:**
+
- `internal/atproto/pds/client.go` - Add `PutRecord` method and interface
+
- `internal/atproto/pds/errors.go` - Add `ErrConflict` error type
+
+
**Testing:**
+
- Unit test: Verify payload includes `swapRecord` when provided
+
- Integration test: Concurrent updates detect conflict
+
- Integration test: Update without `swapRecord` still works (backwards compatible)
+
+
**Blocked By:** Nothing
+
**Blocks:** "Migrate UpdateComment to use PutRecord"
+
+
---
+
+
### Migrate UpdateComment to Use PutRecord
+
**Added:** 2025-12-04 | **Effort:** 1 hour | **Priority:** Technical Debt
+
**Status:** 📋 TODO (Blocked)
+
**Blocked By:** "Implement PutRecord in PDS Client"
+
+
**Problem:**
+
`UpdateComment` in `internal/core/comments/comment_service.go` uses `CreateRecord` for updates instead of `PutRecord`. This lacks optimistic locking and is semantically incorrect.
+
+
**Current Code (lines 687-690):**
+
```go
+
// TODO: Use PutRecord instead of CreateRecord for proper update semantics with optimistic locking.
+
// PutRecord should accept the existing CID (existingRecord.CID) to ensure concurrent updates are detected.
+
// However, PutRecord is not yet implemented in internal/atproto/pds/client.go.
+
uri, cid, err := pdsClient.CreateRecord(ctx, commentCollection, rkey, updatedRecord)
+
```
+
+
**Solution:**
+
Once `PutRecord` is implemented in the PDS client, update to:
+
```go
+
// Use PutRecord with optimistic locking via existing CID
+
uri, cid, err := pdsClient.PutRecord(ctx, commentCollection, rkey, updatedRecord, existingRecord.CID)
+
if err != nil {
+
if errors.Is(err, pds.ErrConflict) {
+
// Record was modified by another operation - return appropriate error
+
return nil, fmt.Errorf("comment was modified, please refresh and try again: %w", err)
+
}
+
// ... existing error handling
+
}
+
```
+
+
**Files to Modify:**
+
- `internal/core/comments/comment_service.go` - UpdateComment method
+
- `internal/core/comments/errors.go` - Add `ErrConcurrentModification` if needed
+
+
**Testing:**
+
- Unit test: Verify `PutRecord` is called with correct CID
+
- Integration test: Simulate concurrent update, verify conflict handling
+
+
**Impact:** Proper optimistic locking prevents lost updates from race conditions
+
+
---
+
### Consolidate Environment Variable Validation
**Added:** 2025-10-11 | **Effort:** 2-3 hours