Skip to content

feat: collect worker interface (request/response format) on publish#67

Merged
guibeira merged 13 commits intomainfrom
feat/req-res-format-on-publish
May 4, 2026
Merged

feat: collect worker interface (request/response format) on publish#67
guibeira merged 13 commits intomainfrom
feat/req-res-format-on-publish

Conversation

@guibeira
Copy link
Copy Markdown
Collaborator

@guibeira guibeira commented May 3, 2026

Summary

  • Refactors the inline payload-building Python in _publish-registry.yml into three testable scripts under .github/scripts/: build_publish_payload.py, collect_worker_interface.py, resolve_binary_artifacts.py.
  • Spins up the iii engine and starts the worker locally during publish so we can collect the live function/trigger interface (descriptions, request_format, response_format, metadata) and ship it to the registry.
  • Maps the engine's request_format / response_format fields to the registry's request_schema / response_schema payload shape, and normalizes triggers under their owning worker.
  • Adds Rust-worker support to the local-runtime detection so workers without scripts.start (Rust binaries) can still serve their interface via cargo run.

Test plan

  • Smoke-test _publish-registry.yml end-to-end against the staging registry and verify /publish accepts request_schema / response_schema.
  • Publish at least one Rust worker (e.g. image-resize) and one iii-add worker to confirm both runtime modes collect the interface.
  • Verify the mcp worker still publishes correctly (uses the special-cased --no-stdio flag).
  • Confirm collected functions[] is non-empty for every worker that previously published.

Summary by CodeRabbit

Release Notes

  • Chores
    • Refactored worker publishing workflow to improve binary artifact resolution and metadata collection.
    • Enhanced deployment options with support for binary and image modes.
    • Added comprehensive error diagnostics and automatic cleanup during the publish process.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

📝 Walkthrough

Walkthrough

This PR introduces a refactored publish workflow for registry workers. Three new Python scripts are added: collect_worker_interface.py fetches worker/function/trigger metadata from a local III engine; resolve_binary_artifacts.py downloads artifact URLs and checksums from GitHub releases; build_publish_payload.py normalizes collected data and constructs a JSON publish payload. The workflow is rewritten to install the III CLI, start a local engine, launch a worker, orchestrate these scripts sequentially, and remove inline payload-building logic.

Changes

Unified Registry Publish Workflow

Layer / File(s) Summary
III Engine & Worker Startup
.github/workflows/_publish-registry.yml
Installs III CLI, starts local III engine with readiness checks, and launches either a scripted worker via iii worker add or a Rust binary via cargo run, with PID tracking for cleanup.
Worker Interface Collection
.github/scripts/collect_worker_interface.py
Fetches workers, functions, and triggers from the III engine via iii CLI calls, waits for target worker availability, normalizes collected data, and outputs worker-interface.json.
Binary Artifact Resolution
.github/scripts/resolve_binary_artifacts.py
Resolves downloadable binary URLs and SHA-256 checksums from GitHub release assets for configured platform targets, emitting warnings on missing checksums.
Payload Normalization & Building
.github/scripts/build_publish_payload.py
Reads worker metadata files, normalizes dependencies, function names, and trigger schemas, constructs the final publish JSON with worker details and interface data, enforces deploy-mode validation (binary or image).
Workflow Integration & Cleanup
.github/workflows/_publish-registry.yml
Orchestrates script execution in sequence, captures worker-interface.json and binaries.json outputs, passes them to payload builder, adds diagnostic logging on failure, and stops worker and engine processes on cleanup.

Sequence Diagram

