manithy-sdk 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.
- manithy_sdk-0.1.0/PKG-INFO +218 -0
- manithy_sdk-0.1.0/README.md +207 -0
- manithy_sdk-0.1.0/pyproject.toml +35 -0
- manithy_sdk-0.1.0/setup.cfg +4 -0
- manithy_sdk-0.1.0/src/manithy/__init__.py +17 -0
- manithy_sdk-0.1.0/src/manithy/config.py +42 -0
- manithy_sdk-0.1.0/src/manithy/core/__init__.py +10 -0
- manithy_sdk-0.1.0/src/manithy/core/canonical.py +99 -0
- manithy_sdk-0.1.0/src/manithy/core/envelope.py +200 -0
- manithy_sdk-0.1.0/src/manithy/core/hasher.py +44 -0
- manithy_sdk-0.1.0/src/manithy/interfaces/__init__.py +6 -0
- manithy_sdk-0.1.0/src/manithy/interfaces/buffer.py +76 -0
- manithy_sdk-0.1.0/src/manithy/sdk.py +132 -0
- manithy_sdk-0.1.0/src/manithy_sdk.egg-info/PKG-INFO +218 -0
- manithy_sdk-0.1.0/src/manithy_sdk.egg-info/SOURCES.txt +18 -0
- manithy_sdk-0.1.0/src/manithy_sdk.egg-info/dependency_links.txt +1 -0
- manithy_sdk-0.1.0/src/manithy_sdk.egg-info/requires.txt +4 -0
- manithy_sdk-0.1.0/src/manithy_sdk.egg-info/top_level.txt +1 -0
- manithy_sdk-0.1.0/tests/test_core.py +252 -0
- manithy_sdk-0.1.0/tests/test_sdk.py +158 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: manithy-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Manithy SDK — Authority-Grade Audit Capture (Zero Dependencies)
|
|
5
|
+
License: Proprietary
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
10
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
11
|
+
|
|
12
|
+
# Manithy SDK (Python)
|
|
13
|
+
|
|
14
|
+
**Authority-Grade Audit Capture — Zero Dependencies**
|
|
15
|
+
|
|
16
|
+
Manithy captures tamper-evident audit proofs at the application layer.
|
|
17
|
+
Each proof is a **J01 CommitBoundaryEvent** — a structured record that
|
|
18
|
+
marks the exact t-1 boundary before an irreversible action and freezes
|
|
19
|
+
only facts already resolved in the execution context.
|
|
20
|
+
|
|
21
|
+
No lookups. No inference. No enrichment.
|
|
22
|
+
|
|
23
|
+
## Design Constraints
|
|
24
|
+
|
|
25
|
+
| Constraint | Guarantee |
|
|
26
|
+
|---|---|
|
|
27
|
+
| **Zero Network I/O** | The SDK never opens sockets or makes HTTP calls. |
|
|
28
|
+
| **Determinism** | Identical inputs always yield identical commit-IDs. |
|
|
29
|
+
| **Fail-Closed** | Internal errors are silently swallowed — the host app never crashes. |
|
|
30
|
+
| **Zero Dependencies** | Only the Python standard library is used at runtime. |
|
|
31
|
+
| **Epistemic Honesty** | The `availability` block declares what was knowable at t-1. Unknown facts must never appear in `observed`. |
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install manithy-sdk
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or install from source:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/VooYee/manithy-sdk.git
|
|
43
|
+
cd manithy-sdk
|
|
44
|
+
pip install .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from manithy import ManithySDK
|
|
51
|
+
|
|
52
|
+
sdk = ManithySDK()
|
|
53
|
+
|
|
54
|
+
result = sdk.capture(
|
|
55
|
+
boundary_kind="REFUND_COMMIT_T_MINUS_1",
|
|
56
|
+
boundary_seq=1,
|
|
57
|
+
same_thread=True,
|
|
58
|
+
observed={
|
|
59
|
+
"action_kind": "REFUND",
|
|
60
|
+
"amount_minor": 12900,
|
|
61
|
+
"currency": "EUR",
|
|
62
|
+
"refund_mode": "FULL",
|
|
63
|
+
"order_channel": "WEB",
|
|
64
|
+
"payment_method": "CARD",
|
|
65
|
+
"merchant_region": "EU",
|
|
66
|
+
"customer_present": False,
|
|
67
|
+
"operator_initiated": False,
|
|
68
|
+
},
|
|
69
|
+
availability={
|
|
70
|
+
"psp_refund_capability_known": True,
|
|
71
|
+
"original_payment_state_known": True,
|
|
72
|
+
"chargeback_state_known": False,
|
|
73
|
+
},
|
|
74
|
+
reentrancy_guard="SINGLE_CAPTURE_ENFORCED",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
print(result)
|
|
78
|
+
# {"status": "CAPTURED", "id": "a3f8c9..."}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Output (stdout):
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
MANITHY_PROOF::{"schema_id":"manithy.commit_boundary_event.v1","boundary_kind":"REFUND_COMMIT_T_MINUS_1","boundary_seq":1,"same_thread":true,"reentrancy_guard":"SINGLE_CAPTURE_ENFORCED","observed":{...},"availability":{...}}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Capture Parameters
|
|
88
|
+
|
|
89
|
+
| Parameter | Type | Purpose |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| **`boundary_kind`** | `str` | Which irreversible boundary this event refers to. Consumer-defined closed enum (e.g. `"REFUND_COMMIT_T_MINUS_1"`). |
|
|
92
|
+
| **`boundary_seq`** | `int` | Supports rare cases of multiple irreversible calls in one execution path. Small integer (0–255). |
|
|
93
|
+
| **`same_thread`** | `bool` | Runtime assertion that capture happened same-thread at t-1. |
|
|
94
|
+
| **`observed`** | `dict[str, str\|int\|bool]` | Runtime facts already resolved in the execution context. Values must be primitives only — no floats, no `None`, no nested structures. |
|
|
95
|
+
| **`availability`** | `dict[str, bool]` | Epistemic visibility at t-1. Each key declares whether a fact was knowable before the irreversible action. |
|
|
96
|
+
| **`reentrancy_guard`** | `str` | Capture enforcement mode. Consumer-defined (e.g. `"SINGLE_CAPTURE_ENFORCED"`). |
|
|
97
|
+
|
|
98
|
+
### The `observed` Block
|
|
99
|
+
|
|
100
|
+
All fields in `observed` must already be resolved in the execution context at t-1.
|
|
101
|
+
|
|
102
|
+
**Allowed:** `str`, `int`, `bool`.
|
|
103
|
+
**Forbidden:** `float`, `None`, nested `dict`/`list`, any value fetched or inferred after execution.
|
|
104
|
+
|
|
105
|
+
### The `availability` Block
|
|
106
|
+
|
|
107
|
+
`availability` is not data. It is a **declaration of epistemic visibility** at t-1.
|
|
108
|
+
|
|
109
|
+
Each key answers one question:
|
|
110
|
+
> "At the exact moment before the irreversible action, was this fact already knowable inside the execution context — yes or no?"
|
|
111
|
+
|
|
112
|
+
| `_known` value | Meaning | Effect |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| `True` | Fact was knowable at t-1 | May appear in `observed` |
|
|
115
|
+
| `False` | Fact was NOT knowable at t-1 | Must NOT appear in `observed` |
|
|
116
|
+
|
|
117
|
+
If the fact was not knowable, Manithy records **ignorance**, not a value.
|
|
118
|
+
That ignorance is structural and permanent.
|
|
119
|
+
|
|
120
|
+
### Forbidden Fields
|
|
121
|
+
|
|
122
|
+
The following fields must **never** appear in a CommitBoundaryEvent:
|
|
123
|
+
|
|
124
|
+
| Field | Why Forbidden |
|
|
125
|
+
|---|---|
|
|
126
|
+
| `producer_invocation_id` | High joinability risk. Single-capture is enforced via guard state, not IDs. |
|
|
127
|
+
| `callsite_id` | High joinability risk. |
|
|
128
|
+
| `producer_build_id` | Belongs in PackInit / EvidencePack provenance, not J01. |
|
|
129
|
+
|
|
130
|
+
## Custom Buffer
|
|
131
|
+
|
|
132
|
+
Route proofs to a file, queue, or any destination by subclassing `CaptureBuffer`:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
import json
|
|
136
|
+
from manithy import ManithySDK
|
|
137
|
+
from manithy.interfaces.buffer import CaptureBuffer
|
|
138
|
+
|
|
139
|
+
class FileBuffer(CaptureBuffer):
|
|
140
|
+
def __init__(self, path: str):
|
|
141
|
+
self._file = open(path, "a", encoding="utf-8")
|
|
142
|
+
|
|
143
|
+
def emit(self, envelope: dict) -> None:
|
|
144
|
+
self._file.write(json.dumps(envelope, separators=(",", ":")) + "\n")
|
|
145
|
+
self._file.flush()
|
|
146
|
+
|
|
147
|
+
sdk = ManithySDK(buffer=FileBuffer("audit.log"))
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Configuration
|
|
151
|
+
|
|
152
|
+
### Kill-Switch
|
|
153
|
+
|
|
154
|
+
Disable all capture at runtime without code changes:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
export MANITHY_ENABLED=false # Linux/macOS
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```powershell
|
|
161
|
+
$env:MANITHY_ENABLED = "false" # Windows PowerShell
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
When disabled, `capture()` returns `{"status": "SKIPPED"}` immediately.
|
|
165
|
+
|
|
166
|
+
### Debug Mode
|
|
167
|
+
|
|
168
|
+
Log internal SDK errors to stderr (useful during development):
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
export MANITHY_DEBUG=true
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## How It Works
|
|
175
|
+
|
|
176
|
+
1. **Kill-switch check** — reads `MANITHY_ENABLED`. If `"false"`, returns `SKIPPED`.
|
|
177
|
+
2. **Validation** — enforces type constraints on all fields; rejects forbidden fields, floats in `observed`, non-bool in `availability`, and unknown facts that leak into `observed`.
|
|
178
|
+
3. **Event assembly** — builds a J01 `CommitBoundaryEvent` with schema `manithy.commit_boundary_event.v1`.
|
|
179
|
+
4. **Hashing** — canonicalizes the event (sorted keys, no whitespace, floats like `100.0` → `100`) and computes SHA-256 → 64-char hex `commit_id`.
|
|
180
|
+
5. **Emit** — writes the event to the configured buffer (default: stdout with `MANITHY_PROOF::` prefix).
|
|
181
|
+
|
|
182
|
+
If any step fails, the error is swallowed and `{"status": "ERROR", "error": "Internal SDK Error"}` is returned. The host application is **never** affected.
|
|
183
|
+
|
|
184
|
+
## Development
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
# Create a virtual environment
|
|
188
|
+
python -m venv .venv
|
|
189
|
+
|
|
190
|
+
# Activate it
|
|
191
|
+
source .venv/bin/activate # Linux/macOS
|
|
192
|
+
.venv\Scripts\Activate.ps1 # Windows PowerShell
|
|
193
|
+
|
|
194
|
+
# Install in editable mode with dev dependencies
|
|
195
|
+
pip install -e ".[dev]"
|
|
196
|
+
|
|
197
|
+
# Run tests
|
|
198
|
+
pytest tests/ -v
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Project Structure
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
src/manithy/
|
|
205
|
+
├── __init__.py # Public API: exposes ManithySDK
|
|
206
|
+
├── sdk.py # Main entry point (capture pipeline + fail-closed)
|
|
207
|
+
├── config.py # Environment variable loader (kill-switch + debug)
|
|
208
|
+
├── core/
|
|
209
|
+
│ ├── canonical.py # Deterministic JSON canonicalization
|
|
210
|
+
│ ├── hasher.py # SHA-256 commit-ID generation
|
|
211
|
+
│ └── envelope.py # J01 CommitBoundaryEvent assembly + validation
|
|
212
|
+
└── interfaces/
|
|
213
|
+
└── buffer.py # Abstract CaptureBuffer + StdoutBuffer
|
|
214
|
+
tests/
|
|
215
|
+
├── vectors.json # Golden test vectors (canonical + hash)
|
|
216
|
+
├── test_core.py # Core module tests (canonical, hasher, J01 event)
|
|
217
|
+
└── test_sdk.py # SDK integration tests (capture, kill-switch, fail-closed)
|
|
218
|
+
```
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Manithy SDK (Python)
|
|
2
|
+
|
|
3
|
+
**Authority-Grade Audit Capture — Zero Dependencies**
|
|
4
|
+
|
|
5
|
+
Manithy captures tamper-evident audit proofs at the application layer.
|
|
6
|
+
Each proof is a **J01 CommitBoundaryEvent** — a structured record that
|
|
7
|
+
marks the exact t-1 boundary before an irreversible action and freezes
|
|
8
|
+
only facts already resolved in the execution context.
|
|
9
|
+
|
|
10
|
+
No lookups. No inference. No enrichment.
|
|
11
|
+
|
|
12
|
+
## Design Constraints
|
|
13
|
+
|
|
14
|
+
| Constraint | Guarantee |
|
|
15
|
+
|---|---|
|
|
16
|
+
| **Zero Network I/O** | The SDK never opens sockets or makes HTTP calls. |
|
|
17
|
+
| **Determinism** | Identical inputs always yield identical commit-IDs. |
|
|
18
|
+
| **Fail-Closed** | Internal errors are silently swallowed — the host app never crashes. |
|
|
19
|
+
| **Zero Dependencies** | Only the Python standard library is used at runtime. |
|
|
20
|
+
| **Epistemic Honesty** | The `availability` block declares what was knowable at t-1. Unknown facts must never appear in `observed`. |
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install manithy-sdk
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or install from source:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/VooYee/manithy-sdk.git
|
|
32
|
+
cd manithy-sdk
|
|
33
|
+
pip install .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from manithy import ManithySDK
|
|
40
|
+
|
|
41
|
+
sdk = ManithySDK()
|
|
42
|
+
|
|
43
|
+
result = sdk.capture(
|
|
44
|
+
boundary_kind="REFUND_COMMIT_T_MINUS_1",
|
|
45
|
+
boundary_seq=1,
|
|
46
|
+
same_thread=True,
|
|
47
|
+
observed={
|
|
48
|
+
"action_kind": "REFUND",
|
|
49
|
+
"amount_minor": 12900,
|
|
50
|
+
"currency": "EUR",
|
|
51
|
+
"refund_mode": "FULL",
|
|
52
|
+
"order_channel": "WEB",
|
|
53
|
+
"payment_method": "CARD",
|
|
54
|
+
"merchant_region": "EU",
|
|
55
|
+
"customer_present": False,
|
|
56
|
+
"operator_initiated": False,
|
|
57
|
+
},
|
|
58
|
+
availability={
|
|
59
|
+
"psp_refund_capability_known": True,
|
|
60
|
+
"original_payment_state_known": True,
|
|
61
|
+
"chargeback_state_known": False,
|
|
62
|
+
},
|
|
63
|
+
reentrancy_guard="SINGLE_CAPTURE_ENFORCED",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
print(result)
|
|
67
|
+
# {"status": "CAPTURED", "id": "a3f8c9..."}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Output (stdout):
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
MANITHY_PROOF::{"schema_id":"manithy.commit_boundary_event.v1","boundary_kind":"REFUND_COMMIT_T_MINUS_1","boundary_seq":1,"same_thread":true,"reentrancy_guard":"SINGLE_CAPTURE_ENFORCED","observed":{...},"availability":{...}}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Capture Parameters
|
|
77
|
+
|
|
78
|
+
| Parameter | Type | Purpose |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| **`boundary_kind`** | `str` | Which irreversible boundary this event refers to. Consumer-defined closed enum (e.g. `"REFUND_COMMIT_T_MINUS_1"`). |
|
|
81
|
+
| **`boundary_seq`** | `int` | Supports rare cases of multiple irreversible calls in one execution path. Small integer (0–255). |
|
|
82
|
+
| **`same_thread`** | `bool` | Runtime assertion that capture happened same-thread at t-1. |
|
|
83
|
+
| **`observed`** | `dict[str, str\|int\|bool]` | Runtime facts already resolved in the execution context. Values must be primitives only — no floats, no `None`, no nested structures. |
|
|
84
|
+
| **`availability`** | `dict[str, bool]` | Epistemic visibility at t-1. Each key declares whether a fact was knowable before the irreversible action. |
|
|
85
|
+
| **`reentrancy_guard`** | `str` | Capture enforcement mode. Consumer-defined (e.g. `"SINGLE_CAPTURE_ENFORCED"`). |
|
|
86
|
+
|
|
87
|
+
### The `observed` Block
|
|
88
|
+
|
|
89
|
+
All fields in `observed` must already be resolved in the execution context at t-1.
|
|
90
|
+
|
|
91
|
+
**Allowed:** `str`, `int`, `bool`.
|
|
92
|
+
**Forbidden:** `float`, `None`, nested `dict`/`list`, any value fetched or inferred after execution.
|
|
93
|
+
|
|
94
|
+
### The `availability` Block
|
|
95
|
+
|
|
96
|
+
`availability` is not data. It is a **declaration of epistemic visibility** at t-1.
|
|
97
|
+
|
|
98
|
+
Each key answers one question:
|
|
99
|
+
> "At the exact moment before the irreversible action, was this fact already knowable inside the execution context — yes or no?"
|
|
100
|
+
|
|
101
|
+
| `_known` value | Meaning | Effect |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `True` | Fact was knowable at t-1 | May appear in `observed` |
|
|
104
|
+
| `False` | Fact was NOT knowable at t-1 | Must NOT appear in `observed` |
|
|
105
|
+
|
|
106
|
+
If the fact was not knowable, Manithy records **ignorance**, not a value.
|
|
107
|
+
That ignorance is structural and permanent.
|
|
108
|
+
|
|
109
|
+
### Forbidden Fields
|
|
110
|
+
|
|
111
|
+
The following fields must **never** appear in a CommitBoundaryEvent:
|
|
112
|
+
|
|
113
|
+
| Field | Why Forbidden |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `producer_invocation_id` | High joinability risk. Single-capture is enforced via guard state, not IDs. |
|
|
116
|
+
| `callsite_id` | High joinability risk. |
|
|
117
|
+
| `producer_build_id` | Belongs in PackInit / EvidencePack provenance, not J01. |
|
|
118
|
+
|
|
119
|
+
## Custom Buffer
|
|
120
|
+
|
|
121
|
+
Route proofs to a file, queue, or any destination by subclassing `CaptureBuffer`:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import json
|
|
125
|
+
from manithy import ManithySDK
|
|
126
|
+
from manithy.interfaces.buffer import CaptureBuffer
|
|
127
|
+
|
|
128
|
+
class FileBuffer(CaptureBuffer):
|
|
129
|
+
def __init__(self, path: str):
|
|
130
|
+
self._file = open(path, "a", encoding="utf-8")
|
|
131
|
+
|
|
132
|
+
def emit(self, envelope: dict) -> None:
|
|
133
|
+
self._file.write(json.dumps(envelope, separators=(",", ":")) + "\n")
|
|
134
|
+
self._file.flush()
|
|
135
|
+
|
|
136
|
+
sdk = ManithySDK(buffer=FileBuffer("audit.log"))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Configuration
|
|
140
|
+
|
|
141
|
+
### Kill-Switch
|
|
142
|
+
|
|
143
|
+
Disable all capture at runtime without code changes:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
export MANITHY_ENABLED=false # Linux/macOS
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```powershell
|
|
150
|
+
$env:MANITHY_ENABLED = "false" # Windows PowerShell
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
When disabled, `capture()` returns `{"status": "SKIPPED"}` immediately.
|
|
154
|
+
|
|
155
|
+
### Debug Mode
|
|
156
|
+
|
|
157
|
+
Log internal SDK errors to stderr (useful during development):
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
export MANITHY_DEBUG=true
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## How It Works
|
|
164
|
+
|
|
165
|
+
1. **Kill-switch check** — reads `MANITHY_ENABLED`. If `"false"`, returns `SKIPPED`.
|
|
166
|
+
2. **Validation** — enforces type constraints on all fields; rejects forbidden fields, floats in `observed`, non-bool in `availability`, and unknown facts that leak into `observed`.
|
|
167
|
+
3. **Event assembly** — builds a J01 `CommitBoundaryEvent` with schema `manithy.commit_boundary_event.v1`.
|
|
168
|
+
4. **Hashing** — canonicalizes the event (sorted keys, no whitespace, floats like `100.0` → `100`) and computes SHA-256 → 64-char hex `commit_id`.
|
|
169
|
+
5. **Emit** — writes the event to the configured buffer (default: stdout with `MANITHY_PROOF::` prefix).
|
|
170
|
+
|
|
171
|
+
If any step fails, the error is swallowed and `{"status": "ERROR", "error": "Internal SDK Error"}` is returned. The host application is **never** affected.
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Create a virtual environment
|
|
177
|
+
python -m venv .venv
|
|
178
|
+
|
|
179
|
+
# Activate it
|
|
180
|
+
source .venv/bin/activate # Linux/macOS
|
|
181
|
+
.venv\Scripts\Activate.ps1 # Windows PowerShell
|
|
182
|
+
|
|
183
|
+
# Install in editable mode with dev dependencies
|
|
184
|
+
pip install -e ".[dev]"
|
|
185
|
+
|
|
186
|
+
# Run tests
|
|
187
|
+
pytest tests/ -v
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Project Structure
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
src/manithy/
|
|
194
|
+
├── __init__.py # Public API: exposes ManithySDK
|
|
195
|
+
├── sdk.py # Main entry point (capture pipeline + fail-closed)
|
|
196
|
+
├── config.py # Environment variable loader (kill-switch + debug)
|
|
197
|
+
├── core/
|
|
198
|
+
│ ├── canonical.py # Deterministic JSON canonicalization
|
|
199
|
+
│ ├── hasher.py # SHA-256 commit-ID generation
|
|
200
|
+
│ └── envelope.py # J01 CommitBoundaryEvent assembly + validation
|
|
201
|
+
└── interfaces/
|
|
202
|
+
└── buffer.py # Abstract CaptureBuffer + StdoutBuffer
|
|
203
|
+
tests/
|
|
204
|
+
├── vectors.json # Golden test vectors (canonical + hash)
|
|
205
|
+
├── test_core.py # Core module tests (canonical, hasher, J01 event)
|
|
206
|
+
└── test_sdk.py # SDK integration tests (capture, kill-switch, fail-closed)
|
|
207
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "manithy-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Manithy SDK — Authority-Grade Audit Capture (Zero Dependencies)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "Proprietary" }
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
|
|
13
|
+
# ─────────────────────────────────────────────────────────────────────
|
|
14
|
+
# CRITICAL CONSTRAINT: Zero External Dependencies
|
|
15
|
+
# ─────────────────────────────────────────────────────────────────────
|
|
16
|
+
# The Manithy SDK must NEVER add third-party runtime dependencies.
|
|
17
|
+
# Every capability (hashing, JSON handling, I/O) must rely exclusively
|
|
18
|
+
# on the Python standard library. This guarantees:
|
|
19
|
+
# 1. No supply-chain attack surface.
|
|
20
|
+
# 2. No version-conflict issues in host applications.
|
|
21
|
+
# 3. Deterministic, reproducible builds.
|
|
22
|
+
# ─────────────────────────────────────────────────────────────────────
|
|
23
|
+
dependencies = []
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"pytest>=7.0",
|
|
28
|
+
"pytest-cov>=4.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manithy SDK — Authority-Grade Audit Capture
|
|
3
|
+
============================================
|
|
4
|
+
|
|
5
|
+
Public API surface:
|
|
6
|
+
from manithy import ManithySDK
|
|
7
|
+
|
|
8
|
+
Constraints:
|
|
9
|
+
- Zero Network I/O → The SDK never opens sockets or makes HTTP calls.
|
|
10
|
+
- Determinism → Identical inputs always yield identical outputs.
|
|
11
|
+
- Fail-Closed → Any internal error is silently swallowed; the host
|
|
12
|
+
application must never crash because of the SDK.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from manithy.sdk import ManithySDK # noqa: F401
|
|
16
|
+
|
|
17
|
+
__all__ = ["ManithySDK"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def is_enabled() -> bool:
|
|
7
|
+
"""Return whether the Manithy SDK is enabled.
|
|
8
|
+
|
|
9
|
+
Reads the ``MANITHY_ENABLED`` environment variable via
|
|
10
|
+
``os.environ``.
|
|
11
|
+
|
|
12
|
+
* If the variable is **absent** → return ``True`` (enabled by
|
|
13
|
+
default).
|
|
14
|
+
* If the variable is set to ``"false"`` (case-insensitive) →
|
|
15
|
+
return ``False``.
|
|
16
|
+
* Any other value → return ``True``.
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
bool
|
|
21
|
+
``True`` if capture should proceed, ``False`` otherwise.
|
|
22
|
+
"""
|
|
23
|
+
raw = os.environ.get("MANITHY_ENABLED", "true")
|
|
24
|
+
return raw.strip().lower() != "false"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def is_debug() -> bool:
|
|
28
|
+
"""Return whether debug mode is enabled.
|
|
29
|
+
|
|
30
|
+
Reads the ``MANITHY_DEBUG`` environment variable.
|
|
31
|
+
|
|
32
|
+
* If the variable is set to ``"true"`` (case-insensitive) →
|
|
33
|
+
return ``True``.
|
|
34
|
+
* Any other value (or absence) → return ``False``.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
bool
|
|
39
|
+
``True`` if debug logging should be active, ``False`` otherwise.
|
|
40
|
+
"""
|
|
41
|
+
raw = os.environ.get("MANITHY_DEBUG", "false")
|
|
42
|
+
return raw.strip().lower() == "true"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
manithy.core
|
|
3
|
+
~~~~~~~~~~~~
|
|
4
|
+
|
|
5
|
+
Core deterministic building blocks. Every function in this package is
|
|
6
|
+
pure (no side-effects, no I/O) and must be fully deterministic:
|
|
7
|
+
• canonical - JSON canonicalization
|
|
8
|
+
• hasher - SHA-256 commit-ID generation
|
|
9
|
+
• envelope - J01 CommitBoundaryEvent assembly
|
|
10
|
+
"""
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
manithy.core.canonical
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
|
|
5
|
+
Deterministic JSON Canonicalization.
|
|
6
|
+
|
|
7
|
+
This module converts an arbitrary Python data structure (dicts, lists,
|
|
8
|
+
primitives) into a **canonical byte string** that is identical across
|
|
9
|
+
Python and Node.js for the same logical input.
|
|
10
|
+
|
|
11
|
+
Owner: [Dev A]
|
|
12
|
+
|
|
13
|
+
Cross-Language Determinism Rules
|
|
14
|
+
--------------------------------
|
|
15
|
+
1. **Sort object keys** — recursively, at every nesting level, using
|
|
16
|
+
standard lexicographic (Unicode code-point) order.
|
|
17
|
+
|
|
18
|
+
2. **Strip insignificant whitespace** — the output must contain no
|
|
19
|
+
spaces or newlines between tokens (equivalent to
|
|
20
|
+
``json.dumps(separators=(',', ':'))``).
|
|
21
|
+
|
|
22
|
+
3. **Normalize numbers** —
|
|
23
|
+
* Floats that are mathematically integers (e.g. ``100.0``, ``3.0``)
|
|
24
|
+
**must** be emitted as integers (``100``, ``3``) so the byte output
|
|
25
|
+
matches what ``JSON.stringify`` produces in Node.js.
|
|
26
|
+
* Non-integer floats (e.g. ``3.14``) should be serialized with full
|
|
27
|
+
precision, avoiding trailing zeros.
|
|
28
|
+
|
|
29
|
+
4. **Encoding** — the final output must be UTF-8 encoded ``bytes``.
|
|
30
|
+
|
|
31
|
+
5. **No external libraries** — only ``json`` from the standard library.
|
|
32
|
+
|
|
33
|
+
Example
|
|
34
|
+
-------
|
|
35
|
+
>>> to_canonical_bytes({"b": 1, "a": [3.0, {"d": 4, "c": 5}]})
|
|
36
|
+
b'{"a":[3,{"c":5,"d":4}],"b":1}'
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import json
|
|
42
|
+
from typing import Any
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _normalize(data: Any, _seen: set) -> Any:
|
|
46
|
+
"""Recursively normalize *data* for deterministic serialization.
|
|
47
|
+
|
|
48
|
+
- Sorts dict keys lexicographically.
|
|
49
|
+
- Converts whole-number floats to ints (the "float trap").
|
|
50
|
+
- Detects circular references via object-id tracking.
|
|
51
|
+
"""
|
|
52
|
+
obj_id = id(data)
|
|
53
|
+
if isinstance(data, (dict, list)):
|
|
54
|
+
if obj_id in _seen:
|
|
55
|
+
raise ValueError("Circular reference detected")
|
|
56
|
+
_seen.add(obj_id)
|
|
57
|
+
|
|
58
|
+
if isinstance(data, dict):
|
|
59
|
+
result = {k: _normalize(v, _seen) for k, v in sorted(data.items())}
|
|
60
|
+
_seen.discard(obj_id)
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
if isinstance(data, list):
|
|
64
|
+
result = [_normalize(item, _seen) for item in data]
|
|
65
|
+
_seen.discard(obj_id)
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
if isinstance(data, float):
|
|
69
|
+
if data.is_integer():
|
|
70
|
+
return int(data)
|
|
71
|
+
return data
|
|
72
|
+
|
|
73
|
+
return data
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def to_canonical_bytes(data: Any) -> bytes:
|
|
77
|
+
"""Convert *data* to deterministic, canonical UTF-8 JSON bytes.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
data : Any
|
|
82
|
+
Arbitrary JSON-serializable Python object (dict, list, str,
|
|
83
|
+
int, float, bool, None).
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
bytes
|
|
88
|
+
UTF-8 encoded canonical JSON with sorted keys, no whitespace,
|
|
89
|
+
and floats-that-are-integers coerced to ints.
|
|
90
|
+
|
|
91
|
+
Raises
|
|
92
|
+
------
|
|
93
|
+
TypeError
|
|
94
|
+
If *data* contains types that are not JSON-serializable.
|
|
95
|
+
ValueError
|
|
96
|
+
If *data* contains circular references.
|
|
97
|
+
"""
|
|
98
|
+
normalized = _normalize(data, set())
|
|
99
|
+
return json.dumps(normalized, separators=(",", ":"), sort_keys=False).encode("utf-8")
|