[Feature] Establish platform optimistic concurrency and conflict-reconciliation contract #277

Closed
opened 2026-07-30 05:26:17 +02:00 by zemion · 1 comment
Owner

Objective

Prevent silent lost updates across GovOPlaN and provide a consistent path from strict optimistic concurrency to safe reconciliation. First consumer: GovOPlaN/govoplan-campaign#23.

Recommended contract

Phase 1: strong revisions

  • Every mutable aggregate exposes an opaque monotonic revision and a strong resource ETag.
  • Mutations carry If-Match (or an equivalent typed expected_revision for non-HTTP capability calls).
  • A stale HTTP precondition returns 412 Precondition Failed; a domain compare-and-set conflict returns 409 Conflict.
  • The response contains only bounded metadata: current revision, submitted base revision, conflicting resource reference, retryability, and a refresh link. It does not echo secrets or the entire current object by default.
  • The update and its revision increment occur in one database transaction. Worker/capability mutations use the same compare-and-set primitive.

Phase 2: reconciliation

Use a three-way comparison of base, local, and current state, expressed as stable JSON/object paths:

  • Auto-merge disjoint scalar/map paths.
  • Auto-merge collection edits only when elements have stable IDs and operations are disjoint.
  • Treat same-path edits, reorder conflicts, delete-versus-edit, and unkeyed arrays as conflicts.
  • Never auto-merge workflow transitions, send/delivery controls, ownership/security changes, locks, frozen evidence, signatures, or cryptographic material.
  • Return a structured conflict set and optional safe merge candidate. The UI lets the actor inspect current/local values, accept safe merges, choose per conflict, or discard/reload.

Core ownership

  • Revision/ETag helpers and compare-and-set transaction primitives.
  • Typed conflict DTOs and path/operation vocabulary.
  • Conservative three-way merge library with explicit protected-path policy hooks.
  • Shared WebUI conflict dialog/panel.

Resource modules define aggregate boundaries, protected paths, stable collection identities, and validation after merge.

Campaign first slice

  • Add a monotonic edit revision to each mutable CampaignVersion.
  • Send the loaded revision with autosave/manual save and reject stale writes.
  • Preserve the loaded base draft client-side and calculate stable change paths.
  • Auto-merge only disjoint configuration fields; keep recipient rows keyed by stable recipient ID.
  • Require explicit resolution for recipient reorder/delete conflicts and every workflow/delivery/ownership path.
  • Audit conflict detection and final resolution without logging recipient values.

Platform acceptance criteria

  • Concurrent integration test proves one of two writes cannot silently overwrite the other.
  • Disjoint-field reconciliation test preserves both edits.
  • Same-field and protected-path tests remain explicit conflicts.
  • Optional module combinations can use the contract through Core without importing another module.
  • API documentation distinguishes cache ETags (If-None-Match) from strong mutation preconditions (If-Match).
## Objective Prevent silent lost updates across GovOPlaN and provide a consistent path from strict optimistic concurrency to safe reconciliation. First consumer: `GovOPlaN/govoplan-campaign#23`. ## Recommended contract ### Phase 1: strong revisions - Every mutable aggregate exposes an opaque monotonic revision and a strong resource ETag. - Mutations carry `If-Match` (or an equivalent typed `expected_revision` for non-HTTP capability calls). - A stale HTTP precondition returns `412 Precondition Failed`; a domain compare-and-set conflict returns `409 Conflict`. - The response contains only bounded metadata: current revision, submitted base revision, conflicting resource reference, retryability, and a refresh link. It does not echo secrets or the entire current object by default. - The update and its revision increment occur in one database transaction. Worker/capability mutations use the same compare-and-set primitive. ### Phase 2: reconciliation Use a three-way comparison of base, local, and current state, expressed as stable JSON/object paths: - Auto-merge disjoint scalar/map paths. - Auto-merge collection edits only when elements have stable IDs and operations are disjoint. - Treat same-path edits, reorder conflicts, delete-versus-edit, and unkeyed arrays as conflicts. - Never auto-merge workflow transitions, send/delivery controls, ownership/security changes, locks, frozen evidence, signatures, or cryptographic material. - Return a structured conflict set and optional safe merge candidate. The UI lets the actor inspect current/local values, accept safe merges, choose per conflict, or discard/reload. ## Core ownership - Revision/ETag helpers and compare-and-set transaction primitives. - Typed conflict DTOs and path/operation vocabulary. - Conservative three-way merge library with explicit protected-path policy hooks. - Shared WebUI conflict dialog/panel. Resource modules define aggregate boundaries, protected paths, stable collection identities, and validation after merge. ## Campaign first slice - [ ] Add a monotonic edit revision to each mutable `CampaignVersion`. - [ ] Send the loaded revision with autosave/manual save and reject stale writes. - [ ] Preserve the loaded base draft client-side and calculate stable change paths. - [ ] Auto-merge only disjoint configuration fields; keep recipient rows keyed by stable recipient ID. - [ ] Require explicit resolution for recipient reorder/delete conflicts and every workflow/delivery/ownership path. - [ ] Audit conflict detection and final resolution without logging recipient values. ## Platform acceptance criteria - [ ] Concurrent integration test proves one of two writes cannot silently overwrite the other. - [ ] Disjoint-field reconciliation test preserves both edits. - [ ] Same-field and protected-path tests remain explicit conflicts. - [ ] Optional module combinations can use the contract through Core without importing another module. - [ ] API documentation distinguishes cache ETags (`If-None-Match`) from strong mutation preconditions (`If-Match`).
Author
Owner

Implemented in 9b88ae3, with Campaign adoption in cd223cb: strong resource ETags, mandatory If-Match/base revisions, bounded conflict DTOs, SQLAlchemy revision claiming, conservative three-way merge, and a shared WebUI reconciliation dialog. Unit, Campaign, integration, and module-permutation checks pass.

Implemented in `9b88ae3`, with Campaign adoption in `cd223cb`: strong resource ETags, mandatory `If-Match`/base revisions, bounded conflict DTOs, SQLAlchemy revision claiming, conservative three-way merge, and a shared WebUI reconciliation dialog. Unit, Campaign, integration, and module-permutation checks pass.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: GovOPlaN/govoplan-core#277