+
+
+
+```mermaid
+%%{init: {"theme": "base", "themeVariables": {"fontSize": "16px", "fontFamily": "Inter, system-ui, sans-serif", "primaryTextColor": "#E8EDF5", "lineColor": "#62d2f5"}, "flowchart": {"nodeSpacing": 36, "rankSpacing": 40, "padding": 16}}}%%
+flowchart LR
+ subgraph clients ["External Clients"]
+ direction LR
+ REST["REST clients"]
+ GRPC["gRPC clients"]
+ MCP["MCP / AI agents"]
+ end
+
+ classDef client fill:#1a3a4a,stroke:#37c4f0,stroke-width:2px,color:#E8EDF5
+ class REST,GRPC,MCP client
+ style clients fill:#151920,stroke:#37c4f0,stroke-width:2px,color:#37c4f0
+```
+
+
+
+
requests ↓
+
+
+
+```mermaid
+%%{init: {"theme": "base", "themeVariables": {"fontSize": "16px", "fontFamily": "Inter, system-ui, sans-serif", "primaryTextColor": "#E8EDF5", "lineColor": "#62d2f5"}, "flowchart": {"nodeSpacing": 36, "rankSpacing": 40, "padding": 16}}}%%
+flowchart LR
+ subgraph layerC ["Layer C · Bindings · src/bindings/"]
+ direction LR
+ RestAPI["REST API
FastAPI :8000"]
+ GrpcSrv["gRPC Server
:50051"]
+ McpSrv["MCP Server"]
+ Factory["ConnectorFactory"]
+ Config["connectors.yaml"]
+ end
+
+ RestAPI --> Factory
+ GrpcSrv --> Factory
+ McpSrv --> Factory
+ Config -. "loads" .-> Factory
+
+ classDef bindings fill:#243044,stroke:#37c4f0,stroke-width:2px,color:#E8EDF5
+ classDef config fill:#242930,stroke:#8A9BAC,stroke-width:2px,color:#E8EDF5
+ class RestAPI,GrpcSrv,McpSrv,Factory bindings
+ class Config config
+ style layerC fill:#151920,stroke:#37c4f0,stroke-width:2px,color:#37c4f0
+```
+
+
+
+
ConnectorFactory ↓
+
+
+
+```mermaid
+%%{init: {"theme": "base", "themeVariables": {"fontSize": "40px", "fontFamily": "Inter, system-ui, sans-serif", "primaryTextColor": "#E8EDF5", "lineColor": "#62d2f5"}, "flowchart": {"nodeSpacing": 156, "rankSpacing": 40, "padding": 16}}}%%
+flowchart LR
+ subgraph layerA ["Layer A · Runtime · src/node_wire_runtime/"]
+ direction LR
+ Validate["Pydantic validation"]
+ Policy["PolicyHook"]
+ Resilience["Retries & circuit breaker"]
+ Errors["ErrorMapper"]
+ Otel["OpenTelemetry"]
+ end
+
+ Validate --> Policy
+ Policy --> Resilience
+ Resilience --> Errors
+ Otel -. "traces" .-> Resilience
+
+ classDef runtime fill:#3a3420,stroke:#ecb32e,stroke-width:2px,color:#E8EDF5
+ classDef telemetry fill:#242930,stroke:#8A9BAC,stroke-width:2px,color:#E8EDF5
+ class Validate,Policy,Resilience,Errors runtime
+ class Otel telemetry
+ style layerA fill:#151920,stroke:#ecb32e,stroke-width:2px,color:#ecb32e
+```
+
+
+
+
BaseConnector.run ↓
+
+
+
+```mermaid
+%%{init: {"theme": "base", "themeVariables": {"fontSize": "16px", "fontFamily": "Inter, system-ui, sans-serif", "primaryTextColor": "#E8EDF5", "lineColor": "#62d2f5"}, "flowchart": {"nodeSpacing": 32, "rankSpacing": 36, "padding": 16}}}%%
+flowchart TB
+ subgraph layerB ["Layer B · Connectors · src/node_wire_*/"]
+ direction TB
+ subgraph bRow1 [" "]
+ direction LR
+ GDrive["Google Drive"]
+ SMTP["SMTP"]
+ Stripe["Stripe"]
+ FHIR["FHIR Epic/Cerner"]
+ end
+ subgraph bRow2 [" "]
+ direction LR
+ SFDC["Salesforce"]
+ Slack["Slack"]
+ HTTP["HTTP Generic"]
+ end
+ end
+
+ classDef connector fill:#3a2430,stroke:#e01d5a,stroke-width:2px,color:#E8EDF5
+ class GDrive,SMTP,Stripe,FHIR,SFDC,Slack,HTTP connector
+ style layerB fill:#151920,stroke:#e01d5a,stroke-width:2px,color:#e01d5a
+ style bRow1 fill:transparent,stroke:transparent,color:transparent
+ style bRow2 fill:transparent,stroke:transparent,color:transparent
+```
+
+
+
+
outbound calls ↓
+
+
+
+```mermaid
+%%{init: {"theme": "base", "themeVariables": {"fontSize": "16px", "fontFamily": "Inter, system-ui, sans-serif", "primaryTextColor": "#E8EDF5", "lineColor": "#62d2f5"}, "flowchart": {"nodeSpacing": 36, "rankSpacing": 40, "padding": 16}}}%%
+flowchart LR
+ subgraph external ["External Systems"]
+ ThirdParty["Third-party APIs & services"]
+ end
+
+ classDef ext fill:#1a3338,stroke:#62d2f5,stroke-width:2px,color:#E8EDF5
+ class ThirdParty ext
+ style external fill:#151920,stroke:#62d2f5,stroke-width:2px,color:#62d2f5
+```
+
+
+
+
↑ ConnectorResponse returns through Layer C to clients
+
+
+
### Data Flow (Simplified)
1. A request arrives via REST, gRPC, or MCP.
diff --git a/docs/compliance/hipaa-considerations.md b/docs/compliance/hipaa-considerations.md
index 2443070..98a3a6d 100644
--- a/docs/compliance/hipaa-considerations.md
+++ b/docs/compliance/hipaa-considerations.md
@@ -1,6 +1,8 @@
-# SPDX-FileCopyrightText: 2026 AOT Technologies
-#
-# SPDX-License-Identifier: Apache-2.0
+
# HIPAA Compliance Considerations
diff --git a/docs/configuration.md b/docs/configuration.md
index 752d454..11033b8 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,6 +1,8 @@
-# SPDX-FileCopyrightText: 2026 AOT Technologies
-#
-# SPDX-License-Identifier: Apache-2.0
+
# Configuration Guide
diff --git a/docs/connectors.md b/docs/connectors.md
index 282cb27..08d505c 100644
--- a/docs/connectors.md
+++ b/docs/connectors.md
@@ -23,13 +23,14 @@ Each connector is a **top-level package** under `src/` (e.g. `node_wire_fhir_epi
| File | Role |
|------|------|
+| `__init__.py` | Required empty file — marks the directory as a Python package. |
| `schema.py` | Pydantic input/output models. Each input model has an `action: Literal[...]` discriminator field (often combined into a discriminated union). |
| `logic.py` | Connector class: `BaseConnector` subclass — either explicit `@nw_action` methods, or **`action_specs`** plus an optional `_execute_action_spec` override for SDK dispatch. |
| `action_spec.py` (optional) | Declarative `SdkActionSpec` entries mapping validated models to vendor SDK calls (see Google Drive). |
| `registration.py` | Optional: registers connector-specific exceptions with `ErrorMapper`. |
| `exceptions.py` | Optional: custom exception types. |
-At startup, call **`node_wire_runtime.connector_registry.auto_register()`**: it loads entry points in group `node_wire.connectors`, imports each connector's `logic` module (triggering `BaseConnector.__init_subclass__` and registration via `get_connector_registry()`), then imports optional `registration.py` for `ErrorMapper` side effects.
+At startup, call **`node_wire_runtime.connector_registry.auto_register()`**: it loads entry points in group `node_wire.connectors`, imports each connector's `logic` module (triggering `BaseConnector.__init_subclass__`, which populates the registry returned by `get_connector_registry()`), then imports optional `registration.py` for `ErrorMapper` side effects.
---
@@ -112,15 +113,17 @@ GoogleDriveOperationInput = RootModel[_GoogleDriveOperationUnion]
class GoogleDriveOperationOutput(BaseModel):
- raw: dict
+ raw: dict | list
description: str
```
+Use `dict | list` for `raw` when vendor APIs return arrays (e.g. list endpoints); Pydantic validates either shape. Per-action output models can use typed fields instead of a shared envelope.
+
When a connector only has **one** action, the `action` field is still required — the runtime always validates through the discriminated union.
### Step 2 — Map operations to the SDK (`action_spec.py`)
-**`SdkActionSpec`** describes how to turn a validated model into a single SDK call: resource path (`resource_segments`), HTTP-style method name (`method_name`), and how to build `body` / keyword arguments from the model. The full Drive registry lives in [`src/node_wire_google_drive/action_spec.py`](../src/node_wire_google_drive/action_spec.py).
+**`SdkActionSpec`** describes how to turn a validated model into a single SDK call: resource path (`resource_segments`), HTTP-style method name (`method_name`), and how to build `body` / keyword arguments from the model. The full Drive registry lives in [`src/node_wire_google_drive/action_spec.py`](https://github.com/AOT-Technologies/node-wire/blob/main/src/node_wire_google_drive/action_spec.py).
```python
# src/node_wire_google_drive/action_spec.py (illustrative)
@@ -223,27 +226,61 @@ Node Wire provides a shared **`AuthProvider`** abstraction (`src/node_wire_runti
To use authentication, call **`await self.get_auth_headers()`** (inherited from `BaseConnector`). This returns a dictionary of headers (e.g. `{"Authorization": "Bearer