Repository navigation
Release 0.0.20a2 - #69
Open
github-actions[bot] wants to merge 88 commits into
Open
github-actions[bot] wants to merge 88 commits into
github-actions[bot] wants to merge 88 commits into
Conversation
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* fix: stop reading the removed message_blacklist; use sha256 for self-signed cert Supersedes the fork band-aid #12. message_blacklist was removed from the client model (whitelist-only via allowed_types); drop the dead read rather than defaulting it. Also signs the generated self-signed cert with sha256. * feat: migrate to pyproject.toml, modernize CI, add tests - Replace setup.py with pyproject.toml (dynamic version via version.py.__version__) - Add hivemind-bus-client as runtime dep; test extras mirror websocket-protocol - Replace old TigreGotico/gh-automations@master workflows with OpenVoiceOS/gh-automations@dev equivalents (build-tests, coverage, lint, pip-audit, license-check, release-preview, repo-health, publish-stable, release-workflow) - Add __version__ computed string to version.py (required by setuptools dynamic) - Add tests/: unit coverage for decode_auth, cert generation, run() lifecycle, version module; hivescope e2e smoke test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * tests: raise handler coverage to 100% Add test_handlers.py covering get_client (cache hit/miss, invalid key, password handshake, send/disconnect closures, blacklist defaults) and all five HTTP handler classes (ConnectHandler, DisconnectHandler, SendMessageHandler, GetMessagesHandler, GetBinMessagesHandler) including auth-missing, not-connected, exception/500, and inner-loop break paths. Total coverage: 100% (239/239 stmts), 50 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ackstop (#15) * fix: pin poorman-handshake>=2.0.0a1 + disable-able runtime password backstop Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: defensive import of runtime_password_min_bits (works against released hivemind-core) * ci: re-trigger against published hivemind-core 4.7.0a1 (poorman 2.0) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: adapt handlers/tests to hivemind-core 4.7.0a1 (v3 + poorman 2.0) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
The HTTP NetworkProtocol read port = int(self.config.get("port", 5678)),
copying the WebSocket binding's default (hivemind_websocket_protocol also
defaults to 5678). Running both bindings together with no explicit port
config collided them on the same port.
HIVEMIND-TRANSPORT-1 §4 recommends port 5679 for the HTTP binding, and
the shipped hivemind_bus_client.http_client already assumes 5679 as its
default. The server-side default was the one out of step with both the
spec and the client.
Back-compat: this changes the default only. Deployments that set `port`
explicitly are unaffected. A deployment relying on the old 5678 default
for HTTP must set `port: 5678` explicitly or move to 5679 -- and if it
ran HTTP and WebSocket together, it was already broken by the collision.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
get_client() set client.skill_blacklist and client.intent_blacklist on the connection. HiveMindClientConnection has no such fields — the dataclass just accepted the ad-hoc attributes — and nothing ever read them. Blacklist enforcement moved to OVOSAgentPolicy (HiveMind-core#85), which resolves the DB row through client.resolve_user(db) and reads user.skill_blacklist there. These assignments are vestigial from before that change, and read as though the transport applies an ACL when it does not. The two tests asserting the attributes were pinning the dead behaviour. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: sync the client database on api-key miss, not on every request get_client() called db.sync() before every api-key lookup. HTTP is request-oriented, so an unknown key drove a full database sync per request rather than per connection: an unauthenticated caller could sync the database as fast as it could issue GETs. Look the key up first and sync only on a miss, debounced across handlers, then look again. Ported from the websocket protocol, where the same fix landed as part of keeping open() off the hot path. Also drop undelivered_bin on disconnect. do_disconnect() popped undelivered and clients but not undelivered_bin, and since it is a defaultdict every poll for binary data recreated the entry — the queue and whatever it still held survived for the life of the process. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: allow first HTTP client db sync * fix: add shared HTTP session state * fix: reuse server redis config for HTTP sessions * fix: keep HTTP Redis session keys in db prefix * fix: debounce sentinel must not treat early monotonic time as recent A fresh ClientDatabaseSync used 0.0 as the never-synced sentinel and compared it against time.monotonic(); on hosts with low uptime the very first sync landed inside the debounce window and was skipped. Track the last sync as Optional[float] instead. Also call db.sync() directly — every ClientDatabase provides it — instead of feature-detecting it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: give the _Protocol double the identity_rsa_key core now reads HiveMindClientConnection builds its per-connection HandShake from hm_protocol.identity_rsa_key instead of re-importing the PEM per connection, so the double raised AttributeError the moment a client was constructed. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Gaëtan Trellu <gaetan.trellu@gmail.com>
Reconciled with the replica-aware session state from fix/db-sync-on-miss: ClientRegistry is the memory session backend (connections plus pending text/binary queues) and the local connection cache when redis owns the cross-replica state. Also drops the dead skill/intent blacklist assignments in get_client — HiveMindClientConnection declares neither and enforcement lives in the agent policy layer. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The core's handle_client_disconnected ends by calling client.disconnect(), which re-entered this same do_disconnect and recursed until RecursionError on every disconnect. do_disconnect now pops the registry entry first and only calls the core's callback when the entry was still present, so the second, core-triggered call is a no-op. A protocol v3 session carries every post-handshake message as an encrypted binary Noise transport frame, and the HTTP listener handed everything to the core as text: the first message after the handshake was rejected as a non-Noise message on a v3 session, the connection was dropped, and nothing was ever deliverable over HTTP. HTTP has no binary opcode, so the client base64-encodes each frame and marks it binary=1; the listener decodes it with validation back to the bytes the core's decode requires, answers a chunk of a multi-frame message with "buffered", and keeps the legacy text path untouched when the flag is absent. A frame that cannot be decoded is a 400, not a 500. A client that lost its session has to be able to tell "reconnect" from "delivered" by the status alone, so "not connected" is a 409, as is a binary frame arriving on a connection that has no Noise session (a restart on the memory backend, a dropped session): the core would otherwise parse ciphertext as a bitstring and the client would see a 500. The core is told when a connection it dropped goes away, since nothing closes over HTTP, and /disconnect releases the cached connection so a later /connect runs the handshake offer again. Request bodies are capped at one megabyte, which no frame needs. The decrypted message is logged only by type at info, with the envelope at debug: a BUS message carries the user's words. The client model in HiveMind-core 5.x has no crypto_key, so the row is read without requiring one. Tests run two real Noise transports through the real handler and a real client connection, single-frame and multi-frame, plus the refusals, the flag spellings, the logging, and both disconnect paths. The API and operations documents describe the parameter, the new statuses, the body cap, and why a v3 session is bound to one replica. Addresses #49 Co-authored-by: JarbasAi <jarbasai@mailfence.com> Co-authored-by: Gaëtan Trellu <gaetan.trellu@gmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
… handshake gate (#52) The client row and the listener protocol no longer carry crypto_key, handshake_enabled or require_crypto: the Noise handshake is mandatory on every connection (HIVEMIND-CRYPTO-1 §3). Reading them through getattr kept a gate that can never fire; the plugin now pins the core floor that defines the model it reads. Co-authored-by: JarbasAi <jarbasai@mailfence.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…nnot collide (#56) The self-signed private key was written at mode 644, so any account on the host could read it and impersonate the listener. It is now created with mode 600 through os.open rather than chmod-ed afterwards, because a key that is world-readable for an instant is world-readable. The serial was random.randint(0, 2000). Every host builds the same issuer name, so a certificate here is identified by its serial alone and a range of two thousand collides; a store keyed on issuer and serial cannot hold both. It now comes from x509.random_serial_number(). Generation moves from pyOpenSSL's deprecated X509 API to cryptography's CertificateBuilder, which is what surfaced the other two. That removes twelve deprecation warnings and the pyOpenSSL dependency, and both files are written through context managers instead of being left to the garbage collector. Generation is skipped when both files already exist, so an existing deployment keeps the certificate it has until the pair is deleted. Co-authored-by: JarbasAi <jarbasai@mailfence.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Bare `hivescope` dependency resolves to the newest stable release (0.1.0), which predates the alphas the stack actually runs on, so the suite fails at collection/runtime for reasons unrelated to this repository's code. Floor it at the alpha that matches current core. Co-authored-by: JarbasAi <jarbasai@mailfence.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…he wheel (#59) This repository carries BOTH triggers that put the repository root on `sys.path`: `tests/__init__.py` and a `conftest.py` at the root. The root holds a second copy of `hivemind_http_protocol`, and that copy wins the import, so `build_tests` installs a wheel it never tests and cannot detect a packaging defect. T-6298 measured that deleting the marker alone does NOT fix this repository, because the root conftest re-adds the root by itself. Both go. `pytest_plugins` cannot move into `tests/conftest.py`, because pytest refuses that key in a non-top-level conftest, so a new `tests/conftest.py` carries a `pytest_configure` hook that calls `config.pluginmanager.import_plugin("hivescope.pytest_fixtures")` behind the same `try/except ImportError`. The registration and the old conditional contract are both kept, with no file at the root. Deleting `tests/__init__.py` breaks one sibling import that spells the `tests` package, so it loses the prefix: `from tests.test_handlers import ...` becomes `from test_handlers import ...`. With `tests/` no longer a package, pytest puts `tests/` itself on `sys.path`, so the unprefixed form resolves. Measured on dev 84c68d7, with the wheel built by `uv build` and installed non-editable with the `test` extra, which is what `build_tests` passes. The instrument prepends `raise ImportError(...)` to the WORKING TREE copy of `hivemind_http_protocol/__init__.py`, so a green run proves the wheel was imported. Path `tests`: before, clean 107 passed before, tree poisoned 9 errors during collection after, clean 107 passed after, tree poisoned 107 passed Co-authored-by: JarbasAi <jarbasai@mailfence.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
run 36847349204
RetentionQueue.drain copied the deque and then cleared it. A frame put from another thread between the two calls was lost. RetentionStore.sweep and queue_for also iterated and mutated the shared dict without a lock, which raised KeyError when a sweep removed a queue between the membership check and the lookup. Each queue now holds a lock around put, drain and len. The store holds a re-entrant lock around sweep, queue_for, pop, clear and the missing-key path. Public behaviour is unchanged. Fail-before: the new delivery test fails 9 of 10 runs on dev (delivers 5000 to 15000 of 20000 frames); the churn test fails 10 of 10 (KeyError). Both pass 10 of 10 after the fix. Co-authored-by: JarbasAi <jarbasai@mailfence.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
run 39508023469
enqueue_message looked up the client's outbox with queue_for() and then
appended to it after the store lock was released. A TTL sweep on another
thread could remove that outbox between the two steps, and the frame went
into a queue nobody reads. Under a Noise v3 session one lost frame
desynchronises the transport nonce and the client drops the session.
RetentionStore.put() now finds the outbox and appends under one hold of the
store lock, and ClientRegistry.enqueue() routes text and binary frames
through it.
Fail-before: the new test asserts that the frame is delivered or that the
sweep's drop log counts it ("dropped 1 "). On the unfixed code the frame is
neither delivered nor counted (1 failed). With the fix it passes, and the
full suite passes (110).
Co-authored-by: JarbasAi <jarbasai@mailfence.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
RetentionQueue.put() refreshed the idle timestamp, so a client that stopped polling while frames kept arriving was never swept. Only the per-client frame cap bounded it. The README and docs/architecture.md define undelivered_ttl as the time a client may go without polling. put() no longer touches the timestamp; drain() still does. The test that asserted the old behaviour (a client that only receives survives the sweep) is replaced by one asserting the documented behaviour. The test for poll activity puts a frame between polls and asserts the last drain returns it, so removing the reset in drain() fails it. The docs say to keep undelivered_ttl at least ten times the poll interval. Fail-before: the new test fails on the unfixed code (assert 'deaf' not in store), passes with the fix, and the full suite passes (109). Co-authored-by: JarbasAi <jarbasai@mailfence.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
run 39583358617
run 39583383612
tests/e2e only used hivescope's in-process shim, which never routes a frame through this plugin's handlers. The new module starts the listener on a local port with the real route table and drives it with a real HiveMindHTTPClient: Noise handshake, a client-to-hub frame, a hub-to-client frame through the outbox and /get_binary_messages, and a disconnect. Plain HTTP requests cover the 403, 409 and "not connected" answers. The fixture closes the listening socket on teardown, so the port is free afterwards. Mutation check: making enqueue_message discard frames fails the test with "timed out waiting for HiveMind handshake" (1 failed, 4 passed); the unmutated tree passes (5 passed in tests/e2e, 113 in the suite). Co-authored-by: JarbasAi <jarbasai@mailfence.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
run 39586372524
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Human review requested! This supersedes any earlier open release proposal for this repository.