Skip to content

feat(workflow): materialize certified tool capabilities as nodes - #49

Merged
2233admin merged 9 commits into
mainfrom
codex/tool-capability-catalog-bridge-20260728
Jul 27, 2026
Merged

feat(workflow): materialize certified tool capabilities as nodes#49
2233admin merged 9 commits into
mainfrom
codex/tool-capability-catalog-bridge-20260728

Conversation

@2233admin

@2233admin 2233admin commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Outcome

Turn explicitly opted-in, machine-runnable Tool Capabilities into backend-authoritative Canvas catalog nodes instead of leaving them only in the resource list.

Changes

  • add a generic Tool Capability → catalog projection bridge gated by nodeCatalog.authority=backend and canvas.node=true
  • preserve runtime binding, version pin, executor, typed ports, readiness, and resource projection
  • expose the existing deterministic situation-awareness and swarm-simulation capabilities as direct tool nodes
  • accept dynamic catalog IDs only when ui.catalogId == params.toolCapability.id and that ID exists in the backend registry
  • add backend and frontend regression coverage for catalog → node → compile → runtime binding → stale pin rejection

Safety

  • capabilities remain hidden unless they explicitly opt in
  • blocked capabilities remain blocked
  • unknown or forged Tool Capability IDs remain rejected
  • fake/fixture-only realtime tools remain resource-only
  • no new dependency or runtime executor

Verification

  • pytest -q --no-cov tests/unit/test_workflow_tool_catalog_projection.py tests/integration/test_workflow_intelligence_tools.py → 7 passed
  • pnpm --dir frontend run check:node-capabilities → 12 passed
  • targeted py_compile → passed
  • git diff --check 1d8b0e8..HEAD → passed
  • correct pin compiles and binds workflow.external-tool.capability
  • stale pin fails only with tool_capability_version_pin_mismatch

Independent verifier verdict: PASS.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 35339ee9-4a48-426e-a10f-970ff36d98f6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Backend tool capability manifests now project eligible Canvas nodes into workflow catalogs, with runtime recognition and version-pin validation. Frontend and backend regression tests cover catalog lookup, node creation, parameter propagation, runtime resolution, and stale pins.

Changes

Tool Capability Catalog Integration

Layer / File(s) Summary
Manifest metadata and catalog projection
backend/workflow/tool_capabilities.py, backend/workflow/capability_projection.py
Realtime manifests add catalog metadata, and eligible backend tools are projected into workflow catalog capabilities with bindings, parameters, and availability state.
Tool node recognition and pin validation
backend/workflow/node_registry.py, backend/workflow/runtime_registry.py, backend/workflow/compiler.py
Tool-backed nodes receive library origins, matching catalog IDs are treated as external tool capabilities, and capability version pins are validated.
Catalog creation and execution regression coverage
frontend/package.json, frontend/scripts/check-tool-capability-catalog-regressions.mjs, tests/unit/test_workflow_tool_catalog_projection.py
Regression tests cover frontend catalog/node behavior and backend projection, runtime resolution, and stale version-pin rejection.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ToolManifest
  participant CapabilityProjection
  participant WorkflowNodeCatalog
  participant WorkflowCompiler
  ToolManifest->>CapabilityProjection: expose catalog and toolCapability metadata
  CapabilityProjection->>WorkflowNodeCatalog: provide projected catalog entry
  WorkflowNodeCatalog->>WorkflowCompiler: create node with toolCapability and versionPin
  WorkflowCompiler->>WorkflowCompiler: validate matching capability version
Loading

Suggested reviewers: lunnynight

Poem

A rabbit hops through catalogs bright,
Tool pins gleam in morning light.
Nodes bloom with params neat,
Runtime paths make code complete.
Stale pins thump—“Not today!”
Tests keep every hop in play.

🚥 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
Title check ✅ Passed The title clearly summarizes the main change: projecting certified tool capabilities into workflow nodes.
Description check ✅ Passed The description matches the changeset and covers the projection bridge, validation, and regression tests.
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.

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

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

@repowise-bot

repowise-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

✅ Health: 7.3

📋 At a glance
5 new findings introduced.

