<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-node/tests/test_transfer_slots_wire.py, branch 0.16</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.16</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.16'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-09-24T14:45:38Z</updated>
<entry>
<title>refactor(node): move transfer slots and leases out of webrtc_server</title>
<updated>2026-09-24T14:45:38Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-24T07:23:12Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=219f3f7d1caafb45a9f7118f9792f6881501b3e7'/>
<id>urn:sha1:219f3f7d1caafb45a9f7118f9792f6881501b3e7</id>
<content type='text'>
TransferMixin in transport/webrtc/transfer_handlers.py, with the sweep
interval.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>style: the 98 ruff could not fix, so the linter is a signal again</title>
<updated>2026-09-19T12:39:38Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-19T12:39:38Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=9e7b75bb0f6f6649fb00f2dc97059e90b7d52875'/>
<id>urn:sha1:9e7b75bb0f6f6649fb00f2dc97059e90b7d52875</id>
<content type='text'>
The pass before this applied ruff's own fixes. These are the ones needing a
decision, and the point of doing them is that `ruff check .` now passes: a
linter reporting 98 known-acceptable findings reports nothing, because the next
real one arrives invisible.

**Lines over 100 (70).** Mostly wrapped where they stood. Two exceptions: the
aligned trailing comments in `protocol.py`'s message table were shortened rather
than wrapped, because wrapping one row of a table breaks the table; and in
`models.py` the column comments moved above their columns for the same reason.

**Imports below the first statement (14).** `csam.py` kept its FastAPI imports
under a section header halfway down the file; two node tests had a constant and
a `pytestmark` wedged between two import blocks. Moved, not suppressed.

**Bindings nothing reads (4).** Three in tests, where the call stays and only the
name goes — `_user(client, "listener")` is there to create the user, not to
return one. The fourth was in `revocation.py` and was not a lint finding at all:
`_connect_and_listen` opened an httpx stream to the WebSocket URL, did `pass`,
and then opened the real connection through the `websockets` library. One
pointless request per connect, left over from before that library was used
directly. Removed, and `httpx` with it.

**`l` as a name (4)**, **semicolons (6)** in the POC spikes, and the rest.

2893 passed, the same count as the two commits before it.

`meshbay_node/revocation.py` is worth a decision separately: 154 lines that
nothing imports, superseded by `hub_client.maintain_ws`'s `on_revocation`. This
commit only stopped it failing the linter.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</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>
<entry>
<title>fix(node): a running transfer keeps its slot, and a dead grant lets go</title>
<updated>2026-09-08T20:54:16Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-08T20:54:16Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=6803447a8a5cc7a612d08bb858394fd7ae1b049c'/>
<id>urn:sha1:6803447a8a5cc7a612d08bb858394fd7ae1b049c</id>
<content type='text'>
Two defects in the lease machinery, both found in the node's own log, neither
reachable from any test on either side.

**`touch()` was never called.** The node ignored `tr` on `file_req` entirely, so
`used` stayed False for every download ever made and the sweeper revoked each
grant thirty seconds in — while the file was transferring at 20 MB/s. The pool
was correct and the handlers were correct; the call between them was missing,
which is why neither side's tests could see it.

**The requeue was a permanent cycle.** A revoked grant went back in the queue,
was granted again a millisecond later because there was room, and was revoked
again thirty seconds on. The node logged the same two reclaims every thirty
seconds for as long as it ran — minutes after the transfers involved had
finished. Three chances now, then the lease is closed and the peer told.
`test_the_counter_never_drifts` could not have caught it: nothing drifted, the
same lease simply never left.

A lease that starts being used forgets its earlier misses: a client that took
two grants to get going is slow, not abandoned.

Also `transfers show` reported the module defaults rather than the operator's
values until something had transferred, so `transfers set 2 2` answered
"applied now" and the next line said 0/8 — indistinguishable, from outside,
from the hot-swap that did nothing for months. The test asserted the defaults
and so agreed with the bug; found by typing the command.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
</content>
</entry>
<entry>
<title>feat(node): transfer leases, pools and a queue for downloads and uploads</title>
<updated>2026-09-08T12:20:57Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-08T12:20:57Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=bdeffa448cdde9680fbf7bdda036746b101ddf75'/>
<id>urn:sha1:bdeffa448cdde9680fbf7bdda036746b101ddf75</id>
<content type='text'>
Step 2 of ~/next/improve-downloads.md. A download is invisible to the node: it
is a series of independent `file_req` messages, with nothing saying one started
or ended, so there is nothing to count and nothing to cap. The lease is that
missing object.

`meshbay_node/transfers.py` holds the decisions and has no asyncio and no
transport in it, on purpose. The failure modes this has to survive — a slot the
node never gets back, a client waiting on a grant the node has forgotten — are
races through a DataChannel and unprovable there; here the clock is a parameter
and every method returns what changed, so the caller does the I/O and the tests
drive the worst case directly.

What it decides:

- two pools, downloads and uploads, separate from the stream pool: different
  resources with different costs, and merging them makes both caps meaningless;
- per-member cap checked *before* the node-wide one, so a member at their own
  limit queues behind their own transfers rather than holding a slot a second
  member has none of. Per account across their devices, or the cap becomes a
  function of how many tabs somebody opens;
- a queue that skips a member at their cap instead of waiting for them —
  granting strictly in arrival order lets one member's limit stall everyone;
- `tr` drawn by the client and idempotent, which is what makes a reconnect safe;
- bounded per member, because unbounded queues are how a node runs out of
  memory politely.

Every way a slot comes back, with the session teardown as the one that matters
(a closed tab, a quit browser and a dead network all arrive at
`shutdown_tasks`, and none of them needs a timer): explicit close, session
gone, a grant nobody took up in 30 s passed to the next in line, and a granted
transfer silent for 120 s reclaimed with its peer told, so a widget can offer a
resume rather than sit on a lie.

`GET /api/transfers` is the operator's window: when somebody reports a transfer
stuck at waiting, it is the only thing that says whether the node ever had them
in a queue — a log cannot, when the symptom is that nothing is happening. It
carries no filename and no path, which a test pins, because this is exactly
where one would be tempting.

Three things found while writing it, two of them mine:

- the randomised property test rejected `in_use &lt;= cap` at once, and it was
  right to: lowering a cap never interrupts a running transfer, so the count
  legitimately sits above the new value. The invariant is that a *new* grant
  never happens past the cap;
- the sweeper was started with `self._spawn`, which ties a task to one
  session's set. It died with whichever peer opened the first transfer, and
  every other peer's abandoned lease then stopped being reclaimed — a node that
  fills up over days with nothing in the log. It belongs to the node now, with
  its strong reference on the transport context;
- the pools are node-wide while `_peer_registry` is per group (finding H1), so
  a slot freed in one group can grant one in another and the peer to notify is
  not in the notifier's registry. Silently wrong in the first version.

Nothing enforces a lease yet: `file_req` is untouched, no client asks, and the
node grants everything. That is step 4's flag day, and this lands alone.

1148 node, 793 hub, 0 failed.

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