coda-node 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. coda_node-0.1.0/.agents/skills/read-docs/SKILL.md +20 -0
  2. coda_node-0.1.0/.agents/skills/update-docs/SKILL.md +51 -0
  3. coda_node-0.1.0/.github/workflows/ci.yml +42 -0
  4. coda_node-0.1.0/.github/workflows/publish.yml +31 -0
  5. coda_node-0.1.0/.gitignore +12 -0
  6. coda_node-0.1.0/.pre-commit-config.yaml +20 -0
  7. coda_node-0.1.0/.python-version +1 -0
  8. coda_node-0.1.0/Dockerfile.e2e +17 -0
  9. coda_node-0.1.0/LICENSE +21 -0
  10. coda_node-0.1.0/PKG-INFO +348 -0
  11. coda_node-0.1.0/README.md +310 -0
  12. coda_node-0.1.0/docs/INDEX.md +90 -0
  13. coda_node-0.1.0/docs/auth/INDEX.md +31 -0
  14. coda_node-0.1.0/docs/auth/KEYPAIR_LIFECYCLE.md +92 -0
  15. coda_node-0.1.0/docs/auth/SIGNING.md +127 -0
  16. coda_node-0.1.0/docs/configuration/ENVIRONMENT_VARIABLES.md +104 -0
  17. coda_node-0.1.0/docs/configuration/INDEX.md +52 -0
  18. coda_node-0.1.0/docs/configuration/SETTINGS_REFERENCE.md +88 -0
  19. coda_node-0.1.0/docs/frameworks/DEVICE_CONFIG.md +73 -0
  20. coda_node-0.1.0/docs/frameworks/FRAMEWORK_PROTOCOL.md +128 -0
  21. coda_node-0.1.0/docs/frameworks/INDEX.md +57 -0
  22. coda_node-0.1.0/docs/frameworks/REGISTRY.md +56 -0
  23. coda_node-0.1.0/docs/jobs/CONSUMER.md +173 -0
  24. coda_node-0.1.0/docs/jobs/EXECUTOR.md +200 -0
  25. coda_node-0.1.0/docs/jobs/INDEX.md +66 -0
  26. coda_node-0.1.0/docs/jobs/IR_SCHEMA.md +134 -0
  27. coda_node-0.1.0/docs/node/CONNECT_PROTOCOL.md +175 -0
  28. coda_node-0.1.0/docs/node/CREDENTIAL_PERSISTENCE.md +108 -0
  29. coda_node-0.1.0/docs/node/INDEX.md +104 -0
  30. coda_node-0.1.0/docs/node/TOKEN_LIFECYCLE.md +87 -0
  31. coda_node-0.1.0/docs/operations/CLI.md +167 -0
  32. coda_node-0.1.0/docs/operations/ERROR_HANDLING.md +124 -0
  33. coda_node-0.1.0/docs/operations/GRACEFUL_SHUTDOWN.md +78 -0
  34. coda_node-0.1.0/docs/operations/HEALTH_ENDPOINTS.md +101 -0
  35. coda_node-0.1.0/docs/operations/INDEX.md +22 -0
  36. coda_node-0.1.0/docs/vpn/CLOUD_INFRASTRUCTURE.md +125 -0
  37. coda_node-0.1.0/docs/vpn/HEALTH_MONITORING.md +151 -0
  38. coda_node-0.1.0/docs/vpn/INDEX.md +63 -0
  39. coda_node-0.1.0/docs/vpn/TUNNEL_LIFECYCLE.md +130 -0
  40. coda_node-0.1.0/docs/webhooks/DELIVERY.md +98 -0
  41. coda_node-0.1.0/docs/webhooks/INDEX.md +45 -0
  42. coda_node-0.1.0/docs/webhooks/PAYLOAD_FORMAT.md +77 -0
  43. coda_node-0.1.0/pyproject.toml +89 -0
  44. coda_node-0.1.0/src/coda_node/__init__.py +18 -0
  45. coda_node-0.1.0/src/coda_node/errors.py +44 -0
  46. coda_node-0.1.0/src/coda_node/py.typed +0 -0
  47. coda_node-0.1.0/src/coda_node/server/__init__.py +9 -0
  48. coda_node-0.1.0/src/coda_node/server/app.py +191 -0
  49. coda_node-0.1.0/src/coda_node/server/auth.py +192 -0
  50. coda_node-0.1.0/src/coda_node/server/cli.py +360 -0
  51. coda_node-0.1.0/src/coda_node/server/config.py +300 -0
  52. coda_node-0.1.0/src/coda_node/server/consumer.py +625 -0
  53. coda_node-0.1.0/src/coda_node/server/daemon.py +261 -0
  54. coda_node-0.1.0/src/coda_node/server/executor.py +200 -0
  55. coda_node-0.1.0/src/coda_node/server/heartbeat.py +113 -0
  56. coda_node-0.1.0/src/coda_node/server/ir.py +182 -0
  57. coda_node-0.1.0/src/coda_node/server/qasm.py +377 -0
  58. coda_node-0.1.0/src/coda_node/server/webhook.py +161 -0
  59. coda_node-0.1.0/src/coda_node/vpn/__init__.py +49 -0
  60. coda_node-0.1.0/src/coda_node/vpn/guard.py +424 -0
  61. coda_node-0.1.0/src/coda_node/vpn/service.py +593 -0
  62. coda_node-0.1.0/tests/conftest.py +64 -0
  63. coda_node-0.1.0/tests/test_auth.py +73 -0
  64. coda_node-0.1.0/tests/test_cli.py +356 -0
  65. coda_node-0.1.0/tests/test_config.py +212 -0
  66. coda_node-0.1.0/tests/test_consumer.py +564 -0
  67. coda_node-0.1.0/tests/test_daemon.py +170 -0
  68. coda_node-0.1.0/tests/test_frameworks.py +127 -0
  69. coda_node-0.1.0/tests/test_heartbeat.py +249 -0
  70. coda_node-0.1.0/tests/test_ir.py +344 -0
  71. coda_node-0.1.0/tests/test_main.py +133 -0
  72. coda_node-0.1.0/tests/test_node.py +556 -0
  73. coda_node-0.1.0/tests/test_qasm.py +329 -0
  74. coda_node-0.1.0/tests/test_vpn.py +134 -0
  75. coda_node-0.1.0/tests/test_webhook.py +68 -0
  76. coda_node-0.1.0/uv.lock +1068 -0
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: read-docs
3
+ description: Gather context from docs/ before implementing or modifying a feature. Use when starting work on a feature, investigating how something works, or onboarding to the codebase.
4
+ ---
5
+
6
+ Gather context from `docs/` before implementing or modifying a feature.
7
+
8
+ ## How to use
9
+
10
+ 1. **Start at `docs/INDEX.md`** — read it to discover all feature areas and the source layout.
11
+ 2. **Navigate to the relevant area** — open the area's `INDEX.md` to see its topics, key source files, and cloud counterparts.
12
+ 3. **Read the topic files** that relate to your task for detailed behavior, API formats, config fields, sequence diagrams, and cross-references.
13
+ 4. **Check `README.md`** for user-facing quickstart, CLI flags, configuration tables, and endpoint summaries.
14
+
15
+ ## Tips
16
+
17
+ - Area `INDEX.md` files list the source files that implement each feature — use these to locate code quickly.
18
+ - Cross-links between topic files connect related concepts (e.g. auth ↔ node ↔ VPN).
19
+ - Cloud counterpart tables (where present) point to the corresponding cloud-side files and endpoints.
20
+ - If a doc file references a config field or env var, the full reference is in the `configuration/` area.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: update-docs
3
+ description: Keep docs/ and README.md in sync with source code changes. Use after modifying source files to update affected documentation.
4
+ ---
5
+
6
+ Keep `docs/` and `README.md` in sync with source code changes.
7
+
8
+ ## Documentation layout
9
+
10
+ The `docs/` directory is organized into feature-area subdirectories.
11
+ Discover the current structure by reading `docs/INDEX.md` and listing
12
+ subdirectories — do not assume a fixed set of areas or files.
13
+
14
+ Each feature area follows this pattern:
15
+
16
+ ```
17
+ docs/<area>/
18
+ ├── INDEX.md # Overview, topics table, key source files, cloud counterparts
19
+ ├── <topic-a>.md
20
+ └── <topic-b>.md
21
+ ```
22
+
23
+ `docs/INDEX.md` is the top-level entry point linking to all areas.
24
+ `README.md` is the user-facing quickstart (not a duplicate of `docs/`).
25
+
26
+ ## Conventions
27
+
28
+ - Each feature area has its own subdirectory with an `INDEX.md`.
29
+ - `INDEX.md` files contain: overview, topics table, key source files table, and cloud counterparts (if any).
30
+ - Topic files document: purpose, API/format details, code references, and cross-links.
31
+ - Use "node" (not "bootstrap" or "self-service") for provisioning terminology.
32
+ - External identifiers from other codebases keep their original names with a "(cloud-side naming)" annotation.
33
+
34
+ ## How to update
35
+
36
+ 1. **Read the staged diff** (`git diff --cached`) to identify which source files changed and what behavior was added/modified/removed.
37
+ 2. **Discover affected docs**: read `docs/INDEX.md` to understand the current areas, then find the doc files that cover the changed source files. Look at existing cross-references and `INDEX.md` "Key Files" tables to map source → docs.
38
+ 3. **Read the affected doc files** and compare against the new source behavior.
39
+ 4. **Update docs** to reflect changes: signatures, config fields, env vars, error types, endpoints, sequence diagrams, etc.
40
+ 5. **Update `README.md`** if the change affects quickstart, configuration, CLI, endpoints, or error handling.
41
+ 6. **Update `docs/INDEX.md`** if you add/remove/rename a feature area or the source layout changes.
42
+ 7. **Print a short summary** of what was updated and why.
43
+
44
+ ## Rules
45
+
46
+ - Do NOT assume a fixed file/directory structure — always discover it.
47
+ - Do NOT create new feature subdirectories without clear justification.
48
+ - Do NOT add speculative documentation for unimplemented features.
49
+ - Keep doc files concise — describe behavior, not implementation details line-by-line.
50
+ - Every doc file must have a `# Title` and be linked from its area's `INDEX.md`.
51
+ - If no docs need updating (e.g. test-only changes, formatting), say so and exit.
@@ -0,0 +1,42 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ${{ matrix.os }}
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [ubuntu-latest, macos-latest, windows-latest]
15
+ python-version: ["3.11", "3.12"]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - uses: astral-sh/setup-uv@v5
21
+
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+
26
+ - name: Install dependencies
27
+ run: uv sync --dev
28
+
29
+ - name: Ruff lint
30
+ run: uv run ruff check .
31
+
32
+ - name: Ruff format
33
+ run: uv run ruff format --check .
34
+
35
+ - name: Mypy
36
+ run: uv run mypy src/coda_node
37
+
38
+ - name: Package build
39
+ run: uv build
40
+
41
+ - name: Pytest
42
+ run: uv run pytest --cov --cov-report=term-missing
@@ -0,0 +1,31 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types:
6
+ - created
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout repository
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v6
21
+ with:
22
+ python-version: '3.12'
23
+
24
+ - name: Install build dependencies
25
+ run: pip install build
26
+
27
+ - name: Build distribution
28
+ run: python -m build
29
+
30
+ - name: Publish to PyPI
31
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,12 @@
1
+ .coverage
2
+ .coverage.*
3
+ htmlcov/
4
+ .mypy_cache/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .venv/
8
+ __pycache__/
9
+ build/
10
+ dist/
11
+ .DS_Store
12
+ .e2e-credentials.json
@@ -0,0 +1,20 @@
1
+ repos:
2
+ - repo: local
3
+ hooks:
4
+ - id: ruff-format-check
5
+ name: Ruff format check
6
+ entry: uv run ruff format --check .
7
+ language: system
8
+ pass_filenames: false
9
+ - id: ruff-lint
10
+ name: Ruff lint
11
+ entry: uv run ruff check .
12
+ language: system
13
+ pass_filenames: false
14
+ - id: update-docs
15
+ name: Update docs
16
+ entry: bash -c 'claude -p --dangerously-skip-permissions --allowedTools "Read,Edit,Write,Bash,Glob,Grep" --append-system-prompt-file .agents/skills/update-docs/SKILL.md "Review the staged diff and update any docs that are out of date. If nothing needs updating, do nothing." && git add docs/ README.md 2>/dev/null || true'
17
+ language: system
18
+ pass_filenames: false
19
+ files: ^src/coda_node/
20
+ stages: [pre-commit]
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,17 @@
1
+ FROM python:3.12-slim
2
+
3
+ RUN apt-get update \
4
+ && apt-get install -y --no-install-recommends openvpn iproute2 curl ca-certificates \
5
+ && rm -rf /var/lib/apt/lists/*
6
+
7
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
8
+
9
+ WORKDIR /app
10
+ COPY pyproject.toml LICENSE README.md ./
11
+ COPY src/ src/
12
+ COPY tests/ tests/
13
+
14
+ RUN uv sync --frozen --no-install-workspace 2>/dev/null || uv sync
15
+
16
+ ENTRYPOINT ["uv", "run", "pytest"]
17
+ CMD ["tests/test_node_smoketest.py", "-v", "-m", "smoketest"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Conductor Quantum
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,348 @@
1
+ Metadata-Version: 2.4
2
+ Name: coda-node
3
+ Version: 0.1.0
4
+ Summary: Minimal server runtime for connecting execution backends to Coda.
5
+ Author: Conductor Quantum
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Conductor Quantum
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ License-File: LICENSE
28
+ Requires-Python: >=3.11
29
+ Requires-Dist: cryptography>=41.0.0
30
+ Requires-Dist: fastapi>=0.115.0
31
+ Requires-Dist: httpx>=0.27.0
32
+ Requires-Dist: pydantic-settings>=2.0.0
33
+ Requires-Dist: pydantic>=2.11.0
34
+ Requires-Dist: pyjwt>=2.0.0
35
+ Requires-Dist: redis>=5.0.0
36
+ Requires-Dist: uvicorn>=0.30.0
37
+ Description-Content-Type: text/markdown
38
+
39
+ # coda-node
40
+
41
+ Production-ready runtime for connecting an execution backend to the Coda
42
+ cloud platform.
43
+
44
+ It boots a FastAPI service, provisions or reconnects node credentials, manages
45
+ VPN health, consumes Redis jobs, and posts signed execution results back to
46
+ Coda.
47
+
48
+ ## What It Does
49
+
50
+ - Provisions a node from a one-time node token
51
+ - Reconnects on restart with persisted JWT credentials
52
+ - Verifies and continuously monitors VPN connectivity
53
+ - Sends periodic heartbeats to keep QPU status "online"
54
+ - Consumes jobs from Redis Streams with crash recovery
55
+ - Optionally batches Redis jobs when the executor implements `batch_run`
56
+ - Sends JWT-signed webhook results to Coda with retry
57
+ - Drains in-flight work on graceful shutdown
58
+ - Supports pluggable execution backends via factory convention
59
+ - Auto-discovers executor factories from installed packages
60
+
61
+ ## Install
62
+
63
+ ```bash
64
+ uv sync --dev
65
+ ```
66
+
67
+ Requires Python 3.11+. Two equivalent CLI entry points are installed:
68
+
69
+ - `coda`
70
+ - `coda-node`
71
+
72
+ ## Quick Start
73
+
74
+ Provision with a node token:
75
+
76
+ ```bash
77
+ uv run coda-node start --token <node-token>
78
+ ```
79
+
80
+ Or set the token as an environment variable:
81
+
82
+ ```bash
83
+ export CODA_NODE_TOKEN=<node-token>
84
+ uv run coda start
85
+ ```
86
+
87
+ After a successful first run, credentials are persisted to disk and
88
+ subsequent restarts reconnect automatically without a fresh token.
89
+
90
+ ## How It Works
91
+
92
+ On startup the runtime:
93
+
94
+ 1. Loads configuration from `CODA_`-prefixed environment variables, then
95
+ persisted state on disk, then hardcoded defaults.
96
+ 2. Connects to Coda using either a node token or persisted JWT
97
+ credentials (with exponential-backoff retry on transient failures).
98
+ 3. Brings up or validates VPN connectivity when required.
99
+ 4. Starts the FastAPI service, a background Redis Streams consumer, and
100
+ a heartbeat loop that periodically POSTs node status to the cloud.
101
+ 5. Dispatches jobs to the configured executor, optionally in batches,
102
+ and posts signed results back via webhook.
103
+
104
+ On shutdown the runtime drains the in-flight job (up to
105
+ `CODA_SHUTDOWN_DRAIN_TIMEOUT_SEC`), cancels background tasks, closes
106
+ connections, and stops the managed VPN daemon.
107
+
108
+ ## Endpoints
109
+
110
+ | Endpoint | Purpose |
111
+ |---|---|
112
+ | `GET /health` | Liveness probe. Returns `200` if the process is running. |
113
+ | `GET /ready` | Readiness probe. Returns `200` with component status when VPN and Redis are healthy; `503` when either is degraded or the check times out. |
114
+
115
+ The `/ready` response body always includes `vpn_state`, `redis_healthy`,
116
+ and `current_job` fields for observability.
117
+
118
+ ## Configuration
119
+
120
+ All settings are driven by `CODA_`-prefixed environment variables. When
121
+ no node token is provided, the runtime automatically loads
122
+ previously persisted config from disk.
123
+
124
+ ### Core
125
+
126
+ | Variable | Default | Description |
127
+ |---|---|---|
128
+ | `CODA_NODE_TOKEN` | `""` | One-time node token for first-run provisioning. |
129
+ | `CODA_JWT_PRIVATE_KEY` | `""` | PEM-encoded RSA private key for direct JWT startup. |
130
+ | `CODA_JWT_KEY_ID` | `""` | `kid` header value for signed JWTs. |
131
+ | `CODA_REDIS_URL` | `""` | Redis connection string (`redis://…`). |
132
+ | `CODA_WEBAPP_URL` | `https://coda.conductorquantum.com` | Coda cloud base URL. Overridden by the node bundle on first connect. |
133
+ | `CODA_HOST` | `0.0.0.0` | Bind address for the FastAPI server. |
134
+ | `CODA_PORT` | `8080` | Bind port for the FastAPI server. |
135
+ | `CODA_EXECUTOR_FACTORY` | `""` | Import path for a custom executor (see below). Highest priority when set. |
136
+ | `CODA_DEVICE_CONFIG` | `""` | Path to a YAML device config read by the executor factory. Defaults to `./site/device.yaml` if that file exists. The runtime also looks for an optional top-level `executor_factory` key in this file when `CODA_EXECUTOR_FACTORY` is unset. |
137
+
138
+ Provide either `CODA_NODE_TOKEN` for auto-provisioning, or both
139
+ `CODA_JWT_PRIVATE_KEY` and `CODA_JWT_KEY_ID` for direct JWT startup.
140
+
141
+ ### VPN
142
+
143
+ | Variable | Default | Description |
144
+ |---|---|---|
145
+ | `CODA_VPN_REQUIRED` | `true` | Fail preflight if no VPN tunnel is detected. |
146
+ | `CODA_VPN_CHECK_INTERVAL_SEC` | `10` | Seconds between background VPN health checks. |
147
+ | `CODA_VPN_INTERFACE_HINT` | `null` | Specific TUN/TAP interface name to look for. |
148
+ | `CODA_ALLOW_DEGRADED_STARTUP` | `false` | Allow the server to start even if VPN preflight fails. |
149
+
150
+ ### Resilience
151
+
152
+ | Variable | Default | Description |
153
+ |---|---|---|
154
+ | `CODA_NODE_CONNECT_RETRIES` | `3` | Max attempts when connecting to the Coda cloud. |
155
+ | `CODA_SHUTDOWN_DRAIN_TIMEOUT_SEC` | `30` | Seconds to wait for an in-flight job before forced shutdown. |
156
+ | `CODA_NODE_TIMEOUT_SEC` | `15` | HTTP timeout for node connect requests. |
157
+
158
+ ## Persisted State
159
+
160
+ After successful node provisioning, the runtime writes:
161
+
162
+ | File | Contents |
163
+ |---|---|
164
+ | `/tmp/coda.config` | JSON with QPU identity, Redis URL, API paths, and VPN settings. |
165
+ | `/tmp/coda-private-key` | PEM-encoded RSA private key. |
166
+
167
+ Both files use `0600` permissions on POSIX systems and are validated on
168
+ read. They enable token-free reconnects across restarts, preserving JWT
169
+ credentials, machine fingerprint, VPN profile path, and connection
170
+ settings.
171
+
172
+ To wipe persisted state, run `coda reset`.
173
+
174
+ ## CLI
175
+
176
+ ```
177
+ coda start [--token TOKEN] [--host HOST] [--port PORT] [--daemon]
178
+ ```
179
+
180
+ Start the node server. Pass `--token` on first run for node
181
+ provisioning. Use `--daemon` (or `-d`) to run as a background process.
182
+
183
+ ```
184
+ coda stop
185
+ ```
186
+
187
+ Stop the background daemon process.
188
+
189
+ ```
190
+ coda status
191
+ ```
192
+
193
+ Show daemon status (running/stopped, PID, log file, VPN interface).
194
+
195
+ ```
196
+ coda logs [-n LINES]
197
+ ```
198
+
199
+ Show recent daemon log output (default: last 50 lines).
200
+
201
+ ```
202
+ coda doctor
203
+ ```
204
+
205
+ Print a diagnostic summary (endpoints, executor, VPN interface, OpenVPN
206
+ status).
207
+
208
+ ```
209
+ coda stop-vpn
210
+ ```
211
+
212
+ Stop the managed OpenVPN daemon without clearing credentials.
213
+
214
+ ```
215
+ coda reset
216
+ ```
217
+
218
+ Stop the daemon and VPN, then remove all persisted runtime files.
219
+
220
+ ## Executor Resolution
221
+
222
+ `load_executor()` resolves the execution backend in priority order:
223
+
224
+ 1. **`CODA_EXECUTOR_FACTORY`** (explicit) -- set to a `module:attribute`
225
+ import path to force a specific executor factory.
226
+ 2. **`executor_factory` in `CODA_DEVICE_CONFIG`** -- optional top-level YAML
227
+ key used when the env var is unset.
228
+ 3. **Convention-based auto-discovery** -- scan installed packages for
229
+ `<pkg>.executor_factory:create_executor`. If exactly one match is
230
+ found, use it. If multiple match, log a warning and fall back.
231
+ 4. **`NoopExecutor`** fallback -- returns deterministic all-zeros results,
232
+ allowing the service to boot without hardware integration.
233
+
234
+ ### Factory Convention
235
+
236
+ Backend packages should expose a factory at
237
+ `<package>.executor_factory:create_executor`. The factory must be
238
+ either:
239
+
240
+ - A callable that accepts a `Settings` object and returns an object
241
+ with an async `run(ir, shots)` method.
242
+ - A callable that accepts no parameters and returns the same.
243
+ - A pre-built object that already has a `run` method.
244
+
245
+ ### Example: Custom Executor
246
+
247
+ ```python
248
+ from coda_node.server.executor import ExecutionResult
249
+ from coda_node.server.ir import NativeGateIR
250
+
251
+
252
+ class MyExecutor:
253
+ async def run(self, ir: NativeGateIR, shots: int) -> ExecutionResult:
254
+ counts = run_on_hardware(ir, shots)
255
+ return ExecutionResult(
256
+ counts=counts,
257
+ execution_time_ms=42.0,
258
+ shots_completed=shots,
259
+ )
260
+
261
+
262
+ def create_executor() -> MyExecutor:
263
+ return MyExecutor()
264
+ ```
265
+
266
+ Place this in `my_package/executor_factory.py` and install the package.
267
+ The runtime will discover it automatically. Or set it explicitly:
268
+
269
+ ```bash
270
+ export CODA_EXECUTOR_FACTORY="my_package.executor_factory:create_executor"
271
+ ```
272
+
273
+ ## Error Handling
274
+
275
+ All domain exceptions inherit from `CodaError`, making it easy to
276
+ distinguish expected operational errors from unexpected bugs:
277
+
278
+ | Exception | When |
279
+ |---|---|
280
+ | `ConfigError` | Invalid or missing configuration. |
281
+ | `AuthError` | JWT signing or verification failure. |
282
+ | `VPNError` | VPN tunnel or health check failure. |
283
+ | `NodeError` | Node provisioning or reconnect failure. |
284
+ | `ExecutorError` | Executor loading or job execution failure. |
285
+ | `WebhookError` | Webhook delivery failure. |
286
+
287
+ Import from the top-level package:
288
+
289
+ ```python
290
+ from coda_node import CodaError
291
+ from coda_node.errors import ConfigError, VPNError
292
+ ```
293
+
294
+ ## Development
295
+
296
+ Install dependencies (including dev tools):
297
+
298
+ ```bash
299
+ uv sync --dev
300
+ ```
301
+
302
+ Run the full quality check suite:
303
+
304
+ ```bash
305
+ uv run ruff check .
306
+ uv run ruff format --check .
307
+ uv run mypy src/coda_node
308
+ uv build
309
+ uv run pytest --cov --cov-report=term-missing
310
+ ```
311
+
312
+ Run the opt-in live smoke tests against
313
+ `https://coda.conductorquantum.com`:
314
+
315
+ ```bash
316
+ export CODA_RUN_E2E=1
317
+ export CODA_E2E_NODE_TOKEN=<node-token>
318
+ uv run pytest -m e2e
319
+ ```
320
+
321
+ To include the live VPN tunnel check as well:
322
+
323
+ ```bash
324
+ export CODA_RUN_VPN_E2E=1
325
+ uv run pytest -m e2e
326
+ ```
327
+
328
+ Install pre-commit hooks (runs ruff format and lint on every commit):
329
+
330
+ ```bash
331
+ uv run pre-commit install
332
+ ```
333
+
334
+ Run all hooks manually:
335
+
336
+ ```bash
337
+ uv run pre-commit run --all-files
338
+ ```
339
+
340
+ ## Architecture
341
+
342
+ - **FastAPI** -- HTTP service, lifespan management, health endpoints
343
+ - **Pydantic Settings** -- environment-driven configuration with layered
344
+ defaults and persisted state
345
+ - **Redis Streams** -- job delivery with consumer groups and crash recovery
346
+ - **httpx** -- async HTTP for webhooks and node API calls
347
+ - **RS256 JWT** -- authentication between the node and the Coda cloud
348
+ - **OpenVPN** -- managed as a subprocess when VPN connectivity is required