sequenceDiagram
    participant GHA as GitHub Actions
    participant CLI as III CLI
    participant Engine as III Engine
    participant Worker as Local Worker
    participant Scripts as Collection Scripts
    participant Pub as Publish Registry

    GHA->>GHA: Install III CLI
    GHA->>Engine: Start III Engine (background)
    Engine-->>GHA: Ready (readiness probe)
    
    GHA->>Worker: Start Worker (via iii worker add/cargo run)
    Worker->>Engine: Register
    
    GHA->>Scripts: Run collect_worker_interface.py
    Scripts->>CLI: iii trigger engine::workers::list
    CLI->>Engine: Query workers
    Engine-->>CLI: Workers JSON
    CLI-->>Scripts: Worker data
    Scripts->>CLI: iii trigger engine::functions::list
    CLI->>Engine: Query functions
    Engine-->>CLI: Functions JSON
    CLI-->>Scripts: Functions data
    Scripts->>CLI: iii trigger engine::triggers::list (optional)
    CLI->>Engine: Query triggers
    Engine-->>CLI: Triggers JSON
    CLI-->>Scripts: Triggers data
    Scripts->>Scripts: Normalize interface (workers_json, functions, triggers)
    Scripts-->>GHA: worker-interface.json
    
    GHA->>Scripts: Run resolve_binary_artifacts.py
    Scripts->>Scripts: Fetch .sha256 files from GitHub releases
    Scripts-->>GHA: binaries.json
    
    GHA->>Scripts: Run build_publish_payload.py
    Scripts->>Scripts: Load worker YAML, README, config
    Scripts->>Scripts: Build payload with interface + binaries
    Scripts-->>GHA: publish-payload.json
    
    GHA->>Pub: POST payload
    Pub-->>GHA: Published

    GHA->>GHA: Stop worker & engine (cleanup)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • fix(ci): accept map-form dependencies in iii.worker.yaml #43: Adds dependency normalization logic (normalize_dependencies) and integrates it into the publish payload builder to accept map-form dependencies and convert them to the registry's array-of-objects format, directly complementing the new payload building infrastructure introduced here.

Suggested reviewers

  • andersonleal
  • sergiofilhowz

Poem

🐰 Hop, hop—the workflow's been refined!
Interface collection, binaries aligned,
Three scripts now dance with the engine so true,
From worker to payload—a clean publish flow through!
No more inline logic—just clarity and grace,
Registry publishing finds its new place.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main objective of the PR: collecting worker interface with request/response format data during the publish workflow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/req-res-format-on-publish

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.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/scripts/build_publish_payload.py:
- Around line 10-17: The normalize_dependencies function returns raw lists
unchanged, which can leave string entries like "foo" instead of dicts; update
the list branch in normalize_dependencies to map each item to a dict of the form
{"name": ..., "version": ...}: if an item is a str, convert to {"name": item,
"version": ""} (or None if you prefer), if it's already a dict keep it as-is (or
validate it has "name"/"version"), and if an item is another type raise a
ValueError; this ensures normalize_dependencies always returns list[dict[str,
Any]].
- Around line 141-153: The loop that builds the functions list should fail fast
when a worker_function_ids entry has no corresponding details in functions_by_id
instead of appending empty placeholders; update the code around the for loop
that iterates worker_function_ids (the logic that reads details =
functions_by_id.get(function_id, {}), derive_registry_function_name, and
functions.append) to detect missing details (e.g., if function_id not in
functions_by_id) and raise an exception or exit with a clear error including the
missing function_id so the publish stops rather than emitting blanks.

In @.github/scripts/collect_worker_interface.py:
- Around line 24-55: Add a timeout to the subprocess.run() call in run_iii()
(e.g., timeout=some_seconds) so iii calls cannot hang indefinitely, and update
collect_triggers() to also catch subprocess.TimeoutExpired (in addition to
subprocess.CalledProcessError and json.JSONDecodeError) and treat it as a
soft-fail (returning None or the same behavior used for other caught
exceptions). Locate run_iii and collect_triggers in this file to add the timeout
argument to subprocess.run(...) and include subprocess.TimeoutExpired in the
except clause in collect_triggers().

In @.github/workflows/_publish-registry.yml:
- Around line 55-59: The workflow step named "Install iii CLI" currently
downloads the floating installer at install.iii.dev/iii/main/install.sh; update
it to pin to a specific release and invoke the installer with the version flag
(e.g., pass "-s -- v0.11.3") instead of tracking main. Concretely, modify the
curl target and the subsequent sh invocation in that step so you fetch the
release-specific installer URL or pass the version to the installer script and
run it with the secure flag, ensuring the installer verifies SHA256 checksums as
intended.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 32a2cc6a-62fd-4ee2-bae6-c44516ada299

📥 Commits

Reviewing files that changed from the base of the PR and between 94feebe and 25158b4.

📒 Files selected for processing (4)
  • .github/scripts/build_publish_payload.py
  • .github/scripts/collect_worker_interface.py
  • .github/scripts/resolve_binary_artifacts.py
  • .github/workflows/_publish-registry.yml

Comment thread .github/scripts/build_publish_payload.py
Comment thread .github/scripts/build_publish_payload.py
Comment thread .github/scripts/collect_worker_interface.py Outdated
Comment on lines +55 to +59
- name: Install iii CLI
run: |
set -euo pipefail
curl -fsSL https://install.iii.dev/iii/main/install.sh -o /tmp/install-iii.sh
sh /tmp/install-iii.sh
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "_publish-registry.yml" -o -name "*publish*registry*"

Repository: iii-hq/workers

Length of output: 100


🏁 Script executed:

cat .github/workflows/_publish-registry.yml

Repository: iii-hq/workers

Length of output: 8462


🌐 Web query:

III installer versioning checksums signatures install.iii.dev documentation

💡 Result:

The install.iii.dev scripts for iii engine, console, and related tools download platform-specific binaries from GitHub releases of iii-hq/iii and verify them using SHA256 checksums published alongside each release (e.g., iii-x86_64-unknown-linux-musl.tar.gz.sha256 files containing the hash). The install.sh scripts explicitly state they verify the SHA256 checksum before installation. No cryptographic signatures (e.g., GPG, cosign .sig/.pem) on checksums or binaries are mentioned in documentation or releases. Binaries are unsigned (macOS requires xattr -d com.apple.quarantine). Versioning uses semantic tags like iii/v0.11.3; installers support pinning via -s -- v0.11.3 or default to latest stable (non-prerelease, iii/v* prefix). Official docs at https://iii.dev/docs/install recommend the curl | sh method without manual verification steps.

Citations:


Pin the III installer to a specific version instead of tracking main.

This step downloads and executes a floating remote shell script from install.iii.dev/iii/main/install.sh. Because the publish secret is used later in the same job, any unexpected change or compromise in the main branch installer can own the release path. Switch to a version-pinned install (e.g., install.sh -s -- v0.11.3). The installer automatically verifies SHA256 checksums, so no additional manual verification is needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/_publish-registry.yml around lines 55 - 59, The workflow
step named "Install iii CLI" currently downloads the floating installer at
install.iii.dev/iii/main/install.sh; update it to pin to a specific release and
invoke the installer with the version flag (e.g., pass "-s -- v0.11.3") instead
of tracking main. Concretely, modify the curl target and the subsequent sh
invocation in that step so you fetch the release-specific installer URL or pass
the version to the installer script and run it with the secure flag, ensuring
the installer verifies SHA256 checksums as intended.

- normalize_dependencies now converts list-form string entries to {name, version} dicts and validates each item
- normalize_worker_interface fails fast when worker_function_ids reference missing function details, instead of publishing blank schemas
- run_iii has a 30s subprocess timeout, and collect_triggers handles TimeoutExpired alongside the existing soft-fail paths
- pin the iii CLI installer to v0.11.5 via the VERSION env var so the publish job no longer tracks main
@guibeira guibeira marked this pull request as ready for review May 4, 2026 10:11
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/scripts/build_publish_payload.py:
- Around line 141-143: The code currently coerces falsy non-list values to [] by
using matches[0].get("functions") or [], which masks malformed payloads; change
the assignment to capture the raw value (e.g., worker_function_ids =
matches[0].get("functions")) and keep the existing type check (if not
isinstance(worker_function_ids, list): raise ValueError(...)) so that non-list
falsy values like {} or "" fail fast instead of being converted to an empty
list; ensure any subsequent logic handles a None case explicitly if you expect
the key to be optional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a8dc27d-d914-4e58-a164-267819016a4c

📥 Commits

Reviewing files that changed from the base of the PR and between 25158b4 and 9ef41a4.

📒 Files selected for processing (3)
  • .github/scripts/build_publish_payload.py
  • .github/scripts/collect_worker_interface.py
  • .github/workflows/_publish-registry.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/scripts/collect_worker_interface.py
  • .github/workflows/_publish-registry.yml

Comment on lines +141 to +143
worker_function_ids = matches[0].get("functions") or []
if not isinstance(worker_function_ids, list):
raise ValueError("worker `functions` must be an array")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't coerce invalid functions payloads to [].

matches[0].get("functions") or [] turns falsy non-lists like {} or "" into a valid empty list. That weakens the fail-fast path below and can still let a malformed engine response publish with no functions.

Proposed fix
-    worker_function_ids = matches[0].get("functions") or []
-    if not isinstance(worker_function_ids, list):
+    raw_worker_function_ids = matches[0].get("functions")
+    if raw_worker_function_ids is None:
+        worker_function_ids = []
+    elif not isinstance(raw_worker_function_ids, list):
         raise ValueError("worker `functions` must be an array")
+    else:
+        worker_function_ids = raw_worker_function_ids
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
worker_function_ids = matches[0].get("functions") or []
if not isinstance(worker_function_ids, list):
raise ValueError("worker `functions` must be an array")
raw_worker_function_ids = matches[0].get("functions")
if raw_worker_function_ids is None:
worker_function_ids = []
elif not isinstance(raw_worker_function_ids, list):
raise ValueError("worker `functions` must be an array")
else:
worker_function_ids = raw_worker_function_ids
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/scripts/build_publish_payload.py around lines 141 - 143, The code
currently coerces falsy non-list values to [] by using
matches[0].get("functions") or [], which masks malformed payloads; change the
assignment to capture the raw value (e.g., worker_function_ids =
matches[0].get("functions")) and keep the existing type check (if not
isinstance(worker_function_ids, list): raise ValueError(...)) so that non-list
falsy values like {} or "" fail fast instead of being converted to an empty
list; ensure any subsequent logic handles a None case explicitly if you expect
the key to be optional.

@guibeira guibeira merged commit 705e45f into main May 4, 2026
7 checks passed
sergiofilhowz pushed a commit that referenced this pull request May 4, 2026
…#69)

* fix(ci): bump iii to v0.11.6-next.1 and refetch annotated release tag

- _publish-registry.yml: pin iii install to v0.11.6-next.1 with --next. The previous v0.11.5 pin (added in #67) didn't surface external workers correctly in `engine::workers::list`, so `collect_worker_interface.py` raised `ValueError: expected exactly one worker matching 'image-resize', found 0` even when the worker was alive and connected. iii-hq/iii#1598 traced the same symptom on engine workers to PR iii-hq/iii#1585 landing in 0.11.6.
- release.yml: actions/checkout@v4 fetches all tags first, then re-fetches the target SHA with `--no-tags +SHA:refs/tags/<tag>`, which silently overwrites the annotated tag with a lightweight one and drops the message. Add an explicit `git fetch origin +refs/tags/<tag>:refs/tags/<tag>` so `git tag -l --format=%(contents)` can still read the `registry-tag:` line and route the publish to next/latest as intended.

* fix(ci): fall back to namespace match when worker has no name

The Rust SDK's register_worker(url, opts) doesn't take a worker name, so external Rust workers (image-resize, mcp, proof, iii-lsp) register with id=<UUID> and name=null. Matching by `name == worker_name` always failed on these, even with the worker alive and connected.

Add a fallback that looks for a worker whose registered functions are namespaced under the worker dir's snake_case (image-resize → image_resize::*). If exactly one worker matches, use it. On failure, include the workers list summary in the error so future regressions are debuggable from the log.

Direct name/id match still wins so the iii-add path (Node/Python workers with explicit names) is unaffected.
sergiofilhowz pushed a commit that referenced this pull request May 5, 2026
The mcp/v0.5.1 publish failed because `_publish-registry.yml` started the worker with `cargo run --bin mcp -- --no-stdio`, but mcp's CLI no longer accepts that flag (mcp/src/main.rs only declares `--config`, `--url`, `--manifest`). cargo exited with `error: unexpected argument '--no-stdio' found` before the worker could connect, the 2s `kill -0` post-spawn check passed because cargo itself was still draining, and `collect_worker_interface` then waited 120s for a worker that never registered before raising `ValueError: could not match worker 'mcp'`.

The flag was added in #67 specifically to keep mcp from binding stdio in the publish job; that concern is moot now (default mcp run is fine for interface collection). Drop the special case so mcp goes through the same `cargo run --bin mcp --` path as every other rust worker.
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