Skip to content

feat(sync): rfc-003 phase a.2.2.1 — payload_hash + hlc total-order helpers - #54

Merged
InstaZDLL merged 2 commits into
mainfrom
feat/sync-v2-phase-a-2-2-1-payload-hash-helpers
Jun 14, 2026
Merged

feat(sync): rfc-003 phase a.2.2.1 — payload_hash + hlc total-order helpers#54
InstaZDLL merged 2 commits into
mainfrom
feat/sync-v2-phase-a-2-2-1-payload-hash-helpers

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Jun 13, 2026

Copy link
Copy Markdown
Owner

What

Phase A.2.2 building block. Three pure functions in a new src/payload_hash.rs module that the apply pipeline handlers will consume in A.2.2.2 (profile-scoped: playlist / library / track) and A.2.2.3 (user-scoped: liked / rating).

Follows #50 / #51 / #52 / #53. Pure addition — no apply pipeline change yet.

API

Function Returns Purpose
canonical_serialize(fields, hlc, origin_device_id) Vec<u8> Deterministic byte stream — sorted keys, source-order arrays, wrapper with fields / hlc / origin_device_id
compute_payload_hash(fields, hlc, origin_device_id) [u8; 32] BLAKE3-256 over the canonical bytes — sized for direct BYTEA bind
hlc_strict_gt(incoming, existing) bool RFC-003 §2 total order on (wall, logical, origin_device_id)

HlcTriple wraps the three §2 components and derives Ord so the comparator is just a tuple >.

Design notes

  • Wrapper structure rather than flat-merged keys so a field named hlc / origin_device_id can't shadow the clock. Hash stays unambiguous.
  • BTreeMap recursive sort for objects — bytes-identical across platforms regardless of source serde_json emission order.
  • Arrays keep source order — a multi-artist tag [Tyler, Earl] is semantically distinct from [Earl, Tyler]. Sorting them here would let the apply pipeline silently swap primary / secondary on re-emit.
  • Option<Uuid> tiebreaker uses Rust's derived Ord (None < Some(any UUID)), which matches A.1.1's "legacy backfilled rows lose to any v2 op" intent — legacy rows backfill with origin_device_id = NULL, every v2 op then strictly outranks them on tiebreak.

What this does NOT do

  • No apply-pipeline change — A.2.2.2 / A.2.2.3 wire the helpers in.
  • No metadata_digest_version bump helper — that's a thin SQL helper that lives in src/db.rs next to the entity writes (added in A.2.2.2).
  • No on-the-fly canonicalization of existing rows — backfilling payload_hash on legacy rows is Phase B.

Test plan

  • cargo check --all-targets --all-features — clean
  • cargo clippy --all-targets --all-features -- -D warnings — clean
  • cargo test --all-features --lib payload_hash — 11 unit tests pass (~10 ms, no Postgres):
    • canonical_serialize_is_deterministic_across_key_order
    • canonical_serialize_changes_with_field_value
    • canonical_serialize_changes_with_hlc
    • canonical_serialize_changes_with_origin_device_id
    • canonical_serialize_array_order_is_preserved
    • canonical_serialize_nested_objects_are_sorted
    • hlc_strict_gt_compares_wall_first
    • hlc_strict_gt_tiebreaks_on_logical
    • hlc_strict_gt_tiebreaks_on_origin_device_id
    • hlc_strict_gt_none_loses_to_some
    • hlc_strict_gt_rejects_equal_triple

Refs

Summary by CodeRabbit

Notes de version

  • New Features
    • Une nouvelle API publique pour la gestion d'horodatages et le calcul de hashes de payloads a été exposée et est désormais accessible aux consommateurs du crate.

…lpers

Phase A.2.2 building block. Three pure functions in a new
src/payload_hash.rs module that the apply pipeline handlers will
consume in A.2.2.2 / A.2.2.3.

- canonical_serialize(fields, hlc, origin_device_id) -> Vec<u8>
  Wraps the synced entity fields under a top-level "fields" key
  alongside "hlc" + "origin_device_id" so a field name colliding
  with the HLC names can't shadow the clock. Keys sorted
  recursively via BTreeMap so the byte form is identical on every
  platform. Arrays keep source order (multi-artist tag order is
  semantically significant).

- compute_payload_hash(...) -> [u8; 32]
  BLAKE3-256 over the canonical bytes, sized for direct BYTEA bind
  into Postgres without a hex round-trip.

- hlc_strict_gt(incoming, existing) -> bool
  RFC-003 §2 total order on (wall, logical, origin_device_id).
  Uses Rust's derived lex Ord, which gives None < Some(any UUID)
  automatically — matches A.1.1's "legacy backfilled rows lose to
  any v2 op" intent.

Module is unit-test only (no Postgres). 11 cases cover key-order
determinism, field-value sensitivity, HLC sensitivity, origin UUID
sensitivity, array-order preservation, nested object sort, plus
each branch of the total-order comparator (wall / logical /
origin_device_id tiebreaks, None < Some, equal-triple no-op).

Pure addition — no apply pipeline change yet. A.2.2.2 wires this
into the profile-scoped handlers (playlist / library / track);
A.2.2.3 wires into the user-scoped ones (liked / rating).

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

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Ajout du fichier src/payload_hash.rs introduisant HlcTriple, hlc_strict_gt, canonical_serialize, compute_payload_hash et la fonction privée canonicalize. Le module est enregistré publiquement dans src/lib.rs.

Modifications

Module payload_hash : ordre total HLC + hash canonique BLAKE3

