<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-node/src/meshbay_node, branch 0.20</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.20</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.20'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-10-15T15:45:41Z</updated>
<entry>
<title>chore: bump version to 0.20.0</title>
<updated>2026-10-15T15:45:41Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-15T15:43:15Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=63aa0ddd8c0ec49fe8b52b6b486b079887ed3112'/>
<id>urn:sha1:63aa0ddd8c0ec49fe8b52b6b486b079887ed3112</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>test(node): the event-loop disk guard watches os.stat again</title>
<updated>2026-10-15T12:37:45Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-15T12:37:45Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=9dd6d1003cf92c0e4e7f23414565314691aa5516'/>
<id>urn:sha1:9dd6d1003cf92c0e4e7f23414565314691aa5516</id>
<content type='text'>
test_a_scan_asks_the_disk_nothing_from_the_event_loop patched Path.stat,
and since the indexer learned Windows' long paths it calls
os.stat(long_path(p)) and os.scandir instead: the guard saw no call at all,
and asserting that the calls ran on the indexer's thread failed on an empty
set. It watches os.stat and os.scandir now (Path.stat goes through os.stat
too), counting what the root holds and not the root, whose availability
check runs on the roots' I/O thread. Nothing runs on the loop; a stat added
there makes it fail again.

The node CLI's parser names itself: under `python -m pytest`, Python 3.14
named the program after the module, so test_cli_golden saw "python -m
pytest" in the help. The installed meshbay-node was not affected.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node): a removal made at the machine is in audit.db</title>
<updated>2026-10-15T12:24:44Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-15T12:24:44Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=283771ff468bd9e1a60a4902a5ada4319c063073'/>
<id>urn:sha1:283771ff468bd9e1a60a4902a5ada4319c063073</id>
<content type='text'>
The Node page and the CLI remove access through the loopback API, and
revoke and unpin there wrote nothing to audit.db; only the signed MNP door
did. A removed person's next connection then showed an auth_failed with
nothing before it to explain it. Both routes now record member_revoke and
member_unpin once the operation has succeeded, with the node's own account
as the actor and 127.0.0.1 as the address, which tells the two doors apart.
A refusal changes nothing and records nothing.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>refactor(chat): no thread_id — chat has no threads</title>
<updated>2026-10-15T10:57:51Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-15T10:57:51Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=a86bced1640ac1a695b95ac68b95e3fc581e687d'/>
<id>urn:sha1:a86bced1640ac1a695b95ac68b95e3fc581e687d</id>
<content type='text'>
Nothing ever set it (the page and the CLI sent null) and nothing read it
(the node's get_thread had no caller). It is no longer sent by the page or
the CLI, in clear or in the sealed payload, nor relayed or served by the
node; get_thread and its index are gone, and the protocol and design docs
no longer name it.

The node still accepts a chat_msg carrying one, from the desktop apps and
CLIs already installed, and drops it. The column stays, empty, in chat.db:
a node downgraded to an older version names it in every query.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node): an operator who was a member first holds the authority at once</title>
<updated>2026-10-15T10:32:24Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-15T10:32:24Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=e0a1036e0fee15b3c6c83f3e935d50f472a2e776'/>
<id>urn:sha1:e0a1036e0fee15b3c6c83f3e935d50f472a2e776</id>
<content type='text'>
A member pinned on the node who then redeems the operator pairing code was
written down as the operator but the node kept its "nobody can authorize"
flag until it restarted: every admin operation — removing a folder, inviting,
the apps' settings — was refused meanwhile. Only a key paired fresh set it.

The join result now names `operator` for an account holding the node-wide
operator row, whatever its row in the group says. The page and the CLI read
that role to know what to offer, and refused before asking deletions the node
would have accepted. Nothing is granted by it: every admin operation is still
checked against the operators' keys.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(hub): one account, several linked nodes</title>
<updated>2026-10-15T07:33:28Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-15T07:33:28Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=1e8eae1f3de765fcd8163716de6e5fe5aab5a034'/>
<id>urn:sha1:1e8eae1f3de765fcd8163716de6e5fe5aab5a034</id>
<content type='text'>
An account links up to MAX_NODES_PER_ACCOUNT node keys (node_keys table)
instead of the one users.pk_node_ed25519; the migration carries each
account's key over and the downgrade keeps the last one linked.
/v1/nodes/auth accepts any linked key, and its token names the key that
won it (nk). PUT /me/node_key adds a key, idempotently, 409 past the
limit; DELETE /me/node_key names the key: a browser session any of its
own, a node only its own. Unlinking a key closes that node's socket and
refuses its tokens at once, on REST and on the socket; the account's other
nodes are untouched. /pubkeys no longer gives a node key to other accounts,
and gives the caller its last linked one, which installed desktop apps read.