🚨 Change risk: 9.1/10 (high)
This change's risk is driven by:

  • more lines added than baseline
  • more scattered than baseline

📊 Full report · ⭐ Star Repowise · 📥 Install bot · Last updated 2026-07-27 19:57 UTC
Silence on a single PR with [skip repowise] in the title · Per-repo toggle on repowise.dev/settings?tab=bot

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@2233admin
2233admin marked this pull request as ready for review July 27, 2026 20:02
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Independent verifier: PASS. Python 7/7, frontend 12/12, compile/runtime/pin closed loop verified.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

@2233admin I’ll review PR #49, taking the verified catalog → node → compile → runtime binding and stale-pin behavior into account.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@2233admin
2233admin merged commit 0a4a2fe into main Jul 27, 2026
1 check was pending

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
backend/workflow/runtime_registry.py (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated tool-capability/catalogId matching logic across two files. _is_external_tool_capability in runtime_registry.py and the inline gate in _validate_capability_version_pin in compiler.py both independently re-derive whether a node is a catalog-opted or generic external-tool-capability node from node.ui.catalogId and node.params.toolCapability.id. This PR had to update both in lockstep to add the same rule; the shared root cause is the lack of one common predicate.

  • backend/workflow/runtime_registry.py#L1136-1142: export/expose _is_external_tool_capability (or an equivalent helper) for reuse.
  • backend/workflow/compiler.py#L388-399: replace the inline catalog_id not in {"external.tool.capability", tool_id} check with a call to the shared predicate from runtime_registry.py to prevent the two gates from silently diverging on a future change.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/runtime_registry.py` at line 1, Expose the existing
`_is_external_tool_capability` predicate from `runtime_registry.py` for reuse,
then update `_validate_capability_version_pin` in `compiler.py` to call it
instead of independently checking `node.ui.catalogId` and
`node.params.toolCapability.id`. Preserve the current catalog-opted and generic
external-tool-capability matching behavior while ensuring both validation paths
share one predicate.
backend/workflow/tool_capabilities.py (1)

307-311: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

catalog["id"] duplicates the id kwarg; hardcoded kind/capability limit reuse.

Two things worth tightening in _realtime_tool:

  1. Both call sites pass catalog["id"] equal to the already-supplied id= kwarg (Lines 308, 344). _is_registered_tool_capability_node in node_registry.py requires catalog_id == tool_id to recognize the node as node_library; if a future call site's catalog["id"] ever drifts from id, the tool silently falls back to legacy origin with no error surfaced. Deriving nodeCatalog.id from the id parameter directly (instead of requiring callers to repeat it) removes this invariant entirely.
  2. kind: "action" and capability: "store" (Lines 426-427) are hardcoded inside the helper rather than taken from the catalog dict, even though category/icon are parameterized. Any future catalog-opted tool needing a different kind/capability (e.g. "summarize", "agent") will require editing the shared helper rather than just its call site.
♻️ Suggested tightening
-            catalog={
-                "id": SITUATION_AWARENESS_TOOL_CAPABILITY_ID,
-                "category": "processing",
-                "icon": "Radar",
-            },
+            catalog={
+                "category": "processing",
+                "icon": "Radar",
+                "kind": "action",
+                "capability": "store",
+            },
             "canvas": {"node": catalog is not None},
             **(
                 {
                     "nodeCatalog": {
-                        "id": catalog["id"],
+                        "id": id,
                         "authority": "backend",
                         "origin": "tool-capability",
                         "category": catalog["category"],
-                        "kind": "action",
-                        "capability": "store",
+                        "kind": catalog["kind"],
+                        "capability": catalog["capability"],
                     },
                     "presentation": {"icon": catalog["icon"]},
                 }

Also applies to: 343-347, 388-433

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/tool_capabilities.py` around lines 307 - 311, Update
_realtime_tool so nodeCatalog.id is derived directly from its id parameter,
removing the duplicated catalog["id"] values at both call sites. Read kind and
capability from the catalog argument instead of hardcoding them, while
preserving the existing category and icon handling. Ensure each catalog-opted
call site supplies the required kind and capability values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/workflow/capability_projection.py`:
- Line 1369: Update _tool_catalog_capabilities and its related catalog
capability projections to track emitted catalog_id values across all projected
tools and static WORKFLOW_CATALOG_IDS entries; detect collisions and surface
them through the module’s established error/logging mechanism instead of
allowing duplicate IDs to overwrite entries in callers’ ID maps.

In `@backend/workflow/node_registry.py`:
- Around line 255-269: Update _is_registered_tool_capability_node to require the
same catalog opt-in conditions as _tool_catalog_capabilities, including
manifest.canvas.node and manifest.nodeCatalog.authority == "backend", before
accepting a matching toolCapability.id. Reuse the existing catalog eligibility
logic or symbols rather than treating resolve_workflow_tool_capability(tool_id)
alone as sufficient.

---

Nitpick comments:
In `@backend/workflow/runtime_registry.py`:
- Line 1: Expose the existing `_is_external_tool_capability` predicate from
`runtime_registry.py` for reuse, then update `_validate_capability_version_pin`
in `compiler.py` to call it instead of independently checking
`node.ui.catalogId` and `node.params.toolCapability.id`. Preserve the current
catalog-opted and generic external-tool-capability matching behavior while
ensuring both validation paths share one predicate.

In `@backend/workflow/tool_capabilities.py`:
- Around line 307-311: Update _realtime_tool so nodeCatalog.id is derived
directly from its id parameter, removing the duplicated catalog["id"] values at
both call sites. Read kind and capability from the catalog argument instead of
hardcoding them, while preserving the existing category and icon handling.
Ensure each catalog-opted call site supplies the required kind and capability
values.
🪄 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 Plus

Run ID: c50a9dce-93dd-4388-aea6-3cae0b4a7098

📥 Commits

Reviewing files that changed from the base of the PR and between cfcf3d9 and a1ec917.

📒 Files selected for processing (8)
  • backend/workflow/capability_projection.py
  • backend/workflow/compiler.py
  • backend/workflow/node_registry.py
  • backend/workflow/runtime_registry.py
  • backend/workflow/tool_capabilities.py
  • frontend/package.json
  • frontend/scripts/check-tool-capability-catalog-regressions.mjs
  • tests/unit/test_workflow_tool_catalog_projection.py

return rows


def _tool_catalog_capabilities() -> list[WorkflowRuntimeCapability]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

No de-duplication of catalog_id across projected tools.

_tool_catalog_capabilities() emits one row per matching tool without checking whether catalog_id collides with another tool's nodeCatalog.id (or with a static WORKFLOW_CATALOG_IDS entry). Since callers commonly build a {item.id: item} map, a collision means one capability silently disappears from the catalog rather than surfacing a config error.

Consider tracking seen ids and raising/logging on collision (or filtering) so a copy-paste mistake in a future catalog={...} block doesn't silently hide a tool node.

Also applies to: 1457-1457, 1479-1479

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/capability_projection.py` at line 1369, Update
_tool_catalog_capabilities and its related catalog capability projections to
track emitted catalog_id values across all projected tools and static
WORKFLOW_CATALOG_IDS entries; detect collisions and surface them through the
module’s established error/logging mechanism instead of allowing duplicate IDs
to overwrite entries in callers’ ID maps.

Comment on lines +255 to +269
def _is_registered_tool_capability_node(
node: WorkflowProjectNode,
catalog_id: str | None,
) -> bool:
tool = node.params.get("toolCapability")
if not isinstance(tool, dict):
return False
tool_id = _read_string(tool.get("id"))
if tool_id is None or catalog_id != tool_id:
return False

# Local import avoids making the registry depend on this origin guard at import time.
from backend.workflow.tool_capabilities import resolve_workflow_tool_capability

return resolve_workflow_tool_capability(tool_id) is not None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how WorkflowNodeOrigin.kind == "node_library" is consumed downstream
rg -n -B2 -A6 'node_library' backend/workflow/compiler.py backend/workflow/runtime_registry.py

Repository: 2233admin/opencli-admin

Length of output: 4707


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files 'backend/workflow/*.py' | sed -n '1,120p'

echo "== node_registry origin functions =="
fgrep -n "_is_registered_tool_capability_node\|resolve_node_origin\|_tool_catalog_capabilities\|workflow_tool_capability\|nodeCatalog\|canvas.node" backend/workflow/node_registry.py backend/workflow/capability_projection.py backend/workflow/tool_capabilities.py backend/workflow/runtime_registry.py backend/workflow/compiler.py 2>/dev/null | sed -n '1,240p'

echo "== imports == capability_projection.py =="
sed -n '1,220p' backend/workflow/capability_projection.py
echo "== tool_capabilities.py outline/imports =="
sed -n '1,220p' backend/workflow/tool_capabilities.py

Repository: 2233admin/opencli-admin

Length of output: 2473


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant lines node_registry.py =="
sed -n '230,285p' backend/workflow/node_registry.py

echo "== resolve origin usage =="
rg -n -B3 -A8 "resolve_node_origin|WorkflowNodeOrigin|external.tool.capability|tool_capability|node_library" backend/workflow/compiler.py backend/workflow/runtime_registry.py backend/workflow/node_registry.py

echo "== capability_projection.py first 220 =="
sed -n '1,220p' backend/workflow/capability_projection.py

echo "== tool_capabilities.py first 220 =="
sed -n '1,220p' backend/workflow/tool_capabilities.py

Repository: 2233admin/opencli-admin

Length of output: 49698


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tool catalog projection lines =="
rg -n -B8 -A18 "_tool_catalog_capabilities|canvas\.node|nodeCatalog|authority|external.tool.capability|tool.intelligence.native" backend/workflow/capability_projection.py backend/workflow/tool_capabilities.py

echo "== resolved external tool capability flow =="
sed -n '133,150p' backend/workflow/runtime_registry.py
sed -n '784,814p' backend/workflow/runtime_registry.py
sed -n '1136,1143p' backend/workflow/runtime_registry.py

echo "== behavioral probe: node origin vs catalog eligibility data =="
python3 - <<'PY'
from pathlib import Path
import ast

node_registry = Path("backend/workflow/node_registry.py").read_text()
catalog = Path("backend/workflow/capability_projection.py").read_text()

node_origin_tree = ast.parse(node_registry)
catalog_tree = ast.parse(catalog)

for name in ("_is_registered_tool_capability_node", "_tool_catalog_capabilities"):
    for fn in ast.walk(node_origin_tree if name == "_is_registered_tool_capability_node" else catalog_tree):
        if isinstance(fn, ast.FunctionDef) and fn.name == name:
            print(f"== {name} ==")
            print(Path("backend/workflow/node_registry.py" if name == "_is_registered_tool_capability_node" else "backend/workflow/capability_projection.py").read_text().splitlines()[fn.lineno-1:fn.end_lineno])
            print("canvas.node occurrence:", fn.lineno <= next((line for line in ast.get_source_segment(catalog if name == "_tool_catalog_capabilities" else node_registry, fn) or "").splitlines() + ["no match"], ["no match"]))
PY

Repository: 2233admin/opencli-admin

Length of output: 25270


Align tool capability origin checks with catalog opt-in eligibility.

resolve_node_origin now treats any matching toolCapability.id as node_library, but _tool_catalog_capabilities() only exposes tools with manifest.canvas.node AND manifest.nodeCatalog.authority == "backend". A non-catalog-node tool can still pass as node_library, while the same id in external-tool runtime handling only goes through workflow.external-tool.capability binding. Use the catalog opt-in gate here if node_library is meant to preserve the existing external.tool.capability behavior for that path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/node_registry.py` around lines 255 - 269, Update
_is_registered_tool_capability_node to require the same catalog opt-in
conditions as _tool_catalog_capabilities, including manifest.canvas.node and
manifest.nodeCatalog.authority == "backend", before accepting a matching
toolCapability.id. Reuse the existing catalog eligibility logic or symbols
rather than treating resolve_workflow_tool_capability(tool_id) alone as
sufficient.

@2233admin
2233admin deleted the codex/tool-capability-catalog-bridge-20260728 branch July 28, 2026 09:45
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.

1 participant