ANetBBS Changelog — Beta History (pre-v1.0.0)

Archived history of every internal beta build number (v1.0a1.1 through
v1.0b2.239) from before ANetBBS's v1.0.0 full release in August 2026.
Preserved as-is — a real record of what changed at each step during
development — but split out of the main CHANGELOG.md
once it passed 6,000 lines, to keep that file scannable for the
v1.0.0-onward history that actually matters day to day. Newest-first,
same as the main changelog.

v1.0b2.73 — Close the remaining inbound packet-record parser desync (July 2026)

  • FIX: v1.0b2.70's fix for the embedded-null desync only checked the 2 bytes immediately after a candidate null terminator for MSG_TYPE_2 (\x02\x00) before accepting it as a message boundary. A BINKP_DEBUG_DUMP_DIR capture of real inbound traffic (added in v1.0b2.71/72 specifically to root-cause this) caught the gap directly: a message's real body ("Men In Black: The Secret Terror Among Us", from a live rescan) happened to contain the exact byte sequence 0x00 0x02 0x00 as ordinary embedded content, with no relation to any real message boundary. The 2-byte check couldn't tell the difference and falsely split the packet there, corrupting that message and the next. Traced byte-for-byte against the actual captured packet (anetbbs/echomail/binkp.py:_parse_ftn_packet()) rather than inferred from database fallout this time. Fix: additionally require a date-shaped string ("DD Mon YY") exactly where the packed message header's date field would sit — every real FTS-0001 message record has this by definition, and a coincidental byte sequence satisfying both the type marker and this pattern is astronomically unlikely. 1 new regression test reproducing the exact false-positive byte pattern found in production, confirmed to fail against the v1.0b2.70-72 parser and pass against this fix.

v1.0b2.72 — Widen diagnostic capture to cover arcmail-bundled packets (July 2026)

  • DIAGNOSTIC: v1.0b2.71's BINKP_DEBUG_DUMP_DIR capture only instrumented the raw-.pkt-over-the-wire path in _import_completed(). A live poll transcript showed the actual bulk rescan traffic arriving as FTS-0006 arcmail bundles (0000ff91.fr7, .fr8 — day-abbreviation + sequence naming, ZIP-compressed) rather than bare packets, which are unpacked via a separate code path (_is_zip() → per-member zipfile extraction → _parse_ftn_packet(inner)) that the diagnostic never touched. Refactored the dump into a shared _debug_dump_packet() helper and wired it into both call sites, so a capture now covers every inbound FTS-0001 packet regardless of whether it arrived bare or inside a ZIP bundle. Still zero-cost and no behavior change unless the env var is set.

v1.0b2.71 — Diagnostic capture for the still-open inbound packet desync (July 2026)

  • DIAGNOSTIC: the v1.0b2.70 fix for the embedded-null packet desync reduced but did not eliminate the corruption — confirmed live, with multiple consecutive garbled netmail_messages rows still appearing after deploying v1.0b2.70 and running a fresh %RESCAN. Guessing further from already-corrupted database rows isn't productive: by the time a misparsed message is stored, the true wire-level byte layout that caused the misparse is gone. Added an opt-in, zero-cost-by-default diagnostic: setting BINKP_DEBUG_DUMP_DIR on the server saves the raw bytes of every inbound FTS-0001 packet to that directory before parsing (anetbbs/echomail/binkp.py:_import_completed()), so the actual wire bytes can be captured and inspected directly instead of reverse-engineering them from corrupted output. No behavior change when the env var is unset (the default). Root-cause fix to follow once real packet bytes are captured.

v1.0b2.70 — Fix inbound FTS-0001 packet-record parser desync on embedded null bytes (July 2026)

  • FIX: anetbbs/echomail/binkp.py:_parse_ftn_packet() located the end of each message's text by scanning for the first 0x00 byte after the header fields. FTS-0001 message records have no explicit length field, so this is normally correct — but a real inbound packet (a Mystic hub's AreaFix %RESCAN reply, delivering thousands of backlogged messages after the v1.0b2.69 capValidate fix let a resubscribe actually land) contained an [ANSI]-tagged post whose raw ANSI-art body had an embedded 0x00 byte partway through. The old code truncated that message's body at the embedded null and left the read cursor pointed mid-body — not at a real message boundary — permanently desyncing every message record parsed after it in the same packet. Everything downstream came out as a fabricated "message" with From/To/Subject fields built from fragments of real body text, until the cursor happened to re-align on a genuine boundary a message or two later. Confirmed against a live database dump: raw \r bytes embedded in from_name/subject columns, and English sentence fragments split across fields that should never contain them. The fix only accepts a candidate null as the real terminator if what follows it looks like a valid boundary (another message's type marker, the packet's own end-of-data marker, or end of buffer); otherwise it keeps scanning past the embedded byte. Because this only fires on messages containing an embedded null (rare — mostly raw ANSI-art posts), the bulk of a rescan's messages imported correctly even before this fix; only a handful landed corrupted in the Netmail Inbox instead of their real echo areas. 3 new tests in tests/test_binkp_embedded_null_desync.py, including one confirmed to fail against the pre-fix parser.

