Skip to content

feat(bridge): wire BRIDGE interface keywords and DEFAULT bridge per scope - #3697

Open
ryanmelt wants to merge 1 commit into
pr3-bridge-runtimefrom
pr4-interface-wiring
Open

feat(bridge): wire BRIDGE interface keywords and DEFAULT bridge per scope#3697
ryanmelt wants to merge 1 commit into
pr3-bridge-runtimefrom
pr4-interface-wiring

Conversation

@ryanmelt

Copy link
Copy Markdown
Member

Stacked PR 4 of 5 — base: pr3-bridge-runtime (#3696). Part of the #3688 split. Needs PR 2 + 3.

Wires the bridge into interface configuration:

  • New keywords: BRIDGE, BRIDGE_OPTION, BRIDGE_PROTOCOL, BRIDGE_SECRET
  • deploy_bridge / undeploy_bridge on the interface model
  • DEFAULT bridge per scope (migration 20260704000000_default_bridge.rb + scope_model)
  • interface_modifiers.yaml docs

Tests

Has the only tests in the series (interface_model_spec, scope_model_spec, interface_docs_spec). Also updates two Playwright admin tests, which start failing here because the DEFAULT bridge now adds a secret + microservice to the demo (row-scoped locators instead of positional).

Review after #3696.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.66667% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.28%. Comparing base (1d9b428) to head (e4bf403).

Files with missing lines Patch % Lines
openc3/lib/openc3/models/interface_model.rb 72.54% 14 Missing ⚠️
Additional details and impacted files
@@                  Coverage Diff                   @@
##           pr3-bridge-runtime    #3697      +/-   ##
======================================================
- Coverage               79.31%   79.28%   -0.03%     
======================================================
  Files                     885      887       +2     
  Lines                   65368    65511     +143     
  Branches                 2585     2537      -48     
======================================================
+ Hits                    51845    51941      +96     
- Misses                  12852    12897      +45     
- Partials                  671      673       +2     
Flag Coverage Δ
frontend 63.51% <ø> (-0.14%) ⬇️
python 81.53% <ø> (+<0.01%) ⬆️
ruby-api 82.18% <ø> (-0.09%) ⬇️
ruby-backend 84.05% <76.66%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ryanmelt
ryanmelt force-pushed the pr4-interface-wiring branch from 84b8e09 to 013b3f9 Compare August 10, 2026 19:51
…cope

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ryanmelt
ryanmelt force-pushed the pr4-interface-wiring branch from 013b3f9 to e4bf403 Compare August 10, 2026 23:17
@sonarqubecloud

Copy link
Copy Markdown

python_example: |
INTERFACE DATA_INT openc3/interfaces/tcpip_client_interface.py host.docker.internal 8080 8081 10.0 nil BURST
MAP_TARGET DATA
PROTOCOL READ openc3/interfaces/protocols/ignore_packet_protocol.py INST IMAGE # Drop all INST IMAGE packets

@ryan-pratt ryan-pratt Aug 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BRIDGE_PROTOCOL READ

since: 7.4.0
parameters:
- name: Bridge Name
required: false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name = "#{scope}__BRIDGE__DEFAULT"
next if MicroserviceModel.get_model(name: name, scope: scope)

BridgeModel.build_microservice(bridge_name: "DEFAULT", scope: scope, shard: scope_model.shard).create

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this also need to call deploy()? That's what ScopeModel does here

# removed. Leaving the model untouched lets the relay adapt live instead.
def deploy_bridge_relay
relay = MicroserviceModel.get_model(name: bridge_relay_name, scope: @scope)
raise "Bridge #{@bridge_name} does not exist" unless relay

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this causes orphaned data in redis if not caught up by the caller for explicit cleanup

Comment on lines -73 to -79

it "should have Ruby / Python parity" do
ruby_not_python = @ruby_interfaces - @python_interfaces - PYTHON_TODO_INTERFACES
expect(ruby_not_python).to be_empty, "Interfaces found in Ruby but not Python: #{ruby_not_python}"
python_not_ruby = @python_interfaces - @ruby_interfaces - %w(__init__)
expect(python_not_ruby).to be_empty, "Interfaces found in Python but not Ruby: #{python_not_ruby}"
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk if we want to remove this entirely... Probably warrants a discussion

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to remove this ... not yet. Simply add bridge_interface to the list at line 77

received by the interface while write acts on the data before it is sent out. READ_WRITE applies
the protocol to both reading and writing.<br/><br/>
For information on creating your own custom protocol please see [Protocols](../configuration/protocols.md)
since: 7.4.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throughout: Replace with since: _Coming Soon_ which will be updated on release


when 'BRIDGE'
parser.verify_num_parameters(1, 1, "#{keyword} <Bridge Name>")
@bridge_name = parameters[0]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should normalize the name to upper case. Also options: [['BRIDGE_NAME', bridge_name.to_s.upcase]] in build_microservice.

relay = MicroserviceModel.get_model(name: "DEFAULT__BRIDGE__MYBRIDGE", scope: "DEFAULT")
expect(relay).to_not be_nil
expect(relay.cmd[1]).to eql "bridge_microservice.py"
expect(relay.options).to include(["BRIDGE_NAME", "mybridge"])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"mybridge" => "MYBRIDGE"

@target_names.each { |target_name| ensure_target_exists(target_name) }
microservice.create
microservice.deploy(gem_path, variables)
deploy_bridge_relay

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't actually deploying anything ... it's checking that the MicroserviceModel exists. Safe to do in the validate case and should be done before creating the microservice (see ryan-pratt comment).

Maybe the ensure_target_exists should also move outside the validate_only check?

# the connection options and secrets needed to open the device are included.
def deploy_host_microservice
host = HostMicroserviceModel.new(
name: @name,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interface and router could share the same name and overwrite each other. We should probably just disallow this at a higher level because I don't think we account for that anywhere.

Comment on lines +173 to +174
# connection options/secret_options belong to the host interface and
# are omitted, but protocols and target mapping stay in COSMOS.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are those options omitted? I see lines 197 and 199 iterating over the options and secret_options.

Comment on lines -73 to -79

it "should have Ruby / Python parity" do
ruby_not_python = @ruby_interfaces - @python_interfaces - PYTHON_TODO_INTERFACES
expect(ruby_not_python).to be_empty, "Interfaces found in Ruby but not Python: #{ruby_not_python}"
python_not_ruby = @python_interfaces - @ruby_interfaces - %w(__init__)
expect(python_not_ruby).to be_empty, "Interfaces found in Python but not Ruby: #{python_not_ruby}"
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to remove this ... not yet. Simply add bridge_interface to the list at line 77

MAP_TARGET DATA
PROTOCOL READ openc3/interfaces/protocols/ignore_packet_protocol.py INST IMAGE # Drop all INST IMAGE packets
BRIDGE_OPTION:
summary: Set a parameter on an bridge interface

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on a bridge

BRIDGE_OPTION:
summary: Set a parameter on an bridge interface
description:
When an bridge option is set the bridge interface class calls the set_option method.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a bridge

required: false
description: Name of the secret store for stores with multipart keys
values: .*
example: |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include the same context as BRIDGE_OPTION

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.

3 participants