From 32a86417d0edc3bf5c4cf859243c9e34b29bf1ef Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Thu, 10 Sep 2026 19:26:10 +0200 Subject: fix(node): the handshake ack dropped one app's directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ack was assembled from its own tuple of application names, a copy of the daemon's `APP_DIR_KEYS`, and the two had drifted: the copy was missing `helloworld`. So the reference application — the one that exists to prove a new application needs no special-casing — was the single application whose configured folders never reached a client, which made the plugin claim false exactly where it is demonstrated. Fixed by removing the copy rather than syncing it. The ack now emits whatever `_directories` the group context carries, and `_app_directories_ctx` is the only thing that puts one there, so the two cannot disagree again. The transport names an application in one place, `ALLOWED_APPS`, which is enforcement rather than a directory list. The client had the same fault one layer up: `group-page.js` read three names by hand from the ack while the live-update path beside it was already generic. It derives the map from the ack's own keys now, so the fix reaches the settings pane instead of stopping at the wire. A first attempt moved the list to `roster.py`, where directory *storage* lives, and `test_helloworld_proves_the_plugin_claim.py` refused it: the roster, the ops, the config and the root set must name no application at all. That test is the architecture's own guard and it was right — the list belongs on the daemon, which is what wires a group's context, and everything downstream is derived from it. Two new tests, both verified to fail against the previous shape: the ack carries an application the node names nowhere else, and the ack keeps no list of its own. `test_the_lists_are_read_under_one_name_each` now asserts the shell names no application rather than that it names exactly three. Two stale comments went with it — the ack's, which described scalars removed in 07ff8b4, and the client's, which said those scalars still rode the wire for MNP 1.0 peers that can no longer connect. Full suite: 2258 passed, 4 skipped. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YVoHVCcfBqud6ZjG4db3y7 --- .../meshbay-hub/tests/test_app_settings_plugin.py | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'packages/meshbay-hub/tests/test_app_settings_plugin.py') diff --git a/packages/meshbay-hub/tests/test_app_settings_plugin.py b/packages/meshbay-hub/tests/test_app_settings_plugin.py index af86f12..940d975 100644 --- a/packages/meshbay-hub/tests/test_app_settings_plugin.py +++ b/packages/meshbay-hub/tests/test_app_settings_plugin.py @@ -256,15 +256,28 @@ def test_each_app_takes_a_list_of_directories(app, prop): def test_the_lists_are_read_under_one_name_each(): """ - One name per app on the ack — `_directories`, always a list. A second - name for the same answer means a page that reads whichever it thinks of - first, and a node that fills only the other one empties a working tab. + One name per app on the ack — `_directories`, always a list — and the + shell names none of them. + + It read three by hand until 2026-09-10, and `helloworld` was not among them: + the app that exists to prove a new one needs no special-casing had its + directories dropped on arrival, which made the plugin claim false exactly + where it is demonstrated. An app the node knows about must be one this page + already handles, and the only way to guarantee that is to read the ack's own + keys instead of a list kept here. + + A second *name* for the same answer would be the older fault: a page reading + whichever it thinks of first, and a node filling only the other one emptying + a working tab. """ page = GROUP_PAGE.read_text(encoding="utf-8") - block = page[page.index("setAppDirectories({"):] + block = page[page.index("setAppDirectories("):] block = block[:block.index("setChatDirectory")] - assert "ack.video_directories" in block and "ack.music_directories" in block - assert "ack.photo_directories" in block + assert "endsWith('_directories')" in block, ( + "the shell must derive the map from the ack's keys, not name apps") + for named in ("ack.video_directories", "ack.music_directories", + "ack.photo_directories"): + assert named not in block, f"{named} is an application named by the shell" for gone in ("ack.video_root", "ack.audio_root", "ack.photo_roots"): assert gone not in block, f"{gone} is a second name for the same answer" -- cgit v1.2.3