The node reports waiting_for_node_key while its key is unlinked and signs
in again as soon as the hub says so, so a key linked again is back within
seconds; unlinking itself signs in first to hold a token naming its key.

Profile -&gt; Linked nodes lists each node (connected, last seen, IP, linked
since, This machine in the desktop app) with Copy and Unlink, and adds a
key by hand. The Node page banner, Create group and the desktop app add
this machine's key instead of replacing another's. Messages that pointed
at Settings -&gt; Link Node point at Profile -&gt; Linked nodes.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node): a chat-epoch notice answers no request, not even the asker's</title>
<updated>2026-10-14T22:56:04Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-14T22:56:04Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=16d92c96bde3a65fc54a93b4662df30cafc831e4'/>
<id>urn:sha1:16d92c96bde3a65fc54a93b4662df30cafc831e4</id>
<content type='text'>
Revoking a device or removing a member opens a chat epoch, announced to
everyone connected to the group, the asker included. Sent inside the
handler, the asker's copy was stamped with its request's id and arrived as
the answer to device_revoke, before the real one. It now leaves outside the
reply context.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(node): accept a device link request before the proof (MNP 6.3)</title>
<updated>2026-10-14T20:20:15Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-14T20:20:15Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=28c88908b88c58cba4de08710f6bb268ede02d57'/>
<id>urn:sha1:28c88908b88c58cba4de08710f6bb268ede02d57</id>
<content type='text'>
A new device of a known account holds no identity the node pinned, so it
cannot complete a handshake until a pinned device approves it: it now files
device_add_request in the pre-proof window, as the desktop app's case
already required. The request stays inert until countersigned; at most 10
are pending per account, the oldest dropped. A 6.2 node answers it
"Handshake required", which a client reads as "cannot link here".

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node): index, serve and write files past Windows' MAX_PATH</title>
<updated>2026-10-14T09:17:38Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-14T09:17:38Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=daec4bc6a2a9f9e038e5269586b06018ff327a27'/>
<id>urn:sha1:daec4bc6a2a9f9e038e5269586b06018ff327a27</id>
<content type='text'>
With the system's default setting (LongPathsEnabled off) every call given
a plain path of 260 characters or more refuses it, so a deep library was
partly invisible: the walk could not list such a directory and its files
never reached the members.

The node now hands each call that touches a group's disk the extended
form (paths.long_path), and keeps the plain path in the index, the hash
cache and the log. A directory is listed under its long form and its
entries named from the plain one (indexer.walk_files, wire._walk_dirs).
Covered: the walk, stats and hashing, reconcile and the watcher, the
folder list members see, serving over WebRTC and QUIC, deletion, uploads
and their recovery, folder creation and removal, audio tags and the
cover beside a track, photos, and a show's episode numbering. ffmpeg
extends a long path itself and keeps the plain one.

long_path now normalises before prefixing (the extended form skips the
system's normalisation) and starts at 248 characters, where creating a
directory is already refused.

test_long_paths.py runs with the setting off: 15 of its 17 tests fail
without the change. Checked by hand in the installed app with files at
312 to 380 characters.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(node): skip a file the disk stops answering for, then pause its root</title>
<updated>2026-10-14T08:17:49Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-14T08:17:49Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=80ac882346b474bb7ea4e8c8afd4694bd13dd9dd'/>
<id>urn:sha1:80ac882346b474bb7ea4e8c8afd4694bd13dd9dd</id>
<content type='text'>
A Windows tester's scan stuck for good on one directory of a large disk:
one read that never returned held the indexer's only thread, and with it
every root behind that one.

- A disk call is timed by its silence, not its length: each directory
  entry listed and each megabyte read counts as an answer. After 60 s of
  silence the call is abandoned on its daemon thread, the file is noted
  unread and the scan goes on.
- Three silent files in a row pause the root (not persisted). Resume
  skips the files already known to give no answer; a restart, a replug
  or a change to the file retries them.
- The walk is per directory with os.scandir: a subdirectory that cannot
  be listed costs itself, and a listing that stops answering pauses the
  root at once. A scan no longer stats anything from the event loop.
- The log names the file after 10 s of silence, the indexing band shows
  it, and Group settings shows the paused root, where it stuck and the
  unread files with their reason, on the node's machine only.
- generate_http_api.py writes LF on Windows too.

Verified with real kernel-blocked opens (an unacknowledged oplock) in the
installed frozen app.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
