<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-node, branch 0.13</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.13</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.13'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-09-10T23:33:05Z</updated>
<entry>
<title>feat(packaging): one radio page for Windows autostart, firewall every mode</title>
<updated>2026-09-10T23:33:05Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-10T23:33:05Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=e0607a9cee6ce9596e775baaed43eb67e1e5a293'/>
<id>urn:sha1:e0607a9cee6ce9596e775baaed43eb67e1e5a293</id>
<content type='text'>
Operator feedback on the 0.13.0 installer:

- The all-users / current-user page (electron-builder's PAGE_INSTALL_MODE)
  only ever showed "anyone who uses this computer" disabled -- MeshBay is
  per-user only (account-bound keystore/DPAPI, MESHBAY_DESIGN.md 11.2) and
  build.nsis forbids elevation. customInstallMode forces $isForceCurrentInstall
  so the page is skipped.

- The two nested Yes/No MessageBoxes are one nsDialogs radio page
  (customPageAfterChangeDir): only-while-open / at-sign-in / background service,
  default background service. customInit seeds MB_AutoMode "2" for silent
  installs where the page never runs. "At sign-in" now writes the Startup .vbs
  from the installer (meshbay-node autostart install, unelevated); the old
  per-user branch set up nothing.

- The firewall rules go in for every mode, not behind a second opt-in -- a node
  that accepts no connections is the failure mode MESHBAY_DESIGN.md 7.5 names.
  Folded into the service elevation for mode 2; their own single elevation for
  0/1. Unelevated short-circuit kept but narrower: firewall.ps1 check AND
  service.ps1 status must both pass to skip mode 2's UAC.

Var MB_AutoMode lives inside customPageAfterChangeDir, not at file scope: the
uninstaller compile pass inserts none of the macros that read it and
makensis -WX turns "unused Var" (6001) into a hard error.

Not yet exercised on a real machine -- the NSIS UI cannot be driven from the
build env. test_packaging_win.py pins the script shape.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node): the handshake ack dropped one app's directories</title>
<updated>2026-09-10T17:26:10Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-10T17:26:10Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=32a86417d0edc3bf5c4cf859243c9e34b29bf1ef'/>
<id>urn:sha1:32a86417d0edc3bf5c4cf859243c9e34b29bf1ef</id>
<content type='text'>
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
`&lt;app&gt;_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 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01YVoHVCcfBqud6ZjG4db3y7
</content>
</entry>
<entry>
<title>docs(node): the ack comment described scalars that are gone</title>
<updated>2026-09-10T16:54:22Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-10T16:54:22Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=2baebeebecd1c1a201d1ce5649ab5d0e04c3617b'/>
<id>urn:sha1:2baebeebecd1c1a201d1ce5649ab5d0e04c3617b</id>
<content type='text'>
The handshake ack's app-directories entry still said "the same three answers in
one shape" and "the scalars above are derived from these and kept for MNP 1.0
clients". Neither is true since the per-app ops were folded into one: there are
no scalars above, and a 1.0 client cannot reach this code at all — the floor
moved to 3.0 with the lease flag day.

A comment that contradicts the code beside it is worse than no comment, because
one of them is wrong and the reader cannot tell which. This is the same fault
the leaseless-bound comment had, one commit earlier.

What it says instead is what is actually load-bearing: `&lt;app&gt;_directories` is
the only form on the wire, and `chat_directory` below is safe as a second name
for one of them because `_app_directories_ctx` derives it on every build rather
than storing it alongside — which is precisely what the removed scalars did not
do.

daemon.py had the same stale reference three lines from the code that produces
these, pointing at `video_root` for the shape a per-group signed setting takes.

Comments only; no behaviour change. Node suite green.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01YVoHVCcfBqud6ZjG4db3y7
</content>
</entry>
<entry>
<title>refactor(mnp)!: one operation for an app's folders, not one per app</title>
<updated>2026-09-10T15:49:58Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-10T15:49:58Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=07ff8b4f6143039fcc74b8cf7c423282bce093c1'/>
<id>urn:sha1:07ff8b4f6143039fcc74b8cf7c423282bce093c1</id>
<content type='text'>
`video_root`, `audio_root` and `photo_roots` are gone — the messages, the
signed operations, the handlers, the `ops` wrappers, the three scalars on the
handshake ack, and the client's handlers for their acks. `app_directories`
does the same thing for every application, keyed by the app's own registry
name, and it is what the SPA has been sending.

The three were the same instruction three times, differing only in the key they
wrote and whether they carried a string or a list. That shape is what made
adding an application mean adding a message type, an op, a handler and a widget;
it also meant three validation paths, and the older ones validated nothing —
a typo was stored and then quietly matched no entry, an app showing an empty tab
with no way to tell "misconfigured" from "no files yet".

**What stays, and why.** `Roster.LEGACY_DIR_KEYS` still reads `video_root` and
friends out of `group_settings`: that is a key on an operator's disk, not on the
wire, and a node upgraded into this must find its own configuration. The Search
page still reads its own older cache keys, for the same reason — the cache
outlives a deploy. `CTX_ALIASES` keeps only `chat`, which is the one app whose
second name something still reads.

The two per-app policy test files go with the messages. What only they held —
the real challenge/response path from message to database, which no other test
exercises — is retargeted at `app_directories` in
`test_app_directories_signed.py`, and the handler's own refusals (unknown app,
malformed `directories`, nobody to authorize it) join `test_app_directories.py`.

Node and common suites 1368 passed.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
</content>
</entry>
<entry>
<title>docs(code): drop the references to a plan file that no longer exists</title>
<updated>2026-09-10T15:30:40Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-10T15:30:40Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=d5442e3a335327b445d27d0f7e8798726ba20973'/>
<id>urn:sha1:d5442e3a335327b445d27d0f7e8798726ba20973</id>
<content type='text'>
Seven comments pointed at sections of `~/next/improve-downloads.md`, which is
not in the tree and not anywhere a reader of this repository can follow. Each
now states the thing it was citing: why a paused transfer holds nothing, why the
lease is taken after the save target and not before, why a chunk request marks
a lease alive, where the leaseless bound's number comes from.

The leaseless comment also said "two files at a time" three paragraphs under
`MAX_LEASELESS_IN_FLIGHT = 12`, left behind when the bound was raised. A comment
that contradicts the constant beside it is worse than no comment: one of them is
wrong and the reader cannot tell which.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
</content>
</entry>
<entry>
<title>fix(node): device messages are authenticated-only, and the code now says so</title>
<updated>2026-09-10T15:30:22Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-10T15:30:22Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=803e654086ac63e2d01fa4eb9cca50a4ebebcb73'/>
<id>urn:sha1:803e654086ac63e2d01fa4eb9cca50a4ebebcb73</id>
<content type='text'>
`device_add_request` and `device_hello` were dispatched behind
`and self._nonce_node`, which reads as "pre-proof, once the challenge has gone
out" — and is not what happens: both branches sit after the
`self._user_id is None` guard, so the nonce is always set by the time either is
reached, and a peer that has not finished its handshake gets "Handshake
required" instead.

The guard is removed rather than the branches moved. Filing a device is not
something a peer needs *in order to* prove possession of the group key, which is
the only reason anything is served pre-proof: the request is countersigned later
by a device already pinned, so requiring the caller to finish its own handshake
first costs nothing and keeps the pre-proof surface at three messages.

A test drives all six device messages through the real dispatcher on an
unauthenticated session, because this is a property of the order of its branches
and of nothing else.

Node suite 1216 passed.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
</content>
</entry>
<entry>
<title>refactor(common): delete the sender-key implementation nothing uses</title>
<updated>2026-09-10T15:30:22Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-10T15:30:22Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=6cf21a019963468cb853e5c763ef0097115efa46'/>
<id>urn:sha1:6cf21a019963468cb853e5c763ef0097115efa46</id>
<content type='text'>
`senderkeys.py` and its 13 tests implemented Signal-style sender keys, and
production has never called them: chat is a key per group, per epoch, per
device, derived by name. The reasoning that ruled the ratchet out stays where it
belongs — in `chatbox.py`, at the top of the module that replaced it — because
the argument is the useful part, and it now stands on its own instead of
pointing at a file to compare against.

Kept code that nothing calls is worse than absent code: it reads as an
alternative somebody may reach for, and it has to be maintained past every
refactor to stay compiling, which is maintenance spent on a decision already
made.

The three comments naming `GroupSenderKeyStore` are rewritten to say the thing
they were illustrating.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
</content>
</entry>
<entry>
<title>refactor(node): QUIC does not relay chat it cannot check</title>
<updated>2026-09-10T15:30:00Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-10T15:30:00Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=efc93c187dba9027292b51ff0e9caa29349c953e'/>
<id>urn:sha1:efc93c187dba9027292b51ff0e9caa29349c953e</id>
<content type='text'>
The QUIC handler stored `payload` as it arrived and broadcast it: no envelope,
no signature check, no `device_hello` to check one against. A message reaching
a group's archive that way is a plaintext row in an encrypted history, and it
would be indistinguishable from one somebody actually wrote.

Removed rather than gated. The transport implements neither the per-device
sealing nor the device identification the WebRTC path requires, so refusing
here would mean maintaining a second, weaker set of rules for a transport with
no client; an unimplemented type is logged and dropped, like every other message
this transport does not have.

The comment on the peer registry loses its chat fan-out aside for the same
reason.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
</content>
</entry>
<entry>
<title>refactor(mnp)!: one answer to "may this member write", and it is the root</title>
<updated>2026-09-10T15:23:40Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-10T15:23:40Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=1dcedc77083b908b7b3b431bad679a4813884355'/>
<id>urn:sha1:1dcedc77083b908b7b3b431bad679a4813884355</id>
<content type='text'>
The group-wide `member_upload` switch is gone: the message, the signed
operation, the field on the handshake ack, the `upload` alias on every root in
the index payload, and the client's fallback path to it.

Whether a member may write has been a property of each root for a while, and
that is the model that survives: a single flag over the group cannot express
"this library is published read-only and that folder is a drop box", which is
the ordinary arrangement. What was left of the switch was a handler that logged
a deprecation and acted on nothing, and a client that read `ack.member_upload`
whenever the roots carried no `writable` — a second source for one question,
with whichever the code consulted first deciding it.

`roots.describe()` drops `upload` for the same reason: it was `writable` under
an older name, and two names for one boolean is one too many.

The paperclip now says "nowhere to write" rather than picking a root, in a group
that has none writable. That is the honest answer; the fallback picked whatever
came first and failed at send time.

Node suite 1215 passed.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
</content>
</entry>
<entry>
<title>fix(node): report the transfer cap the node actually enforces</title>
<updated>2026-09-10T10:20:07Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-10T10:20:07Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=6964ff112fec47498ed778a491cf2a1490392c79'/>
<id>urn:sha1:6964ff112fec47498ed778a491cf2a1490392c79</id>
<content type='text'>
`transfer_state` read `slots.per_member` — the node-wide default — while
`_has_room` decides with `member_cap()`, which prefers the group's own signed
limit, and the handshake ack announces that same `member_cap()`. Three readings
of one number, and one of them was the odd one out.

In a group where the operator signed a higher limit, every lease update told the
client "cap: 2" while the node would grant five: the transfers widget draws
`used &gt;= cap` as saturated, so a member with two transfers running saw the rest
of their slots disappear. Lowered the other way it is worse in the other
direction — the interface offers slots the node will queue.

Nothing was ever granted or refused wrongly; the enforcement was right on both
paths. It is the number beside it that contradicted them.

Two tests, one override above the default and one below, because a bug that
reads the node-wide value passes the first whenever the default happens to be
the larger number.

Node suite 1215 passed.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
</content>
</entry>
</feed>
