Skip to content

feat(cli): add plugin invocation protocol#155

Merged
QMalcolm merged 3 commits into
mainfrom
qmalcolm--feat-plugin-io-protocol
Jul 23, 2026
Merged

feat(cli): add plugin invocation protocol#155
QMalcolm merged 3 commits into
mainfrom
qmalcolm--feat-plugin-io-protocol

Conversation

@QMalcolm

@QMalcolm QMalcolm commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Stacked on #154

Summary

  • Adds Request, Response, and Issue JSON envelope types for the plugin stdin/stdout protocol
  • Adds Invoke(ctx, pluginDir, invoke, req, pluginStderr) — runs a plugin subprocess, pipes the request JSON to stdin, captures stdout as the response, and enforces a context timeout
  • A non-empty Issues slice in the response is not a Go error; the caller (defined in a later segment of work) will decide whether error-severity issues produce a non-zero exit code

@QMalcolm

Copy link
Copy Markdown
Contributor Author

@QMalcolm
QMalcolm force-pushed the qmalcolm--feat-plugin-discovery branch from 9b858f5 to 9811e05 Compare June 23, 2026 21:05
@QMalcolm
QMalcolm force-pushed the qmalcolm--feat-plugin-io-protocol branch from 37a0bc7 to fce4b4d Compare June 23, 2026 22:06
@QMalcolm
QMalcolm marked this pull request as ready for review June 23, 2026 22:22
@QMalcolm
QMalcolm force-pushed the qmalcolm--feat-plugin-discovery branch from 4cdc89d to bca8161 Compare June 24, 2026 21:30
@QMalcolm
QMalcolm force-pushed the qmalcolm--feat-plugin-io-protocol branch from fce4b4d to fdecf45 Compare June 24, 2026 21:31
@QMalcolm
QMalcolm force-pushed the qmalcolm--feat-plugin-discovery branch from bca8161 to f2873b5 Compare July 17, 2026 21:38
Base automatically changed from qmalcolm--feat-plugin-discovery to main July 22, 2026 15:12
QMalcolm added 3 commits July 22, 2026 12:12
C1 (osi convert base) needs a subprocess layer to pipe requests to
plugins and receive responses. This adds the Request/Response/Issue
envelope types and an Invoke() function that owns the full lifecycle:
marshal request to stdin, capture stdout, forward stderr, enforce
context timeout, and decode the JSON response.

Design decisions:
- Invoke takes pluginDir and invoke []string directly rather than
  *Plugin — decouples the invocation layer from the discovery types
  and makes the function independently testable
- cmd.Stdout assigned as *bytes.Buffer rather than using cmd.Output()
  — cmd.Output() is incompatible with a pre-assigned cmd.Stderr writer
  and would silently discard the caller's stderr destination
- ctx.Err() checked after cmd.Run() for timeout detection — exec
  kills the process with SIGKILL and returns "signal: killed", not
  context.DeadlineExceeded, so the context must be checked explicitly
- Issue.Path uses omitempty — an absent key is semantically distinct
  from an empty string; plugins that have no path omit the field
- A non-empty Issues slice is NOT a Go error — the caller (C1) is
  responsible for inspecting severities and determining exit code

Tests use the TestMain subprocess self-invocation pattern: the test
binary re-invokes itself with GO_TEST_PLUGIN=1, enters the fake
plugin branch in TestMain, and exits without running any tests.
This avoids compiling a separate helper binary and works portably.

Caveats:
- File collection, output writing, issue rendering, and wiring into
  cmd/convert.go are deferred to C1
A Request with nil Files marshals as {"files":null} rather than
{"files":{}}. Plugins that iterate over the files object may crash
on a null value. Guard added at the top of Invoke(); test added to
verify the wire format is always an object.
An empty invoke slice would panic with an index out of range on
invoke[0]. While F2 validation prevents this in normal usage, an
explicit check with a descriptive error is safer and makes the
failure mode obvious if the invariant is ever violated.
@QMalcolm
QMalcolm force-pushed the qmalcolm--feat-plugin-io-protocol branch from fdecf45 to 2deeacd Compare July 22, 2026 18:01

var stdout bytes.Buffer
cmd := exec.CommandContext(ctx, invoke[0], invoke[1:]...)
cmd.Dir = pluginDir

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.

Would this fail nicely if pluginDir is not accessible ? Would be good to add error handling for this case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Currently, when the pluginDir is not accessible the failure looks like

plugin process failed: chdir /nonexistent/path/xyz: no such file or directory

Additionally it's gets wrapped in a normal Go error instead of becoming a panic. Considering that, I think we can say it fails gracefully 🙂

@khush-bhatia

khush-bhatia commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

LGTM, but some good follow ups for future for anyone interested:

  1. It will be great to support plugins directly reading the semantic model files.
  2. The execution contract for these plugins is something we should continue to evolve as support for query API lands.

Should be we track these as Issues ?

@QMalcolm

Copy link
Copy Markdown
Contributor Author

LGTM, but some good follow ups for future for anyone interested:

1. It will be great to support plugins directly reading the semantic model files.

2. The execution contract for these plugins is something we should continue to evolve as support for query API lands.

Should be we track these as Issues ?

I definitely think we should track (1) as an issue. I'm not sure exactly how we want to do this long term. Having the CLI handle read/write means that each plugin doesn't need to implement handing that with the OS, and ensures that glob based file lookup is always handled the same regardless of plugin used. However for sufficiently large semantic layers, it will probably be meaningfully impactful if the plugins handle file system read/write themselves.

For item (2), if we have specific areas (like protocol spec for query API), those would probably be good to create issues to track 🙂

@QMalcolm
QMalcolm merged commit 0ed17a3 into main Jul 23, 2026
2 checks passed
@QMalcolm
QMalcolm deleted the qmalcolm--feat-plugin-io-protocol branch July 23, 2026 22:20
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