diff options
Diffstat (limited to 'packages/meshbay-common/src/meshbay_common/__init__.py')
| -rw-r--r-- | packages/meshbay-common/src/meshbay_common/__init__.py | 53 |
1 files changed, 41 insertions, 12 deletions
diff --git a/packages/meshbay-common/src/meshbay_common/__init__.py b/packages/meshbay-common/src/meshbay_common/__init__.py index ca4c6eb..cb6ad15 100644 --- a/packages/meshbay-common/src/meshbay_common/__init__.py +++ b/packages/meshbay-common/src/meshbay_common/__init__.py @@ -90,19 +90,48 @@ __version__ = "0.11.0" # makes the *next* breaking change cost a refusal message instead of a second # flag day. `MNP_MIN_SUPPORTED` in `handshake.py` is the other half. # -# **2.0 (2026-09-07): chat is encrypted, and there is no way to turn it off.** -# A MAJOR bump because it is a real break: a 1.x peer cannot produce a sealed -# chat message and cannot read one, so it is refused at the handshake with -# `version_too_old` rather than connecting and then failing to speak. Expressing -# the break in the version is what makes it a stated refusal instead of a -# conversation that silently does not work — `MNP_MIN_SUPPORTED` moves with it. +# **2.0 (2026-09-07): the write path is sealed, chat is encrypted, and the last +# unencrypted content message is gone.** Three changes that landed together and +# share one version, because they share one flag day. # -# There is deliberately no per-group switch. Every node in existence is a test -# node, so an opt-in flag would buy nothing and cost a compatibility path to -# maintain; existing node data is migrated by `QE/migrate-chat-encryption.py`. +# - `file_upload` and `file_upload_ack` travel sealed under a GEK-derived +# subkey (`groupbox.PURPOSE_UPLOAD`). The filename, the destination folder +# and the bytes all ride inside the seal; `upload_id` — a fresh +# client-chosen correlation id — and `chunk_index` stay in clear because +# the node routes and orders on them. `filename` used to be the +# correlation key and cannot be any more, which is what forced `upload_id`. +# - `chat_msg` is sealed under a per-device subkey of the group's chat epoch +# key (`meshbay_common.chatbox`) and signed over the ciphertext with the +# device key the node pinned. There is deliberately **no per-group switch**: +# every node in existence is a test node, so an opt-in flag would buy +# nothing and leave a plaintext branch reachable. Existing node data is +# migrated by `QE/migration/migrate_chat_encryption.py`. +# - `stream_seg` is **removed**. It answered with an MPEG-TS segment as +# base64 with no encryption at all, on both transports, to any +# authenticated member — the one content-plane message that never went +# through a GEK-derived key. `stream_data` has done the job properly since +# Phase 12, and `fetchStreamSegment`, its only browser caller, was defined +# and never once invoked. # -# The index at rest, `index_progress` (counters only, never a path — see -# `groupbox.py` and daemon.py `_push_index_progress`), and file content on the -# operator's disk are all deliberately unchanged. +# **Breaking, on the wire every deployed client speaks**, and MAJOR by the same +# rule 1.0 was. +# +# **`MNP_MIN_SUPPORTED` moves to "2.0" with it, and that is a change of plan +# worth reading.** The sealed upload alone was a *confined* break: a 1.x peer +# could still connect, browse, download, stream and chat, and only its uploads +# were refused — so the floor stayed at "1.0" and the refusal was per message +# (`upload_not_sealed`). Mandatory chat encryption ends that confinement. A 1.x +# peer can neither produce a sealed chat message nor read one, so it would +# connect, appear to work, and then be unable to say anything or read anything +# anyone else said. Refusing it at the handshake with `version_too_old` and a +# sentence saying so is the honest form: a stated refusal is a bug report, a +# chat that quietly does not work is a support case. The per-message +# `upload_not_sealed` path stays, unchanged — it is still the right answer if +# the floor is ever lowered again. +# +# Still deliberately in clear, and none of it is content: the handshake itself, +# `index_progress` (counters only — see daemon.py `_push_index_progress`), the +# admin and configuration acks, and the media-metadata replies. The index at +# rest and file content on the operator's disk are unchanged. MNP_VERSION = "2.0" MHP_VERSION = "0.1" |