feat(history): two-phase commit journaling and idempotency fingerprinting #43

Merged
yuris merged 1 commit from feat/issue-5-journal into main 2026-09-22 19:19:04 +00:00
Owner

Motivation

Issue #5: interacting with remote CalDAV servers has no distributed-transaction guarantee, so mutations must be journaled locally before dispatch and their outcomes committed atomically afterwards. This implements the two-phase commit boundary and idempotency fingerprinting specified in docs/issues/005-two-phase-journal-idempotency.md.

Changes

  • src/history/fingerprint.rs (new): canonical SHA-256 request fingerprint over HTTP method, normalized route, recursively key-sorted JSON body, baseline ETags and approval digest; transport IDs and tokens are excluded. Format: sha256:<64-char-hex>.
  • src/history/journal.rs (new): JournalService with two-phase boundary:
    • prepare_operation (Phase 1): inserts operations row with state = 'prepared'; same key + identical fingerprint returns PrepareResult::Replay (recorded outcome, no new dispatch); divergent fingerprint returns JournalError::IdempotencyConflict; any journal failure surfaces as JournalUnavailable so no remote write can proceed.
    • commit_operation_result (Phase 3): single SQLite transaction updates operations.state (applied/failed/unknown) + result_json and appends all life_events atomically.
    • get_operation for recorded-outcome retrieval.
  • src/domain/mod.rs: OperationState now matches the baseline schema (prepared, applied, failed, unknown, needs_reconciliation) with FromStr/Display/as_str.
  • tests/history_journal_tests.rs (new): scenarios R01 (identical replay returns recorded outcome, single recorded event), R02 (same key, different fingerprint -> idempotency_conflict, journal untouched), R04 (journal unavailable blocks dispatch), plus result-commit atomicity and NotFound behaviour.

Verification

  • nix develop --command cargo test — all suites pass (58 tests), including the new history_journal_tests (R01, R02, R04, atomicity, NotFound).
  • nix develop --command cargo clippy --all-targets -- -D warnings — clean.
  • nix develop --command cargo fmt --check — clean.

Closes #5

## Motivation Issue #5: interacting with remote CalDAV servers has no distributed-transaction guarantee, so mutations must be journaled locally before dispatch and their outcomes committed atomically afterwards. This implements the two-phase commit boundary and idempotency fingerprinting specified in `docs/issues/005-two-phase-journal-idempotency.md`. ## Changes - `src/history/fingerprint.rs` (new): canonical SHA-256 request fingerprint over HTTP method, normalized route, recursively key-sorted JSON body, baseline ETags and approval digest; transport IDs and tokens are excluded. Format: `sha256:<64-char-hex>`. - `src/history/journal.rs` (new): `JournalService` with two-phase boundary: - `prepare_operation` (Phase 1): inserts `operations` row with `state = 'prepared'`; same key + identical fingerprint returns `PrepareResult::Replay` (recorded outcome, no new dispatch); divergent fingerprint returns `JournalError::IdempotencyConflict`; any journal failure surfaces as `JournalUnavailable` so no remote write can proceed. - `commit_operation_result` (Phase 3): single SQLite transaction updates `operations.state` (applied/failed/unknown) + `result_json` and appends all `life_events` atomically. - `get_operation` for recorded-outcome retrieval. - `src/domain/mod.rs`: `OperationState` now matches the baseline schema (`prepared`, `applied`, `failed`, `unknown`, `needs_reconciliation`) with `FromStr`/`Display`/`as_str`. - `tests/history_journal_tests.rs` (new): scenarios R01 (identical replay returns recorded outcome, single recorded event), R02 (same key, different fingerprint -> `idempotency_conflict`, journal untouched), R04 (journal unavailable blocks dispatch), plus result-commit atomicity and NotFound behaviour. ## Verification - `nix develop --command cargo test` — all suites pass (58 tests), including the new `history_journal_tests` (R01, R02, R04, atomicity, NotFound). - `nix develop --command cargo clippy --all-targets -- -D warnings` — clean. - `nix develop --command cargo fmt --check` — clean. ## Related Issues Closes #5
yuris merged commit 62b365cb1c into main 2026-09-22 19:19:04 +00:00
yuris changed title from feat(history): two-phase commit journaling and idempotency fingerprinting (#5) to feat(history): two-phase commit journaling and idempotency fingerprinting 2026-09-23 05:12:08 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
yuris/life-service!43
No description provided.