zerynex-python 0.2.0__py3-none-any.whl
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.
- zerynex_py/__init__.py +21 -0
- zerynex_py/agent_workflows.py +125 -0
- zerynex_py/client.py +406 -0
- zerynex_py/fixtures/clean-v6.bin +0 -0
- zerynex_py/fixtures/refund-isolated.json +21 -0
- zerynex_py/fixtures/refund-regression-expanded.json +29 -0
- zerynex_py/fixtures/refund-risky.json +21 -0
- zerynex_py/fixtures/rule-catalog.json +95 -0
- zerynex_py/fixtures/transparent-v6.bin +0 -0
- zerynex_py/fixtures/zallet-pczt-lifecycle-contract.json +87 -0
- zerynex_py/fixtures/zallet-pczt-lifecycle-synthetic-openrpc.json +127 -0
- zerynex_py/mcp_doctor.py +152 -0
- zerynex_py/mcp_server.py +391 -0
- zerynex_py/node.py +189 -0
- zerynex_py/preflight.py +42 -0
- zerynex_py/project.py +313 -0
- zerynex_py/refund_privacy.py +252 -0
- zerynex_py/refund_workflows.py +118 -0
- zerynex_py/rpc_contract.py +172 -0
- zerynex_py/rules.py +55 -0
- zerynex_py/z3.py +107 -0
- zerynex_py/zallet_fixture.py +158 -0
- zerynex_python-0.2.0.dist-info/METADATA +287 -0
- zerynex_python-0.2.0.dist-info/RECORD +28 -0
- zerynex_python-0.2.0.dist-info/WHEEL +5 -0
- zerynex_python-0.2.0.dist-info/entry_points.txt +10 -0
- zerynex_python-0.2.0.dist-info/licenses/LICENSE +21 -0
- zerynex_python-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"tool": "zerynex",
|
|
4
|
+
"rule_set_version": "1.0.0",
|
|
5
|
+
"scope": "Selected structural PCZT disclosure-policy observations only. No cryptographic proof, wallet, consensus or anonymity verification.",
|
|
6
|
+
"rules": [
|
|
7
|
+
{
|
|
8
|
+
"id": "PCZT-DUMMY-KEY",
|
|
9
|
+
"title": "Intermediate dummy signing key exposed",
|
|
10
|
+
"severity": "warning",
|
|
11
|
+
"category": "handoff",
|
|
12
|
+
"applies_to": [
|
|
13
|
+
"analysis",
|
|
14
|
+
"stage_comparison"
|
|
15
|
+
],
|
|
16
|
+
"summary": "An accessible dummy spending key is present in an intermediate PCZT handoff."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "PCZT-GLOBAL-METADATA",
|
|
20
|
+
"title": "Unreviewed global metadata",
|
|
21
|
+
"severity": "warning",
|
|
22
|
+
"category": "metadata",
|
|
23
|
+
"applies_to": [
|
|
24
|
+
"analysis",
|
|
25
|
+
"stage_comparison"
|
|
26
|
+
],
|
|
27
|
+
"summary": "Global proprietary fields require an explicit disclosure review."
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "PCZT-RESTRICTED-DISCLOSURE",
|
|
31
|
+
"title": "Restricted handoff field present",
|
|
32
|
+
"severity": "warning",
|
|
33
|
+
"category": "handoff",
|
|
34
|
+
"applies_to": [
|
|
35
|
+
"analysis",
|
|
36
|
+
"stage_comparison"
|
|
37
|
+
],
|
|
38
|
+
"summary": "The receiving role could see a field prohibited by a selected restrictive application policy."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "PCZT-SAPLING-POLICY",
|
|
42
|
+
"title": "Sapling components denied by policy",
|
|
43
|
+
"severity": "warning",
|
|
44
|
+
"category": "pool_policy",
|
|
45
|
+
"applies_to": [
|
|
46
|
+
"analysis",
|
|
47
|
+
"stage_comparison"
|
|
48
|
+
],
|
|
49
|
+
"summary": "Sapling components exist despite an opt-in application restriction; Sapling itself is not prohibited by Zcash consensus."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "PCZT-STRUCTURE-DRIFT",
|
|
53
|
+
"title": "PCZT component count changed",
|
|
54
|
+
"severity": "error",
|
|
55
|
+
"category": "regression",
|
|
56
|
+
"applies_to": [
|
|
57
|
+
"stage_comparison"
|
|
58
|
+
],
|
|
59
|
+
"summary": "A covered component count changed between intermediate stages; values and cryptographic equivalence are not compared."
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "PCZT-TRANSPARENT-INPUTS",
|
|
63
|
+
"title": "Transparent inputs denied by policy",
|
|
64
|
+
"severity": "warning",
|
|
65
|
+
"category": "pool_policy",
|
|
66
|
+
"applies_to": [
|
|
67
|
+
"analysis",
|
|
68
|
+
"stage_comparison"
|
|
69
|
+
],
|
|
70
|
+
"summary": "Transparent inputs were found while the selected policy denies transparent components."
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "PCZT-TRANSPARENT-OUTPUTS",
|
|
74
|
+
"title": "Transparent outputs denied by policy",
|
|
75
|
+
"severity": "warning",
|
|
76
|
+
"category": "pool_policy",
|
|
77
|
+
"applies_to": [
|
|
78
|
+
"analysis",
|
|
79
|
+
"stage_comparison"
|
|
80
|
+
],
|
|
81
|
+
"summary": "Transparent outputs were found while the selected policy denies transparent components."
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "PCZT-UNREVIEWED-METADATA",
|
|
85
|
+
"title": "Unreviewed component metadata",
|
|
86
|
+
"severity": "warning",
|
|
87
|
+
"category": "metadata",
|
|
88
|
+
"applies_to": [
|
|
89
|
+
"analysis",
|
|
90
|
+
"stage_comparison"
|
|
91
|
+
],
|
|
92
|
+
"summary": "Proprietary input, output or action fields are present and require disclosure review."
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"methods": [
|
|
4
|
+
{
|
|
5
|
+
"name": "rpc.discover",
|
|
6
|
+
"params": []
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "pczt_create",
|
|
10
|
+
"params": [
|
|
11
|
+
{
|
|
12
|
+
"name": "from",
|
|
13
|
+
"required": true
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "amounts",
|
|
17
|
+
"required": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "minconf",
|
|
21
|
+
"required": false
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "privacy_policy",
|
|
25
|
+
"required": false
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "fund_source",
|
|
29
|
+
"required": false
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "pczt_combine",
|
|
35
|
+
"params": [
|
|
36
|
+
{
|
|
37
|
+
"name": "pczts",
|
|
38
|
+
"required": true
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "pczt_inspect",
|
|
44
|
+
"params": [
|
|
45
|
+
{
|
|
46
|
+
"name": "pczt",
|
|
47
|
+
"required": true
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "pczt_prove",
|
|
53
|
+
"params": [
|
|
54
|
+
{
|
|
55
|
+
"name": "pczt",
|
|
56
|
+
"required": true
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "pczt_sign",
|
|
62
|
+
"params": [
|
|
63
|
+
{
|
|
64
|
+
"name": "pczt",
|
|
65
|
+
"required": true
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "privacy_policy",
|
|
69
|
+
"required": false
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "strict",
|
|
73
|
+
"required": false
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "pczt_extract",
|
|
79
|
+
"params": [
|
|
80
|
+
{
|
|
81
|
+
"name": "pczt",
|
|
82
|
+
"required": true
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openrpc": "1.3.2",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Synthetic PCZT lifecycle schema - NOT a Zallet release snapshot",
|
|
5
|
+
"version": "synthetic"
|
|
6
|
+
},
|
|
7
|
+
"methods": [
|
|
8
|
+
{
|
|
9
|
+
"name": "rpc.discover",
|
|
10
|
+
"params": []
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "pczt_create",
|
|
14
|
+
"params": [
|
|
15
|
+
{
|
|
16
|
+
"name": "from",
|
|
17
|
+
"required": true,
|
|
18
|
+
"schema": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "amounts",
|
|
24
|
+
"required": true,
|
|
25
|
+
"schema": {
|
|
26
|
+
"type": "array"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "minconf",
|
|
31
|
+
"required": false,
|
|
32
|
+
"schema": {
|
|
33
|
+
"type": "number"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "privacy_policy",
|
|
38
|
+
"required": false,
|
|
39
|
+
"schema": {
|
|
40
|
+
"type": "string"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "fund_source",
|
|
45
|
+
"required": false,
|
|
46
|
+
"schema": {
|
|
47
|
+
"type": "string"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "pczt_combine",
|
|
54
|
+
"params": [
|
|
55
|
+
{
|
|
56
|
+
"name": "pczts",
|
|
57
|
+
"required": true,
|
|
58
|
+
"schema": {
|
|
59
|
+
"type": "array"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "pczt_inspect",
|
|
66
|
+
"params": [
|
|
67
|
+
{
|
|
68
|
+
"name": "pczt",
|
|
69
|
+
"required": true,
|
|
70
|
+
"schema": {
|
|
71
|
+
"type": "string"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "pczt_prove",
|
|
78
|
+
"params": [
|
|
79
|
+
{
|
|
80
|
+
"name": "pczt",
|
|
81
|
+
"required": true,
|
|
82
|
+
"schema": {
|
|
83
|
+
"type": "string"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "pczt_sign",
|
|
90
|
+
"params": [
|
|
91
|
+
{
|
|
92
|
+
"name": "pczt",
|
|
93
|
+
"required": true,
|
|
94
|
+
"schema": {
|
|
95
|
+
"type": "string"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "privacy_policy",
|
|
100
|
+
"required": false,
|
|
101
|
+
"schema": {
|
|
102
|
+
"type": "string"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "strict",
|
|
107
|
+
"required": false,
|
|
108
|
+
"schema": {
|
|
109
|
+
"type": "boolean"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "pczt_extract",
|
|
116
|
+
"params": [
|
|
117
|
+
{
|
|
118
|
+
"name": "pczt",
|
|
119
|
+
"required": true,
|
|
120
|
+
"schema": {
|
|
121
|
+
"type": "string"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
zerynex_py/mcp_doctor.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Verify a REAL local MCP stdio connection and Rust-backed synthetic suite.
|
|
2
|
+
|
|
3
|
+
A standalone diagnostic; never reads or prints transaction contents. No
|
|
4
|
+
HTTP service, provider account or AI model is involved.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import shutil
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def check(server: Path, cli: Path, fixtures: Path) -> dict[str, object]:
|
|
18
|
+
try:
|
|
19
|
+
from mcp import Client
|
|
20
|
+
from mcp.client.stdio import StdioServerParameters
|
|
21
|
+
except ImportError:
|
|
22
|
+
raise RuntimeError("Install the optional MCP Python SDK before running the doctor.") from None
|
|
23
|
+
|
|
24
|
+
params = StdioServerParameters(
|
|
25
|
+
command=str(server),
|
|
26
|
+
args=[],
|
|
27
|
+
env={
|
|
28
|
+
"ZERYNEX_CLI": str(cli),
|
|
29
|
+
"ZERYNEX_AGENT_ALLOWED_ROOT": str(fixtures),
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
async with Client(params, raise_exceptions=True, read_timeout_seconds=35) as agent:
|
|
33
|
+
tools = await agent.list_tools()
|
|
34
|
+
names = sorted(tool.name for tool in tools.tools)
|
|
35
|
+
needed = {
|
|
36
|
+
"analyze_synthetic_pczt",
|
|
37
|
+
"compare_synthetic_pczt_stages",
|
|
38
|
+
"list_synthetic_fixtures",
|
|
39
|
+
"explain_zerynex_rule",
|
|
40
|
+
"run_synthetic_smoke_suite",
|
|
41
|
+
"zerynex_policy_schema",
|
|
42
|
+
"zerynex_core_capabilities",
|
|
43
|
+
"zerynex_rule_catalog",
|
|
44
|
+
"check_synthetic_zallet_rpc_contract",
|
|
45
|
+
"run_zerynex_developer_preflight",
|
|
46
|
+
"audit_synthetic_refund_privacy",
|
|
47
|
+
"compare_synthetic_refund_privacy",
|
|
48
|
+
}
|
|
49
|
+
if not needed.issubset(names):
|
|
50
|
+
raise RuntimeError("Local server does not expose all expected MCP capabilities.")
|
|
51
|
+
registry = await agent.call_tool("zerynex_rule_catalog", {})
|
|
52
|
+
rule_data = registry.structured_content
|
|
53
|
+
if not isinstance(rule_data, dict):
|
|
54
|
+
from mcp.types import TextContent
|
|
55
|
+
blocks = [item for item in registry.content if isinstance(item, TextContent)]
|
|
56
|
+
rule_data = json.loads(blocks[0].text) if len(blocks) == 1 else None
|
|
57
|
+
if not isinstance(rule_data, dict) or rule_data.get("rule_set_version") != "1.0.0" or len(rule_data.get("rules", [])) != 8:
|
|
58
|
+
raise RuntimeError("Installed Rust rule registry and bundled SDK are incompatible.")
|
|
59
|
+
suite = await agent.call_tool("run_synthetic_smoke_suite", {})
|
|
60
|
+
result = suite.structured_content
|
|
61
|
+
# Some supported protocol versions return text blocks instead.
|
|
62
|
+
if not isinstance(result, dict):
|
|
63
|
+
from mcp.types import TextContent
|
|
64
|
+
blocks = [item for item in suite.content if isinstance(item, TextContent)]
|
|
65
|
+
if len(blocks) != 1:
|
|
66
|
+
raise RuntimeError("Invalid structured response from the MCP tool.")
|
|
67
|
+
result = json.loads(blocks[0].text)
|
|
68
|
+
if not isinstance(result, dict):
|
|
69
|
+
raise RuntimeError("Invalid synthetic suite response.")
|
|
70
|
+
ok = result.get("passed") is True and result.get("total") == 4 and result.get("matched") == 4
|
|
71
|
+
readiness = await agent.call_tool("run_zerynex_developer_preflight", {})
|
|
72
|
+
preflight = readiness.structured_content
|
|
73
|
+
if not isinstance(preflight, dict):
|
|
74
|
+
from mcp.types import TextContent
|
|
75
|
+
blocks = [item for item in readiness.content if isinstance(item, TextContent)]
|
|
76
|
+
preflight = json.loads(blocks[0].text) if len(blocks) == 1 else None
|
|
77
|
+
if not isinstance(preflight, dict):
|
|
78
|
+
raise RuntimeError("Invalid developer preflight response.")
|
|
79
|
+
regression = await agent.call_tool("check_synthetic_zallet_rpc_contract", {"scenario": "new_required_parameter"})
|
|
80
|
+
drift = regression.structured_content
|
|
81
|
+
if not isinstance(drift, dict):
|
|
82
|
+
from mcp.types import TextContent
|
|
83
|
+
blocks = [item for item in regression.content if isinstance(item, TextContent)]
|
|
84
|
+
drift = json.loads(blocks[0].text) if len(blocks) == 1 else None
|
|
85
|
+
if not isinstance(drift, dict):
|
|
86
|
+
raise RuntimeError("Invalid regression response.")
|
|
87
|
+
safe_case = await agent.call_tool("audit_synthetic_refund_privacy", {"scenario": "isolated"})
|
|
88
|
+
unsafe_case = await agent.call_tool("audit_synthetic_refund_privacy", {"scenario": "risky"})
|
|
89
|
+
delta = await agent.call_tool("compare_synthetic_refund_privacy",
|
|
90
|
+
{"baseline": "risky", "candidate": "regression-expanded"})
|
|
91
|
+
def content_data(value):
|
|
92
|
+
data = value.structured_content
|
|
93
|
+
if not isinstance(data, dict):
|
|
94
|
+
from mcp.types import TextContent
|
|
95
|
+
blocks = [item for item in value.content if isinstance(item, TextContent)]
|
|
96
|
+
data = json.loads(blocks[0].text) if len(blocks) == 1 else None
|
|
97
|
+
return data if isinstance(data, dict) else {}
|
|
98
|
+
refund_ready = (content_data(safe_case).get("report", {}).get("passed") is True
|
|
99
|
+
and content_data(unsafe_case).get("report", {}).get("finding_count") == 5
|
|
100
|
+
and content_data(delta).get("report", {}).get("new_risk_events") == 1
|
|
101
|
+
and preflight.get("checks", {}).get("synthetic_refund_privacy", {}).get("matched") == 6)
|
|
102
|
+
ready = preflight.get("passed") is True and preflight.get("synthetic_only") is True
|
|
103
|
+
negative = (drift.get("contract_passed") is False and drift.get("matched_expectation") is True
|
|
104
|
+
and drift.get("finding_count") == 1)
|
|
105
|
+
return {
|
|
106
|
+
"connected": True,
|
|
107
|
+
"tool_count": len(names),
|
|
108
|
+
"tools": names,
|
|
109
|
+
"suite_passed": ok,
|
|
110
|
+
"suite_matched": result.get("matched", 0),
|
|
111
|
+
"suite_total": result.get("total", 4),
|
|
112
|
+
"preflight_passed": ready,
|
|
113
|
+
"rpc_regression_detected": negative,
|
|
114
|
+
"refund_privacy_verified": refund_ready,
|
|
115
|
+
"note": "MCP stdio subprocess and real Rust CLI. Synthetic inputs only.",
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def main() -> None:
|
|
120
|
+
parser = argparse.ArgumentParser(description="Check a local Zerynex MCP server against synthetic fixtures.")
|
|
121
|
+
parser.add_argument("--cli", default=os.environ.get("ZERYNEX_CLI", ""), help="Absolute path to the trusted Rust CLI")
|
|
122
|
+
parser.add_argument("--fixtures", default=os.environ.get("ZERYNEX_AGENT_ALLOWED_ROOT", ""), help="Absolute synthetic-only fixture directory")
|
|
123
|
+
parser.add_argument("--server", default=shutil.which("zerynex-mcp") or "", help="Absolute path to local zerynex-mcp executable")
|
|
124
|
+
parser.add_argument("--json", action="store_true", help="Print a sanitized JSON outcome")
|
|
125
|
+
args = parser.parse_args()
|
|
126
|
+
# Avoid including user paths in errors, logs, and diagnostic output.
|
|
127
|
+
if not args.cli or not args.fixtures or not args.server:
|
|
128
|
+
parser.error("Provide --cli, --fixtures and --server for an explicit local setup.")
|
|
129
|
+
try:
|
|
130
|
+
cli, fixtures, server = (Path(path).expanduser().resolve(strict=True)
|
|
131
|
+
for path in (args.cli, args.fixtures, args.server))
|
|
132
|
+
if not cli.is_file() or not server.is_file() or not fixtures.is_dir():
|
|
133
|
+
raise RuntimeError("Invalid configured local paths.")
|
|
134
|
+
report = asyncio.run(check(server, cli, fixtures))
|
|
135
|
+
except Exception: # MCP transport can raise ExceptionGroup; always redact errors.
|
|
136
|
+
print("Zerynex MCP connection or synthetic suite failed. Check local setup.", file=sys.stderr)
|
|
137
|
+
raise SystemExit(1) from None
|
|
138
|
+
if args.json:
|
|
139
|
+
print(json.dumps(report, indent=2))
|
|
140
|
+
else:
|
|
141
|
+
print("MCP stdio: connected")
|
|
142
|
+
print("Discovered tools:", report["tool_count"])
|
|
143
|
+
print("Synthetic Rust checks:", str(report["suite_matched"]) + "/" + str(report["suite_total"]))
|
|
144
|
+
print("Developer preflight:", "PASS" if report["preflight_passed"] else "FAIL")
|
|
145
|
+
print("Synthetic RPC drift detected:", "YES" if report["rpc_regression_detected"] else "NO")
|
|
146
|
+
print("Synthetic refund privacy:", "PASS" if report["refund_privacy_verified"] else "FAIL")
|
|
147
|
+
print("Result:", "PASS" if report["suite_passed"] and report["preflight_passed"] and report["rpc_regression_detected"] and report["refund_privacy_verified"] else "FAIL")
|
|
148
|
+
raise SystemExit(0 if report["suite_passed"] and report["preflight_passed"] and report["rpc_regression_detected"] and report["refund_privacy_verified"] else 2)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
main()
|