v1.0b2.69 — Fix malformed FTS-0001 packet header rejected by strict tossers (July 2026)

  • FIX: every outbound FTN packet (netmail and echomail, both the outbound-poll client path and the hub-to-downstream-node server path — anetbbs/echomail/binkp.py:_build_ftn_packet() is the single shared writer for both) had a malformed Type-2+ header extension field. capValidate (header offset 40-41) was written as a plain little-endian copy of capWord (offset 44-45); per FSC-0048 it must instead hold the same value byte-swapped (big-endian) — confirmed directly against the hpt/husky tosser's own source (pktread.c/pktwrite.c). A plain identical copy only happens to produce a matching pair for palindromic capWord values (high byte == low byte); our CAP_WORD = 0x0001 is not one, so every packet we ever sent silently failed this check on any strict receiving tosser. Root-caused from a real report by an external FTN sysop (SmallTime BBS, running hpt), whose tosser logged the packet rejection outright: CapabilityWord error in following pkt! rtfm: IgnoreCapWord. Other tossers (e.g. Mystic, which several of this install's own upstream hubs run) may fail the same way without surfacing as clear an error — silently dropping the packet instead, which is indistinguishable from "the hub has nothing queued for us" from our side. 5 new tests in tests/test_binkp_capword_byteswap.py, decoding the header exactly the way hpt's own reader does rather than just asserting the bytes "look right."

v1.0b2.68 — Close the confirmed gaps from the Synchronet/Mystic comparison (July 2026)

