mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 11:48:24 +00:00
docs(cordn): record the real reason multi-device is a non-goal
Both plans said there was "nothing interoperable to implement." That is
true of exactly one field and false of the feature, and the difference
decides what a future revisit should be looking for.
A group document's `clientState` is library-private on purpose —
multi-device.md §4.2 says it is "library-serialized and intentionally not
pinned to a wire format", with TLS pinned only for
`lastResortKeyPackage`. So cross-client sync is ruled out by design and
no work on our side changes it. But everything §14 lists as a MUST is
vendor-neutral: the document shapes, the NIP-44 v2 DEK seal, sha256
addressing, highest-epoch-wins reconciliation, the {gid, epoch}
tombstone, the sibling-skip rule, the tip format. An Amethyst-only fleet
is buildable with MlsGroupState in that one field.
It stays a non-goal on cost, now written down as such: it would ship
§10's symmetric commit race, which the spec leaves unresolved (§15:
"equal-epoch MLS states have no merge function"; automatic resolution is
"possible but unspecified"), behind an always-on discipline — republish
plus full tip rewrite after every epoch-advancing Commit, full reconcile
before opening any delivery stream — on a Draft spec, for a feature no
mixed-client user benefits from.
Each plan now names the two independent conditions that would reopen it:
a library-neutral clientState, or a specified equal-epoch tiebreaker.
Also corrected the backup section's cross-reference, which borrowed §4.6's
reasoning: it applies to cross-client portability specifically, and the
fork-on-restore hazard is the same equal-epoch divergence, reached by
restore rather than by a race.
Docs only; no code touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BfD4txdnsaPRXmNXbup9n
This commit is contained in:
@@ -391,13 +391,38 @@ client-side from Commits and never transmits them (synthetic kind `-1`). Derivin
|
||||
better here — it cannot disagree with the MLS state it describes, and it costs no bytes — and it
|
||||
is what a cordn peer will do anyway. Take theirs for cordn. Do not change Marmot.
|
||||
|
||||
### 5.3 Multi-device stays a non-goal
|
||||
### 5.3 Multi-device stays a non-goal — but not for the reason first recorded
|
||||
|
||||
`spec/applications/multi-device.md` ships `base64(serialized ts-mls ClientState)` inside sealed
|
||||
documents. That is **their library's internal state**, not a wire format — nothing but ts-mls can
|
||||
produce or consume it, so there is nothing interoperable to implement. The interop plan already
|
||||
records this (§4.6) and it should stay recorded rather than quietly reappearing as a settings
|
||||
screen. If upstream ever specifies a library-neutral device-sync format, revisit.
|
||||
The original wording here said there was "nothing interoperable to implement." That is true of
|
||||
exactly one field and false of the feature. Corrected, because the distinction decides what a
|
||||
future revisit would even be looking for:
|
||||
|
||||
- **Cross-client sync is impossible by design.** A group document's `clientState` is
|
||||
`base64(serialized MLS ClientState)`, and `multi-device.md` §4.2 states that it is
|
||||
*"library-serialized and intentionally not pinned to a wire format."* TLS is pinned for
|
||||
`lastResortKeyPackage` and nothing else. So an Amethyst device and a cordn-web device can never
|
||||
share a leaf, no matter what we build.
|
||||
- **Our own fleet is buildable.** Everything §14 lists as a MUST is vendor-neutral: the document
|
||||
JSON shapes, the NIP-44 v2 seal to a per-identity DEK, `sha256` addressing, highest-epoch-wins
|
||||
reconciliation, the `{gid, epoch}` tombstone, the sibling-skip rule, the tip format. Only that
|
||||
one field is ours to choose, and `MlsGroupState` fits it.
|
||||
|
||||
It stays a non-goal on cost, which is the part worth re-reading on a revisit:
|
||||
|
||||
- Amethyst-only device sync. A user who mixes clients gets nothing from it.
|
||||
- §10's **symmetric commit race has no resolution in the spec**. Two devices committing inside one
|
||||
delivery round-trip both land on epoch N+1 with different states; the forward-only epoch check
|
||||
cannot break a tie, and §15 concedes *"equal-epoch MLS states have no merge function."* The
|
||||
spec's answer is refuse-to-commit-while-behind plus a manual re-sync prompt. Shipping this means
|
||||
shipping that race and a conflict UI for it.
|
||||
- The discipline is always-on: a document republish and a full tip rewrite after **every**
|
||||
epoch-advancing Commit (§10.5), and a full reconcile before opening any delivery stream (§10.6).
|
||||
- The spec is still Draft.
|
||||
|
||||
So it should stay recorded rather than quietly reappearing as a settings screen. Revisit if
|
||||
upstream pins a library-neutral `clientState` (which would make it cross-client and change the
|
||||
first bullet) **or** specifies the equal-epoch tiebreaker (which would remove the race). Either
|
||||
one alone moves the decision; neither has happened.
|
||||
|
||||
### 5.4 Which backup story — DECIDED: our own format, and LANDED
|
||||
|
||||
@@ -409,7 +434,7 @@ file will not import). Needs a call; no strong opinion here.
|
||||
|
||||
- *Portability was never available.* The valuable content of any cordn backup is MLS group
|
||||
state, which is an engine's internal serialization rather than a wire format — the same
|
||||
reasoning §4.6 of the interop plan uses to rule out multi-device. Theirs is ts-mls's
|
||||
reasoning §4.6 of the interop plan uses to rule out *cross-client* multi-device. Theirs is ts-mls's
|
||||
`ClientState`, ours is `MlsGroupState`, and neither reads the other whatever container wraps
|
||||
it. A byte-compatible file would buy a restore that cannot restore. Their client also sits
|
||||
outside the two MIT packages, and the spec prose is unlicensed.
|
||||
@@ -419,7 +444,9 @@ file will not import). Needs a call; no strong opinion here.
|
||||
|
||||
So: `CordnBackup` — our own versioned format, scrypt at NIP-49's cost plus ChaCha20-Poly1305.
|
||||
Restoring **replaces** a device rather than merging, because an MLS state export is a cloneable
|
||||
identity and two devices committing from one state fork the ratchet tree (§5.3 again).
|
||||
identity and two devices committing from one state fork the ratchet tree — the same equal-epoch
|
||||
divergence §5.3 records as unresolved in `multi-device.md` §10, reached here by restore instead of
|
||||
by a race.
|
||||
|
||||
## 6. Out of scope, and why
|
||||
|
||||
|
||||
@@ -215,11 +215,41 @@ the file key with `aad = mime‖0x00‖filename‖0x00‖sha256(plaintext)`, whe
|
||||
`HKDF-Expand(exporter, context)` with `aad = "mip04-v2"‖0x00‖hash‖0x00‖mime‖0x00‖filename`.
|
||||
Separate codec; shared primitives (ChaCha20-Poly1305, NIP-92 `imeta`, Blossom) all already exist.
|
||||
|
||||
### 4.6 Multi-device is not interoperable — explicit non-goal
|
||||
### 4.6 Multi-device — cross-client interop is impossible; the feature is not
|
||||
|
||||
`spec/applications/multi-device.md` ships `base64(serialized ts-mls ClientState)` inside sealed
|
||||
Blossom documents advertised by an opaque tip. That is a ts-mls-internal serialization, not an MLS
|
||||
wire format. There is nothing to implement against. Do not attempt it.
|
||||
`spec/applications/multi-device.md` gives a user's devices **one shared MLS leaf** per group and
|
||||
carries that group's `ClientState` inside sealed Blossom documents advertised by an opaque tip.
|
||||
|
||||
**What is not implementable: interop with their devices.** The document's `clientState` is
|
||||
`base64(serialized MLS ClientState)`, and §4.2 says outright that it is *"library-serialized and
|
||||
intentionally not pinned to a wire format"* — the only pinned MLS serialization in the document is
|
||||
TLS, used for `lastResortKeyPackage`. So the field is ts-mls's private encoding by design, not by
|
||||
omission. An Amethyst device and a cordn-web device can never share a leaf, and no work on our side
|
||||
changes that.
|
||||
|
||||
**What IS implementable: our own fleet.** Everything else §14 lists as a MUST is pinned and
|
||||
vendor-neutral — the two document JSON shapes, the NIP-44 v2 seal to a per-identity DEK, `sha256`
|
||||
content addressing, the highest-epoch-wins reconciliation rule, the `{gid, epoch}` tombstone shape,
|
||||
the sibling-skip rule, and the tip format. Amethyst-device ↔ Amethyst-device sync would work with
|
||||
`MlsGroupState` in that one field.
|
||||
|
||||
**Why it stays a non-goal anyway**, which is the honest reason and not the one above:
|
||||
|
||||
- It buys Amethyst-only device sync. A user who mixes clients gets nothing.
|
||||
- §10's **symmetric commit race is unresolved in the spec**. Two devices committing inside one
|
||||
delivery round-trip both reach epoch N+1 with different states; the forward-only epoch check
|
||||
cannot break the tie, and §15 concedes that *"equal-epoch MLS states have no merge function."*
|
||||
The spec offers only refuse-to-commit-while-behind and a manual re-sync prompt; automatic
|
||||
resolution is *"possible but unspecified."* We would be shipping that race.
|
||||
- It is a heavy, always-on discipline for a Draft spec: a document republish plus a full tip
|
||||
rewrite after **every** epoch-advancing Commit (§10.5), and a full reconcile before opening any
|
||||
delivery stream on startup (§10.6), because a backlog fetched while behind arrives sealed under
|
||||
epochs the device has not adopted.
|
||||
- It needs an `MlsGroupState` ⇄ document codec and a `prev`-chain walk (§8.5) that nothing else
|
||||
in the codebase wants.
|
||||
|
||||
So: do not attempt it — but record it as a cost/benefit call on a Draft spec with a known race,
|
||||
not as "there is nothing to implement."
|
||||
|
||||
## 5. What we already have
|
||||
|
||||
@@ -1155,8 +1185,10 @@ Still open in Stage 3:
|
||||
- **A ts-mls `ClientState` export.** `verify.ts` carries an optional gate that
|
||||
decodes a Kotlin-exported ts-mls state and sends from it. We write no such
|
||||
file, so it is skipped. Producing one means re-encoding `MlsGroupState` into
|
||||
ts-mls's layout — real work, and only needed for multi-device, an explicit
|
||||
non-goal (§4.6).
|
||||
ts-mls's layout — real work, and the only thing it would buy is cross-client
|
||||
multi-device, which §4.6 shows the spec rules out by design (`clientState` is
|
||||
deliberately library-private). So this gate has no reachable purpose, not
|
||||
merely a deferred one.
|
||||
- **Tier B**, live against `ghcr.io/cordn-msg/cordn:latest`.
|
||||
|
||||
### Stage 4 — App integration — LANDED (disclosure UI + headless layer); group UI open
|
||||
@@ -1255,7 +1287,9 @@ The original scope, for reference:
|
||||
|
||||
### Non-goals
|
||||
|
||||
- Multi-device (§4.6) — nothing interoperable to build.
|
||||
- Multi-device (§4.6). Cross-client sync is impossible by design — `clientState` is
|
||||
deliberately library-private — and our own fleet is buildable but not worth it: it would
|
||||
ship §10's unresolved equal-epoch commit race, for an Amethyst-only feature, on a Draft spec.
|
||||
- Reusing any `Marmot*`/`Mip*` type for cordn.
|
||||
- A production Kotlin ContextVM *server*. The Tier C fixture (§6.4) plays the server role for
|
||||
tests only. For a real coordinator, `cordn-rs` exists, is faster, and shares the SQLite schema —
|
||||
|
||||
Reference in New Issue
Block a user