Layer / File(s) Résumé
HlcTriple et comparaison stricte
src/lib.rs, src/payload_hash.rs
HlcTriple encapsule wall: i64, logical: i32, origin_device_id: Option<Uuid> avec un constructeur depuis Hlc. hlc_strict_gt teste la stricte supériorité via l'ordre lexicographique dérivé. Le module est déclaré pub dans src/lib.rs.
Sérialisation canonique et hash BLAKE3
src/payload_hash.rs
canonical_serialize construit un wrapper JSON (fields, hlc, origin_device_id) et le passe à canonicalize (tri récursif des clés par BTreeMap). compute_payload_hash applique BLAKE3-256 sur les octets résultants et retourne [u8; 32].
Suite de tests unitaires
src/payload_hash.rs
Tests couvrant : invariance du hash face à l'ordre des clés, sensibilité aux changements de champs/HLC/origin_device_id, conservation de l'ordre des tableaux, tri récursif des objets imbriqués, ordre lexical des clés de premier niveau, et tous les cas de hlc_strict_gt (wall, logical, origin_device_id, égalité, None vs Some).

Diagramme de séquence

sequenceDiagram
  participant Appelant
  participant canonical_serialize
  participant canonicalize
  participant compute_payload_hash
  participant blake3

  rect rgba(70, 130, 180, 0.5)
    Note over Appelant,canonicalize: Sérialisation canonique
    Appelant->>canonical_serialize: fields, hlc, origin_device_id
    canonical_serialize->>canonicalize: Value JSON brut
    canonicalize-->>canonical_serialize: Value avec clés triées (BTreeMap)
    canonical_serialize-->>Appelant: octets canoniques
  end

  rect rgba(60, 179, 113, 0.5)
    Note over Appelant,blake3: Calcul du hash
    Appelant->>compute_payload_hash: fields, hlc, origin_device_id
    compute_payload_hash->>canonical_serialize: délègue
    canonical_serialize-->>compute_payload_hash: octets canoniques
    compute_payload_hash->>blake3: hash(bytes)
    blake3-->>compute_payload_hash: hash 32 octets
    compute_payload_hash-->>Appelant: hash BYTEA Postgres
  end
Loading

Effort de revue estimé

🎯 3 (Moderate) | ⏱️ ~20 minutes

PRs potentiellement liées

  • InstaZDLL/waveflow-server#51 : Introduit les colonnes SQL payload_hash, origin_device_id, hlc_wall et hlc_logical, directement consommées par les fonctions de ce PR.

Labels suggérés

scope: sync

Poème

🔐 Les clés sont triées, l'ordre est strict,
Wall, logical, UUID — le triple prédit.
BLAKE3 hache le tout en 32 octets nets,
Canonique et pur, jamais de secrets.
Le payload est signé, Postgres est content ! 🦀

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Le titre décrit précisément la principale addition : l'exposition du module payload_hash avec les helpers de total-order HLC pour la phase A.2.2.1 de RFC-003.
Description check ✅ Passed La description fournie est complète : elle explique le contexte (Phase A.2.2), détaille l'API des trois fonctions, justifie les choix de design, couvre le test plan avec résultats, et respecte la structure attendue. Les sections du template générique (Bun tasks) ne s'appliquent pas à ce projet Rust.
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-1-payload-hash-helpers

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

@github-actions github-actions Bot added type: feat New feature size: m 50-200 lines scope: server Server core (Rust) scope: api Native /api/v2 surface and removed size: m 50-200 lines labels Jun 13, 2026
@InstaZDLL InstaZDLL self-assigned this Jun 13, 2026
Review fix on #54: the wrapper map + the hlc sub-map were only
sorted by insertion-order accident — "fields" / "hlc" /
"origin_device_id" and "logical" / "wall" happened to be inserted
in alphabetical order, so the byte form was deterministic. But if
serde_json gets compiled with the `preserve_order` feature (IndexMap
backing for Map), insertion order is the serialisation order, and a
future edit that swapped two inserts would silently flap every
existing hash.

Fix: run the FULL tree through `canonicalize()` at the end so the
top-level wrapper + hlc sub-map go through the BTreeMap sort
explicitly, regardless of how `serde_json::Map` is backed.

New test `canonical_serialize_top_level_keys_are_sorted` asserts
on the raw byte form to lock the lex order in — catches a future
regression where someone removes the canonicalize wrap.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@github-actions github-actions Bot added type: feat New feature size: m 50-200 lines and removed type: feat New feature labels Jun 13, 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

🤖 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/payload_hash.rs`:
- Around line 43-140: Move the pure RFC-003 protocol logic functions and types
from this file to the waveflow-core repository instead of keeping them in
waveflow-server. Specifically, extract and relocate the HlcTriple struct,
hlc_strict_gt function, canonical_serialize function, and compute_payload_hash
function to waveflow-core since they implement reusable protocol rules with no
server-specific dependencies. Remove these definitions from src/payload_hash.rs
and remove any exports of these functions from src/lib.rs, then add the
necessary imports from waveflow-core in the server code where these functions
are needed.
🪄 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: 8ae037fe-775a-451d-b754-6cc4bcdfe9d1

📥 Commits

Reviewing files that changed from the base of the PR and between d526392 and 0c4eb73.

📒 Files selected for processing (2)
  • src/lib.rs
  • src/payload_hash.rs

Comment thread src/payload_hash.rs
@InstaZDLL
InstaZDLL merged commit 9096fb8 into main Jun 14, 2026
13 of 14 checks passed
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: server Server core (Rust) size: m 50-200 lines type: feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant