Skip to content

Add per-command metadata propagation to interfaces - #3777

Open
jwalters2 wants to merge 7 commits into
OpenC3:mainfrom
jwalters2:command-extra-metadata
Open

Add per-command metadata propagation to interfaces#3777
jwalters2 wants to merge 7 commits into
OpenC3:mainfrom
jwalters2:command-extra-metadata

Conversation

@jwalters2

@jwalters2 jwalters2 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Replaces #3666, whose original head fork was deleted.

Problem

Command callers sometimes need to attach correlation and workflow metadata to a single command and make that metadata available to the interface that writes the packet. Previously, the cmd API did not provide a supported way to carry caller metadata through command topics, queues, and command retries.

Changes

  • Add an optional extra Hash/dict to the Ruby and Python cmd API families and scripting wrappers.
  • JSON-encode extra on the command topic and merge it into command.extra before the interface writes the packet.
  • Preserve metadata through command queues and hazardous-command retries.
  • Use the COSMOS JSON encoders for queued metadata so binary values and special numeric values follow the same serialization rules as command parameters.
  • Keep COSMOS-owned audit fields authoritative. Caller values cannot override cmd_string, username, interface_name, queue_username, or approver.
  • Accept both serialized metadata and the earlier raw-Hash queue representation when processing queued commands.
  • Document extra usage for Ruby and Python scripts.

Commands.build_cmd continues to clear template packet metadata. Caller metadata is applied only to the newly built command inside InterfaceMicroservice, preventing stale metadata from leaking between commands.

Compatibility

  • Existing callers are unchanged because extra is optional.
  • Commands without metadata retain their existing topic and queue behavior.
  • Queue processing remains compatible with entries created by the earlier implementation of this branch.

Testing

  • Ruby focused suite: 376 examples, 0 failures.
  • Python focused suite: 117 tests passed.
  • Coverage included the command APIs, scripting wrappers, interface microservices, queue model/processor, command topics, and preidentified protocol framing.
  • Verified queued binary metadata round-trips through the COSMOS JSON encoders.
  • Ran a Docker-based COSMOS Core end-to-end test using the PR images:
    • submitted a command through the JSON-RPC API with flow_uuid and hv_id metadata;
    • confirmed the interface completed the command write;
    • confirmed both raw and decomposed post-write command topics retained the metadata;
    • confirmed the emitted CCSDS command contained 8 packet bytes with APID 2;
    • confirmed caller-supplied queue_username and approver values were removed.
  • git diff --check, Ruby syntax checks, Python compilation, and the repository security/code-analysis checks passed.

@jwalters2
jwalters2 force-pushed the command-extra-metadata branch from 06f7730 to c30dd3f Compare August 27, 2026 05:09
@jmthomas
jmthomas requested review from clayandgen and jmthomas and a balanced review from Copilot and removed request for clayandgen September 9, 2026 17:12

Copilot AI left a comment

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.

🟡 Changes recommended

Binary Python metadata can fail after transmission, and caller metadata can forge command-validation result fields.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds per-command metadata propagation from Ruby/Python command APIs through queues and interfaces.

Changes:

  • Adds optional extra metadata to command APIs and scripting wrappers.
  • Serializes metadata across topics, queues, and retries while protecting audit fields.
  • Adds tests and scripting documentation.
