ANetBBS Changelog

Versions are internal build numbers. Public releases are tagged
separately. Current release: v1.0b2.58 (July 2026). Full release: August 1 2026.

v1.0b2.43 — Public "apply to join this network" form (July 2026)

  • FEATURE: a hub sysop can now enable a public web page (/join/) where anyone can apply to join their echomail network — read the rules, download the full infopack, check a box confirming they read the rules, then submit an application covering both BinkP and QWK transports in one form (leave either section blank if not applicable). Configured from Hub Management's new "Join Form" tab: enable the feature, upload a single infopack zip, and the system auto-picks the largest .txt member inside as the rules text (correctly picks annet.txt over readme.txt/systems.txt in a real-world infopack), with a dropdown to override if the auto-pick is wrong. Applications land in a new "Join Requests" review queue (mirrors the existing QWK node request flow) and notify every admin via the notification system built earlier this release cycle. On approval, creates a BinkPNode and/or QWKNode — whichever transport(s) the applicant filled in, zero, one, or both — with hub-generated passwords (never applicant-supplied, matching the existing QWK approval rule), and emails the applicant their credentials if SMTP relay is configured (falls back to a flash message telling the sysop to relay them manually if not). Denials can include a reason, also emailed if SMTP is set up. Rate-limited per IP to guard the fully public, unauthenticated form against spam. Not hardcoded to any one network — any sysop running their own ANetBBS hub can enable this and upload their own infopack. 29 new tests across tests/test_network_join.py and extensions to tests/test_admin_review_notifications.py.

v1.0b2.42 — Admin notifications for things needing sysop review (July 2026)

  • FEATURE: the sysop asked "for any incoming request, I should have a notification... I don't know unless I go looking for it," specifically citing MSP federation join requests and QWK node applications. A research pass confirmed the in-app notification system (anetbbs/features/notify.py) already existed with per-kind user preferences but had zero call sites for any admin-review event — a sysop had to manually visit each admin page to discover any of them. Added a notify_admins(kind, ...) helper (notifies every is_admin=True user, each still honoring their own per-kind notify_prefs toggle) and wired it into four gaps at their actual creation site: MSP federation registry join requests (fires once the registrant verifies their contact email, not at the earlier unverified-registration step, since that's the first point actually actionable for the sysop), QWK node applications (both the hub-facing API route and the direct-terminal-write path used when someone applies while connected straight to the hub), new users pending NUV approval (deliberately not the separate email-verification path, which is fully self-service and would just be noise), and newly-discovered unknown/bad echomail areas (fires once per area, not once per message, so a large batch of misdirected mail can't flood the inbox). New admin-only preference toggles appear in Notification Settings, visible only to admin accounts. 7 new tests in tests/test_admin_review_notifications.py.

v1.0b2.41 — Stop self-referential polls from flooding the poll log (July 2026)

  • FIX (live-caught): a hub's own network row pointing at itself (e.g. a QWK hub's local "ANotherNetwork" rows) gets correctly skipped by the poller every minute, but was logging a full EchomailPollLog row each time — dozens of identical "skipped" entries drowned out real poll activity in the admin UI within 20 minutes of uptime. This is static, unchanging configuration state, not a real poll attempt, so it no longer creates a log row at all; a single debug-level line in the application log (invisible at the default INFO level) is enough for anyone actually debugging this specific thing.

v1.0b2.40 — Fix total QWK message loss on every packet + Subscribe to All (July 2026)

  • FIX (live-caught, critical, affects every message to every QWK node ever): _build_messages_dat() in anetbbs/echomail/qwk_hub_ftp.py wrote every message-header field from byte 113 onward at the wrong offset compared to what _parse_messages_dat() (the reading side, verified against real Dove-Net packets) expects — including using binary encoding for the block-count field where the real QWK format requires 6-char ASCII text. The conference number specifically landed 3 bytes off from where the reader looks for it, so every message's conference number came back as garbage on read — which never matched a real conference in CONTROL.DAT, so every single message was silently dropped inside the parser itself, before ever reaching the poller's "received" count. Net effect: a QWK poll would report clean "success" with 0 messages received and no error at all, no matter how many real messages existed in the subscribed areas on the hub — every message, to every node, always silently vanished on the wire. Found live testing the real ANotherNetwork QWK flow end-to-end, after confirming real messages existed on the hub and a fresh high-water mark, ruling out every other explanation first. Fixed by rewriting the header to the correct, verified byte layout. 3 new tests in tests/test_qwk_packet_roundtrip.py doing a full hub-write → client-parse round trip, verified to reproduce total message loss before the fix and pass after.
  • FEATURE: Hub Management → QWK Nodes → node detail gets a "Subscribe to All" button, adding every active area on QWK-transport networks in one click instead of one area at a time. Deliberately scoped to QWK-type networks only — a QWK node has no business receiving areas that only exist on a BinkP-only network. 3 new tests in tests/test_qwk_subscribe_all.py.

v1.0b2.39 — Fix QWK node FTP home directory path-doubling (July 2026)

  • FIX (live-caught, real bug, affects every QWK node): AnetbbsAuthorizer.validate_authentication() in anetbbs/ftp/server.py computed a QWK node's FTP home directory by passing self.qwk_root (already <DATA_DIR>/qwk-hub, set at construction) into ensure_node_dir() — which appends its own qwk-hub segment and expects the bare DATA_DIR instead. This doubled the path to <DATA_DIR>/qwk-hub/qwk-hub/<PACKET_ID>, one level away from where on_login()'s generate_node_packet() (which correctly receives the bare DATA_DIR) actually writes the packet. Net effect: login always succeeded, packet generation always "succeeded" with no exception, but the FTP client's session was rooted at an empty directory no matter how correctly everything else was configured — RETR always failed with 550 No such file or directory. Found live, testing the real ANotherNetwork QWK flow end-to-end between bbs.a-net.fyi and a Pi peer install, after ruling out the network, Python version, eventlet, and the stored credentials as the cause one at a time. Fixed by computing the home directory directly from self.qwk_root without the extra ensure_node_dir() call. 2 new tests in tests/test_ftp_qwk_node_home_dir.py, verified to reproduce the exact bug before the fix and pass after.

v1.0b2.38 — Better diagnostics for QWK download failures (July 2026)

  • FIX: QWKClient.poll()'s error message included only str(exc), which is empty for some exception types (e.g. a bare connection timeout) — producing an uninformative "QWK: failed to download packet:" with nothing after the colon. Hit live trying to diagnose a real QWK poll failure between bbs.a-net.fyi and a Pi peer install, where the FTP connection itself was confirmed reachable (a raw curl -v ftp:// test succeeded end-to-end, including passive-mode data transfer) but the poller's own connection attempt failed instantly with no useful detail and no _ftp_download() login log line ever appearing. Fixed to always include the exception's type name alongside its message, so the next occurrence of whatever's actually failing shows up clearly in the poll log instead of a bare colon. 2 new tests in tests/test_qwk_error_detail.py.

v1.0b2.37 — Fix QWK node application crash + FTP login confusion (July 2026)

  • FIX (live-caught, real bug): checking QWK node application status a second time (via the terminal's Echomail → "A" apply screen, after already being approved) crashed with DetachedInstanceError at req.created_at.strftime(...) in _show_qwk_request_status(). Root cause: _apply_qwk_node() queried the existing QWKNodeRequest row inside one with _app().app_context(): block, then passed that ORM object to _show_qwk_request_status() after the block (and its DB session) had already closed — and that function's own internal hub-refresh commit (in a second, nested app_context()) expired the row's attributes, so touching them afterward with no active session raised the error instead of silently re-fetching. Fixed by having _show_qwk_request_status() take an id and do one fresh query inside a single, consistent app_context, extracting every field it needs into plain local variables before that context closes. 3 new tests in tests/test_qwk_apply_status_detached_instance.py.
  • FIX (live-caught, real bug): the QWK poller used only the qwk_username field for the FTP login username, with no fallback — but QNET-FTP-style hubs (like ANotherNetwork) authenticate using the Packet ID as the username. A sysop who filled in Packet ID but left QWK Username blank (an easy mistake, since the old form label didn't explain the two fields needed to match) got a silent, always-failing login with an unhelpfully empty error message. Fixed with a defensive fallback — blank qwk_username now falls back to qwk_packet_id, which can only help since a blank username always failed anyway. Also rewrote the QWK Username field's label in Admin → Echomail Networks to explain this requirement explicitly, and updated the applicant-facing approval screen to tell the sysop to set both fields to the same value. 2 new tests in tests/test_qwk_username_fallback.py.

v1.0b2.36 — Fix unreachable QWK node application on a fresh install (July 2026)

  • FIX (live-caught, real bug): the terminal "Echomail Networks" screen (list_echo_areas() in anetbbs/features/bbs_ui.py) returned immediately with "No echomail areas configured." whenever a sysop had zero active networks and zero subscribed areas — which is exactly the state of every fresh install, since both EchomailNetwork.is_active and EchoArea.is_subscribed default to false on seed. That early return happened before the code ever reached the "A = Apply for ANotherNetwork QWK node" option a few lines down, making the apply flow completely unreachable for precisely the sysop it exists for: someone who hasn't joined the network yet. Found live by Jerry testing his Pi peer install after updating and trying to apply for a node. Fixed by no longer bailing out early — the screen now always offers the Apply option, showing a "no areas configured yet — apply below" message instead of just quitting when there's nothing to list. 2 new tests in tests/test_echo_areas_empty_apply_option.py, verified to reproduce the exact bug before the fix and pass after.

v1.0b2.35 — Full documentation + wiki accuracy pass (July 2026)

A ground-up re-audit of every doc file and all 43 wiki pages (5 parallel review passes, then 4 parallel fix passes), covering both correctness and completeness — not just "is this wrong" but "is everything that exists actually documented."

  • FIX (real bug, not just docs): install.sh hardcoded MRC_BRIDGE_PORT=8080 unconditionally, while test/behind install modes default WEB_PORT to 8080 too — a fresh Pi/hobbyist-mode install with the MRC bridge left at its (also-default) enabled setting would have gunicorn and the MRC bridge both trying to bind 127.0.0.1:8080. Fixed by deriving the MRC bridge's default port from WEB_PORT+1 instead of a fixed value, computed once and reused for both the .env template and the bridge's own config.json.
  • FIX (real bug): the preflight check meant to catch exactly this class of collision after the fact (anetbbs/web/preflight.py) was itself broken — it tried to regex a port number out of the MRC bridge's systemd ExecStart line, but that line never contains one (the bridge reads its port from its own config.json, not a CLI arg), so the check silently never fired. Fixed to read config.json's web_listen_port directly. 4 new tests in tests/test_preflight_port_collision.py.
  • FIX (docs, real mistakes found and corrected): wrong config var cited for terminal-node count (GAMES_MAX_NODESBBS_NODES) in the install doc; /auth/register's rate limit misclassified as in-memory when it's actually DB-backed; missing Finger/BinkP systemd unit instructions and an FTP setup walkthrough in the manual-install doc; wrong systemd unit name (anetbbs-webanetbbs) for the FTP capability fix in two places; hub_generate_nodelist missing from the scheduled-events handler table; two Synchronet-compat-shim doc claims that flatly contradicted the actual shim code (user.security flags, bbs.exec()/bbs.menu()).
  • FIX (wiki, real mistakes): wrong Telnet/SSH port numbers on the two most-viewed pages (home, features said TCP 23/22; real defaults are 2233/2234); a fabricated claim of a real Gemini-protocol (TCP 1965) listener that doesn't exist; a wrong admin path for QWK hub management; a wrong field list for bulletins (claimed Title/Body/Active/Order — real fields are Title/Content/Pinned/Expires-At); journalctl -u anetbbs-telnet (retired unit) instead of the real unified anetbbs service, in two places; a fictional flask db migrate/alembic migration workflow that contradicts the wiki's own (correct) Architecture page describing the real auto-sweep mechanism; a reference to a nonexistent anetbbs/seed_data.py; a wiki page (irc-bridge) describing an admin-configured Chat↔IRC bridge that doesn't exist, rewritten to describe the real MrcIrcBridge (no web UI, one MRC room ↔ one IRC channel, run via a systemd template unit).
  • NEW CONTENT (docs + wiki): substantial coverage added for previously-undocumented-or-barely-documented features — ANotherNetwork as a named, pre-configured network (26 message + 9 file echo areas, new dedicated wiki page), the EchomailNetwork.ftn_domain field, AreaFix, netmail as a first-class concept, the Hub Management "Generation & Distribution" panel (nodelist/QWK preview/TIC distribution), auto-hatch-on-upload for network-attached file areas, the achievements system, the casino/wallet economy, the ebook reader, the separate anetbbs-install/anetbbs-upgrade Python installer toolchain (documented as a lightweight alternative to install.sh, not brought to parity — its real gaps vs. install.sh are disclosed), a new wiki page for the real per-user /irc/ personal IRC client, and a Docker-containers disclaimer noting the path is unit-tested with mocks but not yet run against a real Docker daemon.

v1.0b2.34 — Hub "Generation & Distribution" admin UI (July 2026)

  • FEATURE: Hub Management (/admin/echomail/hub/) gets a new "Generation & Distribution" panel with three tabs:
  • Nodelist: manual "Generate Now" button + a weekly-by-default schedule (via the existing ScheduledEvent framework), publishing the ANotherNetwork nodelist directly into the ANN.FILES.NODELIST file area (replacing the prior copy) so peers pull it like any other file-echo entry, instead of only the pre-existing public HTTP link. New EchomailNetwork-agnostic handler hub_generate_nodelist, only seeded on installs with REGISTRY_MODE_ENABLED (a peer install has no downstream BinkPNode data of its own to publish).
  • QWK Packets: a "Preview" button per node builds and downloads a packet on demand for testing/verification — deliberately does not call mark_qwk_sent(), so previewing never consumes the node's real unsent-message queue.
  • TIC / File Distribution: status dashboard (pending/failed counts) for a new real gap that got fixed alongside this — files uploaded to any ANN.FILES.* (or any other network-attached) file area now automatically queue for TIC distribution to every subscribed peer (hatch_local_file() in echomail/tic.py, mirroring the existing inbound-TIC hatch-out fan-out logic), across all 4 upload code paths (regular upload, sysop direct upload, smart-upload, and file-queue approval). Purely local file areas (network_id is None) are unaffected.
  • 6 new tests in tests/test_hub_generation_features.py covering nodelist replace-on-generate, the ScheduledEvent hub-only seeding gate, hatch fan-out to multiple peers, a real route-level upload test confirming auto-hatch end-to-end, confirming local-only areas don't hatch, and confirming QWK preview doesn't touch the high-water mark.

v1.0b2.33 — ANotherNetwork file areas + real infopack (July 2026)

  • FEATURE: ANotherNetwork gets 9 new TIC file-echo areas (ANN.FILES.NODELIST, .INFOPACK, .BBSSOFT, .DOORS, .EBOOKS, .LINUX, .RETRO, .ANSIART, .TEST), seeded automatically the same way the 26 message areas already are. ANN.FILES.NODELIST is flagged is_nodelist_source so inbound TICs there auto-populate the nodelist. Found and fixed a real bug while wiring this up: unlike EchoArea.tag, FileArea.tag has a database-level UNIQUE constraint, so file areas attach to the BinkP network entry only (not duplicated across both BinkP and QWK entries the way message areas are) — TIC distribution is a BinkP-native mechanism anyway.
  • New distributable infopack for other sysops to apply with: annetinfo.zip (goals, rules, full area list, application form, a new CP437 ANSI banner) and annetareas.zip (machine-readable .na area lists for both message and file areas), built from the project's real seed data rather than placeholder text.
  • FIX (docs): README's ANotherNetwork description had a stale category count (said 8, actual is 9 including Test) and didn't mention the new file areas.
  • FEATURE: EchomailNetwork gets a new optional ftn_domain field — the qualified BinkP address's domain suffix (addr@domain) previously always derived from the network's display name, truncated/lowercased to 8 chars, which produced an awkward anothern for "ANotherNetwork". Set explicitly to anet in the seed data; exposed as an editable field in Admin → Echomail Networks for any network. Blank/NULL falls back to the old name-derived behavior, so existing networks are unaffected.
  • FIX (live-caught, real bug not just config confusion): activating the seeded "ANotherNetwork"/"ANotherNetwork (QWK)" network rows on the hub itself (bbs.a-net.fyi) made the poller try dialing its own address every 60 seconds, failing both BinkP auth and QWK download — and simply deactivating those rows wasn't a safe workaround either, since the terminal echomail menu (list_echo_areas) also filters areas by the network's is_active flag, which would have hidden the areas from the hub's own local users too. Fixed properly: the poller now detects when a network's configured address matches its own hub address (BinkP: our_address == hub_address) or own public host (QWK: qwk_host matches BBS_PUBLIC_HOST/BBS_DOMAIN) and skips the dial-out with a clear "skipped" poll-log entry instead of attempting and logging a failure — areas stay visible either way, since visibility was never the thing that needed to change. 3 new tests in tests/test_poller_self_referential.py, verified to reproduce the exact live error before the fix and pass after.

v1.0b2.32 — Sysop-specific paths scrubbed from shared code (July 2026)

  • FIX: several hardcoded fallback paths/usernames from the maintainer's own install had leaked into shared code (drop-file path resolution, default gallery seed data, installer/upgrade-wizard fallbacks, an IRC CTCP reply URL, the sudoers template) — real bugs that would misconfigure a different sysop's install if the normal config lookup ever failed, not just cosmetic. Fixed all of them; also scrubbed the same path from the in-app tutorial, the seeded wiki content, and the GitHub issue template.
  • FIX: 3 systemd unit files (anetbbs-binkp.service, anetbbs-finger.service, anetbbs-mrc-irc-bridge@.service) were missed in the first pass and still had the maintainer's own User=/path hardcoded — found via a follow-up audit. Fixed to match every other unit's generic anetbbs//opt/anetbbs convention.
  • FIX: a stray third-party email address (copied from wherever a bundled tool was originally adapted from) in tools/bbs-files-tool.py's help text pointed sysops at an unrelated stranger instead of this project's own issue tracker.
  • FIX: tests/msp_loopback_test.py (a manual diagnostic script, not a real test) matched pytest's default test-file naming pattern and had no if __name__ == '__main__': guard — every pytest tests/ run was silently starting real MSP/SYSTAT network listeners and mutating process-wide environment variables as an import-time side effect. Renamed to msp_loopback_check.py (no longer matches pytest's collection pattern) and gated its body behind a proper main()/__main__ guard. Also fixed a second, unrelated hardcoded absolute path in the same file left over from a different machine's mount point.
  • FIX: two more test files (test_ebook_terminal_menu.py, test_qwk_hub_gating.py) had the same os.environ['FLASK_ENV'] leak pattern as last version's TestingConfig.SQLALCHEMY_DATABASE_URI fix — set on every test run, never restored. Fixed both.
  • FIX: anetbbs/web/healthz.py and anetbbs/web/preflight.py checked a FINGER_PORT config key that was never actually set anywhere (the real setting is FINGER_LISTEN_PORT), so the finger service's health check/preflight validation silently ignored any non-default port a sysop configured. Added FINGER_LISTEN_PORT/FINGER_LISTEN_HOST to Config and fixed both call sites.
  • FIX (docs): docs/00-overview.md's table of contents was missing links to two existing doc files (17-development.md, 20-federation.md); README's wiki page count was stale (41 → actual 43); README's Doors feature list didn't mention the bundled door_rlogin/A-Net Online game server; a few wiki pages used dead internal [[Page]]-style links for external tools (SyncTERM, NetRunner, DOSBox-staging) instead of the real external links used elsewhere in the same content.
  • FIX (docs, full accuracy pass): a broad cross-check of every doc file, the in-app sysop tutorial, and the seeded wiki against the actual current code turned up a large batch of stale/wrong content, most notably: docs/12-upgrading.md and docs/01-installing.md described an entirely stale install/upgrade flow (wrong backup path — /tmp/anetbbs-backup-*, not next to the install; wrong service names — the unified anetbbs.service replaced the legacy split anetbbs-telnet/anetbbs-ssh a while ago; a wizard prompt list missing half the real prompts; automatic rollback described as an interactive "offer"); the sudoers install instructions in docs/02-sysop-daily-ops.md, the in-app tutorial, and two wiki pages all broke when the sudoers template's placeholder was renamed to __SERVICE_USER__ earlier in this same version — a plain cp now installs a non-functional rule, fixed everywhere to show the required sed substitution; the wiki's Themes page listed five theme names that don't exist anywhere in the codebase (real theme count is 9, not the fabricated list); the wiki's Doors page and docs/17-development.md undercounted door types (7 vs the real 10, missing door_dosemu/door_telnet/door_dos_browser); a wiki page referenced a tools/db_backup.sh script that doesn't exist; docs/INSTALL.md was missing the anetbbs system-user creation step its own systemd section depends on and had a wrong apt package list; a real (non-doc) bug found along the way: anetbbs/web/healthz.py/preflight.py checked a FINGER_PORT config key that was never actually set anywhere, silently ignoring any custom Finger port a sysop configured — fixed by adding the real FINGER_LISTEN_PORT/FINGER_LISTEN_HOST to Config.
  • No behavior change for the maintainer's own existing install from any of the above.

v1.0b2.31 — Docker support (July 2026)

  • FEATURE: Docker deployment alongside the traditional install.sh/systemd path — single-container quick start (supervisord-managed) and a docker-compose "correct way" (one container per service), both built multi-arch (amd64+arm64). See docs/22-containers.md.
  • FEATURE: Sysop Control Panel restart/upgrade works under docker-compose via a Docker-socket backend instead of systemctl; one-click self-upgrade pulls a new image tag and recreates containers.
  • FIX (hardening, also benefits bare metal): SQLite WAL mode + busy-timeout (anetbbs/models.py); MSP_PORT/SYSTAT_PORT now environment-overridable.
  • FIX: the admin DB Backup download button streamed the SQLite file with a plain copy and no checkpoint — under the new WAL mode, recent writes could sit only in the -wal file and be missed. Now forces PRAGMA wal_checkpoint(TRUNCATE) first. update.sh's own automated backups were already safe (sqlite3 .backup).
  • FIX (critical, live-caught): reading any echomail message via terminal (SSH/telnet/rlogin) failed with "Menu action failed (see server log)". Root cause: the read-only message viewer (_ViewerScreen.draw_text() in anedit.py) was never updated to accept the misspell parameter added when spell-check shipped (v1.0b2.20) — the shared redraw code the viewer inherits from the editor always passes it. Latent since v1.0b2.20; nobody had tried reading an echomail message via terminal since. Fixed + regression-tested (tests/test_aneview_draw_text.py, verified it reproduces the exact live traceback before the fix and passes after).
  • FIX (release hygiene): build-release.sh rewritten to build from git's own tracked/ignored file list instead of a manually maintained --exclude denylist, which had let bbs.log (containing a plaintext admin password from a local test boot) leak into 7 prior release tarballs, plus assorted stray test-scratch files. See .gitignore for the accompanying rule additions.
  • 30 new tests. Docker builds/runs themselves still need verification with real Docker access — not available in the environment this was built in.

v1.0b2.30 — Federation "Register with Hub" nav fix (July 2026)

  • FIX: the "Register this BBS with a federation hub" admin page existed but had no link anywhere in the UI. Added a Register with Hub card to Admin → Network.
  • FIX: the v1.0b2.29 hub-gating fix left the Hub Management card linking to a now-404 blueprint on every non-hub install. Hidden unless REGISTRY_MODE_ENABLED=true.
  • 2 new tests in tests/test_qwk_hub_gating.py.

v1.0b2.29 — Local chat, terminal colors, QWK/federation hub gating (July 2026)

  • FIX (critical): terminal Local Chat (Chat → 1) was a stub that only echoed your own message back to yourself, never broadcasting to other nodes. Now delegates to the already-working real-time broadcast/queue system in multinode.py (previously only reachable via a dead, unwired multinode menu action).
  • FIX (critical): broadcast()'s self-exclusion compared by username, not by node — two nodes logged in under the same account (e.g. testing with one account on two terminals) both matched the check, so the message was dropped for everyone. Now excludes by the specific sending node's slot.
  • FIX (critical): MagiTerm, NetRunner, and PuTTY showed no color on most screens (wall, file areas, boards, PMs, bulletins, MRC chat, pre-login welcome, games list) while the main menu rendered fine. The main menu's .ans art uses the classic ANSI.SYS bright-color convention (bold + base color); nearly everywhere else used the newer "aixterm" 90-97 SGR range with no bold, which those three clients don't recognize (SyncTerm supports both, masking the bug there). Converted every affected color table/constant across 10 files to the classic convention.
  • FIX (critical): the terminal QWK-node-application wizard wrote to whichever BBS install ran it instead of the real hub, and the hub admin review queue was exposed on every install with no gate ("all the sysops try to put in for a node and it goes to their system"). Added a real hub-side API (/qwkhub/apply, /qwkhub/status/<token>) and gated the hub-admin blueprint behind REGISTRY_MODE_ENABLED. The hub install needs REGISTRY_MODE_ENABLED=true set in its .env.
  • FIX: the federation self-registration wizard always showed "success" even when REGISTRY_URL was blank and nothing was sent. REGISTRY_URL now defaults to https://bbs.a-net.fyi, and the registration tick reports back real success/failure.
  • 9 new tests in tests/test_multinode_chat.py, plus manual verification against two real concurrent telnet sessions.
  • 4 new tests in tests/test_ansi_color_compat.py, plus manual verification against a live telnet session.
  • 10 new tests in tests/test_qwk_hub_gating.py, 4 in tests/test_registry_tick_status.py.