Skip to content

Commit 30d5112

Browse files
shumkovclaude
andcommitted
docs(dashpay): sync specs to the implemented state of this PR
Make the DashPay design docs match what actually shipped on this branch: - SYNC_CORRECTNESS_SPEC (Spec 0): REVIEWED→IMPLEMENTED — both stages shipped (paginated/high-water request sync + id-keyed contact-profile cache), durably persisted to both the SQLite persister and SwiftData. - CONTACTINFO_FORMAT_SPEC (Spec 1): the §4 contactInfo reject/block field marked DEFINED-but-NOT-ADOPTED — R1 resolved ignore as local-only, so it is not carried on contactInfo; the shipped codec is alias/note/displayHidden/acceptedAccounts. - BLOCK_SPEC: PAUSED→SUPERSEDED — replaced by the implemented per-sender, reversible, local-only Ignore (Spec 2); the contactInfo cross-device route was rejected (R1); cross-device deferred to a future encrypted profile field (contract track). - SPEC.md: added a 2026-06-18 status block mapping the Part-0 gap table (G1–G15) to its resolution, plus accountReference (keep-ours) and the account-index path fix (upstream rust-dashcore#813 via dashcore bump PR #3936). Points at TODO.md as the authoritative item-by-item status. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 822b1ee commit 30d5112

4 files changed

Lines changed: 71 additions & 23 deletions

File tree

docs/dashpay/BLOCK_SPEC.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
# DashPay "Block sender" — design spec
22

3-
Status: **PAUSED (2026-06-17)** — superseded in sequence by the cross-device
4-
rework. This single-device design is 4-lens reviewed (§0 R1–R10) and stays valid,
5-
but we now do block (and reject) **via `contactInfo`** so they sync across devices.
6-
New order:
7-
1. **`docs/dashpay/CONTACTINFO_FORMAT_SPEC.md`** — migrate privateData CBOR→DIP-15
8-
+ define the reject/block field. *(written, needs review)*
9-
2. **(TODO) reject → on-chain** via that field — must resolve the R1 non-established
10-
privacy question first.
11-
3. **(this, revisited) block via `contactInfo`** — cross-device.
3+
Status: **SUPERSEDED (2026-06-18) — replaced by the implemented local-only Ignore.**
4+
This single-device design is 4-lens reviewed (§0 R1–R10) and stays valid as the
5+
reference, but the **shipped** feature is the simpler per-sender, reversible
6+
**Ignore** (= block) in `docs/dashpay/SYNC_CORRECTNESS_SPEC.md` / Spec 2 — see the
7+
TODO. The intermediate plan to carry block/reject **via `contactInfo`** for
8+
cross-device sync was **REJECTED**: R1 found that a `contactInfo` about a
9+
*non-established* sender leaks *who* you ignored (its public `$createdAt`/`$updatedAt`
10+
correlate with the inbound `contactRequest`'s timestamp via the public indexes), and
11+
the DIP-15 ≥2-contacts ambiguity gate doesn't cover a fresh non-established sender.
12+
What actually landed:
13+
1. **`CONTACTINFO_FORMAT_SPEC.md`** — privateData CBOR→DIP-15 varint. **IMPLEMENTED**
14+
(carries alias/note/displayHidden/acceptedAccounts; does NOT carry ignore — R1).
15+
2. **Ignore = per-sender, reversible, LOCAL-ONLY.** **IMPLEMENTED** across all layers
16+
(changeset, FFI, SwiftData, *and* the SQLite persister's `ignored_senders` table).
17+
3. **Cross-device ignore** — deferred to a future **encrypted field on the `profile`
18+
document** (contract / governance track), whose update timing is conflated with
19+
normal profile edits so it doesn't leak the per-sender existence/count. Not built.
1220

1321
Owner: platform-wallet / swift-sdk
1422
Relates to: `docs/dashpay/SPEC.md` (G5 rejection), the existing per-request

docs/dashpay/CONTACTINFO_FORMAT_SPEC.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,18 @@ bump locks old readers out. So:
9595
- decoders MUST be **tolerant**: read the fields the known minor defines, ignore
9696
trailing bytes; on an unknown **major**, discard.
9797

98-
## 4. The reject/block fields (define here, populate later)
98+
## 4. The reject/block fields — DEFINED but NOT ADOPTED (R1, resolved 2026-06-18)
9999

100-
Appended after `acceptedAccounts`, present from **minor 1**:
100+
> **Resolution.** This field was the proposed cross-device carrier for
101+
> reject/block. It is **not implemented** and is **not part of the shipped DIP-15
102+
> codec** (which carries only `aliasName` / `note` / `displayHidden` /
103+
> `acceptedAccounts`). Per R1, a `contactInfo` about a *non-established* sender
104+
> leaks *who* you ignored (the timing-correlation argument below), so **Ignore is
105+
> local-only** (Spec 2) and cross-device sync is deferred to a future **encrypted
106+
> field on the `profile` document** (contract / governance track) — NOT to
107+
> `contactInfo`. The design below is retained for reference only.
108+
109+
Appended after `acceptedAccounts`, present from **minor 1** (design only — unused):
101110

102111
| # | Field | Type | Meaning |
103112
|---|-------|------|---------|
@@ -109,19 +118,22 @@ the "both set" ambiguity, and extends cleanly (e.g. 3 = muted). `displayHidden`
109118
(field 3) stays as-is for backward DIP-15 compat; `relationshipState` is the
110119
richer superset we read first when present.
111120

112-
**Scope boundary (critical):** this spec only **defines** the field + its
121+
**Scope boundary (critical):** this spec only **defined** the field + its
113122
encoding. *Whether and how* a `contactInfo` is created to carry it — especially
114-
for a **non-established** declined/blocked sender — is the **privacy question
115-
(R1 from the block review)** deferred to Spec 2/3:
123+
for a **non-established** declined/blocked sender — was the **privacy question
124+
(R1 from the block review)**, **RESOLVED (2026-06-18): not via `contactInfo` at
125+
all.** Ignore is local-only (Spec 2); cross-device goes through an encrypted
126+
`profile` field later. Kept for context:
116127

117128
> A `contactInfo` *about a non-contact* is a brand-new on-chain document whose
118129
> existence + `$createdAt` can be timing-correlated with the inbound
119130
> `contactRequest` (public `userIdCreatedAt` index) to re-identify *who* you
120131
> blocked — even though `encToUserId` is encrypted, and the ≥2-contacts gate
121-
> (dip-0015.md:697-699) can't cover a non-contact. **Spec 2 must resolve whether
122-
> non-established reject/block is carried per-sender (leaky) or in a single
123-
> owner-scoped self-encrypted list (bounded leak), or only for established
124-
> contacts (no leak, partial coverage).** This format spec is agnostic to that
132+
> (dip-0015.md:697-699) can't cover a non-contact. **Spec 2 resolved this: a
133+
> per-sender `contactInfo` is leaky (above) and even a single owner-scoped list
134+
> on `contactInfo` still signals "an ignore happened", so ignore is kept
135+
> local-only and cross-device is deferred to an encrypted `profile` field whose
136+
> update timing is conflated with ordinary profile edits.** This format spec is agnostic to that
125137
> choice — it just provides the field.
126138
127139
## 5. Padding / 48-byte floor

docs/dashpay/SPEC.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,27 @@
1111
> and lays out the remaining work + test plan. It is *not* a greenfield design —
1212
> it is a finish-and-polish plan.
1313
>
14+
> **Status update (2026-06-18) — the finish-and-polish work is essentially done
15+
> on `feat/dashpay-m1-sync-correctness` (PR #3841).** Resolution of the Part-0 gap
16+
> table: **G1, G2, G12, G13, G14, G15** (the P0 sync/wire/key-purpose blockers) and
17+
> **G3, G6, G7, G8, G9, G10** — all **DONE** (M1–M4). **G5** reworked and shipped as
18+
> a per-sender, reversible, **local-only Ignore** (Spec 2) across every layer incl.
19+
> the SQLite persister; cross-device sync deferred to a future encrypted `profile`
20+
> field (contract track — the `contactInfo` route was rejected for the R1 leak).
21+
> **G11**: the `network/` layer now has unit coverage; the live cross-client e2e
22+
> ride PR #3549 and stay blocked on devnet funding. **G4** (watch-only ECDH) is
23+
> **deferred** with an amended design (needs xpub hooks, not just an ECDH hook).
24+
> Three follow-on specs were written and **implemented** this pass:
25+
> **`SYNC_CORRECTNESS_SPEC.md`** (Spec 0 — paginated/high-water sync + contact-profile
26+
> cache + durable persistence), **`CONTACTINFO_FORMAT_SPEC.md`** (Spec 1 — privateData
27+
> CBOR→DIP-15 varint), and Spec 2 (Ignore). Also resolved: `accountReference`
28+
> byte-order (**keep ours** — recipient-ignored one-time pad, no interop break) and
29+
> the friendship-path `account'` hardcode (fixed upstream in **rust-dashcore#813**,
30+
> pulled in via the dashcore bump **PR #3936**). Remaining is all blocked on external
31+
> resources (devnet funding for e2e/UAT; contract governance for cross-device ignore
32+
> + DoS filter; an upstream rust-dashcore change for multi-account). See
33+
> [`TODO.md`](./TODO.md) for the authoritative item-by-item status.
34+
>
1435
> **How to read.** Part 0 is the TL;DR. Parts 1–2 are reference (protocol +
1536
> architecture). Part 3 is the current-state inventory. Part 4 is the prioritized
1637
> gap/bug list. Part 5 is the work plan. Part 6 is the Swift UI design. Part 7 is

docs/dashpay/SYNC_CORRECTNESS_SPEC.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
# DashPay sync correctness — contact requests **and** profiles (mirror Android `PlatformSyncService`)
22

3-
Status: **REVIEWED** (5-lens multi-agent review folded in — see §9; ready to implement)
3+
Status: **IMPLEMENTED (2026-06-18)** — both stages shipped on
4+
`feat/dashpay-m1-sync-correctness` (PR #3841), 5-lens review (§9) folded in first.
5+
Stage 1 = paginated retrieve-all + per-identity high-water cursor + 10-min overlap
6+
at a 15s cadence (`network/contact_requests.rs`); stage 2 = id-keyed
7+
`contact_profiles` cache for established + pending senders (`network/contact_info.rs`,
8+
`accessors.rs`). Both are surfaced in the UI and **durably persisted** through the
9+
changeset pipeline to *both* backends (SQLite persister + SwiftData); the high-water
10+
cursor stays in-memory by design (a cold restore does one safe full re-fetch).
411
Owner: rs-sdk / platform-wallet
512
Priority: **FIRST** of the DashPay correctness track (ahead of the contactInfo
613
format migration and the ignore feature).
714

815
This spec covers **two consecutive stages of the same Android sync loop**:
916

10-
| Stage | Android (`PlatformSyncService`) | Us today | This spec |
11-
|-------|--------------------------------|----------|-----------|
12-
| 1. Contact-request fetch | `updateContactRequests()` — incremental, paginated, high-water | present but **broken** (truncates at 100, no high-water) | fix it |
13-
| 2. Contact-profile fetch | `updateContactProfiles(userIds)` — batch `whereIn $ownerId` | **absent** (we sync only our *own* profile) | add it |
17+
| Stage | Android (`PlatformSyncService`) | Us before | Delivered |
18+
|-------|--------------------------------|-----------|-----------|
19+
| 1. Contact-request fetch | `updateContactRequests()` — incremental, paginated, high-water | present but **broken** (truncated at 100, no high-water) | **fixed** — retrieve-all + high-water cursor |
20+
| 2. Contact-profile fetch | `updateContactProfiles(userIds)` — batch `whereIn $ownerId` | **absent** (synced only our *own* profile) | **added** — id-keyed cache, established + pending senders |
1421

1522
Neither is an optimization: stage 1 is a **correctness bug** (real requests are
1623
permanently buried) and stage 2 is a **missing feature** (contacts have no name

0 commit comments

Comments
 (0)