fix(sync): flush card PATCHes before the connection and dependency writes - #110
Conversation
…ites Every AgilePlace write bumps a card's resource version by exactly 1 -- connection POSTs, dependency POSTs and comment writes included, not just card PATCHes. The run flushed its queued PATCHes last, after writing each card's edges, so it staled the very version those PATCHes carry: a card that both gained an edge and carried queued ops ate a guaranteed 409/428 plus a refetch plus a retry, reported under a "version bumped by an unrelated change" note that was not true -- the run bumped it itself. Flush first, then reconcile edges. Comment sync bumps the version too, so it stays behind the flush, where it already sat for its own state-gate reason. Prevention over recovery, the same reasoning as intake._card_for_link_write; the issue #105 conflict retry remains the safety net for genuine concurrent human edits, which no ordering prevents. The op queue moves into agilesync/syncers/card_ops.py: sync.py was already at the 800-line hard cap, and the queue, its lane-conflict poisoning and its one flush are a single cohesive unit that now carries the ordering contract in its own docstring. Closes #107. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughCard mutations now use ChangesCard synchronization ordering
Sequence Diagram(s)sequenceDiagram
participant sync.main
participant CardOpQueue
participant AgilePlace
participant EdgeSync
participant CommentSync
sync.main->>CardOpQueue: queue card mutations
sync.main->>CardOpQueue: flush queued patches
CardOpQueue->>AgilePlace: PATCH cards
sync.main->>EdgeSync: sync connections and dependencies
sync.main->>CommentSync: sync comments when clean
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
|
This was written agentically; verify its assertions and edit accordingly: Adversarial cross-harness review — Codex
|
|
@coderabbitai full review |
✅ Action performedFull review finished. |
This was written agentically; verify its assertions and edit accordingly:
Why
Every AgilePlace write bumps a card's resource version by exactly 1 — connection POSTs, dependency POSTs and comment writes included, not just card PATCHes (confirmed by arithmetic over the 2026-07-30 live smoke run; see the issue and the amended
docs/API-VALIDATION.mdsection).sync.main()flushed its queued PATCHes last, after writing each card's edges — so the run staled the very version those PATCHes carry. A card that both gained an edge and carried queued ops ate a guaranteed 409/428, a refetch and a retry, every run, reported under aversion bumped by an unrelated changenote that was not true: the run bumped it itself. The 2026-07-29 live run hit exactly this on four cards at once (an epic plus its three children).What
The flush now runs before the child-connection and dependency steps, so no card is written between its own snapshot and its own flush. Nothing in those steps reads what the flush writes, and both of their inputs (
poisoned,managed_card_ids) were already computable the moment step 2 finished. Comment sync bumps the version too, so it stays after the flush — where it already sat, for its own state-gate reason.Issue #105's conflict retry stays exactly as it is: it remains the safety net for genuine concurrent edits by humans, which no ordering can prevent. This removes the conflicts the run inflicted on itself.
The op queue moves to
agilesync/syncers/card_ops.py—sync.pywas already at the repo's 800-line hard cap, and the queue, its lane-conflict poisoning and its single flush are one cohesive unit, which now carries the ordering contract in its own docstring. Behavior there is unchanged (same WARN text, same wholesale poisoned-card skip, same clean gate).Not in this PR
version, onePOST /card/dependency, readversion). The measurement in hand is a total over 14 writes, not per-call; it holds unless one write bumps twice while another does not bump at all. That probe belongs insmoke.py, which is blocked on the smoke.py is at 776/800 lines — extract before the next live probe (blocks the #105 conflict-retry probe) #108 extraction — the doc records it as inferred-from-the-total rather than individually measured, and the caveat is written down there.Testing
tests/test_sync_flush_order.py: no card is written between its snapshot and its flush PATCH, driven through the realmain(). Watched it fail first (writes landedconnect, dependency, patch, patch)tests/test_run.py's end-to-end HTTP-boundary sequence updated tocreate, gh, patch, connect, depend— the oracle change this requirement drivescleangate unchanged (test_sync_comments_call_site.py,test_sync_lane_conflict.py,test_sync_contested_cards.pygreen; the poisoning seam's monkeypatch target moved with the extraction)--applyrun shows noversion bumpednote where none is warranted — needs the maintainer: this checkout has no.env, so I could not run it🤖 Co-authored by Claude Opus 5. Closes #107.
Summary by CodeRabbit
Improvements
Documentation
Tests