aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-25 17:43:28 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-25 17:43:28 +0200
commit3f3c67a4aff7b800c271e88e2bc5e5294b010fb9 (patch)
tree7280157a5921605b95fb8d1640f4b656067c8136 /packages
parentbce962c39fcb2d124506e33f77c3ca9082f145dd (diff)
downloadmeshbay-3f3c67a4aff7b800c271e88e2bc5e5294b010fb9.tar.gz
feat(protocol): MNP 4.0 flag day for the node-audience token (B2)
The node-audience token (previous commit) is a change to what a peer must present, so it is a MAJOR per the versioning rule (§5.6): a pre-4.0 client presents its hub session token and a 4.0 node refuses it, and there is no compatibility branch, because leaving one would keep a hub credential reachable by every node (C6's lesson). So the floor moves with the version. - MNP_VERSION 3.4 -> 4.0 and MNP_MIN_SUPPORTED 3.0 -> 4.0 (meshbay_common); transport.js MNP_V/MNP_V_MIN -> 4.0 to match. - MIN_CLIENT_VERSION 0.13.0 -> 0.16.0 so a stale desktop client is told to update before connecting rather than meeting a handshake refusal it cannot read; the browser reloads this build from the hub. - Regenerate tests/golden/dispatch.json: the only change is the `v` the node stamps on outbound messages, 3.4 -> 4.0 (56 cases, v field only). - Document the split and the flag day: MESHBAY_DESIGN.md §5.2 (the handshake token is the MNP-audience token), §5.6 (the 4.0 flag day), register E10 and decision 23; MESHBAY_NODE_PROTOCOL.md §6.3 (authorize_token binds MNP_AUD) and the wire-version banner. Deploy is coordinated and atomic (common+hub+node+SPA together); a live browser-to-node validation and the deploy itself remain. common (173), node (1489, the pre-existing test_cli_golden argparse/prog artifact aside) and hub (1471) suites all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-common/src/meshbay_common/__init__.py10
-rw-r--r--packages/meshbay-common/src/meshbay_common/handshake.py6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/hub.py9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js19
-rw-r--r--packages/meshbay-node/tests/golden/dispatch.json112
5 files changed, 86 insertions, 70 deletions
diff --git a/packages/meshbay-common/src/meshbay_common/__init__.py b/packages/meshbay-common/src/meshbay_common/__init__.py
index 216a18f..842c52d 100644
--- a/packages/meshbay-common/src/meshbay_common/__init__.py
+++ b/packages/meshbay-common/src/meshbay_common/__init__.py
@@ -241,5 +241,13 @@ __version__ = "0.16.0"
# `invite_link_result` and `invite_cancel`, a code bound to no account until it
# is redeemed. Additive in the same way — a 3.3 node answers `unknown message
# type`, and no client can hold a link code for a node that could not issue one.
-MNP_VERSION = "3.4"
+#
+# 4.0 (2026-09-25) is a MAJOR — a change to what a peer must *present*, not an
+# additive message (§5.6). A member now authenticates to a node with a
+# short-lived MNP-audience token (aud=MNP_AUD), never its hub session token: the
+# node operator holds whatever is presented, and the session token opens the hub
+# API. A pre-4.0 client presents the session token and is refused at the
+# handshake — there is no compatibility branch, because leaving one would keep
+# the disclosure reachable on every node. So the floor moves with it.
+MNP_VERSION = "4.0"
MHP_VERSION = "0.1"
diff --git a/packages/meshbay-common/src/meshbay_common/handshake.py b/packages/meshbay-common/src/meshbay_common/handshake.py
index 2393df1..dedfeb1 100644
--- a/packages/meshbay-common/src/meshbay_common/handshake.py
+++ b/packages/meshbay-common/src/meshbay_common/handshake.py
@@ -85,7 +85,11 @@ CHALLENGE_PREFIX = b"meshbay:mnp:challenge:v1"
# has no vocabulary to understand — or serving it outside every cap the operator
# set, which makes the caps decoration. Neither is honest, so it is refused
# here, with a code and a sentence.
-MNP_MIN_SUPPORTED = "3.0"
+# 4.0 (2026-09-25): a member authenticates with an MNP-audience token, not the
+# hub session token (MNP_VERSION note). A pre-4.0 peer presents the session
+# token, which this node now refuses — so the floor moves to 4.0 rather than
+# leaving a branch that would keep a hub credential reachable by every node.
+MNP_MIN_SUPPORTED = "4.0"
ROLE_CLIENT = "client"
ROLE_NODE = "node"
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/hub.py b/packages/meshbay-hub/src/meshbay_hub/api/hub.py
index cab60c8..efebc4d 100644
--- a/packages/meshbay-hub/src/meshbay_hub/api/hub.py
+++ b/packages/meshbay-hub/src/meshbay_hub/api/hub.py
@@ -78,8 +78,13 @@ async def hub_pubkey():
# handshake refusal anyway. The operator is updating every client, node and hub
# by hand for this flag day, which is what makes that acceptable exactly once.
# The gate is in place for the next one, where it will work as intended.
-MIN_CLIENT_VERSION = "0.13.0"
-RECOMMENDED_CLIENT_VERSION = "0.13.0"
+#
+# 0.16.0 is the MNP 4.0 flag day: a client older than this presents its hub
+# session token to a node, which a 4.0 node refuses. A desktop client below
+# 0.16.0 is told to update *before* it connects rather than meeting a handshake
+# refusal it cannot read; the browser reloads this build from the hub.
+MIN_CLIENT_VERSION = "0.16.0"
+RECOMMENDED_CLIENT_VERSION = "0.16.0"
@router.get("/version")
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
index 27edc4e..f3683eb 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -305,16 +305,15 @@ window.addEventListener('hashchange', () => {
// The `v: '0.1'` on every other message in this file is the historical value
// and is read by nothing; it is left alone deliberately. The range is
// negotiated once, at the start, not restated per message.
-const MNP_V = '3.1';
-// **Not** raised with it, and that is the whole difference between 3.0 and 3.1.
-// 3.0 was a flag day because a node older than it cannot grant the lease this
-// client opens for every download and upload, so talking to one would mean
-// every transfer failing for a reason the person cannot act on. 3.1 only adds
-// `user_blob_*`: a 3.0 node answers "unknown message type" and the client
-// stores its playlists on the next node it reaches, keeping its own copy
-// meanwhile (docs/playlists.md §6.4). Refusing every 3.0 node over a feature
-// that degrades this quietly would be the flag day nobody needed.
-const MNP_V_MIN = '3.0';
+const MNP_V = '4.0';
+// Raised with it: 4.0 is a flag day. A member now presents a short-lived
+// MNP-audience token in the handshake, not its hub session token — a node
+// older than 4.0 expected the session token, and one newer refuses it, so the
+// two cannot authenticate across the break. This is the C6 rule: no
+// compatibility branch, or the old path (a hub credential handed to a node)
+// stays reachable. The desktop client is gated by client.minimum before it
+// even connects; the browser picks up this build on reload.
+const MNP_V_MIN = '4.0';
// Codes a NODE sends us, in its own vocabulary (meshbay_common/handshake.py's
// check_version): `version_too_old` means *we* are too old for it,
diff --git a/packages/meshbay-node/tests/golden/dispatch.json b/packages/meshbay-node/tests/golden/dispatch.json
index 98911f5..5d15057 100644
--- a/packages/meshbay-node/tests/golden/dispatch.json
+++ b/packages/meshbay-node/tests/golden/dispatch.json
@@ -2228,7 +2228,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2551,7 +2551,7 @@
"subject": "gggggggggggggggggggggggggggggggg",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2570,7 +2570,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2589,7 +2589,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2608,7 +2608,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2878,7 +2878,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2892,7 +2892,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2906,7 +2906,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2920,7 +2920,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2934,7 +2934,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2948,7 +2948,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2962,7 +2962,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -2976,7 +2976,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -8855,7 +8855,7 @@
"subject": "gggggggggggggggggggggggggggggggg",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -8874,7 +8874,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -8893,7 +8893,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -8912,7 +8912,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -9247,7 +9247,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -9266,7 +9266,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -9285,7 +9285,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -9620,7 +9620,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -9639,7 +9639,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -9658,7 +9658,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -11961,7 +11961,7 @@
"subject": "link:gggggggggggggggggggggggggggggggg",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -14060,7 +14060,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -14079,7 +14079,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -14098,7 +14098,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -14433,7 +14433,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -14452,7 +14452,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -14471,7 +14471,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -16532,7 +16532,7 @@
"req_id": 4242,
"token": null,
"type": "pong",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -16547,7 +16547,7 @@
"x"
],
"type": "pong",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -16560,7 +16560,7 @@
"req_id": 4242,
"token": 7,
"type": "pong",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -16573,7 +16573,7 @@
"req_id": 4242,
"token": "x",
"type": "pong",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -16586,7 +16586,7 @@
"req_id": 4242,
"token": null,
"type": "pong",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -16601,7 +16601,7 @@
"x"
],
"type": "pong",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -16614,7 +16614,7 @@
"req_id": 4242,
"token": 7,
"type": "pong",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -16627,7 +16627,7 @@
"req_id": 4242,
"token": "x",
"type": "pong",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -16962,7 +16962,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -16981,7 +16981,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -17000,7 +17000,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -17335,7 +17335,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -17354,7 +17354,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -17373,7 +17373,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -17708,7 +17708,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -17727,7 +17727,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -17746,7 +17746,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -18081,7 +18081,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -18100,7 +18100,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -18119,7 +18119,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -18454,7 +18454,7 @@
"subject": "['x']:rw=on,rem=on",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -18473,7 +18473,7 @@
"subject": "7:rw=on,rem=on",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -18492,7 +18492,7 @@
"subject": "x:rw=on,rem=on",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -21439,7 +21439,7 @@
"subject": "custom_token=no,language=default",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -21482,7 +21482,7 @@
"subject": "custom_token=yes,language=x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []
@@ -23369,7 +23369,7 @@
"subject": "d=7,u=7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "3.4"
+ "v": "4.0"
}
],
"spawned": []