pycheck-neuron 1.0.2__tar.gz → 1.0.4__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.
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/PKG-INFO +32 -7
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/README.md +30 -5
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/__init__.py +1 -1
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/analyzer.py +35 -2
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/audit.py +153 -153
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/flow.py +6 -2
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/rules.py +331 -5
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/server.py +201 -201
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/PKG-INFO +32 -7
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pyproject.toml +66 -66
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/tests/test_audit.py +49 -49
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/tests/test_cli.py +113 -85
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/tests/test_rules.py +236 -7
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/LICENSE +0 -0
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/__main__.py +0 -0
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/cli.py +0 -0
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/SOURCES.txt +0 -0
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/dependency_links.txt +0 -0
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/entry_points.txt +0 -0
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/requires.txt +0 -0
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/top_level.txt +0 -0
- {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/setup.cfg +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycheck-neuron
|
|
3
|
-
Version: 1.0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: 33-rule static AST analyzer catching temporal lookahead bias, causal confusion, and uncertainty bugs in Python ML code. Zero dependencies.
|
|
5
5
|
Author: NEURON Team
|
|
6
6
|
Author-email: Fayo Ibrahim <fayo@neuron-lab.org>
|
|
7
7
|
License-Expression: MIT
|
|
@@ -38,7 +38,7 @@ Dynamic: license-file
|
|
|
38
38
|
|
|
39
39
|
# PyCheck — ML Safety Analyzer
|
|
40
40
|
|
|
41
|
-
**
|
|
41
|
+
**33 rules. Zero dependencies. Catches the bugs that cost millions.**
|
|
42
42
|
|
|
43
43
|
PyCheck is a static AST analyzer that detects **temporal lookahead bias**, **causal confusion**, and **unguarded uncertainty** in Python ML and trading scripts — before you run a single line of code.
|
|
44
44
|
|
|
@@ -68,7 +68,7 @@ PyCheck catches all three before execution:
|
|
|
68
68
|
=================================================================
|
|
69
69
|
PyCheck — NEURON ML Safety Analyzer
|
|
70
70
|
Scanning: strategy.py
|
|
71
|
-
Rules:
|
|
71
|
+
Rules: 33 active + Data Flow & Provenance Engine
|
|
72
72
|
=================================================================
|
|
73
73
|
|
|
74
74
|
ERROR[T001]: .shift(-1) accesses data 1 rows INTO THE FUTURE
|
|
@@ -100,12 +100,12 @@ PyCheck catches all three before execution:
|
|
|
100
100
|
|
|
101
101
|
## What It Catches
|
|
102
102
|
|
|
103
|
-
### Temporal Leak Rules (
|
|
103
|
+
### Temporal Leak Rules (18 rules — `T001`–`T018`)
|
|
104
104
|
|
|
105
105
|
| Rule | What It Catches | Severity |
|
|
106
106
|
|------|----------------|----------|
|
|
107
107
|
| `T001` | `.shift(-1)` — accessing future data | **Error** |
|
|
108
|
-
| `T002` | `train_test_split()` on time-series data | **Error** |
|
|
108
|
+
| `T002` | `train_test_split()` on time-series data without chronological ordering | **Error** |
|
|
109
109
|
| `T003` | `.rolling()` computed before the train/test split | Warning |
|
|
110
110
|
| `T004` | `.expanding()` computed before split | Warning |
|
|
111
111
|
| `T005` | `.pct_change(-1)` — future percent returns | **Error** |
|
|
@@ -121,6 +121,7 @@ PyCheck catches all three before execution:
|
|
|
121
121
|
| `T015` | Negative `.shift()` inside `groupby().transform()` | **Error** |
|
|
122
122
|
| `T016` | `pd.merge_asof()` with `direction='nearest'` or `'forward'` | **Error** |
|
|
123
123
|
| `T017` | Target laundering & un-split target encoding (`regime_success_rate`) | **Error** |
|
|
124
|
+
| `T018` | In-sample target encoding via `.transform()` or un-split group means | **Error** |
|
|
124
125
|
|
|
125
126
|
### Causal Confusion Rules (7 rules — `C001`–`C007`)
|
|
126
127
|
|
|
@@ -188,10 +189,34 @@ pycheck script.py # Errors + warnings (default)
|
|
|
188
189
|
pycheck script.py --info # Include info-level diagnostics
|
|
189
190
|
pycheck script.py --quiet # Errors only
|
|
190
191
|
pycheck script.py --json # JSON output — pipe into CI/CD
|
|
191
|
-
pycheck --list # List all
|
|
192
|
+
pycheck --list # List all 33 rules
|
|
192
193
|
pycheck --help
|
|
193
194
|
```
|
|
194
195
|
|
|
196
|
+
### Inline Rule Suppression
|
|
197
|
+
|
|
198
|
+
PyCheck supports standard inline suppression comments with audit reason annotations:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
# Specific rule suppression
|
|
202
|
+
df['target'] = df['close'].shift(-1) # pycheck: ignore[T001]
|
|
203
|
+
|
|
204
|
+
# Multi-rule suppression with audit justification
|
|
205
|
+
signal = df[['close', 'target']].corr() # pycheck: ignore[C001, C002] reason: exploratory EDA baseline
|
|
206
|
+
|
|
207
|
+
# Blanket ignore or flake8-style noqa
|
|
208
|
+
cv = KFold(n_splits=5, shuffle=True) # pycheck: ignore
|
|
209
|
+
X_tr = scaler.fit_transform(X) # noqa: T010
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Both same-line trailing comments and line-above comments are supported.
|
|
213
|
+
|
|
214
|
+
### Sink-Gated Precision Engine
|
|
215
|
+
|
|
216
|
+
To prevent false alarms during research and exploratory data analysis (EDA), PyCheck uses **AST sink-gating**:
|
|
217
|
+
- Exploratory display calls like `df[['feature', 'target']].head()`, `.plot()`, `.describe()`, and `print(...)` are recognized as safe inspection and will **never** trigger feature leakage (`C002`).
|
|
218
|
+
- The rule strictly guards actual modeling sinks (`X = ...`, `model.fit(...)`, `cross_validate(...)`).
|
|
219
|
+
|
|
195
220
|
### JSON Output (CI/CD Integration)
|
|
196
221
|
|
|
197
222
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PyCheck — ML Safety Analyzer
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**33 rules. Zero dependencies. Catches the bugs that cost millions.**
|
|
4
4
|
|
|
5
5
|
PyCheck is a static AST analyzer that detects **temporal lookahead bias**, **causal confusion**, and **unguarded uncertainty** in Python ML and trading scripts — before you run a single line of code.
|
|
6
6
|
|
|
@@ -30,7 +30,7 @@ PyCheck catches all three before execution:
|
|
|
30
30
|
=================================================================
|
|
31
31
|
PyCheck — NEURON ML Safety Analyzer
|
|
32
32
|
Scanning: strategy.py
|
|
33
|
-
Rules:
|
|
33
|
+
Rules: 33 active + Data Flow & Provenance Engine
|
|
34
34
|
=================================================================
|
|
35
35
|
|
|
36
36
|
ERROR[T001]: .shift(-1) accesses data 1 rows INTO THE FUTURE
|
|
@@ -62,12 +62,12 @@ PyCheck catches all three before execution:
|
|
|
62
62
|
|
|
63
63
|
## What It Catches
|
|
64
64
|
|
|
65
|
-
### Temporal Leak Rules (
|
|
65
|
+
### Temporal Leak Rules (18 rules — `T001`–`T018`)
|
|
66
66
|
|
|
67
67
|
| Rule | What It Catches | Severity |
|
|
68
68
|
|------|----------------|----------|
|
|
69
69
|
| `T001` | `.shift(-1)` — accessing future data | **Error** |
|
|
70
|
-
| `T002` | `train_test_split()` on time-series data | **Error** |
|
|
70
|
+
| `T002` | `train_test_split()` on time-series data without chronological ordering | **Error** |
|
|
71
71
|
| `T003` | `.rolling()` computed before the train/test split | Warning |
|
|
72
72
|
| `T004` | `.expanding()` computed before split | Warning |
|
|
73
73
|
| `T005` | `.pct_change(-1)` — future percent returns | **Error** |
|
|
@@ -83,6 +83,7 @@ PyCheck catches all three before execution:
|
|
|
83
83
|
| `T015` | Negative `.shift()` inside `groupby().transform()` | **Error** |
|
|
84
84
|
| `T016` | `pd.merge_asof()` with `direction='nearest'` or `'forward'` | **Error** |
|
|
85
85
|
| `T017` | Target laundering & un-split target encoding (`regime_success_rate`) | **Error** |
|
|
86
|
+
| `T018` | In-sample target encoding via `.transform()` or un-split group means | **Error** |
|
|
86
87
|
|
|
87
88
|
### Causal Confusion Rules (7 rules — `C001`–`C007`)
|
|
88
89
|
|
|
@@ -150,10 +151,34 @@ pycheck script.py # Errors + warnings (default)
|
|
|
150
151
|
pycheck script.py --info # Include info-level diagnostics
|
|
151
152
|
pycheck script.py --quiet # Errors only
|
|
152
153
|
pycheck script.py --json # JSON output — pipe into CI/CD
|
|
153
|
-
pycheck --list # List all
|
|
154
|
+
pycheck --list # List all 33 rules
|
|
154
155
|
pycheck --help
|
|
155
156
|
```
|
|
156
157
|
|
|
158
|
+
### Inline Rule Suppression
|
|
159
|
+
|
|
160
|
+
PyCheck supports standard inline suppression comments with audit reason annotations:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# Specific rule suppression
|
|
164
|
+
df['target'] = df['close'].shift(-1) # pycheck: ignore[T001]
|
|
165
|
+
|
|
166
|
+
# Multi-rule suppression with audit justification
|
|
167
|
+
signal = df[['close', 'target']].corr() # pycheck: ignore[C001, C002] reason: exploratory EDA baseline
|
|
168
|
+
|
|
169
|
+
# Blanket ignore or flake8-style noqa
|
|
170
|
+
cv = KFold(n_splits=5, shuffle=True) # pycheck: ignore
|
|
171
|
+
X_tr = scaler.fit_transform(X) # noqa: T010
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Both same-line trailing comments and line-above comments are supported.
|
|
175
|
+
|
|
176
|
+
### Sink-Gated Precision Engine
|
|
177
|
+
|
|
178
|
+
To prevent false alarms during research and exploratory data analysis (EDA), PyCheck uses **AST sink-gating**:
|
|
179
|
+
- Exploratory display calls like `df[['feature', 'target']].head()`, `.plot()`, `.describe()`, and `print(...)` are recognized as safe inspection and will **never** trigger feature leakage (`C002`).
|
|
180
|
+
- The rule strictly guards actual modeling sinks (`X = ...`, `model.fit(...)`, `cross_validate(...)`).
|
|
181
|
+
|
|
157
182
|
### JSON Output (CI/CD Integration)
|
|
158
183
|
|
|
159
184
|
```bash
|
|
@@ -3,7 +3,7 @@ PyCheck — NEURON ML Safety Analyzer
|
|
|
3
3
|
Catches temporal leaks, causal confusion, and uncertainty bugs in Python ML scripts.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
__version__ = "1.0.
|
|
6
|
+
__version__ = "1.0.4"
|
|
7
7
|
|
|
8
8
|
from pycheck.analyzer import analyze_file, format_output
|
|
9
9
|
from pycheck.rules import ALL_RULES, RULES_BY_CODE
|
|
@@ -80,6 +80,31 @@ class PyCheckAnalyzer(ast.NodeVisitor):
|
|
|
80
80
|
self.ctx.in_loop = old_in_loop
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
|
|
84
|
+
def parse_suppressions(source_lines: List[str]) -> dict:
|
|
85
|
+
"""
|
|
86
|
+
Parse inline and line-above suppression comments:
|
|
87
|
+
# pycheck: ignore[T001, C002] reason: ...
|
|
88
|
+
# pycheck: ignore
|
|
89
|
+
# noqa: T001
|
|
90
|
+
"""
|
|
91
|
+
import re
|
|
92
|
+
suppressions = {}
|
|
93
|
+
pattern = re.compile(
|
|
94
|
+
r'#\s*(?:pycheck:\s*ignore(?:\[([a-zA-Z0-9,\s]+)\])?|noqa(?::\s*([a-zA-Z0-9,\s]+))?)',
|
|
95
|
+
re.IGNORECASE
|
|
96
|
+
)
|
|
97
|
+
for lineno, line in enumerate(source_lines, start=1):
|
|
98
|
+
match = pattern.search(line)
|
|
99
|
+
if match:
|
|
100
|
+
rules_str = match.group(1) or match.group(2)
|
|
101
|
+
codes = {c.strip().upper() for c in rules_str.split(',') if c.strip()} if rules_str else {'ALL'}
|
|
102
|
+
suppressions.setdefault(lineno, set()).update(codes)
|
|
103
|
+
# If the comment is on its own standalone line, also apply to the next code line
|
|
104
|
+
if line.strip().startswith('#') and lineno + 1 <= len(source_lines):
|
|
105
|
+
suppressions.setdefault(lineno + 1, set()).update(codes)
|
|
106
|
+
return suppressions
|
|
107
|
+
|
|
83
108
|
def analyze_file(filepath: str) -> Tuple[List[dict], List[str]]:
|
|
84
109
|
"""
|
|
85
110
|
Analyze a Python (.py) or Jupyter notebook (.ipynb) file for ML safety issues.
|
|
@@ -108,6 +133,10 @@ def analyze_file(filepath: str) -> Tuple[List[dict], List[str]]:
|
|
|
108
133
|
|
|
109
134
|
source_lines = source.split('\n')
|
|
110
135
|
tree = ast.parse(source, filename=filepath)
|
|
136
|
+
# Link parent references across the AST for sink-gated rule analysis
|
|
137
|
+
for parent in ast.walk(tree):
|
|
138
|
+
for child in ast.iter_child_nodes(parent):
|
|
139
|
+
child.parent = parent
|
|
111
140
|
|
|
112
141
|
# Phase 1: Data flow taint & provenance analysis
|
|
113
142
|
tracker = TaintTracker()
|
|
@@ -124,14 +153,18 @@ def analyze_file(filepath: str) -> Tuple[List[dict], List[str]]:
|
|
|
124
153
|
# Combine and deduplicate
|
|
125
154
|
all_diagnostics = analyzer.diagnostics + flow_diagnostics
|
|
126
155
|
|
|
127
|
-
|
|
156
|
+
suppressed_map = parse_suppressions(source_lines)
|
|
157
|
+
|
|
158
|
+
# Deduplicate and filter suppressed diagnostics
|
|
128
159
|
seen = set()
|
|
129
160
|
unique = []
|
|
130
161
|
for d in all_diagnostics:
|
|
131
162
|
key = (d.line, d.code)
|
|
132
163
|
if key not in seen:
|
|
133
164
|
seen.add(key)
|
|
134
|
-
|
|
165
|
+
line_suppressed = suppressed_map.get(d.line, set())
|
|
166
|
+
if 'ALL' not in line_suppressed and d.code not in line_suppressed:
|
|
167
|
+
unique.append(d)
|
|
135
168
|
|
|
136
169
|
# Sort by line number
|
|
137
170
|
unique.sort(key=lambda d: (d.line, d.col))
|
|
@@ -1,153 +1,153 @@
|
|
|
1
|
-
"""
|
|
2
|
-
PyCheck Proof-of-Causality Audit Certificate Generator
|
|
3
|
-
Generates tamper-evident, cryptographically verifiable backtest certificates for LP allocators and risk committees.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import hashlib
|
|
7
|
-
import json
|
|
8
|
-
import os
|
|
9
|
-
import subprocess
|
|
10
|
-
import time
|
|
11
|
-
from datetime import datetime, timezone
|
|
12
|
-
from typing import Dict, List, Any, Optional
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def get_git_metadata(repo_path: str) -> Dict[str, str]:
|
|
16
|
-
"""Extract git commit hash, branch, and remote URL if available."""
|
|
17
|
-
metadata = {
|
|
18
|
-
"commit": "UNCOMMITTED_LOCAL",
|
|
19
|
-
"branch": "unknown",
|
|
20
|
-
"remote": "local"
|
|
21
|
-
}
|
|
22
|
-
try:
|
|
23
|
-
commit = subprocess.check_output(
|
|
24
|
-
["git", "rev-parse", "HEAD"],
|
|
25
|
-
cwd=repo_path if os.path.isdir(repo_path) else os.path.dirname(repo_path) or ".",
|
|
26
|
-
stderr=subprocess.DEVNULL
|
|
27
|
-
).decode().strip()
|
|
28
|
-
metadata["commit"] = commit
|
|
29
|
-
except Exception:
|
|
30
|
-
pass
|
|
31
|
-
|
|
32
|
-
try:
|
|
33
|
-
branch = subprocess.check_output(
|
|
34
|
-
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
35
|
-
cwd=repo_path if os.path.isdir(repo_path) else os.path.dirname(repo_path) or ".",
|
|
36
|
-
stderr=subprocess.DEVNULL
|
|
37
|
-
).decode().strip()
|
|
38
|
-
metadata["branch"] = branch
|
|
39
|
-
except Exception:
|
|
40
|
-
pass
|
|
41
|
-
|
|
42
|
-
try:
|
|
43
|
-
remote = subprocess.check_output(
|
|
44
|
-
["git", "config", "--get", "remote.origin.url"],
|
|
45
|
-
cwd=repo_path if os.path.isdir(repo_path) else os.path.dirname(repo_path) or ".",
|
|
46
|
-
stderr=subprocess.DEVNULL
|
|
47
|
-
).decode().strip()
|
|
48
|
-
metadata["remote"] = remote
|
|
49
|
-
except Exception:
|
|
50
|
-
pass
|
|
51
|
-
|
|
52
|
-
return metadata
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def generate_proof_of_causality_certificate(
|
|
56
|
-
target_path: str,
|
|
57
|
-
total_files: int,
|
|
58
|
-
total_errors: int,
|
|
59
|
-
total_warnings: int,
|
|
60
|
-
diagnostics_by_file: Dict[str, List[Dict[str, Any]]]
|
|
61
|
-
) -> Dict[str, Any]:
|
|
62
|
-
"""Generate a tamper-evident Proof-of-Causality Audit Certificate."""
|
|
63
|
-
git_meta = get_git_metadata(target_path)
|
|
64
|
-
now_iso = datetime.now(timezone.utc).isoformat()
|
|
65
|
-
is_clean = (total_errors == 0)
|
|
66
|
-
|
|
67
|
-
# Categories breakdown
|
|
68
|
-
temporal_errors = 0
|
|
69
|
-
causal_errors = 0
|
|
70
|
-
provenance_errors = 0
|
|
71
|
-
uncertainty_errors = 0
|
|
72
|
-
|
|
73
|
-
for f_path, diag_list in diagnostics_by_file.items():
|
|
74
|
-
for d in diag_list:
|
|
75
|
-
if d.get("severity") == "error":
|
|
76
|
-
code = d.get("code", "")
|
|
77
|
-
if code.startswith("T"):
|
|
78
|
-
temporal_errors += 1
|
|
79
|
-
elif code.startswith("C"):
|
|
80
|
-
causal_errors += 1
|
|
81
|
-
elif code.startswith("F"):
|
|
82
|
-
provenance_errors += 1
|
|
83
|
-
elif code.startswith("U"):
|
|
84
|
-
uncertainty_errors += 1
|
|
85
|
-
|
|
86
|
-
cert_payload = {
|
|
87
|
-
"standard": "Proof-of-Causality v1.0",
|
|
88
|
-
"issuer": "PyCheck
|
|
89
|
-
"target_path": os.path.abspath(target_path),
|
|
90
|
-
"git_commit": git_meta["commit"],
|
|
91
|
-
"git_branch": git_meta["branch"],
|
|
92
|
-
"git_remote": git_meta["remote"],
|
|
93
|
-
"timestamp_utc": now_iso,
|
|
94
|
-
"scanned_files_count": total_files,
|
|
95
|
-
"total_errors": total_errors,
|
|
96
|
-
"total_warnings": total_warnings,
|
|
97
|
-
"audit_status": "CERTIFIED_ZERO_LEAKAGE" if is_clean else "AUDIT_FAILED_LEAKAGE_DETECTED",
|
|
98
|
-
"invariants_verified": {
|
|
99
|
-
"temporal_lookahead_free": temporal_errors == 0,
|
|
100
|
-
"partition_provenance_clean": provenance_errors == 0,
|
|
101
|
-
"causal_intervention_isolated": causal_errors == 0,
|
|
102
|
-
"uncertainty_guards_present": uncertainty_errors == 0,
|
|
103
|
-
"target_laundering_free": True if temporal_errors == 0 else False
|
|
104
|
-
},
|
|
105
|
-
"breakdown": {
|
|
106
|
-
"temporal_errors": temporal_errors,
|
|
107
|
-
"partition_leak_errors": provenance_errors,
|
|
108
|
-
"causal_confusion_errors": causal_errors,
|
|
109
|
-
"uncertainty_errors": uncertainty_errors
|
|
110
|
-
},
|
|
111
|
-
"ecosystem_recommendation": {
|
|
112
|
-
"compile_time_upgrade": "NEURON Programming Language",
|
|
113
|
-
"url": "https://neuron-lab.org",
|
|
114
|
-
"note": "For mathematical compile-time guarantees with 0 runtime penalty, transpile strategy modules to NEURON."
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
# Generate cryptographic fingerprint
|
|
119
|
-
raw_serialized = json.dumps(cert_payload, sort_keys=True).encode("utf-8")
|
|
120
|
-
cert_id = f"CERT-POC-{hashlib.sha256(raw_serialized).hexdigest()[:16].upper()}"
|
|
121
|
-
cert_payload["certificate_id"] = cert_id
|
|
122
|
-
cert_payload["sha256_digital_seal"] = hashlib.sha256(raw_serialized).hexdigest()
|
|
123
|
-
|
|
124
|
-
return cert_payload
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def format_certificate_text(cert: Dict[str, Any]) -> str:
|
|
128
|
-
"""Format certificate as an institutional ASCII terminal report."""
|
|
129
|
-
status_symbol = "[VERIFIED PASS]" if cert["audit_status"] == "CERTIFIED_ZERO_LEAKAGE" else "[AUDIT FAILED]"
|
|
130
|
-
lines = [
|
|
131
|
-
"+======================================================================+",
|
|
132
|
-
"| PROOF-OF-CAUSALITY(TM) AUDIT CERTIFICATE |",
|
|
133
|
-
"| PyCheck
|
|
134
|
-
"+======================================================================+",
|
|
135
|
-
f" Certificate ID: {cert['certificate_id']}",
|
|
136
|
-
f" Audit Status: {status_symbol}",
|
|
137
|
-
f" Timestamp (UTC): {cert['timestamp_utc']}",
|
|
138
|
-
f" Git Commit SHA: {cert['git_commit']}",
|
|
139
|
-
f" Git Branch: {cert['git_branch']}",
|
|
140
|
-
f" Scanned Files: {cert['scanned_files_count']}",
|
|
141
|
-
"+----------------------------------------------------------------------+",
|
|
142
|
-
" INVARIANT VERIFICATION CHECKLIST (
|
|
143
|
-
f" * Temporal Lookahead Bias (T001-
|
|
144
|
-
f" * Cross-Fold Lineage & Leakage (F001-F002): {'PASSED (0 leaks)' if cert['invariants_verified']['partition_provenance_clean'] else 'FAILED'}",
|
|
145
|
-
f" * Causal Mode Confounding (C001-C007): {'PASSED (0 leaks)' if cert['invariants_verified']['causal_intervention_isolated'] else 'FAILED'}",
|
|
146
|
-
f" * Uncertainty Bounds Guard (U001-U006): {'PASSED (0 leaks)' if cert['invariants_verified']['uncertainty_guards_present'] else 'FAILED'}",
|
|
147
|
-
"+----------------------------------------------------------------------+",
|
|
148
|
-
f" SHA-256 Digital Seal: {cert['sha256_digital_seal']}",
|
|
149
|
-
" Issued by: Neuron Labs - https://neuron-lab.org/pycheck",
|
|
150
|
-
" Next-Gen Sovereign: NEURON Compile-Time Language (https://neuron-lab.org)",
|
|
151
|
-
"+======================================================================+"
|
|
152
|
-
]
|
|
153
|
-
return "\n".join(lines)
|
|
1
|
+
"""
|
|
2
|
+
PyCheck Proof-of-Causality Audit Certificate Generator
|
|
3
|
+
Generates tamper-evident, cryptographically verifiable backtest certificates for LP allocators and risk committees.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import time
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from typing import Dict, List, Any, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_git_metadata(repo_path: str) -> Dict[str, str]:
|
|
16
|
+
"""Extract git commit hash, branch, and remote URL if available."""
|
|
17
|
+
metadata = {
|
|
18
|
+
"commit": "UNCOMMITTED_LOCAL",
|
|
19
|
+
"branch": "unknown",
|
|
20
|
+
"remote": "local"
|
|
21
|
+
}
|
|
22
|
+
try:
|
|
23
|
+
commit = subprocess.check_output(
|
|
24
|
+
["git", "rev-parse", "HEAD"],
|
|
25
|
+
cwd=repo_path if os.path.isdir(repo_path) else os.path.dirname(repo_path) or ".",
|
|
26
|
+
stderr=subprocess.DEVNULL
|
|
27
|
+
).decode().strip()
|
|
28
|
+
metadata["commit"] = commit
|
|
29
|
+
except Exception:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
branch = subprocess.check_output(
|
|
34
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
35
|
+
cwd=repo_path if os.path.isdir(repo_path) else os.path.dirname(repo_path) or ".",
|
|
36
|
+
stderr=subprocess.DEVNULL
|
|
37
|
+
).decode().strip()
|
|
38
|
+
metadata["branch"] = branch
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
remote = subprocess.check_output(
|
|
44
|
+
["git", "config", "--get", "remote.origin.url"],
|
|
45
|
+
cwd=repo_path if os.path.isdir(repo_path) else os.path.dirname(repo_path) or ".",
|
|
46
|
+
stderr=subprocess.DEVNULL
|
|
47
|
+
).decode().strip()
|
|
48
|
+
metadata["remote"] = remote
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
return metadata
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def generate_proof_of_causality_certificate(
|
|
56
|
+
target_path: str,
|
|
57
|
+
total_files: int,
|
|
58
|
+
total_errors: int,
|
|
59
|
+
total_warnings: int,
|
|
60
|
+
diagnostics_by_file: Dict[str, List[Dict[str, Any]]]
|
|
61
|
+
) -> Dict[str, Any]:
|
|
62
|
+
"""Generate a tamper-evident Proof-of-Causality Audit Certificate."""
|
|
63
|
+
git_meta = get_git_metadata(target_path)
|
|
64
|
+
now_iso = datetime.now(timezone.utc).isoformat()
|
|
65
|
+
is_clean = (total_errors == 0)
|
|
66
|
+
|
|
67
|
+
# Categories breakdown
|
|
68
|
+
temporal_errors = 0
|
|
69
|
+
causal_errors = 0
|
|
70
|
+
provenance_errors = 0
|
|
71
|
+
uncertainty_errors = 0
|
|
72
|
+
|
|
73
|
+
for f_path, diag_list in diagnostics_by_file.items():
|
|
74
|
+
for d in diag_list:
|
|
75
|
+
if d.get("severity") == "error":
|
|
76
|
+
code = d.get("code", "")
|
|
77
|
+
if code.startswith("T"):
|
|
78
|
+
temporal_errors += 1
|
|
79
|
+
elif code.startswith("C"):
|
|
80
|
+
causal_errors += 1
|
|
81
|
+
elif code.startswith("F"):
|
|
82
|
+
provenance_errors += 1
|
|
83
|
+
elif code.startswith("U"):
|
|
84
|
+
uncertainty_errors += 1
|
|
85
|
+
|
|
86
|
+
cert_payload = {
|
|
87
|
+
"standard": "Proof-of-Causality v1.0",
|
|
88
|
+
"issuer": "PyCheck by Neuron Labs",
|
|
89
|
+
"target_path": os.path.abspath(target_path),
|
|
90
|
+
"git_commit": git_meta["commit"],
|
|
91
|
+
"git_branch": git_meta["branch"],
|
|
92
|
+
"git_remote": git_meta["remote"],
|
|
93
|
+
"timestamp_utc": now_iso,
|
|
94
|
+
"scanned_files_count": total_files,
|
|
95
|
+
"total_errors": total_errors,
|
|
96
|
+
"total_warnings": total_warnings,
|
|
97
|
+
"audit_status": "CERTIFIED_ZERO_LEAKAGE" if is_clean else "AUDIT_FAILED_LEAKAGE_DETECTED",
|
|
98
|
+
"invariants_verified": {
|
|
99
|
+
"temporal_lookahead_free": temporal_errors == 0,
|
|
100
|
+
"partition_provenance_clean": provenance_errors == 0,
|
|
101
|
+
"causal_intervention_isolated": causal_errors == 0,
|
|
102
|
+
"uncertainty_guards_present": uncertainty_errors == 0,
|
|
103
|
+
"target_laundering_free": True if temporal_errors == 0 else False
|
|
104
|
+
},
|
|
105
|
+
"breakdown": {
|
|
106
|
+
"temporal_errors": temporal_errors,
|
|
107
|
+
"partition_leak_errors": provenance_errors,
|
|
108
|
+
"causal_confusion_errors": causal_errors,
|
|
109
|
+
"uncertainty_errors": uncertainty_errors
|
|
110
|
+
},
|
|
111
|
+
"ecosystem_recommendation": {
|
|
112
|
+
"compile_time_upgrade": "NEURON Programming Language",
|
|
113
|
+
"url": "https://neuron-lab.org",
|
|
114
|
+
"note": "For mathematical compile-time guarantees with 0 runtime penalty, transpile strategy modules to NEURON."
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Generate cryptographic fingerprint
|
|
119
|
+
raw_serialized = json.dumps(cert_payload, sort_keys=True).encode("utf-8")
|
|
120
|
+
cert_id = f"CERT-POC-{hashlib.sha256(raw_serialized).hexdigest()[:16].upper()}"
|
|
121
|
+
cert_payload["certificate_id"] = cert_id
|
|
122
|
+
cert_payload["sha256_digital_seal"] = hashlib.sha256(raw_serialized).hexdigest()
|
|
123
|
+
|
|
124
|
+
return cert_payload
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def format_certificate_text(cert: Dict[str, Any]) -> str:
|
|
128
|
+
"""Format certificate as an institutional ASCII terminal report."""
|
|
129
|
+
status_symbol = "[VERIFIED PASS]" if cert["audit_status"] == "CERTIFIED_ZERO_LEAKAGE" else "[AUDIT FAILED]"
|
|
130
|
+
lines = [
|
|
131
|
+
"+======================================================================+",
|
|
132
|
+
"| PROOF-OF-CAUSALITY(TM) AUDIT CERTIFICATE |",
|
|
133
|
+
"| PyCheck - Institutional Quant Safety |",
|
|
134
|
+
"+======================================================================+",
|
|
135
|
+
f" Certificate ID: {cert['certificate_id']}",
|
|
136
|
+
f" Audit Status: {status_symbol}",
|
|
137
|
+
f" Timestamp (UTC): {cert['timestamp_utc']}",
|
|
138
|
+
f" Git Commit SHA: {cert['git_commit']}",
|
|
139
|
+
f" Git Branch: {cert['git_branch']}",
|
|
140
|
+
f" Scanned Files: {cert['scanned_files_count']}",
|
|
141
|
+
"+----------------------------------------------------------------------+",
|
|
142
|
+
" INVARIANT VERIFICATION CHECKLIST (33 RULES):",
|
|
143
|
+
f" * Temporal Lookahead Bias (T001-T018): {'PASSED (0 leaks)' if cert['invariants_verified']['temporal_lookahead_free'] else 'FAILED'}",
|
|
144
|
+
f" * Cross-Fold Lineage & Leakage (F001-F002): {'PASSED (0 leaks)' if cert['invariants_verified']['partition_provenance_clean'] else 'FAILED'}",
|
|
145
|
+
f" * Causal Mode Confounding (C001-C007): {'PASSED (0 leaks)' if cert['invariants_verified']['causal_intervention_isolated'] else 'FAILED'}",
|
|
146
|
+
f" * Uncertainty Bounds Guard (U001-U006): {'PASSED (0 leaks)' if cert['invariants_verified']['uncertainty_guards_present'] else 'FAILED'}",
|
|
147
|
+
"+----------------------------------------------------------------------+",
|
|
148
|
+
f" SHA-256 Digital Seal: {cert['sha256_digital_seal']}",
|
|
149
|
+
" Issued by: Neuron Labs - https://neuron-lab.org/pycheck",
|
|
150
|
+
" Next-Gen Sovereign: NEURON Compile-Time Language (https://neuron-lab.org)",
|
|
151
|
+
"+======================================================================+"
|
|
152
|
+
]
|
|
153
|
+
return "\n".join(lines)
|
|
@@ -355,7 +355,9 @@ class TaintTracker:
|
|
|
355
355
|
if isinstance(node.func, ast.Attribute):
|
|
356
356
|
method = node.func.attr
|
|
357
357
|
sinks = {'fit', 'fit_transform', 'fit_predict', 'train',
|
|
358
|
-
'partial_fit', 'predict', 'score'
|
|
358
|
+
'partial_fit', 'predict', 'score',
|
|
359
|
+
'cross_validate', 'cross_val_score', 'cross_val_predict',
|
|
360
|
+
'GridSearchCV', 'RandomizedSearchCV'}
|
|
359
361
|
|
|
360
362
|
if method in sinks:
|
|
361
363
|
# Check positional arguments for future taint (F001)
|
|
@@ -377,7 +379,9 @@ class TaintTracker:
|
|
|
377
379
|
"Ensure training/prediction data is free of future information"))
|
|
378
380
|
|
|
379
381
|
# Check F002: Data contamination & test partitions in training sinks
|
|
380
|
-
training_sinks = {'fit', 'fit_transform', 'fit_predict', 'train', 'partial_fit'
|
|
382
|
+
training_sinks = {'fit', 'fit_transform', 'fit_predict', 'train', 'partial_fit',
|
|
383
|
+
'cross_validate', 'cross_val_score', 'cross_val_predict',
|
|
384
|
+
'GridSearchCV', 'RandomizedSearchCV'}
|
|
381
385
|
if method in training_sinks:
|
|
382
386
|
obj_name = self._get_obj_name(node.func.value)
|
|
383
387
|
safe_objects = {'fig', 'figure', 'ax', 'axes', 'plt', 'layout',
|