conflicts: decided on what the box holds now, addressed by type and id - #272
Merged
Merged
Conversation
Resolve wrote the snapshot recorded at detection over the row, so any local write after the conflict was recorded was silently reverted by all three strategies. Resolve now compares the store's live sync snapshot with the recorded local side inside its transaction, ignoring latest-wins fields, and refuses (409) when they differ; a sync round re-records the conflict and the same resolve then applies. Main's side needs no guard: the recorded remote only becomes the merge base, pinned by tests over a real paged session. A conflict was looked up by entity id alone, so a spec and its room parked together were both reported as not found. Conflicts are now addressed by (type, id): --type on the CLI, ?type= over the API, and an id parked under several types is refused naming them (400). Drops the dead, unguarded second resolve path in ConflictsCommand.
The new --type flag was exercised below the command line only: Resolve.call and Show.call opened their operations through a static factory no test could reach, so a wiring mistake between the flag and the lookup would have passed every test (Resolve.call stood at 9 of 51 lines covered). The three verbs now take their operations through a supplier, as SyncCommand.Status does, and ConflictsCommandTest drives them through picocli over a real control plane: an id parked as spec and room is refused by name until --type says which; --type settles the named conflict and leaves its twin parked; a conflict the box has since written over is refused with the remedy; --merge-file settles a spec and is refused for a room. Dropping --type from the lookup turns three of them red. The entity id and --type were declared twice; they are one picocli mixin.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Spec:
sail-conflict-resolve-current. Two defects insail conflicts resolve, both latent since conflicts shipped, both sail-only.What was wrong
--merge's template is pre-filled from one), so anything this box wrote to the entity after the conflict was recorded was silently reverted — a spec body edited after parking ontitle, or a run parked whilerunninggoing back torunningafter it completed. 0.44.3's per-round re-record narrowed the window; it did not close it.nullunless exactly one matched. A spec and its room share an id by construction, so parked together both were unresolvable, reported as "No open conflict".What changed
One staleness rule, all three strategies. Inside the
BEGIN IMMEDIATEtransactionConflictOperations.resolvealready opens, the store's livecurrentForSyncsnapshot is compared with the conflict's recorded local side, ignoring the store'slatestWinsFields()and_metadata (new pureConflictDetector.drift). Any drift refuses — nothing written, conflict stays pending:409over both the web and local-socket APIs, same message. No--force.Conflicts are addressed by (type, id).
ConflictOperations.find(type, id); the exactly-one-or-null lookup is deleted.sail conflicts show|resolve <id> [--type <type>];GET /v1/conflicts/{id}andPOST /v1/conflicts/{id}/resolvetake?type=. Without a type, one match resolves as before; several are refused naming them —'auth' has open conflicts as spec and room: pass --type— as a400(ErrorCode.BAD_REQUEST; a bareIllegalArgumentExceptionmaps to 422 on the web router). The CLI resolves with the type of the conflict it displayed.Main's side gets no guard — it gets tests. The recorded remote only becomes the merge base; the next round three-ways against main's current row.
Deleted the dead second resolve path in
ConflictsCommand(findUnique,resolverFor,choose,apply): test-only callers, andapplywas an unguarded copy of defect 1.Tests
ConflictOperationsTest(new), conflicts parked over a real paged session (SyncBox.connect+SyncRpcServer):--mine/--theirs/--mergewith the exact message and 409, leaving the row, its rev and the conflict row untouched;resolve --mine+ a round merges both; same field → parks again with fresh base/local/remote;--theirs+ a round lands main's newer value. Main's value is never lost;completed.ApiRouterTest: the same spec+room pair through realConflictOperations—400without?type=(GET and POST),?type=selects,409on a stale resolve,404for a type with no conflict.LocalApiRouterTest:?type=is carried and 409/400 survive the local lane.ConflictDetectorTest:driftunit cases.requireCurrentcall removed, 5 of the new tests fail.NativeFleetITis untouched (it resolves immediately after a sync).Left alone, on purpose
--merge's$EDITORis open (or betweenshowandresolve --theirs) still lets a decision land on snapshots newer than the ones displayed. Closing that needs a "what was shown" token (the conflict row id) threaded from show to resolve — a design change beyond this spec's rule; worth its own spec.--mineon a run rewrites the recorded heartbeat, which may be a few seconds older than the live one; the next stamp moves it forward again.