nexus-control 0.6.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.
- nexus_control-0.6.0/.gitignore +36 -0
- nexus_control-0.6.0/ARCHITECTURE.md +178 -0
- nexus_control-0.6.0/PKG-INFO +288 -0
- nexus_control-0.6.0/QUICKSTART.md +170 -0
- nexus_control-0.6.0/README.md +264 -0
- nexus_control-0.6.0/docs/ATTESTATION_SCHEMA.md +189 -0
- nexus_control-0.6.0/examples/sample_narrative.json +171 -0
- nexus_control-0.6.0/nexus_control/__init__.py +70 -0
- nexus_control-0.6.0/nexus_control/attestation/__init__.py +63 -0
- nexus_control-0.6.0/nexus_control/attestation/_signing.py +371 -0
- nexus_control-0.6.0/nexus_control/attestation/flexiflow_adapter.py +218 -0
- nexus_control-0.6.0/nexus_control/attestation/intent.py +183 -0
- nexus_control-0.6.0/nexus_control/attestation/narrative.py +1262 -0
- nexus_control-0.6.0/nexus_control/attestation/queue.py +235 -0
- nexus_control-0.6.0/nexus_control/attestation/receipt.py +260 -0
- nexus_control-0.6.0/nexus_control/attestation/replay.py +420 -0
- nexus_control-0.6.0/nexus_control/attestation/storage.py +241 -0
- nexus_control-0.6.0/nexus_control/attestation/worker.py +157 -0
- nexus_control-0.6.0/nexus_control/attestation/xrpl/__init__.py +95 -0
- nexus_control-0.6.0/nexus_control/attestation/xrpl/adapter.py +379 -0
- nexus_control-0.6.0/nexus_control/attestation/xrpl/client.py +130 -0
- nexus_control-0.6.0/nexus_control/attestation/xrpl/errors.py +84 -0
- nexus_control-0.6.0/nexus_control/attestation/xrpl/exchange_store.py +314 -0
- nexus_control-0.6.0/nexus_control/attestation/xrpl/jsonrpc_client.py +220 -0
- nexus_control-0.6.0/nexus_control/attestation/xrpl/memo.py +130 -0
- nexus_control-0.6.0/nexus_control/attestation/xrpl/signer.py +84 -0
- nexus_control-0.6.0/nexus_control/attestation/xrpl/transport.py +234 -0
- nexus_control-0.6.0/nexus_control/attestation/xrpl/tx.py +71 -0
- nexus_control-0.6.0/nexus_control/audit_export.py +290 -0
- nexus_control-0.6.0/nexus_control/audit_package.py +381 -0
- nexus_control-0.6.0/nexus_control/bundle.py +433 -0
- nexus_control-0.6.0/nexus_control/canonical_json.py +32 -0
- nexus_control-0.6.0/nexus_control/decision.py +302 -0
- nexus_control-0.6.0/nexus_control/events.py +148 -0
- nexus_control-0.6.0/nexus_control/export.py +308 -0
- nexus_control-0.6.0/nexus_control/import_.py +359 -0
- nexus_control-0.6.0/nexus_control/integrity.py +27 -0
- nexus_control-0.6.0/nexus_control/lifecycle.py +524 -0
- nexus_control-0.6.0/nexus_control/policy.py +172 -0
- nexus_control-0.6.0/nexus_control/store.py +474 -0
- nexus_control-0.6.0/nexus_control/template.py +486 -0
- nexus_control-0.6.0/nexus_control/tool.py +1447 -0
- nexus_control-0.6.0/pyproject.toml +59 -0
- nexus_control-0.6.0/schemas/nexus-control.approve.v0.1.json +59 -0
- nexus_control-0.6.0/schemas/nexus-control.execute.v0.1.json +59 -0
- nexus_control-0.6.0/schemas/nexus-control.inspect.v0.1.json +138 -0
- nexus_control-0.6.0/schemas/nexus-control.request.v0.1.json +96 -0
- nexus_control-0.6.0/schemas/nexus-control.status.v0.1.json +79 -0
- nexus_control-0.6.0/schemas/nexus-control.template.create.v0.1.json +100 -0
- nexus_control-0.6.0/schemas/nexus-control.template.get.v0.1.json +57 -0
- nexus_control-0.6.0/schemas/nexus-control.template.list.v0.1.json +55 -0
- nexus_control-0.6.0/tests/__init__.py +1 -0
- nexus_control-0.6.0/tests/test_approval_threshold.py +218 -0
- nexus_control-0.6.0/tests/test_attestation_queue.py +544 -0
- nexus_control-0.6.0/tests/test_audit_package.py +508 -0
- nexus_control-0.6.0/tests/test_decision_replay.py +304 -0
- nexus_control-0.6.0/tests/test_exchange_store.py +346 -0
- nexus_control-0.6.0/tests/test_execute_links_run.py +371 -0
- nexus_control-0.6.0/tests/test_export_import.py +738 -0
- nexus_control-0.6.0/tests/test_flexiflow_adapter.py +368 -0
- nexus_control-0.6.0/tests/test_inspect.py +327 -0
- nexus_control-0.6.0/tests/test_intent.py +264 -0
- nexus_control-0.6.0/tests/test_lifecycle.py +794 -0
- nexus_control-0.6.0/tests/test_narrative.py +1521 -0
- nexus_control-0.6.0/tests/test_policy_compile.py +230 -0
- nexus_control-0.6.0/tests/test_receipt.py +402 -0
- nexus_control-0.6.0/tests/test_replay.py +431 -0
- nexus_control-0.6.0/tests/test_templates.py +621 -0
- nexus_control-0.6.0/tests/test_xrpl_adapter.py +277 -0
- nexus_control-0.6.0/tests/test_xrpl_dcl.py +581 -0
- nexus_control-0.6.0/tests/test_xrpl_jsonrpc.py +525 -0
- nexus_control-0.6.0/tests/test_xrpl_memo.py +243 -0
- nexus_control-0.6.0/tests/test_xrpl_submit_confirm.py +699 -0
- nexus_control-0.6.0/tests/test_xrpl_tx.py +146 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
env/
|
|
14
|
+
|
|
15
|
+
# Testing
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.benchmarks/
|
|
18
|
+
htmlcov/
|
|
19
|
+
.coverage
|
|
20
|
+
.coverage.*
|
|
21
|
+
coverage.xml
|
|
22
|
+
|
|
23
|
+
# Linting / Type checking
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
.pyright/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.vscode/
|
|
30
|
+
.idea/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
|
|
34
|
+
# OS
|
|
35
|
+
.DS_Store
|
|
36
|
+
Thumbs.db
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Architecture: How Nexus-Control Works
|
|
2
|
+
|
|
3
|
+
## One Sentence
|
|
4
|
+
|
|
5
|
+
Nexus-Control turns governance + execution into a single, verifiable artifact.
|
|
6
|
+
|
|
7
|
+
Not logs. Not reports. A file you can hash.
|
|
8
|
+
|
|
9
|
+
## The Core Flow
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌──────────────────┐
|
|
13
|
+
│ Control Bundle │
|
|
14
|
+
│ (Intent) │
|
|
15
|
+
│ │
|
|
16
|
+
│ - decision │
|
|
17
|
+
│ - policy │
|
|
18
|
+
│ - approvals │
|
|
19
|
+
│ - constraints │
|
|
20
|
+
│ - template │
|
|
21
|
+
└────────┬─────────┘
|
|
22
|
+
│ control_digest
|
|
23
|
+
│
|
|
24
|
+
▼
|
|
25
|
+
┌──────────────────┐ ┌──────────────────────────────────┐
|
|
26
|
+
│ Router │ │ Audit Package │
|
|
27
|
+
│ (Execution) │ │ │
|
|
28
|
+
│ │───────▶│ binding_digest = sha256( │
|
|
29
|
+
│ Reference mode: │ │ canonical_json({ │
|
|
30
|
+
│ run_id │ │ package_version, │
|
|
31
|
+
│ router_digest │ │ control_digest, │
|
|
32
|
+
│ │ │ router_digest, │
|
|
33
|
+
│ Embedded mode: │ │ control_router_link_digest │
|
|
34
|
+
│ full bundle │ │ }) │
|
|
35
|
+
│ cross-check │ │ ) │
|
|
36
|
+
└──────────────────┘ └──────────────────────────────────┘
|
|
37
|
+
▲ ▲
|
|
38
|
+
│ │
|
|
39
|
+
└──── control_router_link_digest ────┘
|
|
40
|
+
(why this execution was allowed)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## What Each Layer Means
|
|
44
|
+
|
|
45
|
+
### 1. Control Bundle — What was allowed
|
|
46
|
+
|
|
47
|
+
Governance intent:
|
|
48
|
+
- **Decision**: the request (goal, mode, constraints)
|
|
49
|
+
- **Policy**: approval rules, allowed modes, capabilities
|
|
50
|
+
- **Approvals**: who approved, when, with what comment
|
|
51
|
+
- **Template**: named policy snapshot (if used)
|
|
52
|
+
|
|
53
|
+
Its integrity is captured as `control_digest`.
|
|
54
|
+
|
|
55
|
+
### 2. Router — What actually ran
|
|
56
|
+
|
|
57
|
+
Execution identity, not a log stream.
|
|
58
|
+
|
|
59
|
+
| Mode | Contains | Use Case |
|
|
60
|
+
|------|----------|----------|
|
|
61
|
+
| **Reference** (default) | `run_id` + `router_digest` | CI, internal systems, continuous operation |
|
|
62
|
+
| **Embedded** | Full router bundle + optional cross-check | Regulators, external auditors, long-term archival |
|
|
63
|
+
|
|
64
|
+
Both modes are cryptographically equivalent at the binding layer.
|
|
65
|
+
|
|
66
|
+
### 3. Control–Router Link — Why this execution was allowed
|
|
67
|
+
|
|
68
|
+
The most important idea. The link explicitly states:
|
|
69
|
+
**this control bundle authorized that router execution.**
|
|
70
|
+
|
|
71
|
+
This prevents:
|
|
72
|
+
- Replay attacks
|
|
73
|
+
- Execution substitution
|
|
74
|
+
- Post-hoc justification
|
|
75
|
+
|
|
76
|
+
Its integrity is captured as `control_router_link_digest`.
|
|
77
|
+
|
|
78
|
+
### 4. Binding Digest — The point of no ambiguity
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
binding_digest = sha256(canonical_json({
|
|
82
|
+
package_version: "0.6",
|
|
83
|
+
control_digest,
|
|
84
|
+
router_digest,
|
|
85
|
+
control_router_link_digest
|
|
86
|
+
}))
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
If **any** component changes — intent, execution, linkage, or schema —
|
|
90
|
+
the digest breaks. This is the audit truth anchor.
|
|
91
|
+
|
|
92
|
+
## Verification Model
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
verify_audit_package()
|
|
96
|
+
│
|
|
97
|
+
├─ binding_digest recompute from binding fields
|
|
98
|
+
├─ control_bundle_digest recompute from control bundle content
|
|
99
|
+
├─ binding_control_match binding ↔ control bundle consistency
|
|
100
|
+
├─ binding_router_match binding ↔ router section consistency
|
|
101
|
+
├─ binding_link_match binding ↔ control-router link consistency
|
|
102
|
+
└─ router_digest embedded router bundle integrity (if applicable)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Properties:
|
|
106
|
+
- **No short-circuiting** — all failures reported
|
|
107
|
+
- **Machine-verifiable** — CI/CD safe
|
|
108
|
+
- **Regulator-readable** — structured JSON output
|
|
109
|
+
|
|
110
|
+
## Event-Sourced Foundation
|
|
111
|
+
|
|
112
|
+
All state is derived by replaying an immutable event log:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
decisions (header)
|
|
116
|
+
└── decision_events (append-only log)
|
|
117
|
+
├── DECISION_CREATED
|
|
118
|
+
├── POLICY_ATTACHED
|
|
119
|
+
├── APPROVAL_GRANTED
|
|
120
|
+
├── APPROVAL_REVOKED
|
|
121
|
+
├── EXECUTION_REQUESTED
|
|
122
|
+
├── EXECUTION_STARTED
|
|
123
|
+
├── EXECUTION_COMPLETED
|
|
124
|
+
└── EXECUTION_FAILED
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
No mutable state. No hidden writes. Replay is deterministic.
|
|
128
|
+
|
|
129
|
+
## System Architecture
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
┌─────────────────────────────────────────────────────────┐
|
|
133
|
+
│ nexus-control │
|
|
134
|
+
│ │
|
|
135
|
+
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────────┐ │
|
|
136
|
+
│ │ request │ │ approve │ │ execute │ │ audit export │ │
|
|
137
|
+
│ └────┬────┘ └────┬────┘ └────┬────┘ └──────┬───────┘ │
|
|
138
|
+
│ │ │ │ │ │
|
|
139
|
+
│ ▼ ▼ ▼ ▼ │
|
|
140
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
141
|
+
│ │ Decision Store (SQLite) │ │
|
|
142
|
+
│ │ - Event log (append-only) │ │
|
|
143
|
+
│ │ - Replay for state │ │
|
|
144
|
+
│ │ - Deterministic digest computation │ │
|
|
145
|
+
│ │ - Export/import with integrity verification │ │
|
|
146
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
147
|
+
│ │ │
|
|
148
|
+
└──────────────────────────┼───────────────────────────────┘
|
|
149
|
+
│
|
|
150
|
+
▼
|
|
151
|
+
┌─────────────────┐
|
|
152
|
+
│ nexus-router │
|
|
153
|
+
│ (execution) │
|
|
154
|
+
└─────────────────┘
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Control plane stores **links, not copies**. Router remains the flight recorder.
|
|
158
|
+
|
|
159
|
+
## Design Guarantees
|
|
160
|
+
|
|
161
|
+
| Guarantee | Mechanism |
|
|
162
|
+
|-----------|-----------|
|
|
163
|
+
| Deterministic digests | Canonical JSON (`sort_keys=True, separators=(",",":")`) |
|
|
164
|
+
| Digest schema immutability | Inputs frozen per `package_version` |
|
|
165
|
+
| Tamper evidence | SHA-256 binding across all components |
|
|
166
|
+
| Full failure visibility | `verify_audit_package()` runs all checks |
|
|
167
|
+
| Portable artifacts | JSON bundles with `from_dict()` / `to_dict()` roundtrip |
|
|
168
|
+
| No hidden state | `meta.exported_at` and provenance excluded from digests |
|
|
169
|
+
|
|
170
|
+
## What This Replaces
|
|
171
|
+
|
|
172
|
+
| Old World | Nexus-Control |
|
|
173
|
+
|-----------|---------------|
|
|
174
|
+
| Logs | Artifacts |
|
|
175
|
+
| Trust | Verification |
|
|
176
|
+
| PDFs | Digests |
|
|
177
|
+
| "Approved in Jira" | Cryptographic linkage |
|
|
178
|
+
| After-the-fact audits | Built-in auditability |
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nexus-control
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Orchestration and approval layer for nexus-router executions
|
|
5
|
+
Project-URL: Homepage, https://github.com/mcp-tool-shop/nexus-control
|
|
6
|
+
Project-URL: Repository, https://github.com/mcp-tool-shop/nexus-control
|
|
7
|
+
Author-email: mcp-tool-shop <64996768+mcp-tool-shop@users.noreply.github.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: approval,audit,mcp,nexus,orchestration
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Requires-Dist: nexus-router>=0.1.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pyright>=1.1.350; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff>=0.3; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
> ⚠️ **This repository has moved to [nexus-suite](https://github.com/mcp-tool-shop/nexus-suite)**
|
|
26
|
+
> Source now lives at: `src/nexus-control/`
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
# nexus-control
|
|
31
|
+
|
|
32
|
+
**Orchestration and approval layer for nexus-router executions.**
|
|
33
|
+
|
|
34
|
+
A thin control plane that turns "router can execute" into "org can safely decide to execute" — with cryptographic proof.
|
|
35
|
+
|
|
36
|
+
## Core Promise
|
|
37
|
+
|
|
38
|
+
Every execution is tied to:
|
|
39
|
+
- A **decision** (the request + policy)
|
|
40
|
+
- A **policy** (approval rules, allowed modes, constraints)
|
|
41
|
+
- An **approval trail** (who approved, when, with what comment)
|
|
42
|
+
- A **nexus-router run_id** (for full execution audit)
|
|
43
|
+
- An **audit package** (cryptographic binding of governance to execution)
|
|
44
|
+
|
|
45
|
+
Everything is exportable, verifiable, and replayable.
|
|
46
|
+
|
|
47
|
+
> See [ARCHITECTURE.md](ARCHITECTURE.md) for the full mental model and design guarantees.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install nexus-control
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or from source:
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/mcp-tool-shop/nexus-control
|
|
58
|
+
cd nexus-control
|
|
59
|
+
pip install -e ".[dev]"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from nexus_control import NexusControlTools
|
|
66
|
+
from nexus_control.events import Actor
|
|
67
|
+
|
|
68
|
+
# Initialize (uses in-memory SQLite by default)
|
|
69
|
+
tools = NexusControlTools(db_path="decisions.db")
|
|
70
|
+
|
|
71
|
+
# 1. Create a request
|
|
72
|
+
result = tools.request(
|
|
73
|
+
goal="Rotate production API keys",
|
|
74
|
+
actor=Actor(type="human", id="alice@example.com"),
|
|
75
|
+
mode="apply",
|
|
76
|
+
min_approvals=2,
|
|
77
|
+
labels=["prod", "security"],
|
|
78
|
+
)
|
|
79
|
+
request_id = result.data["request_id"]
|
|
80
|
+
|
|
81
|
+
# 2. Get approvals
|
|
82
|
+
tools.approve(request_id, actor=Actor(type="human", id="alice@example.com"))
|
|
83
|
+
tools.approve(request_id, actor=Actor(type="human", id="bob@example.com"))
|
|
84
|
+
|
|
85
|
+
# 3. Execute (with your router)
|
|
86
|
+
result = tools.execute(
|
|
87
|
+
request_id=request_id,
|
|
88
|
+
adapter_id="subprocess:mcpt:key-rotation",
|
|
89
|
+
actor=Actor(type="system", id="scheduler"),
|
|
90
|
+
router=your_router, # RouterProtocol implementation
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
print(f"Run ID: {result.data['run_id']}")
|
|
94
|
+
|
|
95
|
+
# 4. Export audit package (cryptographic proof of governance + execution)
|
|
96
|
+
audit = tools.export_audit_package(request_id)
|
|
97
|
+
print(audit.data["digest"]) # sha256:...
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## MCP Tools
|
|
101
|
+
|
|
102
|
+
| Tool | Description |
|
|
103
|
+
|------|-------------|
|
|
104
|
+
| `nexus-control.request` | Create an execution request with goal, policy, and approvers |
|
|
105
|
+
| `nexus-control.approve` | Approve a request (supports N-of-M approvals) |
|
|
106
|
+
| `nexus-control.execute` | Execute approved request via nexus-router |
|
|
107
|
+
| `nexus-control.status` | Get request state and linked run status |
|
|
108
|
+
| `nexus-control.inspect` | Read-only introspection with human-readable output |
|
|
109
|
+
| `nexus-control.template.create` | Create a named, immutable policy template |
|
|
110
|
+
| `nexus-control.template.get` | Retrieve a template by name |
|
|
111
|
+
| `nexus-control.template.list` | List all templates with optional label filtering |
|
|
112
|
+
| `nexus-control.export_bundle` | Export a decision as a portable, integrity-verified bundle |
|
|
113
|
+
| `nexus-control.import_bundle` | Import a bundle with conflict modes and replay validation |
|
|
114
|
+
| `nexus-control.export_audit_package` | Export audit package binding governance to execution |
|
|
115
|
+
|
|
116
|
+
## Audit Packages (v0.6.0)
|
|
117
|
+
|
|
118
|
+
A single JSON artifact that cryptographically binds:
|
|
119
|
+
- **What was allowed** (control bundle)
|
|
120
|
+
- **What actually ran** (router execution)
|
|
121
|
+
- **Why it was allowed** (control-router link)
|
|
122
|
+
|
|
123
|
+
Into one verifiable `binding_digest`.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from nexus_control import export_audit_package, verify_audit_package
|
|
127
|
+
|
|
128
|
+
# Export
|
|
129
|
+
result = export_audit_package(store, decision_id)
|
|
130
|
+
package = result.package
|
|
131
|
+
|
|
132
|
+
# Verify (6 independent checks, no short-circuiting)
|
|
133
|
+
verification = verify_audit_package(package)
|
|
134
|
+
assert verification.ok
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Two router modes:
|
|
138
|
+
|
|
139
|
+
| Mode | Description | Use Case |
|
|
140
|
+
|------|-------------|----------|
|
|
141
|
+
| **Reference** | `run_id` + `router_digest` | CI, internal systems |
|
|
142
|
+
| **Embedded** | Full router bundle included | Regulators, long-term archival |
|
|
143
|
+
|
|
144
|
+
## Decision Templates (v0.3.0)
|
|
145
|
+
|
|
146
|
+
Named, immutable policy bundles that can be reused across decisions:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
tools.template_create(
|
|
150
|
+
name="prod-deploy",
|
|
151
|
+
actor=Actor(type="human", id="platform-team"),
|
|
152
|
+
min_approvals=2,
|
|
153
|
+
allowed_modes=["dry_run", "apply"],
|
|
154
|
+
require_adapter_capabilities=["timeout"],
|
|
155
|
+
labels=["prod"],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Use template with optional overrides
|
|
159
|
+
result = tools.request(
|
|
160
|
+
goal="Deploy v2.1.0",
|
|
161
|
+
actor=actor,
|
|
162
|
+
template_name="prod-deploy",
|
|
163
|
+
override_min_approvals=3, # Stricter for this deploy
|
|
164
|
+
)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Decision Lifecycle (v0.4.0)
|
|
168
|
+
|
|
169
|
+
Computed lifecycle with blocking reasons and timeline:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from nexus_control import compute_lifecycle
|
|
173
|
+
|
|
174
|
+
lifecycle = compute_lifecycle(decision, events, policy)
|
|
175
|
+
|
|
176
|
+
# Blocking reasons (triage-ladder ordered)
|
|
177
|
+
for reason in lifecycle.blocking_reasons:
|
|
178
|
+
print(f"{reason.code}: {reason.message}")
|
|
179
|
+
|
|
180
|
+
# Timeline with truncation
|
|
181
|
+
for entry in lifecycle.timeline:
|
|
182
|
+
print(f" {entry.seq} {entry.label}")
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Export/Import Bundles (v0.5.0)
|
|
186
|
+
|
|
187
|
+
Portable, integrity-verified decision bundles:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
# Export
|
|
191
|
+
bundle_result = tools.export_bundle(decision_id)
|
|
192
|
+
bundle_json = bundle_result.data["canonical_json"]
|
|
193
|
+
|
|
194
|
+
# Import with conflict handling
|
|
195
|
+
import_result = tools.import_bundle(
|
|
196
|
+
bundle_json,
|
|
197
|
+
conflict_mode="new_decision_id",
|
|
198
|
+
replay_after_import=True,
|
|
199
|
+
)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Conflict modes: `reject_on_conflict`, `new_decision_id`, `overwrite`
|
|
203
|
+
|
|
204
|
+
## Data Model
|
|
205
|
+
|
|
206
|
+
### Event-Sourced Design
|
|
207
|
+
|
|
208
|
+
All state is derived by replaying an immutable event log:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
decisions (header)
|
|
212
|
+
└── decision_events (append-only log)
|
|
213
|
+
├── DECISION_CREATED
|
|
214
|
+
├── POLICY_ATTACHED
|
|
215
|
+
├── APPROVAL_GRANTED
|
|
216
|
+
├── APPROVAL_REVOKED
|
|
217
|
+
├── EXECUTION_REQUESTED
|
|
218
|
+
├── EXECUTION_STARTED
|
|
219
|
+
├── EXECUTION_COMPLETED
|
|
220
|
+
└── EXECUTION_FAILED
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Policy Model
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
Policy(
|
|
227
|
+
min_approvals=2,
|
|
228
|
+
allowed_modes=["dry_run", "apply"],
|
|
229
|
+
require_adapter_capabilities=["timeout"],
|
|
230
|
+
max_steps=50,
|
|
231
|
+
labels=["prod", "finance"],
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Approval Model
|
|
236
|
+
|
|
237
|
+
- Counted by distinct `actor.id`
|
|
238
|
+
- Can include `comment` and optional `expires_at`
|
|
239
|
+
- Can be revoked (before execution)
|
|
240
|
+
- Execution requires approvals to satisfy policy **at execution time**
|
|
241
|
+
|
|
242
|
+
## Development
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Install dev dependencies
|
|
246
|
+
pip install -e ".[dev]"
|
|
247
|
+
|
|
248
|
+
# Run tests (203 tests)
|
|
249
|
+
pytest
|
|
250
|
+
|
|
251
|
+
# Type check (strict mode)
|
|
252
|
+
pyright
|
|
253
|
+
|
|
254
|
+
# Lint
|
|
255
|
+
ruff check .
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Project Structure
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
nexus-control/
|
|
262
|
+
├── nexus_control/
|
|
263
|
+
│ ├── __init__.py # Public API + version
|
|
264
|
+
│ ├── tool.py # MCP tool entrypoints (11 tools)
|
|
265
|
+
│ ├── store.py # SQLite event store
|
|
266
|
+
│ ├── events.py # Event type definitions
|
|
267
|
+
│ ├── policy.py # Policy validation + router compilation
|
|
268
|
+
│ ├── decision.py # State machine + replay
|
|
269
|
+
│ ├── lifecycle.py # Blocking reasons, timeline, progress
|
|
270
|
+
│ ├── template.py # Named immutable policy templates
|
|
271
|
+
│ ├── export.py # Decision bundle export
|
|
272
|
+
│ ├── import_.py # Bundle import with conflict modes
|
|
273
|
+
│ ├── bundle.py # Bundle types + digest computation
|
|
274
|
+
│ ├── audit_package.py # Audit package types + verification
|
|
275
|
+
│ ├── audit_export.py # Audit package export + rendering
|
|
276
|
+
│ ├── canonical_json.py # Deterministic serialization
|
|
277
|
+
│ └── integrity.py # SHA-256 helpers
|
|
278
|
+
├── schemas/ # JSON schemas for tool inputs
|
|
279
|
+
├── tests/ # 203 tests across 9 test files
|
|
280
|
+
├── ARCHITECTURE.md # Mental model + design guarantees
|
|
281
|
+
├── QUICKSTART.md
|
|
282
|
+
├── README.md
|
|
283
|
+
└── pyproject.toml
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
MIT
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Quickstart: nexus-control
|
|
2
|
+
|
|
3
|
+
Get operational in 5 minutes.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install nexus-control
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## The 3-Command Flow
|
|
12
|
+
|
|
13
|
+
### 1. Create a Decision
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from nexus_control import NexusControlTools
|
|
17
|
+
from nexus_control.events import Actor
|
|
18
|
+
|
|
19
|
+
tools = NexusControlTools(db_path="control.db")
|
|
20
|
+
|
|
21
|
+
result = tools.request(
|
|
22
|
+
goal="rotate production API keys",
|
|
23
|
+
actor=Actor(type="human", id="alice"),
|
|
24
|
+
mode="apply",
|
|
25
|
+
min_approvals=2,
|
|
26
|
+
allowed_modes=["dry_run", "apply"],
|
|
27
|
+
labels=["prod", "security"],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
request_id = result.data["request_id"]
|
|
31
|
+
print(f"Created: {request_id}")
|
|
32
|
+
# Created: 550e8400-e29b-41d4-a716-446655440000
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Approve (N-of-M)
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# First approval
|
|
39
|
+
tools.approve(
|
|
40
|
+
request_id=request_id,
|
|
41
|
+
actor=Actor(type="human", id="alice"),
|
|
42
|
+
comment="Reviewed plan, looks safe",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Second approval - now approved
|
|
46
|
+
result = tools.approve(
|
|
47
|
+
request_id=request_id,
|
|
48
|
+
actor=Actor(type="human", id="bob"),
|
|
49
|
+
comment="LGTM",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
print(f"Approved: {result.data['is_approved']}")
|
|
53
|
+
# Approved: True
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Execute
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# You provide the router (nexus-router instance)
|
|
60
|
+
from nexus_router import Router
|
|
61
|
+
|
|
62
|
+
router = Router(...) # Your router setup
|
|
63
|
+
|
|
64
|
+
result = tools.execute(
|
|
65
|
+
request_id=request_id,
|
|
66
|
+
adapter_id="subprocess:mcpt:key-rotation",
|
|
67
|
+
actor=Actor(type="system", id="scheduler"),
|
|
68
|
+
router=router,
|
|
69
|
+
dry_run=False,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
print(f"Run ID: {result.data['run_id']}")
|
|
73
|
+
print(f"Steps: {result.data['steps_executed']}")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Check Status
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
status = tools.status(request_id)
|
|
80
|
+
|
|
81
|
+
print(f"State: {status.data['state']}")
|
|
82
|
+
print(f"Goal: {status.data['goal']}")
|
|
83
|
+
print(f"Approvals: {status.data['active_approvals']}/{status.data['policy']['min_approvals']}")
|
|
84
|
+
print(f"Run ID: {status.data['executions'][-1]['run_id']}")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Export Audit Record
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
audit = tools.export_audit_record(request_id)
|
|
91
|
+
|
|
92
|
+
# Canonical JSON for archival
|
|
93
|
+
with open(f"audit-{request_id}.json", "w") as f:
|
|
94
|
+
f.write(audit.data["canonical_json"])
|
|
95
|
+
|
|
96
|
+
# Integrity digest
|
|
97
|
+
print(f"Digest: {audit.data['record_digest']}")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Policy Options
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
tools.request(
|
|
104
|
+
goal="...",
|
|
105
|
+
actor=Actor(type="human", id="alice"),
|
|
106
|
+
|
|
107
|
+
# Approval requirements
|
|
108
|
+
min_approvals=2, # Need 2 distinct approvers
|
|
109
|
+
|
|
110
|
+
# Execution constraints
|
|
111
|
+
mode="apply", # Requested mode
|
|
112
|
+
allowed_modes=["dry_run", "apply"], # What policy permits
|
|
113
|
+
max_steps=50, # Passed to router
|
|
114
|
+
|
|
115
|
+
# Adapter requirements
|
|
116
|
+
require_adapter_capabilities=["timeout", "external"],
|
|
117
|
+
|
|
118
|
+
# Governance
|
|
119
|
+
labels=["prod", "finance"], # For routing/filtering
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Dry Run First
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
# Execute in dry_run mode even if request was for apply
|
|
127
|
+
result = tools.execute(
|
|
128
|
+
request_id=request_id,
|
|
129
|
+
adapter_id="adapter",
|
|
130
|
+
actor=Actor(type="human", id="alice"),
|
|
131
|
+
router=router,
|
|
132
|
+
dry_run=True, # Override to dry_run
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Review output, then execute for real
|
|
136
|
+
result = tools.execute(
|
|
137
|
+
request_id=request_id,
|
|
138
|
+
adapter_id="adapter",
|
|
139
|
+
actor=Actor(type="human", id="alice"),
|
|
140
|
+
router=router,
|
|
141
|
+
dry_run=False,
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Timeline View
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
status = tools.status(request_id, include_events=True)
|
|
149
|
+
|
|
150
|
+
for event in status.data["events"]:
|
|
151
|
+
print(f"{event['ts']} | {event['event_type']} | {event['actor']['id']}")
|
|
152
|
+
|
|
153
|
+
# 2024-01-15T10:00:00Z | DECISION_CREATED | alice
|
|
154
|
+
# 2024-01-15T10:00:00Z | POLICY_ATTACHED | alice
|
|
155
|
+
# 2024-01-15T10:05:00Z | APPROVAL_GRANTED | alice
|
|
156
|
+
# 2024-01-15T10:10:00Z | APPROVAL_GRANTED | bob
|
|
157
|
+
# 2024-01-15T10:15:00Z | EXECUTION_REQUESTED | scheduler
|
|
158
|
+
# 2024-01-15T10:15:01Z | EXECUTION_STARTED | nexus-control
|
|
159
|
+
# 2024-01-15T10:15:30Z | EXECUTION_COMPLETED | nexus-control
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## What Makes This Useful
|
|
163
|
+
|
|
164
|
+
1. **Audit trail**: Every action has an actor, timestamp, and digest
|
|
165
|
+
2. **N-of-M approvals**: Real approval workflows, not just flags
|
|
166
|
+
3. **Policy enforcement**: Mode restrictions, capability requirements
|
|
167
|
+
4. **Router integration**: Links to nexus-router run_id for full execution audit
|
|
168
|
+
5. **Exportable**: Canonical JSON records for compliance/archival
|
|
169
|
+
|
|
170
|
+
That's operational power.
|