Skip to content

feat(sync): rfc-003 phase a.2.2 — apply pipeline stamps hlc + origin_device_id - #55

Merged
InstaZDLL merged 2 commits into
mainfrom
feat/sync-v2-phase-a-2-2-apply-pipeline-hlc
Jun 14, 2026
Merged

feat(sync): rfc-003 phase a.2.2 — apply pipeline stamps hlc + origin_device_id#55
InstaZDLL merged 2 commits into
mainfrom
feat/sync-v2-phase-a-2-2-apply-pipeline-hlc

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Jun 14, 2026

Copy link
Copy Markdown
Owner

What

Phase A.2.2 of the RFC-003 sync v2 rollout. Every entity write in the apply pipeline now stamps the §2 total-order triple (hlc_wall, hlc_logical, origin_device_id) the A.1.2 schema reserved.

  • v1 ops derive (0, lamport_ts) exactly the way the A.1.1 sync_op backfill did — any v2 op with wall > 0 strictly outranks every v1-derived row under §2.
  • v2 ops echo the wire hlc pair verbatim.
  • origin_device_id rides through the existing sync_op.device_id TEXT column (per A.1.1's design), parsed as Uuid. Non-UUID strings (legacy v1 desktops) stamp NULL on the row — payload_hash::HlcTriple already has None < Some(any) so legacy rows lose to v2 ops on the §2 tiebreak.

Follows #50 / #51 / #52 / #53 / #54.

Code change

  • New OpStamp { hlc, origin_device_id } type in src/apply.rs, computed once per op in apply_op and threaded by value (Copy) to every handler.
  • New helpers apply::effective_hlc(op) + apply::parse_origin_device_id(device_id). The v1 narrowing in effective_hlc clamps to i32::MAX as defence-in-depth on the push handler's existing range guard.
  • apply_op signature gains device_id: &str; api/sync.rs::push_ops updated to thread it.
  • INSERT / SET handlers updated (all bind the three columns):
    • profile_resolve::find_or_provision
    • library::insert / library::set_field (all four scalar fields)
    • playlist::insert / playlist::set_field (all four scalar fields)
    • track::insert via TrackInput (3 new fields)
    • liked::insert
    • rating::set UPSERT — also refreshes HLC on conflict so the row's tuple reflects the latest op, not the first one that landed

What this does NOT do

  • payload_hash bind + metadata_digest_version bump — deferred to A.2.3 alongside the digest endpoint that actually consumes them. The payload_hash module (feat(sync): rfc-003 phase a.2.2.1 — payload_hash + hlc total-order helpers #54) stays unused until then; wiring them now without a consumer would be dead weight.
  • LWW SQL gate (WHERE existing.hlc < incoming.hlc) — Phase A says "no behaviour change". Phase C activates true LWW alongside OR-Set / Fractional Index.
  • Sub-entity / auto-materialised stampsplaylist_track is OR-Set territory (Phase C); album / artist / track_artist are auto-materialised, HLC piggybacks on the source track row.
  • Delete handlers — no row to stamp anymore.

Test plan

  • cargo check --all-targets --all-features — clean
  • cargo clippy --all-targets --all-features -- -D warnings — clean
  • cargo test --all-features --test apply — 35 pass (30 existing + 5 new):
    • library_insert_v1_stamps_derived_hlc_and_uuid_origin — v1 path stamps (0, lamport_ts, uuid)
    • library_insert_v2_stamps_verbatim_hlc — v2 path stamps (wall, logical, uuid) verbatim
    • library_insert_non_uuid_device_id_stamps_null_origin — legacy v1 with free-form device_id stamps NULL origin
    • liked_insert_stamps_hlc_on_user_scoped_row — user-scoped entity gets stamped too
    • rating_upsert_refreshes_hlc_on_overwrite — UPSERT path refreshes the tuple, not just preserves the first one
  • Full suite: 235 tests pass. One albums::list_album_tracks_wrong_library_id_returns_404 flapped under parallel load (passes in isolation, unrelated to this change).

Refs

Summary by CodeRabbit

Notes de publication

  • Améliorations

    • Synchronisation enrichie : enregistrement d’horodatages HLC plus précis et du suivi de l’appareil à l’origine des modifications.
    • Les créations auto-provisionnées et les entités mises à jour (listes, bibliothèque, pistes, likes, notes) conservent et propagent désormais ces informations.
    • Les “likes” se mettent à jour en cas de conflit (rafraîchissement de l’état et des horodatages), au lieu d’être ignorés.
  • Tests

    • Ajout de tests end-to-end couvrant le stamping HLC et l’origine selon le type de device_id.

…device_id

Phase A.2.2 — every entity write in the apply pipeline now stamps
the §2 total-order triple (hlc_wall, hlc_logical, origin_device_id)
the A.1.2 schema reserved. v1 ops derive `(0, lamport_ts)` exactly
the way the A.1.1 sync_op backfill did; v2 ops echo the wire pair
verbatim. The §2 tiebreaker rides through the existing
sync_op.device_id TEXT (per A.1.1) parsed as Uuid — non-UUID strings
(legacy v1 desktops) stamp NULL on the row.

Scope:
- New OpStamp type bundling effective Hlc + Option<Uuid> origin,
  computed once per op in apply_op and threaded to every handler.
- New apply::effective_hlc + apply::parse_origin_device_id helpers
  with safe v1 narrowing (clamp to i32::MAX, defence-in-depth on
  the push handler's existing range guard).
- apply_op signature gains device_id: &str; push handler updated.
- profile_resolve::find_or_provision + library::insert/set_field +
  playlist::insert/set_field + track::insert (via TrackInput) +
  liked::insert + rating::set (UPSERT path) — all bind the three
  columns. INSERT, SET, and UPSERT-overwrite paths all stamp; the
  rating UPSERT also refreshes hlc on conflict so the row's tuple
  reflects the latest op, not the first one that landed.

What this does NOT do:
- payload_hash bind + metadata_digest_version bump — deferred to
  A.2.3 alongside the digest endpoint that consumes them. The
  payload_hash module (#54) stays unused until then.
- LWW SQL gate (WHERE existing.hlc < incoming.hlc) — Phase A says
  "no behaviour change", LWW activation lands in Phase C alongside
  OR-Set / Fractional Index.
- playlist_track / album / artist / track_artist sub-entity stamps —
  playlist_track is OR-Set territory (Phase C); album/artist are
  auto-materialised, HLC piggybacks on the source track row.
- Delete handlers — no row left to stamp.

Tests: 5 new cases in tests/apply.rs:
- library_insert_v1_stamps_derived_hlc_and_uuid_origin (v1 path)
- library_insert_v2_stamps_verbatim_hlc (v2 path)
- library_insert_non_uuid_device_id_stamps_null_origin (legacy)
- liked_insert_stamps_hlc_on_user_scoped_row (user-scoped)
- rating_upsert_refreshes_hlc_on_overwrite (UPSERT path)

Full suite: 235 tests pass (30 apply + 5 new + 200 others; one
albums::list_album_tracks_wrong_library_id_returns_404 flapped under
parallel load but passes in isolation — unrelated to this change).

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c1d5f89e-08f6-4a39-9dce-7ee7fd7fb208

📥 Commits

Reviewing files that changed from the base of the PR and between 5db9d6b and 1fe6542.

📒 Files selected for processing (4)
  • src/api/sync.rs
  • src/apply.rs
  • src/payload_hash.rs
  • tests/apply.rs

📝 Walkthrough

Walkthrough

Implémentation de la Phase A.2.2 RFC-003 : un OpStamp (HLC wall/logical + origin_device_id) est calculé depuis device_id et la version de l'op dans apply_op, puis propagé à profile_resolve et aux cinq handlers d'entités (playlist, library, liked_track, track_rating, track) qui écrivent ces colonnes en base via INSERT et ON CONFLICT DO UPDATE.

Changes

Pipeline de stamp HLC — RFC-003 Phase A.2.2

Layer / File(s) Summary
Contrat OpStamp et helpers HLC
src/apply.rs
Ajout de la struct OpStamp (hlc, origin_device_id), et des fonctions effective_hlc (v1 : dérivation depuis lamport_ts, v2 : valeur explicite) et parse_origin_device_id (UUID ou None).
Câblage de apply_op et routing
src/apply.rs, src/api/sync.rs
apply_op reçoit device_id, construit un OpStamp, et le transmet à profile_resolve::find_or_provision et aux cinq handlers. push_ops passe device_id à l'appel.
profile_resolve — INSERT avec stamp
src/apply.rs
find_or_provision étend son INSERT sur profile pour écrire hlc_wall, hlc_logical et origin_device_id depuis le stamp.
Handlers playlist et library — INSERT et SET
src/apply.rs
Les fonctions insert et set_field de playlist et library reçoivent stamp et étendent leur SQL (INSERT + UPDATE, y compris la branche nullable description) pour écrire les colonnes HLC et origin_device_id.
Handlers liked_track et track_rating — INSERT/UPSERT
src/apply.rs
liked::apply étend l'INSERT dans user_liked_track avec les colonnes HLC et change de DO NOTHING à DO UPDATE pour rafraîchir aussi liked_at et le tuple HLC. rating::apply étend le ON CONFLICT DO UPDATE pour écraser hlc_wall, hlc_logical, origin_device_id lors d'un overwrite.
Handler track + TrackInput + upsert_track
src/apply.rs, src/db.rs
track::insert remplit TrackInput avec le stamp. TrackInput gagne trois nouveaux champs. upsert_track étend l'INSERT, le DO UPDATE et les bindings correspondants.
Tests end-to-end du stamping
tests/apply.rs
Cinq tests vérifient : dérivation v1, persistance verbatim v2, origin_device_id = NULL si non-UUID, stamping sur liked_track, écrasement HLC lors d'un upsert track_rating.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • InstaZDLL/waveflow-server#50 : Introduit hlc_wall/hlc_logical sur sync_op lors de l'insertion, fournissant les données que ce PR consomme via effective_hlc pour construire l'OpStamp.
  • InstaZDLL/waveflow-server#51 : Ajoute les colonnes hlc_wall, hlc_logical et origin_device_id côté schéma DB, que ce PR remplit applicativement via les handlers.
  • InstaZDLL/waveflow-server#52 : Introduit le transport HLC dans SyncOpIn/Hlc, que effective_hlc lit directement pour construire l'OpStamp.

Poem

🕰️ L'horloge hybride pointe son museau,
hlc_wall, hlc_logical au tableau,
L'UUID d'origine trace son sillon,
Chaque entité reçoit son estampillon.
Le temps total-ordonné règne en maître ! ⚡

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Le titre résume précisément le changement principal : implémentation de la phase A.2.2 du RFC-003, ajout des champs HLC et origin_device_id dans le pipeline d'application.
Description check ✅ Passed La description couvre les sections essentielles : résumé détaillé avec contexte RFC, changements de code exhaustifs, plan de test complet avec validation des cas v1/v2/legacy. Respecte la structure du template.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sync-v2-phase-a-2-2-apply-pipeline-hlc

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added type: feat New feature scope: server Server core (Rust) scope: db SQLite schema, migrations, queries scope: api Native /api/v2 surface scope: sync User-data sync size: m 50-200 lines labels Jun 14, 2026
@InstaZDLL InstaZDLL self-assigned this Jun 14, 2026
@InstaZDLL

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions github-actions Bot added type: feat New feature and removed type: feat New feature labels Jun 14, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/api/sync.rs (1)

347-360: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

La CI casse encore sur cargo fmt.

Le bloc lamport_max n’est pas au format attendu par rustfmt, donc cargo fmt --all --check échoue déjà sur ce fichier. Lance cargo fmt --all avant merge.

As per coding guidelines, **/*.rs: Run cargo fmt --all --check before committing Rust code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/sync.rs` around lines 347 - 360, The error handling block in the
lamport_max function call in src/api/sync.rs does not conform to Rust formatting
standards expected by rustfmt. Run cargo fmt --all to automatically reformat the
code to meet the project's coding guidelines before merging.

Sources: Coding guidelines, Pipeline failures

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/apply.rs`:
- Around line 901-905: The INSERT statement for user_liked_track uses ON
CONFLICT (user_id, file_hash) DO NOTHING, which means that when a duplicate like
is attempted on the same user and file, the existing row retains its original
hlc_wall, hlc_logical, origin_device_id, and liked_at values instead of being
updated with the new ones. To ensure the materialized row always reflects the
most recent winning operation as required by this PR's contract, replace DO
NOTHING with DO UPDATE SET to update the hlc_wall, hlc_logical,
origin_device_id, and liked_at columns with the new values from the EXCLUDED
clause. This way, subsequent likes on the same user-file pair will refresh the
timestamp and device origin information to match the latest operation.

---

Outside diff comments:
In `@src/api/sync.rs`:
- Around line 347-360: The error handling block in the lamport_max function call
in src/api/sync.rs does not conform to Rust formatting standards expected by
rustfmt. Run cargo fmt --all to automatically reformat the code to meet the
project's coding guidelines before merging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5d642b91-a425-46db-b9ee-1ac6fc86e57f

📥 Commits

Reviewing files that changed from the base of the PR and between 9096fb8 and 5db9d6b.

📒 Files selected for processing (4)
  • src/api/sync.rs
  • src/apply.rs
  • src/db.rs
  • tests/apply.rs

Comment thread src/apply.rs Outdated
Two review fixes on #55:

1. apply::liked::apply — switched ON CONFLICT DO NOTHING to DO UPDATE
   so a repeat like on the same (user, file) refreshes hlc_wall /
   hlc_logical / origin_device_id / liked_at to the latest winning
   op. Without this, two devices liking the same file would converge
   on different row hashes once the A.2.3 digest endpoint goes
   live — the materialised row would keep the first-landing op's
   tuple while the canonical "winning" op (by sync_op.id under
   Phase A implicit LWW) would be the second. Mirrors the rating
   handler's existing UPSERT behaviour.

2. cargo fmt --all — fixes a formatting drift in api/sync.rs that
   #52 introduced and rustfmt would now reject on CI.

Both fixes pass clippy + the apply suite's liked / rating / library
stamp tests.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@github-actions github-actions Bot added type: feat New feature size: l 200-500 lines and removed type: feat New feature size: m 50-200 lines labels Jun 14, 2026
@InstaZDLL
InstaZDLL merged commit 8a28ac4 into main Jun 14, 2026
14 checks passed
@InstaZDLL
InstaZDLL deleted the feat/sync-v2-phase-a-2-2-apply-pipeline-hlc branch June 14, 2026 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: api Native /api/v2 surface scope: db SQLite schema, migrations, queries scope: server Server core (Rust) scope: sync User-data sync size: l 200-500 lines type: feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant