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.txtmember inside as the rules text (correctly picksannet.txtoverreadme.txt/systems.txtin 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 aBinkPNodeand/orQWKNode— 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 acrosstests/test_network_join.pyand extensions totests/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 anotify_admins(kind, ...)helper (notifies everyis_admin=Trueuser, each still honoring their own per-kindnotify_prefstoggle) 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 intests/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
EchomailPollLogrow 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()inanetbbs/echomail/qwk_hub_ftp.pywrote 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 intests/test_qwk_packet_roundtrip.pydoing 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()inanetbbs/ftp/server.pycomputed a QWK node's FTP home directory by passingself.qwk_root(already<DATA_DIR>/qwk-hub, set at construction) intoensure_node_dir()— which appends its ownqwk-hubsegment and expects the bareDATA_DIRinstead. This doubled the path to<DATA_DIR>/qwk-hub/qwk-hub/<PACKET_ID>, one level away from whereon_login()'sgenerate_node_packet()(which correctly receives the bareDATA_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 —RETRalways failed with550 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 fromself.qwk_rootwithout the extraensure_node_dir()call. 2 new tests intests/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 onlystr(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 rawcurl -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 intests/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
DetachedInstanceErroratreq.created_at.strftime(...)in_show_qwk_request_status(). Root cause:_apply_qwk_node()queried the existingQWKNodeRequestrow inside onewith _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, nestedapp_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, consistentapp_context, extracting every field it needs into plain local variables before that context closes. 3 new tests intests/test_qwk_apply_status_detached_instance.py. - FIX (live-caught, real bug): the QWK poller used only the
qwk_usernamefield 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 — blankqwk_usernamenow falls back toqwk_packet_id, which can only help since a blank username always failed anyway. Also rewrote theQWK Usernamefield'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 intests/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()inanetbbs/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 bothEchomailNetwork.is_activeandEchoArea.is_subscribeddefault 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 intests/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.shhardcodedMRC_BRIDGE_PORT=8080unconditionally, whiletest/behindinstall modes defaultWEB_PORTto 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 bind127.0.0.1:8080. Fixed by deriving the MRC bridge's default port fromWEB_PORT+1instead of a fixed value, computed once and reused for both the.envtemplate and the bridge's ownconfig.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 systemdExecStartline, but that line never contains one (the bridge reads its port from its ownconfig.json, not a CLI arg), so the check silently never fired. Fixed to readconfig.json'sweb_listen_portdirectly. 4 new tests intests/test_preflight_port_collision.py. - FIX (docs, real mistakes found and corrected): wrong config var cited for terminal-node count (
GAMES_MAX_NODES→BBS_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-web→anetbbs) for the FTP capability fix in two places;hub_generate_nodelistmissing from the scheduled-events handler table; two Synchronet-compat-shim doc claims that flatly contradicted the actual shim code (user.securityflags,bbs.exec()/bbs.menu()). - FIX (wiki, real mistakes): wrong Telnet/SSH port numbers on the two most-viewed pages (
home,featuressaid 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 unifiedanetbbsservice, in two places; a fictionalflask db migrate/alembic migration workflow that contradicts the wiki's own (correct) Architecture page describing the real auto-sweep mechanism; a reference to a nonexistentanetbbs/seed_data.py; a wiki page (irc-bridge) describing an admin-configured Chat↔IRC bridge that doesn't exist, rewritten to describe the realMrcIrcBridge(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_domainfield, 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 separateanetbbs-install/anetbbs-upgradePython installer toolchain (documented as a lightweight alternative toinstall.sh, not brought to parity — its real gaps vs.install.share 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
ScheduledEventframework), publishing the ANotherNetwork nodelist directly into theANN.FILES.NODELISTfile area (replacing the prior copy) so peers pull it like any other file-echo entry, instead of only the pre-existing public HTTP link. NewEchomailNetwork-agnostic handlerhub_generate_nodelist, only seeded on installs withREGISTRY_MODE_ENABLED(a peer install has no downstreamBinkPNodedata 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()inechomail/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_idisNone) are unaffected. - 6 new tests in
tests/test_hub_generation_features.pycovering 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.NODELISTis flaggedis_nodelist_sourceso inbound TICs there auto-populate the nodelist. Found and fixed a real bug while wiring this up: unlikeEchoArea.tag,FileArea.taghas a database-levelUNIQUEconstraint, 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) andannetareas.zip(machine-readable.naarea 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:
EchomailNetworkgets a new optionalftn_domainfield — 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 awkwardanothernfor "ANotherNetwork". Set explicitly toanetin 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'sis_activeflag, 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_hostmatchesBBS_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 intests/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 ownUser=/path hardcoded — found via a follow-up audit. Fixed to match every other unit's genericanetbbs//opt/anetbbsconvention. - 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 noif __name__ == '__main__':guard — everypytest tests/run was silently starting real MSP/SYSTAT network listeners and mutating process-wide environment variables as an import-time side effect. Renamed tomsp_loopback_check.py(no longer matches pytest's collection pattern) and gated its body behind a propermain()/__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 sameos.environ['FLASK_ENV']leak pattern as last version'sTestingConfig.SQLALCHEMY_DATABASE_URIfix — set on every test run, never restored. Fixed both. - FIX:
anetbbs/web/healthz.pyandanetbbs/web/preflight.pychecked aFINGER_PORTconfig key that was never actually set anywhere (the real setting isFINGER_LISTEN_PORT), so the finger service's health check/preflight validation silently ignored any non-default port a sysop configured. AddedFINGER_LISTEN_PORT/FINGER_LISTEN_HOSTtoConfigand 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 bundleddoor_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.mdanddocs/01-installing.mddescribed an entirely stale install/upgrade flow (wrong backup path —/tmp/anetbbs-backup-*, not next to the install; wrong service names — the unifiedanetbbs.servicereplaced the legacy splitanetbbs-telnet/anetbbs-ssha while ago; a wizard prompt list missing half the real prompts; automatic rollback described as an interactive "offer"); the sudoers install instructions indocs/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 plaincpnow installs a non-functional rule, fixed everywhere to show the requiredsedsubstitution; 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 anddocs/17-development.mdundercounted door types (7 vs the real 10, missingdoor_dosemu/door_telnet/door_dos_browser); a wiki page referenced atools/db_backup.shscript that doesn't exist;docs/INSTALL.mdwas missing theanetbbssystem-user creation step its own systemd section depends on and had a wrongaptpackage list; a real (non-doc) bug found along the way:anetbbs/web/healthz.py/preflight.pychecked aFINGER_PORTconfig key that was never actually set anywhere, silently ignoring any custom Finger port a sysop configured — fixed by adding the realFINGER_LISTEN_PORT/FINGER_LISTEN_HOSTtoConfig. - 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_PORTnow 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
-walfile and be missed. Now forcesPRAGMA 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()inanedit.py) was never updated to accept themisspellparameter 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.shrewritten to build from git's own tracked/ignored file list instead of a manually maintained--excludedenylist, which had letbbs.log(containing a plaintext admin password from a local test boot) leak into 7 prior release tarballs, plus assorted stray test-scratch files. See.gitignorefor 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, unwiredmultinodemenu 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
.ansart 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 behindREGISTRY_MODE_ENABLED. The hub install needsREGISTRY_MODE_ENABLED=trueset in its.env. - FIX: the federation self-registration wizard always showed "success" even when
REGISTRY_URLwas blank and nothing was sent.REGISTRY_URLnow defaults tohttps://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 intests/test_registry_tick_status.py.