Follow-up to the competitive gap analysis against Synchronet and Mystic BBS (see the research artifact referenced in project notes). Ships the 5 tactical gaps plus a unified access-control helper — the scripting-language gap and JAM/Squish message-base interop stay out of scope as accepted limitations for now.

  • FEATURE: unified read-access check. New anetbbs/features/access_control.py:evaluate_access() replaces scattered per-feature min_access_level/is_sysop_only/admin-bypass comparisons (migrated: echomail area access, RSS feed listing, game lobby listing). Fixed two confirmed real gaps found along the way: the web file-area browser never checked an area's min_access_level at all (only the terminal client did), and /search returned sysop-only boards and access-gated echomail to any user, including logged-out visitors, since neither result branch checked access at all.
  • FEATURE: consolidated the 21 separately-duplicated admin-only route gates (anetbbs/web/*.py) into one shared anetbbs/web/access_control.py:require_admin/require_admin_or_403, standardized on abort(403) everywhere — admin.py's routes previously flashed a message and redirected instead, a deliberate, confirmed UX change.
  • FEATURE: file search — /search gained a files result category (scoped to the DB-backed file gallery; the FTN-style file-area browser has no per-file database rows to search cheaply).
  • FEATURE: duplicate-file detection on upload. New anetbbs/features/file_dedup.py:hash_file(); the file gallery gets a real content_hash column, the FTN-style file-area routes get a per-area .hashes.json sidecar cache (no per-file DB row exists there). A notice, not a hard block — the upload still succeeds.
  • FEATURE: archive integrity testing on upload, all four upload routes. New anetbbs/features/archive_meta.py:test_archive_integrity()zipfile.testzip()/rarfile.testrar()/py7zr's .test() where available, best-effort read-through for tar, fails open on anything untestable (missing optional library, unrecognized format) rather than blocking a legitimate upload.
  • FEATURE: FileFix bot — the file-echo counterpart to the existing AreaFix bot, so a downstream peer can self-service their own file-echo subscriptions via netmail the same way AreaFix already does for message echoes. The underlying data model and the outbound half already existed; only the inbound netmail-robot dispatch was missing.
  • FEATURE: menu translation wiring. User.language and MenuTranslation existed as schema but nothing ever read them — the data-driven terminal menu system now looks up per-language overrides when a user's language isn't English, falling back to the source text.
  • 51 new tests across tests/test_admin_gate_consolidation.py, tests/test_evaluate_access.py, tests/test_archive_integrity.py, tests/test_file_dedup.py, tests/test_search_access_and_files.py, tests/test_menu_translation.py, and tests/test_filefix.py.

v1.0b2.67 — Fix netmail import crash on duplicate-check (CI-caught) (July 2026)

  • FIX (caught by GitHub Actions CI, not local testing — no Flask available in the sandbox this was built in): anetbbs/echomail/poller.py's _import_netmail() — used whenever inbound netmail is imported via the poll-response path — queried NetmailMessage.query.filter_by(msg_id=msg_id) for its duplicate-message check, but the model's actual column is msgid (no underscore, confirmed by the very next line's constructor call using it correctly). This raised AttributeError unconditionally any time an inbound netmail carried a MSGID kludge — which is virtually all real FTN netmail — meaning this function crashed before importing the message at all, on this specific path, whenever a MSGID was present. Pre-existing bug (not introduced by v1.0b2.65's notification fix, which touched the same function but not this line) — first actually exercised by the new regression test added for that release, and the crash only surfaced once that test ran somewhere with pytest/Flask installed (the CI Docker image), not in this sandbox. Fixed to query the correct msgid column. Full local test suite (434 tests) now verified green in a real venv with Flask installed, not just py_compile.

v1.0b2.66 — ClamAV scan timeout now sysop-configurable (July 2026)

  • FEATURE: the ClamAV scan timeout (how long anetbbs/features/virus_scan.py's scan_path() waits for clamscan on one file before giving up and letting it through) was a hardcoded 30 seconds with no way to change it short of editing source. Added CLAMSCAN_TIMEOUT — a new field at Admin → Settings (and the matching env var), defaulting to 60 seconds, same pattern as the existing IDLE_TIMEOUT_SECONDS/BOT_GATE_TIMEOUT settings. Takes effect immediately on save, no service restart needed — every scan call site (per-upload scanning in the file areas and file-queue routes, plus the Admin → Bulk Virus Scan page) already re-reads it per call. 7 new tests in tests/test_clamscan_timeout.py.

v1.0b2.65 — Fix missing notification for inbound netmail (July 2026)

  • FIX: new inbound netmail (FTN point-to-point private mail) never triggered any notification — the in-app bell and the terminal "You have new mail" banner both stayed silent even when netmail addressed to you had actually arrived. Root cause was two separate gaps in the two inbound-netmail import paths: the QWK/poll-response path (anetbbs/echomail/poller.py) resolved the message to a local recipient but never called the notification helper, and the real-time BinkP listener path (anetbbs/echomail/binkp_server.py, used whenever another system polls in live) never resolved a local recipient at all — so netmail received that way wasn't even linked to a User account, only findable later by manually browsing and string-matching the "To" name. Both paths now share one recipient-resolution helper (anetbbs/echomail/routing.py:resolve_netmail_recipient, matching by AKA address, then username, then display name, then the network's configured DefaultRecipient) and both now create a Notification for the resolved recipient, which the existing web bell and terminal banner already know how to display — no changes needed on that side. 8 new tests in tests/test_netmail_notification.py.

v1.0b2.64 — Terminal sysop tools: Node Monitor, broadened Sysop menu, MSP picker, profile redesign (July 2026)

  • FEATURE: new terminal (telnet/SSH/rlogin) Node Monitor at Sysop Tools → Node Monitor — a scrollable, Synchronet-UNIX-Monitor-style live list of connected nodes (slot/user/protocol/current page+action/idle time), with per-node Kick and Message actions. Data comes from NodeActivity (already updated on every menu render/action), the same source the web NodeSpy panel uses, so both views stay in sync. Kick sets the same DB flag NodeSpy's kick button does, picked up by the existing 5-second watchdog. Messaging a node reuses the existing sysop-reply inbox; a gap where a pushed message would never surface for installs still on the legacy hard-coded main menu (as opposed to the data-driven BbsMenu one) was closed at the same time.
  • FEATURE: the terminal Sysop Tools menu was 3 static items (Manage Users, Manage Boards, Server Status) — now a scrollable menu of 14 categories, bringing most of the terminal-feasible slice of the web admin UI into telnet/SSH/rlogin for the first time: Users, Boards/Bulletins, Echomail/Hub (networks & areas, QWK node-request approval, bad-area review), Games (active session disconnect, Trade Wars 2002 universe reset), Wall moderation, File Upload Queue (approve/reject), Scheduled Events (cron-style maintenance jobs), RSS Feeds, Login/Logoff Modules, Notifications (Webhooks, Sysop Broadcast, Message of the Day, Sysop Pages), Registry/Peers (federation applicant approval, peer probes), Caller Log, Node Monitor, and Server Status. Deliberately left web-only: the ANSI art editor/theme builder, file/avatar upload, backup restore, in-place upgrades, and full network-join applicant approval (creates multiple node records and emails credentials).
  • FEATURE: sending an Inter-BBS Instant Message from the terminal (New PM) no longer requires already knowing the exact user@host to type — it now offers a scrollable BBS directory, live-probes the picked BBS over SYSTAT for who's currently online there, and lets you pick a name from that list. Manual user@host entry is still available at every step (empty directory, failed/empty probe, or just backing out).
  • FEATURE: Edit Profile in the terminal was a blind sequential prompt-per-field walk (easy to miss settings like Sixel mode entirely). Replaced with a single summary screen showing every current setting plus a scrollable picker for which one to change. Also added three settings that existed on the web profile editor but were unreachable from the terminal: FTN tagline, "show email publicly", and date of birth, plus two that weren't editable from either front end yet (codepage, language preference — currently unused by anything else in the codebase, but now at least settable). Password is never shown or editable here, same as before.
  • FIX: the AreaFix log's Tags column could overflow its table cell on long comma-separated area lists — now wraps.
  • 30+ new tests across tests/test_terminal_node_monitor.py, tests/test_terminal_sysop_menu.py, tests/test_systat_parser.py, and tests/test_terminal_profile_redesign.py.

v1.0b2.63 — Option to hide sysop from Last Callers (July 2026)

  • FEATURE: new "Hide sysop logins from the Last Callers displays" toggle at Admin → Last Callers, off by default. Keeps a sysop who logs in several times a day from flooding the user-facing Last Callers list (terminal screen, terminal inline block, and the web one-liners page) with themselves instead of real users. The admin audit list always shows everyone regardless of this setting, and InterBBS-shared entries from other BBSes are never affected by it. 7 new tests.

v1.0b2.62 — InterBBS Game Score Sharing (July 2026)

  • FEATURE: door/web game high scores can now be shared with other ANetBBS installs, the same InterBBS pattern as the Graffiti Wall and Last Callers (riding echomail, opt-in, one BinkP network at a time). Only new personal bests relay, not every submission — a game's first score is trivially a personal best; after that, only a strictly higher score relays again. Each game has its own on/off switch (Admin → Games → edit a game, defaults on), plus the install-wide switch at Admin → Games → InterBBS Scores (now linked from the main admin dashboard, along with the previously-unlinked InterBBS Wall and Last Callers pages). Casino games (Blackjack/Slots/Video Poker/Hold'em) are included, tracked as "highest weekly balance" and shown as a dollar amount rather than a bare number, both locally and for synced entries. Enabling score sharing resets the four casino weekly starting balances to a shared standard value across every participating install (so a "$50,000 peak" means the same thing everywhere); changing any of those four afterward automatically turns score sharing back off rather than letting the numbers silently drift out of sync. 20 new tests.

v1.0b2.61 — QWK packet format cleanup for better reader compatibility (July 2026)

  • FIX: cleaned up a few spots in the QWK packet writers/readers where fields weren't laid out quite right, which could affect compatibility with some third-party QWK reader software. No changes needed on the sysop side. 4 new tests.

v1.0b2.60 — Updated handshake/CMD_EOB for older bink clients (July 2026)

  • FIX: BinkP session close-out could end a bit too early for some strict FTN mailers, causing a successful file transfer to still be reported as failed on the other end. Adjusted the M_EOB handshake and connection close-out on both the client and server side to match. Also switched outbound packet filenames to the conventional FTN naming style.

v1.0b2.59 — Fix BinkP handshake announcing a hardcoded, permanently-stale version (July 2026)

  • FIX: found while diagnosing a real FTN interop report — a peer's binkd log showed our BinkP handshake announcing VER ANetBBS/1.0a binkp/1.1. The version in that line was a literal, hardcoded string in both binkp.py (client handshake) and binkp_server.py (server handshake) — it has said 1.0a since this code was first written, regardless of which release is actually running, making it useless for diagnosing what version a peer is on from their own connection logs. Fixed by pulling the real version from anetbbs.__version__ in both places. 2 new tests.