Releases: robertelee78/vox
Release list
v0.3.1
v0.3.1 — network changes, router port mappings, anchors
When the network changes
- The daemon notices a network change as it happens. It listens to the operating system (a routing socket on macOS, rtnetlink on Linux) rather than polling, and counts a change only when the machine's routable addresses or default route differ.
- It acts on the change at once. It forgets the outside addresses it had observed, redoes its port mappings, republishes each attached node's record to its rooms' boards and anchors, and redials every peer and anchor. A connected peer gets messages within seconds instead of waiting out a dead connection. The change is said once in the log and to clients, and
vox status --jsonnames when it happened.
Connections on macOS
- A Mac that listens on every address answers from the address it was reached at. A peer that dials it at one of its addresses while the route back leaves by another, such as a Mac on Wi-Fi with a VPN up dialled at its LAN address, gets its replies. Vox carries a patched copy of quinn-udp for this until upstream has the fix.
- A node bound to one IPv6 address doesn't dial
[::1], where it could never be answered. A join whose link gives no address this node can send to, with nothing able to carry a circuit, says so at once instead of waiting 30 s.
Throughput on Linux
- A Linux node keeps its full speed on paths that carry large packets: nodes or agents on one machine, a jumbo-frame LAN, a VM or container bridge. Vox never sends a batch larger than one UDP datagram: Linux refuses such a batch without a word, and it would read as a path that drops large packets. On a congested 200 Mbit/s link shared with a TCP flow, Vox now carries 186 Mbit/s.
Router port mappings
- The router is found on macOS too, for IPv4 and IPv6, from the default route.
vox status --jsonnames, per address family, which routers were asked and which method answered: PCP, NAT-PMP, UPnP-IGD or a pinhole, or that none did.- A PCP mapping keeps its nonce for every renewal and for its deletion, and renews on RFC 6887's schedule: at a random point between 1/2 and 5/8 of its lifetime, and after a failure, again at 3/4 and 7/8.
- Port mappings are deleted when the daemon stops, and a mapping won by a candidate that lost the race is deleted as soon as the race ends.
- Checked against a real home router (a Netgear Orbi): the mapping was granted, renewed and deleted.
Anchors
- After a key rotation an anchor serves the room's current epoch, not only its first, so members reached through it and new joiners keep up.
vox node's "Elsewhere:" list names only addresses another machine can dial: no loopback.- An anchor's board line says what it counts, in words:
bbgxlstzoacg: 1 member, 0 pending, 5 entries. When the number of entries isn't known, it says nothing about entries.
Agent comms
vox room postrefuses a raw claim-protocol message (claim,release,handoff,renew, adeclinenaming a resource) and names the verb to use. An urgent message addressed to nobody wakes nobody.
Containers
- Vox run as root is told at once that the daemon refuses root, and to run as an ordinary user (in a container, a non-root
USER, e.g.podman run --user 1000 …).vox daemonwill not start as root. - The manual says what a container host needs: on a Linux host,
net.core.rmem_maxof at least 4 MiB, set on the host. A container can't raise it, and with a smaller receive buffer a busy host can cut throughput to a fraction of the link.
Data directories
- A data directory not laid out the way this version reads is refused, with the reason, and left unchanged.
Work items delivered
- #108 RP-01: The agent-comms envelope and claim rules hold, through the shipped binary
- #350 V030-28: adapter_stream_proof measures its claim on v0.3.0's single deterministic order
- #356 V210-137: An anchor tracks every epoch of a room, not only epoch 0
- #368 V030-31: Unproven agent-comms guards get real-binary mutants
- #395 V210-170: An anchor's "Elsewhere" list names only addresses another machine can dial
- #396 V210-171: An anchor's board line says what it counts, in words
- #413 V030-39: A node notices when the machine's network changes
- #414 V030-40: A network change is acted on at once: republish and redial
- #415 V030-41: The router's address is found on macOS, for IPv4 and IPv6
- #416 V030-42:
vox statussays which router was asked and which method answered - #417 V030-43: A PCP mapping keeps its nonce and renews on the RFC's schedule
- #418 V030-44: Port mappings are deleted when the daemon stops, and unused grants at once
- #419 V030-45: Port mapping is checked against a real router (opt-in)
- #423 V030-46: Vox carries no code for data from earlier releases
- #424 V030-47: A client running as root is told at once that the daemon refuses root
- #425 V030-48: The user manual describes v0.3.1, including what a container host needs
- #426 V030-49: On Linux, Vox keeps its share of a congested link: a send batch never exceeds one UDP datagram
v0.3.0
v0.3.0 — one daemon, many nodes; services by address; the family LAN
One daemon, and the nodes that use it
- One
vox daemonper data root (VOX_DATA_DIR). It holds the machine's one port and runs every node attached to it. A node is one identity: you, and one for each agent working beside you. vox node create | attach | detach | list.vox node attachstarts the daemon if none is running.--node <name>(orVOX_NODE), given after the verb, says which node a command acts as. With one node attached, that one is used.- Verbs that hold a session start the daemon and attach their node:
serve,connect,up,forward,lan up. The one-shot verbs (room,status,trust,share,service,app) act only as an attached node, and when it is not attached they say so:vox node attach <name>. - Two commands that start the daemon at the same moment both succeed: the one that loses the race waits for the winner's socket.
- What attaching a node says reaches your terminal. Skipped anchors-file lines, and that the node is carrying on with no anchor, are printed by the verb that attached it, by
vox node attachand in the TUI's notice line, as well as in the daemon's log. - A room the daemon does not reopen is named in its log, with the reason.
vox node(the anchor) is a daemon with its anchor node attached, not a process of its own kind.- The terminal client is a client of the daemon. It has no lock: a node takes its passphrase once, when it attaches, and stops only when it is detached. In the client,
:attachattaches your node,:node <name>acts as another, and:linkshows a room's link.
Rooms
vox room link <room>prints the room's link, and how to send its passphrase: another way than the link (in person, a call, a different app). A member joins with the link and the passphrase.vox room leave <room>: the others are told, and the room is removed from your node. Joining again later works.vox room end <room>ends a room for everyone. Only its creator, or an admin the creator named withvox room admin add <room> <member>, may. An end, or a retention change, made by an admin at the same moment as that admin is taken back does not stand.vox room retention <room> <duration>(1h,1w,1mfor a month, seconds, orforever) sets how long the room keeps messages, for everything already in it.vox room readshows a structured post in words: its kind, the work item and the body, or "file offered: NAME (N bytes)".--jsongives the envelope.- A host sees a refused join: in the terminal client's notice line, on the daemon's stderr and in its log, without offering the passphrase.
- A join in progress is quiet. For a joiner it has just let in,
vox servesays nothing about a sync or a dial that fails while the joiner is still settling, until the joiner's first clean session or 60 s. A failure after that is said, andvox status --jsoncounts them all. vox trust addsays why your key for the member waits: the room has not synced since you joined it, or the member is not yet admitted. It is sent as soon as that changes.- A room is forward-only: a member reads what is posted after you trust it.
Services by address, UDP, sharing
- A service is reached as
<service>.<node>.<room>.vox, and only that way.<node>and<room>are your own names for them (or the node's fingerprint and the room's id).vox serve ssh=22names the service; a bare port is refused. vox servenames who can reach the service: the members you trust, by name, and the members of the room who cannot, by fingerprint. It says it again when someone joins.- A forward into a room not yet synced says it is waiting for the room's first sync, and after its patience is refused with that reason; it never guesses what is shared there.
- UDP over Vox:
vox serve dns=53/udp, andvox forwardcarries a UDP service. vox share <room> <path>serves a file or a folder to the members you trust until--countfetches or--fora time. They fetch it withvox room get. An offer that has ended is said to be gone; an offer withdrawn while it is being collected is said so, naming who stopped sharing it; a sharer that cannot be reached is said so.
The family LAN (macOS)
vox lan up <room>puts a room's members on one virtual LAN, each on its own interface, with discovery (mDNS, broadcast) flowing between members who trust each other. Nothing on your machine is reachable over it unless its port is listed with--allow.- Making the interface needs root, and only a small helper has it:
sudo vox lan helperin one terminal,vox lan up <room> --allow <ports>as yourself in another.vox lan upchecks that the helper answers as one before it does anything, and refuses a socket that is not one at once, naming it. - Every LAN interface reaches its room's LAN, with two or more nodes on one Mac: the helper adds a route scoped to each interface, checks it is there, and reports one it could not add.
Agents
- Each agent is its own node, never a person's.
vox agent plugin claude|codex|opencode --node <name>prints the integration for that node, and the hook acts only as the--nodeit is given. - No character reaches a reader or an agent unseen. Zero-width and other invisible characters, the tag characters that can spell text invisibly, bidi controls and other control characters are all shown as
⟨U+XXXX⟩in the terminal client, invox room readand in what an agent's hook gives it. Emoji built with joiners (👨👩👧, 🏳️🌈) and the subdivision flags are shown whole. - An agent's hook never hangs its harness. A hook whose node is detaching is refused after 8 s, and its registration is bounded at 10 s.
- Proved with real agents. Optional live proofs, each run sandboxed, drive real Claude Code, Codex and OpenCode turns against a room, each through its node's hook; all were green on a live run.
Words
- Help and messages speak of nodes, rooms, room links and trust, and name no design document.
- Errors are said in plain words. A reply a command did not expect is said as a sentence, never as a debug dump; a node detached while a command waits says "node was detached from the vox daemon, so this stopped". The same goes for the app's notices, claim outcomes and a ping's reach.
- A peer's reason for closing a connection is shown, also when the close is larger than the path MTU.
Reliability
- A crash costs you neither a room nor a member. A daemon killed during
vox trust addkeeps the posts made after its restart readable to the member just trusted: where the decision stands in the room's order is saved before the trust itself. A daemon killed in the middle of a join leaves the room either absent or one that reopens; a room your node holds closed can be joined again with its passphrase. - A host going offline while a joiner reads the room is said so and retried, like a room not yet on the board. A failed join prints each of its steps once.
- A
vox connectthat is stopped lets go of the room at once: a client that hangs up mid-request is noticed straight away, so its node's goodbye does not wait on the request. - A member opens sync sessions only with members. Anchors' boards are read, so a member that restarted learns where the others are and can release keys to them.
vox statuscounts a flow on a retired connection: datagrams on a peer's connection that a better path replaced are reported.- Two members dialling each other through a relay at the same moment both get through and keep the same connections at both ends; a circuit that is not kept is closed.
- A first relayed connection asks for its circuit at once, with the request for a direct dial-back running beside it.
- A dial at an address heard on the local network is one attempt among others. If the socket cannot use the address (an IPv4 address heard on an IPv6-only node), the member is still reached through the anchor at once.
Documentation
- The user manual,
docs/manual/, describes v0.3.0 command by command. - ADR-017: trusting a member is the operator's act, made by any process running as the node's user, person or agent, behind the 30-minute passphrase window.
- ADR-012 N-49–N-58: network-change detection, gateway discovery beyond Linux and the PCP mapping lifecycle. These are planned for v0.3.1; v0.3.0 does not have them.
- The v0.4.0 UX research (
docs/ux/): the research, the interview answers and the review notes.
Work items delivered
- #18 R15: Addressing uses fingerprints on the wire and shows keyring names
- #19 R17: The filer can hard-lock a claim; a silent holder can be taken over after a window
- #52 R1: A room has no lifetime message limit; reopen, restart and cold catch-up work at any size
- #53 R2: A room works with about 500 member nodes
- #54 R4: Sync stays bounded against a malformed or oversized request
- #56 R6: History is kept forever by default
- #57 R7: A room admin can set and change disappearing messages
- #58 R8: Shortening retention applies retroactively on every node
- #59 R9: A node can set its own retention; the shortest wins
- #60 R10: An expired message leaves nothing visible, but sync and fork detection keep working
- #62 R11: Members never online together can read each other through an always-on member
- #63 R12: An approver chooses "from approval onward" or "full history" per newcomer
- #64 R13: Messages appear in the same order on every node
- #65 R14: Sender keys no longer needed are deleted
- #68 R22: Untrusting a node or removing a service cuts live sessions immediately
- #69 R23: A refused tunnel fails immediately and names the reason locally
- #70 R24: A forward survives the host restarting or the path changing
- #72 R25: Vox carries...
v0.2.10
What's Changed
- v0.2.10: every known problem fixed (release candidate; draft for CI) by @robertelee78 in #229
- [CI evidence, do not merge] V210-90: slot-cap proof meets the cap on Linux by @robertelee78 in #283
- v0.2.10 integration (CI only — do not merge) by @robertelee78 in #260
Full Changelog: v0.2.9...v0.2.10
Work items delivered
- #3 M21.1: Workers on different Vox versions refuse to coordinate, by name
- #4 M21.2: Ownership is per session and a handoff moves it by fingerprint
- #5 M21.3: A renewal extends exactly one acquisition
- #6 M21.4: A retry is one operation and a conflict is explicit
- #7 M21.5: An adapter consumes the room with no gaps and reads the folded board
- #8 M21.6: Agents post structured observations and never re-read their own
- #9 M21.7: The CLI help, ADR index and agent skill match the contract
- #10 M21.8: Ordinary agent work keeps an external tracker accurate with no operator action
- #28 F17: An OpenCode session opened by hand can be interrupted
- #40 V29-05: A restarted peer's dead connection is replaced without reopening the duplicate mismatch
- #41 V29-06: A chat message is pushed on append, not on the next tick
- #42 V29-07: A message over a relay is delivered
- #49 V29-14: A relayed pair is proven to find a direct path
- #50 V29-15: Both ends agree on a duplicate even when they classify its path differently
- #51 V29-16: Shutting a node down releases its profile before it reports done
- #154 M24.1: Design the tapered controller: signals, thresholds, dwell and hysteresis, reviewed by four models
- #155 M24.2: R41 gets a congested arm and a changing-conditions arm before any controller code
- #156 M24.3: Tier 2, loss-aware Cubic, with the delay-rise guard
- #157 M24.4: Tier 3, BBR, with both-way transitions, hysteresis and rate hand-off
- #158 M24.5: The tapered controller ships, proved on every R41 arm
- #160 A correct passphrase is refused right after a wrong one
- #162 An unshaped loopback transfer occasionally collapses to 14 MB/s
- #163 F18: adapter_stream_proof's 120 s extra wait is explained and removed
- #164
install_sh_prooffailed once, fast, with no message captured - #165 F19: Local order is proved to survive a node restart
- #166 F20: The seeded attempt id's two exclusions are proved
- #167 The
perf_r42relay gate took 915 s on one run - #173 V210-01: A relay circuit works on a daemon bound to an IPv6 socket
- #174 V29-26: Path-MTU raising never makes a Linux node's packets smaller
- #179 A post takes seconds while another member is posting
- #180 A member who joins through another member is seen by the third about 25 s late
- #181 A join that finds no room on the board blames the address
- #182 A relayed join found its anchor's board without the room (1 in 12, unexplained)
- #186 V210-13: A release publishes a commit CI already passed
- #187 V210-14: The gates fit their time limits with margin
- #188 V210-15: The drain self-filter proof measures, and cannot be fooled by a shared fixture
- #189 V210-16: Every unbounded collection reply is paged
- #190 V210-17: The test hang behind the watchdog is found and fixed
- #191 V210-18: A failed attach reports its real error on main
- #192 V210-19: No proof is excluded from the blocking gate by name
- #193 V210-20: Every accepted proof gap is closed
- #194 V210-21:
vox room getwrites only where the receiver says, and never replaces or deletes a file (security) - #195 V210-22: An agent's drain cannot be forged or flooded
- #196 V210-23: Common failures say what went wrong and what to do
- #197 An IPv6-only member waits ~20s for its board: the join dials the link's IPv4 routes first, one at a time
- #198 V210-24: No member can appear as another by matching a short fingerprint prefix (security)
- #199 V210-26: Checking the identity passphrase does not stop the node
- #200 V210-27: The simultaneous-session race is proved, not caught by chance
- #201 V210-28: A watchdog abort leaves no process behind
- #202 A peer's refusal of a colliding sync is reported as "malformed governance struct"
- #203 V210-30: Trusting a member who is unreachable does not lose them the posts made meanwhile
- #204 V210-31: Every claim the test deletion left unmeasured is proven by real use
- #205 V210-32: Every binary proof runs every participant as the binary
- #206 V210-33: A tunnel keeps its packet size through congestion loss (quinn's false black hole)
- #208 A restarted vox daemon cannot reopen its rooms (and the error names the room "")
- #209 V210-34: Sync is scheduled like a switch, not a hub (ADR-025)
- #210 V210-37: A key that arrives before its consent must not leave earlier posts unrendered
- #211 V210-38: A malformed or unknown IPC request says so, not "malformed identity bundle"
- #212 V210-39: Two peers with large backlogs for each other always make progress
- #213 V210-36: a hung proof's watchdog dump samples the test process, not the hung vox child
- #214 V210-40: Every at-rest secret is sealed as strongly as the identity (post-quantum)
- #215 V210-41: Opening a forward never holds the node's actor on a dial
- #216 V210-42: A failed sync says which end stopped it (#202 follow-up)
- #217 V210-43: An anchor never refuses a room's member as a pending joiner
- #219 V210-44: A tunnel's slow start ends on a delay rise, not on loss (HyStart++)
- #220 V210-45: A member entitled to a room's whole history reads all of it, not only the last 1,000 derivations' worth
- #221 V210-46: A hole punch lands when the anchor is dual-stack
- #222 V210-47: A node dials only addresses its own socket can reach
- #223 V210-48: A node advertises only addresses its socket actually listens on
- #224 V210-49: Deleting the consent counter does not release a closed room's history
- #225 V210-50: A loopback tunnel sometimes runs a whole setup at 20–55% of the usual rate
- #230 A fresh process's own address is refused by its board for a second, and every cold vox forward says so
- #231 two_backlogs_meet_proof's setup is red 1 run in 5: carol cannot read bob's hello
- #232 V210-53: Two reaches to one peer share one circuit
- #240 V210-54: The TUI member-names proof never hangs on macOS CI
- #241 V210-55: A power loss while the vault is written never loses the identity
- #242 V210-56: A power loss during vox update never leaves a broken binary
- #243 V210-57: A node that loses its anchor reconnects promptly, not on the next 30 s tick
- #246 V210-58: A daemon resumed after a freeze reconnects to live peers
- #247 V210-59: vox update --rollback recovers from an interrupted rollback
- #249 V210-60: two vox room gets at once are each given their own forward
- #250 V210-61: a fresh process whose clock is behind its predecessor's is still taken by its board
- #251 V210-62: the back-to-back join proof bounds the host's wait and catches a one-at-a-time host
- #252 V210-63: Equivocation below the head is detected, not left as a silent stall
- #253 V210-64: A real clock step behind a predecessor is cured on both clocks, with drift bounded
- #254 V210-65: The join proof catches a slow host, wherever the host's delay lands
- #255 V210-66: Equivocation: the anchor keeps it, the TUI names every one, the proof's restart cannot be fooled
- #256 V210-67: The work-version proof tests the version, not a race
- #258 V210-68: A node renews its own address record before it expires
- #259 V210-69: An anchor stops on Ctrl-C even when the signal lands with its tick
- #261 V210-70: A board admits as Member only verified room members, and a stranger cannot abuse it
- #262 V210-71: No peer, and no slow operation, can stall the node's actor or a room lock
- #263 V210-72: The control socket, passphrases and shared files cannot leak to another local user or process
- #264 V210-73: A received message is never overwritten after a restart
- #265 V210-74: A hostile entry cannot freeze, brick or silently empty a room
- #266 V210-75: Anchors and port mappings are renewed and followed for the life of the node
- #267 V210-76: Locking wipes every secret, and consent is recorded atomically
- #268 V210-77: Prekeys, pending consents and a new profile stay usable for the life of the node
- #269 V210-78: Consent and pairwise sessions behave predictably between members
- #270 V210-79: An agent's wake is safe, and claims and loops are bounded
- #271 V210-80: Connection and join bookkeeping has no races across restarts and locks
- #272 V210-81: Tunnels cannot starve a connection's other streams, and a tunnel's last bytes arrive
- #273 V210-82: The TUI shows the room truthfully, and consent acts on the member you chose
- #274 V210-83: Every CLI error names its real cause, and every exit code tells the truth
- #275 V210-84: Two sends of the same file are independent offers
- #277 V210-85: Every vox connect failure says why
- #278 V210-86: After an anchor restarts, every member is back promptly
- #279 V210-87: A newcomer's join completes in the debug build too
- #280 V210-88: A crash right after a consent never costs the member the room
- #281 V210-89: Two members who open sessions at once always end up reading each other
- #282 V210-90: The slot-cap proof measures on Linux
- #285 V210-91: Two vox id runs at once never lose an identity
- #286 V210-92: A stranger cannot block real joins by holding a member's join slots
- #287 V210-93: A node notices that its anchor went away, in every build
- #288 V210-94: A lock wipes every secret at once, including work in flight
- #289 V210-95: A member who has taken the room's first key is remembered as having it
- #292 V210-96: An address is printed only once its room can be joined through it
- #293 V210-97: A post is queued and opened once per port, never twice
- #295 V210-99: Every proof gives a verdict in debug, and a watchdog abort leaves no proces...
v0.2.9
v0.2.9 — relays deliver in milliseconds, tunnels run at line rate, and keys reach the member they were meant for
Gated on macOS locally and on Linux in the release workflow (fmt, clippy, the debug suite, rustdoc and the release proof suite, run to completion).
Faster
- Tunnels no longer throttle the link. Measured against raw TCP over the same emulated link (PRD-001 R41): 98% of line rate on a 1 Gbit/s LAN and at 50 ms round trip (15% before on the WAN-like link), and about 4× the raw efficiency on loopback. The larger path MTU is used only when the operating system actually grants the socket buffer it needs. On Linux with the default
net.core.rmem_max, it falls back to the standard 1452 bytes and says so once per process on stderr; raisenet.core.rmem_maxandnet.core.wmem_maxto at least 4 MiB (for examplesysctl -w net.core.rmem_max=4194304 net.core.wmem_max=4194304) to get the larger packets there. - Chat over a relay: median 26–38 ms, where v0.2.8 took 1.0–1.6 s.
- Restarts: a restarted member is reachable again in about 25 s, down from about 60.
Messages and keys
- A trusted member reads the first room it joins; before, the host's key was refused and never sent again.
- A key the recipient did not take is sent again, and the log says why, instead of being counted as delivered.
- A sync batch no longer loses messages silently for one member when another member's entry in it is refused.
- What a joiner posts the moment its join returns is readable by the host.
- A person reads their own post straight after posting it, even while other members are posting (0 misses in 600, against 68 before), and the room list count agrees with it (#178).
- A room with more than about 256 KiB of history can be read, tailed and worked on from the command line again; before,
vox room read/tail/boardandpost --opfailed on it with "declared size exceeds hard limit". - A post reports its own outcome: two racing posts under one work op no longer both succeed (#177).
No more freezes
- A node no longer stops answering for 30–80 s when a peer vanishes mid-session.
- Building the node's view waits at most 250 ms in total for rooms that sync sessions are busy with, however many rooms there are.
vox trust addno longer freezes the node for up to 60 s when trusted members are offline.
Correctness and safety
- A room's log goes only to that room's anchors, never to an anchor another room named.
- A restarted member's board records are no longer refused as older than its previous ones.
- Work-item interop (PR #14): claims, hand-offs and results between agents.
Known issues
- About 1 relayed message in 250 can wait around 30 s when both ends push at the same instant (#41). Fixed in v0.2.10.
- A first request to a member that just restarted can take about 10 s, roughly 1 in 20 over a relay: the other end keeps the restarted member's old connection for up to 30 s. Fixed in v0.2.10 by the restart probe (#40).
Moved to v0.2.10
v0.2.8 — a second joiner gets in, and a room stops going silent
v0.2.8 — a second joiner gets in, and a room stops going silent
Update with vox update. There's no compatibility step. v0.2.7 was tagged but never published: its release gate caught the sync defect below, and everything it contained ships here.
Fixed
- A second person joining right after the first is no longer locked out for 30 seconds. The node used to finish one connection handshake before it would start the next, and
vox connectexits as soon as it has joined, leaving the host mid-handshake. Handshakes now run concurrently, up to 64 at once. A flood of spoofed connection attempts can't use up those slots: an unverified source address must prove it can receive before anything is allocated to it. Measured: a host with two back-to-back joiners now lets the second in 5 times out of 5, against 0 out of 5 before. - Two members of a room could stop exchanging messages for good. When a member had both an anchor and another member to sync with, a failing sync with the anchor could take the room every round, so the direct sync was skipped every round too. A skipped sync is now owed and retried a second later. This had been failing CI intermittently since before v0.2.5. Measured in the configuration that breaks it: messages crossed in about 2s, where before they never did.
- The two ends of a connection could keep different copies. When a node had two connections to the same peer, each end chose which one to keep by arrival order, so they could disagree, and one end would go on using a connection the other had closed. Both ends now choose with the same order-independent rule. Duplicate pairs where the ends disagreed: 2 of 5 before, 0 of 28 after.
- A board no longer refuses a record it already holds. Re-announcing an unchanged record is now a no-op rather than a reported refusal. A changed record (your routable address, a new prekey bundle) is accepted immediately, where before it was refused for up to 60 seconds after startup.
- A member's node no longer freezes for 30–80 seconds. A peer's board, coordination and relay streams were served one at a time, so each waited behind the last, 20 seconds at a time. They're now served concurrently. Measured: freezes of 60s, 60s and 21s went to a single 20s (the last is being fixed in v0.2.9).
- A daemon started just as another vox finishes with the profile waits for it instead of failing with "another vox already has this profile open".
- Opening a stream can no longer wait forever. It's bounded at 20 seconds, and a dead connection now reports
Unreachable, notInternal. - A rate-limited member is told so (F14), with when it clears, instead of a generic failure.
- An urgent message from another node now interrupts its addressee (F15). Before, only messages written on the same node did.
service_rehearsal_proof, a real sshd reached through the overlay by a stranger who joins, is back in the blocking release gate after being excluded for the 30-second lockout.
Next, in v0.2.9
The remaining known defects, each tracked on the release-hardening board (epic #29): a node can still stall for up to 20s when a sync session holds a room's lock while it publishes; two members who both joined (neither created the room) cannot yet read each other (F12); a non-member can be served a room's sync; and the posting quota is being removed.
v0.2.6
v0.2.5
v0.2.1
v0.2.0
v0.1.0
Full Changelog: https://github.com/robertelee78/vox/commits/v0.1.0