File summaries
File Description
openc3/spec/script/commands_spec.rb Tests Ruby wrapper propagation.
openc3/spec/models/queue_model_spec.rb Tests queued metadata serialization.
openc3/spec/microservices/queue_microservice_spec.rb Tests queued metadata processing.
openc3/spec/microservices/interface_microservice_spec.rb Tests interface metadata merging.
openc3/spec/api/cmd_api_spec.rb Tests Ruby API validation.
openc3/python/test/script/test_commands.py Tests Python wrapper propagation.
openc3/python/test/models/test_queue_model.py Tests binary queue metadata.
openc3/python/test/microservices/test_interface_microservice.py Tests Python interface merging.
openc3/python/test/api/test_cmd_api.py Tests Python API validation.
openc3/python/openc3/topics/command_topic.py Serializes topic metadata.
openc3/python/openc3/script/commands.py Forwards metadata from scripts.
openc3/python/openc3/models/queue_model.py Stores queued metadata.
openc3/python/openc3/microservices/interface_microservice.py Applies metadata before writes.
openc3/python/openc3/api/cmd_api.py Adds Python extra support.
openc3/lib/openc3/topics/command_topic.rb Serializes Ruby topic metadata.
openc3/lib/openc3/script/commands.rb Forwards Ruby script metadata.
openc3/lib/openc3/models/queue_model.rb Persists queue metadata.
openc3/lib/openc3/microservices/queue_microservice.rb Restores queued metadata.
openc3/lib/openc3/microservices/interface_microservice.rb Applies Ruby interface metadata.
openc3/lib/openc3/api/cmd_api.rb Adds Ruby extra support.
docs.openc3.com/docs/guides/scripting-api.md Documents metadata usage.
Review details
  • Files reviewed: 21/21 changed files
  • Comments generated: 4
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread openc3/python/openc3/topics/command_topic.py
Comment thread openc3/lib/openc3/microservices/interface_microservice.rb
Comment thread openc3/python/openc3/microservices/interface_microservice.py
Comment thread docs.openc3.com/docs/guides/scripting-api.md Outdated
@jwalters2
jwalters2 force-pushed the command-extra-metadata branch from c30dd3f to 55faa65 Compare September 9, 2026 18:57
@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.15686% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.51%. Comparing base (679c8b9) to head (795e757).
⚠️ Report is 106 commits behind head on main.

Files with missing lines Patch % Lines
...s-cmd-tlm-api/app/controllers/queues_controller.rb 83.33% 3 Missing ⚠️
openc3/lib/openc3/topics/command_topic.rb 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3777      +/-   ##
==========================================
+ Coverage   79.30%   79.51%   +0.20%     
==========================================
  Files         896      899       +3     
  Lines       67371    67827     +456     
  Branches     2608     2667      +59     
==========================================
+ Hits        53426    53930     +504     
+ Misses      13276    13228      -48     
  Partials      669      669              
Flag Coverage Δ
frontend 66.48% <ø> (-0.08%) ⬇️
python 79.51% <ø> (+0.14%) ⬆️
ruby-api 81.93% <83.33%> (-0.25%) ⬇️
ruby-backend 85.00% <96.96%> (+0.42%) ⬆️

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.

jmthomas and others added 4 commits September 9, 2026 17:54
Caller extra was merged over command.extra, so it overwrote the keys the
packet's accessor had just written during build_cmd. For an HTTP target
that meant HTTP_PATH, HTTP_METHOD, HTTP_HEADERS and HTTP_QUERIES became
caller controlled, letting anyone with cmd permission on the target
redirect the request the interface makes on their behalf.

Merge the other way so packet derived values win. Keys the accessor did
not set still come through, so the feature is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
QueueModel gained an extra keyword but the controller never passed one,
so queueing or editing a command through the API dropped the metadata
attached by cmd(extra: ...).

Pass extra from params on insert_command and update_command, rejecting a
non-Hash with 400. update_command carries the existing entry's extra
forward when the request omits it, since the queue edit APIs have no way
to express it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cmd(extra: ...) puts caller controlled values into packet.extra, which
exposed two problems on the write path.

JSON.generate returns UTF-8. Appending that to the binary log entry or
preidentified frame raises Encoding::CompatibilityError once the buffer
holds a byte >= 0x80, which the packet time nearly always does, so the
packet was silently dropped from the log. Force the JSON to binary.

The extra length field also counted characters instead of bytes, which
would declare a short length and desync every entry after it.

On the Python side PreidentifiedProtocol used plain json.dumps, raising
"Object of type bytes is not JSON serializable" for the binary extra
values COSMOS supports elsewhere. Use JsonEncoder like the rest of the
codebase.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ruby's Float#as_json writes non-finite floats as
{"json_class": "Float", "raw": "NaN"|"Infinity"|"-Infinity"} since bare
NaN/Infinity literals are not valid JSON. JsonDecoder only understood
the String form, so those values reached Python interfaces as dicts
rather than floats. Commands released by the Ruby queue microservice
carry their extra in exactly this encoding.

Decode both forms. The encoder is unchanged: Python writes bare literals
that Ruby already parses with allow_nan, and switching it would break
readers using plain json.loads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Sep 9, 2026

Copy link
Copy Markdown

@jmthomas jmthomas left a comment

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.

I made some additional improvements to the PR. Approved.

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