Skip to content

NOMAD Bridge v0.1.3: runtime hardening, Companion adverts, memory and configuration UI - #5

Draft
yellowcooln wants to merge 28 commits into
mainfrom
dev
Draft

yellowcooln wants to merge 28 commits into
mainfrom
dev

Conversation

@yellowcooln

@yellowcooln yellowcooln commented Sep 12, 2026

Copy link
Copy Markdown
Member

Summary

Promote the accumulated dev changes to main for NOMAD Bridge 0.1.3. This includes the production-runtime work merged in #2, follow-up transport/pacing improvements, a refreshed configuration UI, bounded per-sender conversation memory, manual Companion adverts, and revised defaults.

This is a draft release-promotion PR. The plugin has already been installed and tested on live hardware, and the operator confirms it works. No release has been published by this PR. The final version is plain 0.1.3; intermediate local test-version commits do not represent additional releases.

Relationship to the hardening PR (#2)

The original contributor work is retained in history, not replaced wholesale. #2 began with 3605b96 (runtime hardening) and de76d0b (reply pacing), received follow-up fixes, and was merged into dev as d25eca0.

Used and retained from that work

  • Bounded request admission, active/pending work, per-sender and global request-rate limits, duplicate suppression, and configuration validation.
  • Sender-prefix allowlist enforcement when a list is configured.
  • Bounded NOMAD HTTP requests and response handling rather than unbounded network reads.
  • Conservative radio payload/chunk limits and configurable delay between reply chunks.
  • Silent rejection of unauthorized or overloaded requests instead of generating more radio traffic.
  • Configuration, HTTP, dispatch, failure-path, and pacing regression coverage.
  • The exact openhop-core==1.1.1 dependency pin.

Improved during the hardening PR before merge

  • Reworked the NOMAD HTTP transport around aiohttp plus a dedicated aiodns/c-ares resolver. Supports HTTP(S) DNS names, Docker hostnames, IPv4, and bracketed IPv6 without blocking Python getaddrinfo executor work.
  • Applied a total deadline across DNS, connection/TLS, headers, and response body; cancellation closes transport/resolver resources.
  • Enforced a 256 KiB response-body limit, parser header limits, and a 64 KiB aggregate-header check. Handles repeated standard headers and rejects redirects/compressed responses; TLS verification remains enabled and environment proxies/cookies are not used.
  • Made reply serialization/pacing global, including error replies and concurrent requests. Stops subsequent chunks when Companion send acceptance fails or is uncertain; acceptance is not proof of over-air delivery.
  • Aligned packaged Docker defaults to http://nomad_admin:8080 and the default Companion endpoint to 127.0.0.1:5050, preserving explicit configuration/environment overrides.
  • Added read-only PR CI for Python 3.10/3.12, correctness lint, wheel build and isolated installed-package smoke checks. Added Python 3.10 TOML import compatibility to the release helper.

Deliberately changed after the hardening PR

  • Empty allowlist now means allow everyone who can DM the Companion. A nonempty list enables the whitelist and admits only the listed exact sender prefixes. This replaces Harden NOMAD bridge production runtime for v0.1.3 #2's deny-all-on-empty policy; startup still warns and rate limits remain active.
  • Conversation memory is supported again, but is bounded, private per sender, and RAM-only. This replaces Harden NOMAD bridge production runtime for v0.1.3 #2's requirement to run stateless-only. It does not restore NOMAD server-side session creation or disk-backed session-map behavior.
  • One-shot mode now defaults off. Explicitly saved true/false values are preserved. Enabling it still makes every question stateless.
  • Replaced the stateless-only rejection tests with coverage for bounded memory and both supported modes. Added allow-empty acceptance tests while retaining nonmember rejection tests.

Older statements in #2 about mandatory one-shot mode, deny-all empty lists, or the earlier Companion port are historical, not the current behavior.

New and updated functionality

Configuration page

  • Refreshed grouped configuration layout with responsive light/dark styling and bundled official NOMAD artwork, license, and provenance notice; no runtime CDN dependency.
  • Logo beside the header title, consistent version display, and simpler introductory text: “Configure your Companion connection and NOMAD AI chat.”
  • Clickable setting names with subtle (i) indicators and accessible expandable help. Opening checkbox help does not toggle the checkbox.
  • Help covers Connectivity, NOMAD settings, memory, admission/rate limits, reply/payload controls, and radio prompt settings.
  • Save/reload preserves explicit checkbox choices and unknown persisted configuration keys while excluding runtime/control-only data.
  • Allowlist help explicitly explains empty = allow all, adding keys = whitelist enabled, and entering the first 12 hexadecimal characters of each public key rather than the full key. Blank entries are ignored.

Per-sender conversation memory

  • Up to 10 complete question/answer pairs and 16 KiB of role/content UTF-8 data per sender, with at most 64 senders.
  • Expires after 30 minutes without a successful answer; idle cleanup and oldest-conversation eviction bound retained state.
  • Serializes each sender's ask/reset operations using bounded lock bookkeeping; only successful complete exchanges enter history.
  • /new and /reset clear the sender's conversation, subject to the normal admission/rate controls.
  • Restart clears RAM history. No NOMAD server session is created, read, or modified; legacy session-map files are not implicitly migrated or deleted.

Manual Companion adverts

  • Adds Zero-hop advert and Flood advert buttons under “Send an advert.” Description explains direct neighbors versus mesh forwarding.
  • Uses the plugin's existing Companion TCP connection and existing authenticated plugin settings/runtime APIs. No Repeater code changes, new Repeater endpoints, or second Companion connection are required.
  • Uses Companion command 7 with flags 0/1, serialized with other commands.
  • Process-specific single-use challenges expire; requests are consumed and revalidated under the command lock immediately before transmission, preventing expired/replaced requests from being replayed after waiting or restarting.
  • Reports pending/accepted/error state through plugin-owned runtime status. Disables controls for disconnected/stopped/stale runtime or invalid challenges.
  • Does not automatically retry uncertain acceptance; uncertain sockets are discarded. A successful Companion response confirms command acceptance, not reception by another radio.

Defaults and packaging

  • Version 0.1.3; Python >=3.10; Core remains pinned to 1.1.1.
  • Adds aiohttp>=3.14.3,<4 and aiodns>=3.2,<5.
  • Companion 127.0.0.1:5050; NOMAD http://nomad_admin:8080; existing explicit endpoints remain unchanged.
  • One-shot off; empty allowlist permits all reachable DM senders.
  • Default active concurrency 1, pending limit 1, per-sender rate 2/minute, global rate 4/minute.
  • Default chunk limit 80 bytes and reply-chunk delay 2 seconds; default maximum remains 4 reply chunks.
  • Runtime defaults, packaged JSON, manifest, UI defaults/help, and README updated together.
  • Artwork/license/notice are explicitly included in wheel data files.
  • Existing catalogue/release automation already present on main is not introduced by this PR; the release-helper delta is Python 3.10 TOML compatibility.

Verification

  • Latest local source suite: 182 passed, including real Chromium browser tests, configuration overrides, bounded memory, simulated Companion socket framing, advert expiry/replay protection, and HTTP/pacing regressions.
  • Earlier iterations also passed full Python 3.10/3.12 suites and isolated installed-wheel tests. The latest one-shot-default change has a new regression covering off-by-default and explicit true override; do not interpret earlier artifact results as a final wheel test of this exact head.
  • Previous wheels were built from clean commit archives and checked for runtime/config/UI byte parity. A fresh wheel of the current runtime was built and installed to run the corrected CI smoke check.
  • Live testing: the operator has installed and tested the plugin on hardware and confirms it works, including Companion adverts. Testing was not limited to mocks or local unit tests.
  • Local validation is separate from hosted CI; inspect the checks attached to this draft.

Known limitations / draft follow-ups

  • Corrected the installed-wheel smoke assertion to require settings.one_shot is False, matching the requested default. The exact workflow smoke step passed locally against a freshly built and isolated installed wheel; hosted Python 3.10 and Python 3.12 checks both passed on 356a14b.
  • Hosted browser coverage enabled on Python 3.12 only in 92bf7b1: pinned Playwright 1.62.0 and Chromium/system dependencies are installed before pytest. Both jobs passed; Python 3.12 ran the browser tests without skips (75-second job), while Python 3.10 retained its two browser skips (31-second job).
  • Hosted Python 3.12 built and installed the wheel and passed its entrypoint/defaults/assets smoke check; both Python 3.10 and 3.12 PR jobs passed on 356a14b.
  • Installing updated files does not necessarily restart an already-running plugin. Restart the plugin after installation and verify fresh runtime status.
  • Repeater's pip --upgrade installation can retain old Python code when replacing a different build of the same version, even while extracted UI files change. Such same-version replacement needs an explicit reinstall plus restart; a version label alone is insufficient evidence. Distinct 0.1.3+test.1 builds were used during testing, but this PR remains 0.1.3 as requested.
  • Settings writes are full-document writes without compare-and-swap; concurrent configuration editors can race. Existing request-expiry/revalidation safeguards remain in place.
  • c-ares may use a native event thread and does not provide every OS NSS/mDNS integration; .local reachability depends on the deployment's resolver/network setup.
  • Conversation history is not durable, and Companion acceptance is not an over-air delivery guarantee.

The draft remains open for review; live installation and functional testing have already been performed and confirmed working by the operator.

@yellowcooln

Copy link
Copy Markdown
Member Author

Update since the previous PR revision

Pushed e79b5ca on top of 356a14b. This update contains the radio-reply cleanup and revised prompt developed after the original draft; it does not repeat the earlier hardening/UI/advert feature list.

Better packet utilization

  • Sentence and paragraph boundaries are now preferred only when they use at least 80% of the available text window. Otherwise, the splitter uses a word boundary closer to the byte limit instead of sending an unnecessarily short packet.
  • Numbered-list markers such as 3. are no longer treated as sentence endings.
  • Multipart labels remain inside the configured UTF-8 byte budget. Configured maximum chunk size and maximum reply count are unchanged.

Useful final chunks instead of a notice-only packet

  • When an answer exceeds the maximum chunk count, preserve answer text in the final packet and append ....
  • Removed the behavior that discarded the entire final answer chunk in favor of ...response shortened for MeshCore.
  • Single-message truncation also preserves useful text and no longer adds an unnecessary [1/1] prefix.

Plain-text cleanup

  • Strip paired bold, italic, and inline-code Markdown markers before splitting.
  • Preserve ordinary identifier underscores and arithmetic rather than indiscriminately deleting those characters.

Improved default radio prompt

  • Identifies the assistant as a local AI accessed through Project NOMAD over MeshCore radio.
  • Instructs it to use relevant knowledge-base material supplied with the request, without claiming that every file or guide on the box is accessible.
  • Requests a direct answer first, ideally under 250 characters in one short paragraph, with no Markdown, headings, tables, or numbered lists.
  • Removes filler and repeated questions; requests only essential procedural steps.
  • Prioritizes accuracy and essential safety details over brevity, and explicitly discourages invented names, sources, URLs, and access instructions.
  • Synchronizes the prompt across runtime fallback, packaged defaults, manifest, and UI defaults. The runtime fallback now uses actual newline characters consistently with the packaged template.

NOMAD's existing global knowledge-base setting remains authoritative. No plugin knowledge-base toggle, NOMAD API modification, or Repeater change was added.

Documentation and compatibility

  • Added README documentation for packet packing, truncation, prompt behavior, and saved-template handling.
  • Existing saved/custom prompts are preserved. Existing installations must update their Radio prompt template explicitly to use the new instructions.
  • PR version remains 0.1.3. The +hardened suffix was for local test artifacts only.

Verification

  • 187 local tests passed, including real Chromium tests.
  • Added regressions for Markdown cleanup, short-sentence packet underfilling, numbered-list boundaries, useful single-message truncation, and default prompt consistency; updated maximum-chunk truncation expectations.
  • Ruff correctness checks, JavaScript syntax, and git diff --check passed.
  • The local 0.1.3+hardened artifact containing these changes was built and installed in an isolated environment; entrypoint/default/UI smoke checks and packaged-source parity passed before promotion to the plain release version.
  • Previous live testing of the plugin is already documented in the PR. This new reply-formatting/prompt revision has automated/package verification; no additional live-test result is being claimed for this update.

Hosted Python 3.10 and Python 3.12 PR checks both passed for e79b5ca, including the Python 3.12 wheel build/install smoke check: https://github.com/openhop-dev/openhop-nomad-plugin/actions/runs/34671064168

@yellowcooln
yellowcooln marked this pull request as ready for review September 12, 2026 17:19
@yellowcooln

Copy link
Copy Markdown
Member Author

Ready for review

This PR is now ready for review, promoting dev into main for NOMAD Bridge 0.1.3.

Since the previous update, 92bf7b1 adds hosted Chromium browser regression testing only on Python 3.12:

  • Installs pinned Playwright 1.62.0 and Chromium with its system dependencies before the existing test suite.
  • Exercises configuration/help/save-reload behavior and manual advert UI behavior in GitHub Actions rather than only locally.
  • Leaves Python 3.10 browser tests skipped, avoiding duplicate browser setup across the matrix.

Verified

  • Python 3.12 passed with browser tests running and no skips; the job completed in 1m15s.
  • Python 3.10 passed in 31s, retaining its two browser-test skips.
  • Python 3.12 wheel build and isolated installed-package smoke check passed.
  • The latest local full suite passed 187 tests.
  • Existing live installation and functional testing, including Companion adverts, have been confirmed working by the operator; the newer formatting/prompt changes also have automated and package verification as detailed in the previous update.

The PR includes the documented runtime hardening, bounded conversation memory, default-off one-shot mode, allowlist behavior, configuration UI/help, Companion adverts, improved radio packet packing/Markdown cleanup, and concise knowledge-base-aware prompt. The previous detailed update remains the change-by-change record for those features.

Release version remains 0.1.3. Both hosted checks are green at 92bf7b1; the browser-CI follow-up is complete.

CI: https://github.com/openhop-dev/openhop-nomad-plugin/actions/runs/34707743897

@yellowcooln
yellowcooln marked this pull request as draft September 12, 2026 21:06
- MeshCoreClient.get_contacts() and remove_contact() for Companion contact management
- ContactsControl layer reads config.json requests, executes via meshcore, publishes to runtime.json
- Favorites persistence via nomad_favorites.json
- UI: new Contacts tab with refresh, list, favorite/unfavorite, remove actions
- 18 unit tests for contacts_control and meshcore_client parsing
The Contacts tab added by the address-book feature shifted the tab count from 4 to 5.
Updated the tab count assertion, tab navigation to Companion device, and added a
navigation back to the Companion tab before layout bounding-box checks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants