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.
Files changed (22) hide show
  1. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/PKG-INFO +32 -7
  2. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/README.md +30 -5
  3. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/__init__.py +1 -1
  4. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/analyzer.py +35 -2
  5. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/audit.py +153 -153
  6. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/flow.py +6 -2
  7. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/rules.py +331 -5
  8. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/server.py +201 -201
  9. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/PKG-INFO +32 -7
  10. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pyproject.toml +66 -66
  11. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/tests/test_audit.py +49 -49
  12. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/tests/test_cli.py +113 -85
  13. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/tests/test_rules.py +236 -7
  14. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/LICENSE +0 -0
  15. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/__main__.py +0 -0
  16. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck/cli.py +0 -0
  17. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/SOURCES.txt +0 -0
  18. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/dependency_links.txt +0 -0
  19. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/entry_points.txt +0 -0
  20. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/requires.txt +0 -0
  21. {pycheck_neuron-1.0.2 → pycheck_neuron-1.0.4}/pycheck_neuron.egg-info/top_level.txt +0 -0
  22. {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.2
4
- Summary: 32-rule static AST analyzer catching temporal lookahead bias, causal confusion, and uncertainty bugs in Python ML code. Zero dependencies.
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
- **32 rules. Zero dependencies. Catches the bugs that cost millions.**
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: 32 active + Data Flow & Provenance Engine
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 (17 rules — `T001`–`T017`)
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 32 rules
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
- **32 rules. Zero dependencies. Catches the bugs that cost millions.**
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: 32 active + Data Flow & Provenance Engine
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 (17 rules — `T001`–`T017`)
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 32 rules
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.2"
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
- # Deduplicate by (line, code)
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
- unique.append(d)
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 Cloud 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 Cloud - 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 (32 RULES):",
143
- f" * Temporal Lookahead Bias (T001-T017): {'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)
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',