feat(bridge): add bridge and host microservice models - #3695
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## pr1-alpine-to-debian #3695 +/- ##
========================================================
- Coverage 79.32% 79.30% -0.03%
========================================================
Files 885 885
Lines 65367 65367
Branches 2537 2537
========================================================
- Hits 51855 51838 -17
- Misses 12838 12856 +18
+ Partials 674 673 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| "watchdog (>=6.0.0,<7.0.0)", | ||
| "websockets (>=16.0,<17.0)", | ||
| "pyserial>=3.5", | ||
| "iroh (>=1.0.0,<2.0.0)", |
There was a problem hiding this comment.
Nitpick: Parenthesized style is Poetry-era - https://packaging.python.org/en/latest/specifications/dependency-specifiers/
| { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, | ||
| ] | ||
|
|
||
| [[package]] |
There was a problem hiding this comment.
iroh ships four wheels and no sdist: macosx_11_0_arm64,
manylinux_2_28_aarch64, manylinux_2_28_x86_64, win_amd64. PyPI shows the
same set for 1.0.0 and 1.1.0.
That means uv sync hard-fails on Intel macOS (x86_64-apple-darwin) — no
wheel to install, no source to build. If Intel Mac is still a supported dev
platform, this needs an optional extra or an environment marker rather than an
unconditional core dependency.
The missing musl wheel is presumably why #3694 has to land first — worth saying
so explicitly in the PR body.
| 'public_key' => @public_key, | ||
| 'ticket' => @ticket, | ||
| 'app_public_key' => @app_public_key, | ||
| 'enroll_code' => @enroll_code, |
There was a problem hiding this comment.
enroll_code is serialized by as_json, which is what a ModelController or
API endpoint returns. That ships the pending one-time pairing secret with every
read of the bridge record, so anyone who can read it can redeem the enrollment.
The comment on line 27 says the bridge's private key "lives in the secrets
store, never here" — the enroll code deserves the same treatment, or at minimum
redaction from as_json with a separate accessor for the enroll path.
Same on the Python side at bridge_model.py:106.
There was a problem hiding this comment.
Agreed: enroll_code is returned in normal model JSON, exposing the pairing secret. Redact it and expose it only through enrollment.
| # END NOTE | ||
|
|
||
| @classmethod | ||
| def from_json(cls, json_data: str | dict, scope: str): |
There was a problem hiding this comment.
from_json and get_model here are byte-identical to the base Model
implementations at model.py:71-90, so they're dead code. Same in
bridge_interface_model.py:47 and host_microservice_model.py:50 — about 36
lines total.
Worth noting the existing overrides aren't precedent for this: scope_model.py
drops the scope argument and activity_model.py pops name/scope before
constructing, so both diverge from base for a reason. These three don't. Suggest
deleting all six.
| self, | ||
| name: str, | ||
| scope: str, | ||
| bridge_name: str = None, |
There was a problem hiding this comment.
bridge_name and stream default to None, but the Ruby twin requires both
as keyword args (host_microservice_model.rb:63). As written, Python can
persist a bridged interface with no bridge and no stream. Make them required to
match.
| @needs_dependencies = needs_dependencies | ||
| end | ||
|
|
||
| def as_json(*a) |
There was a problem hiding this comment.
as_json omits 'scope'. The Python twin includes it
(host_microservice_model.py:109), and BridgeModel includes it on both sides.
Round-trips work either way since both from_json implementations inject scope,
but the PR body claims identical JSON shape between the pairs, and this is the
one place it isn't.
jmthomas
left a comment
There was a problem hiding this comment.
Address all existing comments but otherwise LGTM.
| require 'securerandom' | ||
|
|
||
| module OpenC3 | ||
| # Stores the identity of a named Iroh bridge (the bridge_microservice hub). |
There was a problem hiding this comment.
Iroh is a Rust P2P networking stack, used in https://github.com/OpenC3/openc3-cosmos-app
| # Generate a one-time manual-enrollment code, store it on this bridge, and | ||
| # return a token (base64 JSON of bridge name + hub ticket + code) that a | ||
| # remote openc3-app pastes to enroll. Requires the bridge to be up (ticket | ||
| # present). The code is redeemed once over the api/enroll ALPN. |
| 'public_key' => @public_key, | ||
| 'ticket' => @ticket, | ||
| 'app_public_key' => @app_public_key, | ||
| 'enroll_code' => @enroll_code, |
There was a problem hiding this comment.
Agreed: enroll_code is returned in normal model JSON, exposing the pairing secret. Redact it and expose it only through enrollment.


Stacked PR 2 of 5 — base:
pr1-alpine-to-debian(#3694). Part of the #3688 split.Pure additions — nothing references these yet. Ruby/Python bridge, host-microservice, and bridge-interface models, plus the
irohPython dependency (pyproject.toml/uv.lock).Reviewer focus
iroh)🤖 Generated with Claude Code