mirror of
https://github.com/rookiestar28/ComfyUI-OpenClaw.git
synced 2026-08-14 00:48:07 +00:00
docs(architecture): define product boundary contract
This commit is contained in:
@@ -13,6 +13,13 @@ ComfyUI-OpenClaw is a **security-first orchestration layer** for ComfyUI that co
|
||||
- **Now supports 8 major messaging platforms, including Discord, Telegram, WhatsApp, LINE, WeChat, KakaoTalk, Slack, and Feishu/Lark.**
|
||||
- **And more exciting features being added continuously**
|
||||
|
||||
Supported product boundary:
|
||||
|
||||
- **Primary artifact**: ComfyUI custom node pack
|
||||
- **First-class runtime identity**: embedded operator platform
|
||||
- **Optional attached subsystem**: connector-capable control surface via the sidecar runtime
|
||||
- **Decision record**: [ADR-0002 Product Boundary And Packaging Contract](docs/adr/ADR-0002-product-boundary-and-packaging-contract.md)
|
||||
|
||||
---
|
||||
<br>
|
||||
|
||||
@@ -1076,6 +1083,7 @@ Primary references:
|
||||
- [Security deployment guide](docs/security_deployment_guide.md)
|
||||
- [Security checklist](docs/security_checklist.md)
|
||||
- [Config surface ADR](docs/adr/ADR-0001-config-surface-unification.md)
|
||||
- [Product boundary ADR](docs/adr/ADR-0002-product-boundary-and-packaging-contract.md)
|
||||
- [Config and secrets contract](docs/release/config_secrets_contract.md)
|
||||
- [Advanced registry and transforms](docs/advanced_registry_and_transforms.md)
|
||||
- [Connector guide](docs/connector.md)
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# ADR-0002: Product Boundary And Packaging Contract
|
||||
|
||||
- Status: Accepted
|
||||
- Date: 2026-04-23
|
||||
- Owners: OpenClaw maintainers
|
||||
- Related roadmap items: `R160` with follow-up execution in `R161` and `R162`
|
||||
|
||||
## Context
|
||||
|
||||
OpenClaw started as a ComfyUI-focused node pack, but the repository now also contains:
|
||||
|
||||
- embedded HTTP APIs and operator UI surfaces,
|
||||
- a standalone Remote Admin Console route,
|
||||
- connector runtime code for multiple chat platforms,
|
||||
- split control-plane governance for higher-risk deployments.
|
||||
|
||||
That evolution made one question increasingly ambiguous: what is the supported identity of this repo/package today, and which parts are first-class versus optional attached subsystems?
|
||||
|
||||
Without a boundary decision, future work such as config decomposition, connector extraction, or packaging hygiene is forced to rely on repo intuition instead of an explicit contract.
|
||||
|
||||
## Decision
|
||||
|
||||
OpenClaw is defined as a **ComfyUI custom node pack** first, with two explicit first-class identities layered on top of that package:
|
||||
|
||||
1. **ComfyUI custom node pack**
|
||||
- This is the primary distribution artifact and runtime anchor.
|
||||
- `__init__.py` remains the package entrypoint loaded from `custom_nodes/`.
|
||||
|
||||
2. **embedded operator platform**
|
||||
- In-process OpenClaw APIs, runtime/security governance, sidebar UX, and remote admin surfaces are treated as part of the shipped package, not as separate products.
|
||||
|
||||
3. **connector-capable control surface**
|
||||
- Remote chat control is supported through the in-repo connector sidecar, but the connector remains an **optional attached subsystem**, not the primary package artifact.
|
||||
|
||||
## Core vs Attached Subsystems
|
||||
|
||||
Core to the package:
|
||||
|
||||
- custom node pack entrypoint and exported nodes
|
||||
- embedded API/runtime governance (`/openclaw/*`, route bootstrap, control-plane policy)
|
||||
- embedded operator UI surfaces (sidebar plus `/openclaw/admin`)
|
||||
|
||||
Optional attached subsystem:
|
||||
|
||||
- connector sidecar (`python -m connector`) and platform-specific adapters
|
||||
|
||||
This means:
|
||||
|
||||
- the connector is supported and intentionally in-repo,
|
||||
- but the repo is **not** currently defined as a connector-first distribution,
|
||||
- and the repo is **not** currently defined as a standalone generic backend independent of ComfyUI.
|
||||
|
||||
## Supported Topologies
|
||||
|
||||
Supported:
|
||||
|
||||
1. **embedded local/lan**
|
||||
- OpenClaw runs inside the ComfyUI process as the primary package artifact.
|
||||
|
||||
2. **embedded package with split high-risk control plane**
|
||||
- The same package stays primary, while higher-risk control surfaces are externalized according to the split-mode contract.
|
||||
|
||||
3. **embedded package plus optional connector sidecar**
|
||||
- The connector runs as a companion process that calls the local OpenClaw APIs.
|
||||
|
||||
Unsupported as first-class package identities today:
|
||||
|
||||
1. **connector-only distribution**
|
||||
2. **standalone non-ComfyUI backend package**
|
||||
|
||||
Those possibilities are future design questions, not current promises. Connector extraction feasibility remains explicitly deferred to `R162`.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- future extraction/pruning decisions now have one explicit contract to evaluate against
|
||||
- docs and contributor discussions can use the same terms instead of mixing "node pack", "server", and "sidecar" loosely
|
||||
- `R161` and `R162` can narrow their scope around a known boundary instead of re-litigating product identity
|
||||
|
||||
Trade-offs:
|
||||
|
||||
- the repo still carries multiple execution surfaces inside one codebase
|
||||
- connector remains intentionally attached even though it is operationally separable
|
||||
- some public docs must stay careful not to imply standalone server packaging that does not exist yet
|
||||
|
||||
## Rejected Alternatives
|
||||
|
||||
1. Treat the connector as an equal primary package artifact today
|
||||
- Rejected because there is no separate connector package/distribution contract yet.
|
||||
|
||||
2. Define OpenClaw as a generic standalone backend package
|
||||
- Rejected because current runtime ownership still assumes a ComfyUI host process.
|
||||
|
||||
3. Keep the boundary implicit and rely on contributor convention
|
||||
- Rejected because packaging and extraction follow-ups now depend on an explicit contract.
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
The **OpenClaw Connector** (`connector`) is a standalone process that allows you to control your local ComfyUI instance remotely via chat platforms like **Telegram**, **Discord**, **LINE**, **WhatsApp**, **WeChat Official Account**, **KakaoTalk (Kakao i Open Builder)**, **Slack**, and **Feishu/Lark**.
|
||||
|
||||
Per the product boundary contract, the connector is an **optional attached subsystem**. The primary published artifact of this repo remains the **ComfyUI custom node pack**, and the connector augments that package rather than replacing it.
|
||||
|
||||
## How It Works
|
||||
|
||||
The connector runs alongside ComfyUI on your machine.
|
||||
|
||||
@@ -15,4 +15,5 @@
|
||||
## Status Note
|
||||
|
||||
- Bridge APIs and connector runtime are available.
|
||||
- Connector/sidecar runtime remains an optional attached subsystem; the primary package artifact is the ComfyUI custom node pack.
|
||||
- Standalone sidecar/gateway evolution is tracked in `.planning/roadmap.md`.
|
||||
|
||||
@@ -95,4 +95,3 @@ def get_node_portability_mappings() -> Dict[str, Dict[str, Any]]:
|
||||
|
||||
|
||||
NODE_PORTABILITY_MAPPINGS = get_node_portability_mappings()
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Machine-readable product/package boundary contract for OpenClaw.
|
||||
|
||||
Keep this module dependency-light so contract tests and future packaging work can
|
||||
read one stable source of truth without importing heavy runtime surfaces.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import Any, Dict
|
||||
|
||||
PRODUCT_BOUNDARY_CONTRACT_VERSION = 1
|
||||
|
||||
_PRODUCT_BOUNDARY_CONTRACT: Dict[str, Any] = {
|
||||
"version": PRODUCT_BOUNDARY_CONTRACT_VERSION,
|
||||
"package_name": "comfyui-openclaw",
|
||||
"primary_distribution": {
|
||||
"id": "comfyui_custom_node_pack",
|
||||
"label": "ComfyUI custom node pack",
|
||||
"summary": (
|
||||
"The published package artifact is a ComfyUI custom node pack loaded "
|
||||
"from custom_nodes/ and anchored by __init__.py."
|
||||
),
|
||||
},
|
||||
"supported_identities": [
|
||||
{
|
||||
"id": "comfyui_node_pack",
|
||||
"label": "ComfyUI custom node pack",
|
||||
"summary": "Primary distribution and entrypoint ownership surface.",
|
||||
},
|
||||
{
|
||||
"id": "embedded_operator_platform",
|
||||
"label": "embedded operator platform",
|
||||
"summary": (
|
||||
"In-process API, security/runtime governance, embedded sidebar, "
|
||||
"and remote admin surfaces that run alongside ComfyUI."
|
||||
),
|
||||
},
|
||||
{
|
||||
"id": "connector_capable_control_surface",
|
||||
"label": "connector-capable control surface",
|
||||
"summary": (
|
||||
"The repo supports remote chat control through an optional "
|
||||
"connector sidecar, but the connector is not the primary package "
|
||||
"artifact."
|
||||
),
|
||||
},
|
||||
],
|
||||
"core_subsystems": [
|
||||
{
|
||||
"id": "node_pack_entrypoint",
|
||||
"label": "custom node pack entrypoint",
|
||||
"entrypoints": ["__init__.py", "nodes", "web"],
|
||||
},
|
||||
{
|
||||
"id": "embedded_api_and_runtime",
|
||||
"label": "embedded API and runtime governance",
|
||||
"entrypoints": [
|
||||
"api/routes.py",
|
||||
"services/route_bootstrap.py",
|
||||
"services/control_plane.py",
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "embedded_operator_ui",
|
||||
"label": "embedded operator UI surfaces",
|
||||
"entrypoints": [
|
||||
"web/openclaw.js",
|
||||
"web/openclaw_ui.js",
|
||||
"api/remote_admin.py",
|
||||
],
|
||||
},
|
||||
],
|
||||
"attached_subsystems": [
|
||||
{
|
||||
"id": "connector_sidecar",
|
||||
"label": "connector sidecar",
|
||||
"classification": "optional_attached_subsystem",
|
||||
"entrypoints": ["connector/__main__.py", "docs/connector.md"],
|
||||
"summary": (
|
||||
"Standalone chat-platform bridge that remains in-repo but is not "
|
||||
"the primary published package boundary."
|
||||
),
|
||||
}
|
||||
],
|
||||
"supported_topologies": [
|
||||
{
|
||||
"id": "embedded_local",
|
||||
"label": "embedded local/lan",
|
||||
"summary": (
|
||||
"OpenClaw runs in the ComfyUI process as the primary node-pack "
|
||||
"artifact with in-process operator surfaces."
|
||||
),
|
||||
},
|
||||
{
|
||||
"id": "embedded_split_control_plane",
|
||||
"label": "embedded package with split high-risk control plane",
|
||||
"summary": (
|
||||
"The same node-pack artifact remains primary, while high-risk "
|
||||
"control surfaces are externalized according to the control-plane "
|
||||
"contract."
|
||||
),
|
||||
},
|
||||
{
|
||||
"id": "embedded_with_connector_sidecar",
|
||||
"label": "embedded package plus optional connector sidecar",
|
||||
"summary": (
|
||||
"The connector runs as a companion process that calls the local "
|
||||
"OpenClaw APIs; it augments the package but does not replace it."
|
||||
),
|
||||
},
|
||||
],
|
||||
"unsupported_topologies": [
|
||||
{
|
||||
"id": "connector_only_distribution",
|
||||
"label": "connector-only distribution",
|
||||
"summary": (
|
||||
"This repo does not currently define the connector as a standalone "
|
||||
"published package artifact."
|
||||
),
|
||||
},
|
||||
{
|
||||
"id": "standalone_non_comfyui_backend",
|
||||
"label": "standalone non-ComfyUI backend",
|
||||
"summary": (
|
||||
"This repo does not currently publish OpenClaw as a generic "
|
||||
"server package that runs without the ComfyUI host."
|
||||
),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def get_product_boundary_contract() -> Dict[str, Any]:
|
||||
return copy.deepcopy(_PRODUCT_BOUNDARY_CONTRACT)
|
||||
|
||||
@@ -73,9 +73,8 @@ def analyze_workflow_portability(workflow: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
total_nodes = len(entries)
|
||||
portable_mode_required = total_nodes > 0
|
||||
portable_mode_supported = (
|
||||
portable_mode_required
|
||||
and all(entry["portable_mode"] != "unsupported" for entry in entries)
|
||||
portable_mode_supported = portable_mode_required and all(
|
||||
entry["portable_mode"] != "unsupported" for entry in entries
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -116,4 +115,3 @@ def _dedupe_preserve_order(items: Iterable[str]) -> list[str]:
|
||||
seen.add(item)
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import tomllib
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from services.product_boundary import get_product_boundary_contract
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ADR_PATH = ROOT / "docs" / "adr" / "ADR-0002-product-boundary-and-packaging-contract.md"
|
||||
README_PATH = ROOT / "README.md"
|
||||
CONNECTOR_DOC_PATH = ROOT / "docs" / "connector.md"
|
||||
|
||||
|
||||
class TestR160ProductBoundaryContract(unittest.TestCase):
|
||||
def test_contract_terms_and_topologies_are_stable(self):
|
||||
contract = get_product_boundary_contract()
|
||||
|
||||
self.assertEqual(contract["package_name"], "comfyui-openclaw")
|
||||
self.assertEqual(
|
||||
contract["primary_distribution"]["id"],
|
||||
"comfyui_custom_node_pack",
|
||||
)
|
||||
self.assertEqual(
|
||||
[item["label"] for item in contract["supported_identities"]],
|
||||
[
|
||||
"ComfyUI custom node pack",
|
||||
"embedded operator platform",
|
||||
"connector-capable control surface",
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
[item["id"] for item in contract["supported_topologies"]],
|
||||
[
|
||||
"embedded_local",
|
||||
"embedded_split_control_plane",
|
||||
"embedded_with_connector_sidecar",
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
[item["id"] for item in contract["attached_subsystems"]],
|
||||
["connector_sidecar"],
|
||||
)
|
||||
|
||||
def test_contract_entrypoints_exist_in_repo(self):
|
||||
contract = get_product_boundary_contract()
|
||||
|
||||
for section in ("core_subsystems", "attached_subsystems"):
|
||||
for subsystem in contract[section]:
|
||||
for rel_path in subsystem["entrypoints"]:
|
||||
self.assertTrue(
|
||||
(ROOT / rel_path).exists(),
|
||||
f"Missing contract entrypoint: {rel_path}",
|
||||
)
|
||||
|
||||
def test_contract_matches_pyproject_package_name(self):
|
||||
with (ROOT / "pyproject.toml").open("rb") as fh:
|
||||
pyproject = tomllib.load(fh)
|
||||
|
||||
contract = get_product_boundary_contract()
|
||||
self.assertEqual(contract["package_name"], pyproject["project"]["name"])
|
||||
|
||||
def test_docs_reference_boundary_terms(self):
|
||||
adr_text = ADR_PATH.read_text(encoding="utf-8")
|
||||
readme_text = README_PATH.read_text(encoding="utf-8")
|
||||
connector_text = CONNECTOR_DOC_PATH.read_text(encoding="utf-8")
|
||||
|
||||
for phrase in (
|
||||
"ComfyUI custom node pack",
|
||||
"embedded operator platform",
|
||||
"connector-capable control surface",
|
||||
):
|
||||
self.assertIn(phrase, adr_text)
|
||||
self.assertIn(phrase, readme_text)
|
||||
|
||||
self.assertIn("optional attached subsystem", adr_text)
|
||||
self.assertIn("optional attached subsystem", connector_text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user