web3skill 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/archives/web3-audit-orchestrator.skill +0 -0
- package/dist/archives/web3-audit-reporting.skill +0 -0
- package/dist/archives/web3-fuzzing-and-invariants.skill +0 -0
- package/dist/archives/web3-native-operator.skill +0 -0
- package/dist/archives/web3-repo-heuristics.skill +0 -0
- package/dist/archives/web3-research-and-market-intel.skill +0 -0
- package/dist/archives/web3-risk-gate.skill +0 -0
- package/dist/archives/web3-service-orchestrator.skill +0 -0
- package/dist/archives/web3-static-analysis-runner.skill +0 -0
- package/dist/archives/web3-trace-and-state-analysis.skill +0 -0
- package/dist/archives/web3-transaction-simulator.skill +0 -0
- package/dist/archives/web3-wallet-operator.skill +0 -0
- package/dist/manifest.json +170 -0
- package/dist/skills/web3-audit-orchestrator/SKILL.md +79 -0
- package/dist/skills/web3-audit-orchestrator/references/ADAPTER_CONSUMPTION_MAP.md +15 -0
- package/dist/skills/web3-audit-orchestrator/references/OUTPUT_TEMPLATE.md +52 -0
- package/dist/skills/web3-audit-orchestrator/references/REVIEW_STATE_MACHINE.md +25 -0
- package/dist/skills/web3-audit-orchestrator/scripts/render_audit_review.py +95 -0
- package/dist/skills/web3-audit-reporting/SKILL.md +77 -0
- package/dist/skills/web3-audit-reporting/references/FINDING_TEMPLATE.md +54 -0
- package/dist/skills/web3-audit-reporting/references/REPORT_TEMPLATE.md +58 -0
- package/dist/skills/web3-audit-reporting/references/RETEST_TEMPLATE.md +35 -0
- package/dist/skills/web3-audit-reporting/references/SEVERITY_RUBRIC.md +75 -0
- package/dist/skills/web3-fuzzing-and-invariants/SKILL.md +68 -0
- package/dist/skills/web3-fuzzing-and-invariants/references/ADAPTER_CONSUMPTION_MAP.md +14 -0
- package/dist/skills/web3-fuzzing-and-invariants/references/OUTPUT_TEMPLATE.md +40 -0
- package/dist/skills/web3-fuzzing-and-invariants/references/READINESS_AND_FAILURES.md +25 -0
- package/dist/skills/web3-fuzzing-and-invariants/scripts/render_fuzz_summary.py +64 -0
- package/dist/skills/web3-native-operator/SKILL.md +218 -0
- package/dist/skills/web3-native-operator/references/EXECUTION_BUNDLE_TEMPLATE.md +47 -0
- package/dist/skills/web3-native-operator/references/OPERATOR_BUNDLE_TEMPLATE.md +39 -0
- package/dist/skills/web3-native-operator/references/POSTTRADE_FOLLOWUP_BUNDLE_TEMPLATE.md +35 -0
- package/dist/skills/web3-native-operator/references/POSTTRADE_WATCH_TEMPLATE.md +34 -0
- package/dist/skills/web3-native-operator/references/PRETRADE_PACKET_TEMPLATE.md +34 -0
- package/dist/skills/web3-native-operator/references/ROUTE_RECIPES.md +140 -0
- package/dist/skills/web3-native-operator/references/ROUTING_STATE_MACHINE.md +73 -0
- package/dist/skills/web3-native-operator/references/WATCH_CRON_REQUEST_TEMPLATE.md +26 -0
- package/dist/skills/web3-native-operator/references/WATCH_FOLLOWUP_BUNDLE_TEMPLATE.md +35 -0
- package/dist/skills/web3-native-operator/references/WATCH_HEARTBEAT_TEMPLATE.md +31 -0
- package/dist/skills/web3-native-operator/scripts/apply_followup_bundle_to_heartbeat.py +118 -0
- package/dist/skills/web3-native-operator/scripts/render_execution_bundle.py +259 -0
- package/dist/skills/web3-native-operator/scripts/render_operator_bundle.py +800 -0
- package/dist/skills/web3-native-operator/scripts/render_posttrade_followup_bundle.py +118 -0
- package/dist/skills/web3-native-operator/scripts/render_posttrade_watch_status.py +125 -0
- package/dist/skills/web3-native-operator/scripts/render_pretrade_packet.py +205 -0
- package/dist/skills/web3-native-operator/scripts/render_watch_cron_request.py +88 -0
- package/dist/skills/web3-native-operator/scripts/render_watch_followup_bundle.py +118 -0
- package/dist/skills/web3-native-operator/scripts/render_watch_heartbeat.py +52 -0
- package/dist/skills/web3-repo-heuristics/SKILL.md +37 -0
- package/dist/skills/web3-repo-heuristics/references/FOUNDRY.md +49 -0
- package/dist/skills/web3-repo-heuristics/references/HARDHAT.md +47 -0
- package/dist/skills/web3-repo-heuristics/references/VYPER.md +26 -0
- package/dist/skills/web3-research-and-market-intel/SKILL.md +138 -0
- package/dist/skills/web3-research-and-market-intel/references/ADAPTER_CONSUMPTION_MAP.md +66 -0
- package/dist/skills/web3-research-and-market-intel/references/EVIDENCE_QUALITY.md +27 -0
- package/dist/skills/web3-research-and-market-intel/references/OUTPUT_TEMPLATE.md +37 -0
- package/dist/skills/web3-research-and-market-intel/references/PORTFOLIO_STATUS_TEMPLATE.md +51 -0
- package/dist/skills/web3-research-and-market-intel/references/WATCH_STATUS_TEMPLATE.md +39 -0
- package/dist/skills/web3-research-and-market-intel/scripts/render_portfolio_status.py +85 -0
- package/dist/skills/web3-research-and-market-intel/scripts/render_research_brief.py +58 -0
- package/dist/skills/web3-research-and-market-intel/scripts/render_watch_status.py +70 -0
- package/dist/skills/web3-risk-gate/SKILL.md +100 -0
- package/dist/skills/web3-risk-gate/references/OUTPUT_TEMPLATE.md +72 -0
- package/dist/skills/web3-risk-gate/references/SIGNAL_TAXONOMY.md +34 -0
- package/dist/skills/web3-risk-gate/scripts/merge_risk_gate_blocks.py +189 -0
- package/dist/skills/web3-service-orchestrator/SKILL.md +34 -0
- package/dist/skills/web3-static-analysis-runner/SKILL.md +76 -0
- package/dist/skills/web3-static-analysis-runner/references/ADAPTER_CONSUMPTION_MAP.md +13 -0
- package/dist/skills/web3-static-analysis-runner/references/OUTPUT_TEMPLATE.md +45 -0
- package/dist/skills/web3-static-analysis-runner/references/TRIAGE_BUCKETS.md +16 -0
- package/dist/skills/web3-static-analysis-runner/scripts/render_static_analysis_summary.py +64 -0
- package/dist/skills/web3-trace-and-state-analysis/SKILL.md +74 -0
- package/dist/skills/web3-trace-and-state-analysis/references/ADAPTER_CONSUMPTION_MAP.md +27 -0
- package/dist/skills/web3-trace-and-state-analysis/references/OUTPUT_TEMPLATE.md +63 -0
- package/dist/skills/web3-trace-and-state-analysis/references/TRACE_BACKEND_PREFLIGHT.md +47 -0
- package/dist/skills/web3-trace-and-state-analysis/scripts/render_trace_summary.py +99 -0
- package/dist/skills/web3-transaction-simulator/SKILL.md +83 -0
- package/dist/skills/web3-transaction-simulator/references/OUTPUT_TEMPLATE.md +86 -0
- package/dist/skills/web3-transaction-simulator/references/STATUS_AND_FAILURES.md +49 -0
- package/dist/skills/web3-transaction-simulator/scripts/merge_simulation_blocks.py +198 -0
- package/dist/skills/web3-wallet-operator/SKILL.md +52 -0
- package/dist/skills/web3-wallet-operator/references/ACTION_RECIPES.md +56 -0
- package/dist/skills/web3-wallet-operator/references/OUTPUT_TEMPLATE.md +43 -0
- package/dist/skills/web3-wallet-operator/scripts/render_wallet_operation_plan.py +101 -0
- package/index.js +50 -0
- package/package.json +36 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render the full posttrade follow-up bundle from execution receipt fields."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from render_operator_bundle import build_posttrade_bundle
|
|
11
|
+
from render_posttrade_watch_status import build_posttrade_watch_payload
|
|
12
|
+
from render_watch_cron_request import build_cron_request_payload
|
|
13
|
+
from render_watch_heartbeat import build_heartbeat_task
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
17
|
+
parser = argparse.ArgumentParser(description="Render a Web3 posttrade follow-up bundle.")
|
|
18
|
+
parser.add_argument("--venue", required=True)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--receipt-status",
|
|
21
|
+
choices=("SUBMITTED", "PENDING", "CONFIRMED", "FILLED", "FAILED", "REVERTED"),
|
|
22
|
+
required=True,
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument("--settlement", choices=("evm", "venue"), required=True)
|
|
25
|
+
parser.add_argument("--reference-id", default="")
|
|
26
|
+
parser.add_argument("--subject", default="")
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"--severity",
|
|
29
|
+
choices=("low", "medium", "high", "critical"),
|
|
30
|
+
default="",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument("--next-check-hint", default="")
|
|
33
|
+
parser.add_argument("--schedule-hint", default="")
|
|
34
|
+
parser.add_argument("--recommended-adapter", default="")
|
|
35
|
+
parser.add_argument("--reason", action="append", default=[])
|
|
36
|
+
parser.add_argument("--follow-up-skill", action="append", default=[])
|
|
37
|
+
parser.add_argument("--evidence", action="append", default=[])
|
|
38
|
+
parser.add_argument("--task-note", default="")
|
|
39
|
+
parser.add_argument("--every-seconds", type=int, default=0)
|
|
40
|
+
parser.add_argument("--cron-expr", default="")
|
|
41
|
+
parser.add_argument("--tz", default="")
|
|
42
|
+
parser.add_argument("--at", default="")
|
|
43
|
+
return parser
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def maybe_build_cron_request(
|
|
47
|
+
args: argparse.Namespace,
|
|
48
|
+
watch_args: argparse.Namespace,
|
|
49
|
+
) -> dict[str, object] | None:
|
|
50
|
+
if not (
|
|
51
|
+
args.every_seconds > 0
|
|
52
|
+
or args.cron_expr
|
|
53
|
+
or args.at
|
|
54
|
+
or (watch_args.schedule_hint and watch_args.schedule_hint != "manual")
|
|
55
|
+
):
|
|
56
|
+
return None
|
|
57
|
+
cron_expr = args.cron_expr or (
|
|
58
|
+
watch_args.schedule_hint if watch_args.schedule_hint and watch_args.schedule_hint != "manual" else ""
|
|
59
|
+
)
|
|
60
|
+
cron_args = argparse.Namespace(
|
|
61
|
+
subject=watch_args.subject,
|
|
62
|
+
watch_kind="posttrade",
|
|
63
|
+
status=watch_args.status,
|
|
64
|
+
severity=watch_args.severity,
|
|
65
|
+
recommended_adapter=watch_args.recommended_adapter,
|
|
66
|
+
next_check_hint=watch_args.next_check_hint,
|
|
67
|
+
message="",
|
|
68
|
+
every_seconds=args.every_seconds,
|
|
69
|
+
cron_expr=cron_expr,
|
|
70
|
+
tz=args.tz,
|
|
71
|
+
at=args.at,
|
|
72
|
+
)
|
|
73
|
+
return build_cron_request_payload(cron_args)["cron_request"]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_receipt_summary(args: argparse.Namespace) -> dict[str, str]:
|
|
77
|
+
return {
|
|
78
|
+
"receipt_status": args.receipt_status,
|
|
79
|
+
"venue": args.venue,
|
|
80
|
+
"settlement": args.settlement,
|
|
81
|
+
"reference_id": args.reference_id,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def main() -> int:
|
|
86
|
+
args = build_parser().parse_args()
|
|
87
|
+
operator_bundle = build_posttrade_bundle(args)
|
|
88
|
+
watch_payload = build_posttrade_watch_payload(args)["watch_status"]
|
|
89
|
+
watch_args = argparse.Namespace(
|
|
90
|
+
subject=str(watch_payload["subject"]),
|
|
91
|
+
watch_kind="posttrade",
|
|
92
|
+
watch_status=str(watch_payload["status"]),
|
|
93
|
+
status=str(watch_payload["status"]),
|
|
94
|
+
severity=str(watch_payload["severity"]),
|
|
95
|
+
recommended_adapter=str(watch_payload["recommended_adapter"]),
|
|
96
|
+
next_check_hint=str(watch_payload["next_check_hint"]),
|
|
97
|
+
schedule_hint=str(watch_payload["schedule_hint"]),
|
|
98
|
+
task_note=args.task_note,
|
|
99
|
+
)
|
|
100
|
+
heartbeat_task = build_heartbeat_task(watch_args)
|
|
101
|
+
cron_request = maybe_build_cron_request(args, watch_args)
|
|
102
|
+
payload = {
|
|
103
|
+
"posttrade_followup_bundle": {
|
|
104
|
+
"version": 1,
|
|
105
|
+
"execution_receipt": build_receipt_summary(args),
|
|
106
|
+
"operator_bundle": operator_bundle,
|
|
107
|
+
"watch_status": watch_payload,
|
|
108
|
+
"heartbeat_task": heartbeat_task,
|
|
109
|
+
"cron_request": cron_request,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
|
|
113
|
+
sys.stdout.write("\n")
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render a watch_status block from a normalized execution_receipt."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
12
|
+
parser = argparse.ArgumentParser(description="Render a posttrade watch status block.")
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
"--receipt-status",
|
|
15
|
+
choices=("SUBMITTED", "PENDING", "CONFIRMED", "FILLED", "FAILED", "REVERTED"),
|
|
16
|
+
required=True,
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument("--venue", required=True)
|
|
19
|
+
parser.add_argument("--settlement", choices=("evm", "venue"), required=True)
|
|
20
|
+
parser.add_argument("--reference-id", default="")
|
|
21
|
+
parser.add_argument("--subject", default="")
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--severity",
|
|
24
|
+
choices=("low", "medium", "high", "critical"),
|
|
25
|
+
default="",
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument("--next-check-hint", default="")
|
|
28
|
+
parser.add_argument("--schedule-hint", default="")
|
|
29
|
+
parser.add_argument("--recommended-adapter", default="")
|
|
30
|
+
parser.add_argument("--evidence", action="append", default=[])
|
|
31
|
+
return parser
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def default_subject(args: argparse.Namespace) -> str:
|
|
35
|
+
if args.subject:
|
|
36
|
+
return args.subject
|
|
37
|
+
if args.reference_id and args.settlement == "evm":
|
|
38
|
+
return f"tx:{args.reference_id}"
|
|
39
|
+
if args.reference_id:
|
|
40
|
+
return f"{args.venue}:{args.reference_id}"
|
|
41
|
+
return f"{args.venue}:posttrade"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def default_severity(receipt_status: str) -> str:
|
|
45
|
+
if receipt_status in ("FAILED", "REVERTED"):
|
|
46
|
+
return "high"
|
|
47
|
+
if receipt_status in ("CONFIRMED", "FILLED"):
|
|
48
|
+
return "medium"
|
|
49
|
+
return "medium"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def default_schedule_hint(receipt_status: str) -> str:
|
|
53
|
+
if receipt_status in ("SUBMITTED", "PENDING"):
|
|
54
|
+
return "*/5 * * * *"
|
|
55
|
+
if receipt_status in ("CONFIRMED", "FILLED"):
|
|
56
|
+
return "manual"
|
|
57
|
+
return "manual"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def default_next_check_hint(args: argparse.Namespace) -> str:
|
|
61
|
+
if args.receipt_status in ("SUBMITTED", "PENDING"):
|
|
62
|
+
if args.settlement == "evm":
|
|
63
|
+
return "check receipt confirmations and final token balances after 5 minutes"
|
|
64
|
+
return "refresh venue fill status and open positions after 5 minutes"
|
|
65
|
+
if args.receipt_status in ("CONFIRMED", "FILLED"):
|
|
66
|
+
if args.settlement == "evm":
|
|
67
|
+
return "verify final settlement state, logs, and output balances"
|
|
68
|
+
return "verify final position, fills, and venue balances"
|
|
69
|
+
return "inspect failure path, revert reason, and settlement evidence immediately"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def default_adapter(args: argparse.Namespace) -> str:
|
|
73
|
+
if args.recommended_adapter:
|
|
74
|
+
return args.recommended_adapter
|
|
75
|
+
if args.settlement == "evm":
|
|
76
|
+
return "web3-trace-and-state-analysis"
|
|
77
|
+
return args.venue
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def map_watch_status(receipt_status: str) -> str:
|
|
81
|
+
if receipt_status in ("SUBMITTED", "PENDING"):
|
|
82
|
+
return "ACTIVE"
|
|
83
|
+
return "TRIGGERED"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def parse_evidence(entries: list[str], venue: str, receipt_status: str) -> list[dict[str, str]]:
|
|
87
|
+
if not entries:
|
|
88
|
+
return [{"adapter": venue, "detail": f"execution receipt status is {receipt_status}"}]
|
|
89
|
+
parsed: list[dict[str, str]] = []
|
|
90
|
+
for entry in entries:
|
|
91
|
+
adapter, sep, detail = entry.partition(":")
|
|
92
|
+
if sep:
|
|
93
|
+
parsed.append({"adapter": adapter.strip(), "detail": detail.strip()})
|
|
94
|
+
else:
|
|
95
|
+
parsed.append({"adapter": venue, "detail": entry})
|
|
96
|
+
return parsed
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def build_posttrade_watch_payload(args: argparse.Namespace) -> dict[str, object]:
|
|
100
|
+
return {
|
|
101
|
+
"watch_status": {
|
|
102
|
+
"version": 1,
|
|
103
|
+
"status": map_watch_status(args.receipt_status),
|
|
104
|
+
"watch_kind": "posttrade",
|
|
105
|
+
"subject": default_subject(args),
|
|
106
|
+
"trigger_source": args.venue,
|
|
107
|
+
"severity": args.severity or default_severity(args.receipt_status),
|
|
108
|
+
"next_check_hint": args.next_check_hint or default_next_check_hint(args),
|
|
109
|
+
"schedule_hint": args.schedule_hint or default_schedule_hint(args.receipt_status),
|
|
110
|
+
"recommended_adapter": default_adapter(args),
|
|
111
|
+
"evidence": parse_evidence(args.evidence, args.venue, args.receipt_status),
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def main() -> int:
|
|
117
|
+
args = build_parser().parse_args()
|
|
118
|
+
payload = build_posttrade_watch_payload(args)
|
|
119
|
+
json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
|
|
120
|
+
sys.stdout.write("\n")
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render all normalized pretrade artifacts in one bundle."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from render_execution_bundle import resolve_risk_gate, resolve_simulation, validate_merged_context
|
|
12
|
+
from render_operator_bundle import (
|
|
13
|
+
RESEARCH_CONCLUSIONS,
|
|
14
|
+
RESEARCH_FRESHNESS_VALUES,
|
|
15
|
+
build_pretrade_bundle,
|
|
16
|
+
resolve_research_brief,
|
|
17
|
+
wants_risk_context,
|
|
18
|
+
wants_simulation_context,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
RISK_GATE_SCRIPTS_DIR = (
|
|
23
|
+
Path(__file__).resolve().parents[2] / "web3-risk-gate" / "scripts"
|
|
24
|
+
)
|
|
25
|
+
if str(RISK_GATE_SCRIPTS_DIR) not in sys.path:
|
|
26
|
+
sys.path.insert(0, str(RISK_GATE_SCRIPTS_DIR))
|
|
27
|
+
|
|
28
|
+
from merge_risk_gate_blocks import default_next_steps as default_risk_next_steps
|
|
29
|
+
|
|
30
|
+
SIMULATION_SCRIPTS_DIR = (
|
|
31
|
+
Path(__file__).resolve().parents[2] / "web3-transaction-simulator" / "scripts"
|
|
32
|
+
)
|
|
33
|
+
if str(SIMULATION_SCRIPTS_DIR) not in sys.path:
|
|
34
|
+
sys.path.insert(0, str(SIMULATION_SCRIPTS_DIR))
|
|
35
|
+
|
|
36
|
+
from merge_simulation_blocks import default_next_steps as default_simulation_next_steps
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
40
|
+
parser = argparse.ArgumentParser(description="Render a Web3 pretrade packet.")
|
|
41
|
+
parser.add_argument("--intent", required=True)
|
|
42
|
+
parser.add_argument("--chain", required=True)
|
|
43
|
+
parser.add_argument("--research-input-file")
|
|
44
|
+
parser.add_argument("--research-conclusion", choices=RESEARCH_CONCLUSIONS)
|
|
45
|
+
parser.add_argument("--research-freshness", choices=RESEARCH_FRESHNESS_VALUES)
|
|
46
|
+
parser.add_argument("--research-question", default="")
|
|
47
|
+
parser.add_argument("--research-source", action="append", default=[])
|
|
48
|
+
parser.add_argument("--research-evidence", action="append", default=[])
|
|
49
|
+
parser.add_argument("--research-cross-check", action="append", default=[])
|
|
50
|
+
parser.add_argument("--research-next-step", action="append", default=[])
|
|
51
|
+
parser.add_argument("--risk-decision", choices=("ALLOW", "WARN", "BLOCK"))
|
|
52
|
+
parser.add_argument("--risk-coverage", choices=("complete", "partial", "missing"))
|
|
53
|
+
parser.add_argument("--risk-input-file", action="append", default=[])
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--simulation-status",
|
|
56
|
+
choices=("PASS", "WARN", "FAIL", "NO_SIMULATION"),
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--simulation-readiness",
|
|
60
|
+
choices=("ready", "needs-confirmation", "blocked"),
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument("--simulation-venue")
|
|
63
|
+
parser.add_argument("--simulation-input-file", action="append", default=[])
|
|
64
|
+
parser.add_argument("--reason", action="append", default=[])
|
|
65
|
+
parser.add_argument("--follow-up-skill", action="append", default=[])
|
|
66
|
+
return parser
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def build_minimal_risk_gate(
|
|
70
|
+
intent: str,
|
|
71
|
+
chain: str,
|
|
72
|
+
decision: str,
|
|
73
|
+
coverage: str,
|
|
74
|
+
) -> dict[str, object]:
|
|
75
|
+
return {
|
|
76
|
+
"version": 1,
|
|
77
|
+
"decision": decision,
|
|
78
|
+
"action": intent,
|
|
79
|
+
"chain": chain,
|
|
80
|
+
"coverage": coverage,
|
|
81
|
+
"subject": {},
|
|
82
|
+
"next_steps": default_risk_next_steps(decision, intent, coverage),
|
|
83
|
+
"evidence": [],
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def build_minimal_simulation(
|
|
88
|
+
intent: str,
|
|
89
|
+
chain: str,
|
|
90
|
+
status: str,
|
|
91
|
+
readiness: str,
|
|
92
|
+
venue: str,
|
|
93
|
+
) -> dict[str, object]:
|
|
94
|
+
return {
|
|
95
|
+
"version": 1,
|
|
96
|
+
"status": status,
|
|
97
|
+
"action": intent,
|
|
98
|
+
"venue": venue,
|
|
99
|
+
"chain": chain,
|
|
100
|
+
"readiness": readiness,
|
|
101
|
+
"route": {},
|
|
102
|
+
"next_step": default_simulation_next_steps(status, readiness),
|
|
103
|
+
"evidence": [],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main() -> int:
|
|
108
|
+
parser = build_parser()
|
|
109
|
+
args = parser.parse_args()
|
|
110
|
+
try:
|
|
111
|
+
research_conclusion, research_freshness, research_brief = resolve_research_brief(
|
|
112
|
+
intent=args.intent,
|
|
113
|
+
chain=args.chain,
|
|
114
|
+
research_input_file=args.research_input_file,
|
|
115
|
+
research_conclusion=args.research_conclusion,
|
|
116
|
+
research_freshness=args.research_freshness,
|
|
117
|
+
research_question=args.research_question,
|
|
118
|
+
research_source=args.research_source,
|
|
119
|
+
research_evidence=args.research_evidence,
|
|
120
|
+
research_cross_check=args.research_cross_check,
|
|
121
|
+
research_next_step=args.research_next_step,
|
|
122
|
+
)
|
|
123
|
+
risk_decision = None
|
|
124
|
+
risk_coverage = None
|
|
125
|
+
merged_gate = None
|
|
126
|
+
if research_conclusion == "ready-for-risk-gate" or wants_risk_context(args):
|
|
127
|
+
risk_decision, risk_coverage, merged_gate = resolve_risk_gate(
|
|
128
|
+
args.risk_decision,
|
|
129
|
+
args.risk_coverage,
|
|
130
|
+
args.risk_input_file,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
simulation_status = None
|
|
134
|
+
simulation_readiness = None
|
|
135
|
+
simulation_venue = None
|
|
136
|
+
merged_simulation = None
|
|
137
|
+
if research_conclusion == "ready-for-risk-gate" or wants_simulation_context(args):
|
|
138
|
+
simulation_status, simulation_readiness, simulation_venue, merged_simulation = resolve_simulation(
|
|
139
|
+
args.simulation_status,
|
|
140
|
+
args.simulation_readiness,
|
|
141
|
+
args.simulation_venue,
|
|
142
|
+
args.simulation_input_file,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
validate_merged_context(args.intent, args.chain, merged_gate, merged_simulation)
|
|
146
|
+
except ValueError as exc:
|
|
147
|
+
parser.error(str(exc))
|
|
148
|
+
|
|
149
|
+
operator_args = argparse.Namespace(
|
|
150
|
+
intent=args.intent,
|
|
151
|
+
chain=args.chain,
|
|
152
|
+
research_conclusion=research_conclusion,
|
|
153
|
+
research_freshness=research_freshness,
|
|
154
|
+
risk_decision=args.risk_decision,
|
|
155
|
+
risk_coverage=args.risk_coverage,
|
|
156
|
+
simulation_status=args.simulation_status,
|
|
157
|
+
simulation_readiness=args.simulation_readiness,
|
|
158
|
+
simulation_venue=args.simulation_venue,
|
|
159
|
+
follow_up_skill=args.follow_up_skill,
|
|
160
|
+
reason=args.reason,
|
|
161
|
+
)
|
|
162
|
+
operator_bundle = build_pretrade_bundle(
|
|
163
|
+
operator_args,
|
|
164
|
+
research_conclusion=research_conclusion,
|
|
165
|
+
research_freshness=research_freshness,
|
|
166
|
+
risk_decision=risk_decision,
|
|
167
|
+
risk_coverage=risk_coverage,
|
|
168
|
+
simulation_status=simulation_status,
|
|
169
|
+
simulation_readiness=simulation_readiness,
|
|
170
|
+
simulation_venue=simulation_venue,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
packet: dict[str, object] = {
|
|
174
|
+
"version": 1,
|
|
175
|
+
"intent": args.intent,
|
|
176
|
+
"chain": args.chain,
|
|
177
|
+
"research_brief": research_brief,
|
|
178
|
+
"operator_bundle": operator_bundle,
|
|
179
|
+
}
|
|
180
|
+
if risk_decision is not None and risk_coverage is not None:
|
|
181
|
+
packet["risk_gate"] = (
|
|
182
|
+
merged_gate
|
|
183
|
+
if merged_gate is not None
|
|
184
|
+
else build_minimal_risk_gate(args.intent, args.chain, risk_decision, risk_coverage)
|
|
185
|
+
)
|
|
186
|
+
if simulation_status is not None and simulation_readiness is not None:
|
|
187
|
+
packet["simulation"] = (
|
|
188
|
+
merged_simulation
|
|
189
|
+
if merged_simulation is not None
|
|
190
|
+
else build_minimal_simulation(
|
|
191
|
+
args.intent,
|
|
192
|
+
args.chain,
|
|
193
|
+
simulation_status,
|
|
194
|
+
simulation_readiness,
|
|
195
|
+
simulation_venue or "",
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
json.dump({"pretrade_packet": packet}, sys.stdout, ensure_ascii=False, indent=2)
|
|
200
|
+
sys.stdout.write("\n")
|
|
201
|
+
return 0
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render a structured cron request from watch fields."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
12
|
+
parser = argparse.ArgumentParser(description="Render a watch-driven cron request.")
|
|
13
|
+
parser.add_argument("--subject", required=True)
|
|
14
|
+
parser.add_argument("--watch-kind", required=True)
|
|
15
|
+
parser.add_argument("--status", required=True)
|
|
16
|
+
parser.add_argument("--severity", required=True)
|
|
17
|
+
parser.add_argument("--recommended-adapter", required=True)
|
|
18
|
+
parser.add_argument("--next-check-hint", default="")
|
|
19
|
+
parser.add_argument("--message", default="")
|
|
20
|
+
parser.add_argument("--every-seconds", type=int, default=0)
|
|
21
|
+
parser.add_argument("--cron-expr", default="")
|
|
22
|
+
parser.add_argument("--tz", default="")
|
|
23
|
+
parser.add_argument("--at", default="")
|
|
24
|
+
return parser
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_message(args: argparse.Namespace) -> str:
|
|
28
|
+
if args.message:
|
|
29
|
+
return args.message
|
|
30
|
+
next_check = args.next_check_hint or "refresh according to the configured schedule"
|
|
31
|
+
return (
|
|
32
|
+
f"Re-run {args.recommended_adapter} for {args.subject} ({args.watch_kind}) and summarize any new evidence. "
|
|
33
|
+
f"Current status is {args.status} with severity {args.severity}. "
|
|
34
|
+
f"Next check hint: {next_check}. "
|
|
35
|
+
"Escalate immediately if severity becomes high or critical."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def schedule_key_count(args: argparse.Namespace) -> int:
|
|
40
|
+
return sum(
|
|
41
|
+
1
|
|
42
|
+
for active in (
|
|
43
|
+
args.every_seconds > 0,
|
|
44
|
+
bool(args.cron_expr),
|
|
45
|
+
bool(args.at),
|
|
46
|
+
)
|
|
47
|
+
if active
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def validate_schedule_args(args: argparse.Namespace) -> None:
|
|
52
|
+
if schedule_key_count(args) != 1:
|
|
53
|
+
raise SystemExit("exactly one of --every-seconds, --cron-expr, or --at is required")
|
|
54
|
+
if args.tz and not args.cron_expr:
|
|
55
|
+
raise SystemExit("--tz can only be used with --cron-expr")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def build_cron_request_payload(args: argparse.Namespace) -> dict[str, object]:
|
|
59
|
+
validate_schedule_args(args)
|
|
60
|
+
payload: dict[str, object] = {
|
|
61
|
+
"cron_request": {
|
|
62
|
+
"action": "add",
|
|
63
|
+
"message": build_message(args),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
request = payload["cron_request"]
|
|
67
|
+
assert isinstance(request, dict)
|
|
68
|
+
if args.every_seconds > 0:
|
|
69
|
+
request["every_seconds"] = args.every_seconds
|
|
70
|
+
elif args.cron_expr:
|
|
71
|
+
request["cron_expr"] = args.cron_expr
|
|
72
|
+
if args.tz:
|
|
73
|
+
request["tz"] = args.tz
|
|
74
|
+
else:
|
|
75
|
+
request["at"] = args.at
|
|
76
|
+
return payload
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main() -> int:
|
|
80
|
+
args = build_parser().parse_args()
|
|
81
|
+
payload = build_cron_request_payload(args)
|
|
82
|
+
json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
|
|
83
|
+
sys.stdout.write("\n")
|
|
84
|
+
return 0
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render all downstream watch artifacts in one bundle."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from render_operator_bundle import build_watch_bundle
|
|
11
|
+
from render_watch_cron_request import build_cron_request_payload
|
|
12
|
+
from render_watch_heartbeat import build_heartbeat_task
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
16
|
+
parser = argparse.ArgumentParser(description="Render a Web3 watch follow-up bundle.")
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--watch-status",
|
|
19
|
+
choices=("ACTIVE", "TRIGGERED", "CLEARED", "UNAVAILABLE"),
|
|
20
|
+
required=True,
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--watch-kind",
|
|
24
|
+
choices=("research", "portfolio", "posttrade", "trace", "venue", "execution"),
|
|
25
|
+
required=True,
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument("--subject", required=True)
|
|
28
|
+
parser.add_argument("--trigger-source", default="")
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--severity",
|
|
31
|
+
choices=("low", "medium", "high", "critical"),
|
|
32
|
+
default="medium",
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument("--next-check-hint", default="")
|
|
35
|
+
parser.add_argument("--schedule-hint", default="")
|
|
36
|
+
parser.add_argument("--recommended-adapter", default="")
|
|
37
|
+
parser.add_argument("--reason", action="append", default=[])
|
|
38
|
+
parser.add_argument("--follow-up-skill", action="append", default=[])
|
|
39
|
+
parser.add_argument("--task-note", default="")
|
|
40
|
+
parser.add_argument("--every-seconds", type=int, default=0)
|
|
41
|
+
parser.add_argument("--cron-expr", default="")
|
|
42
|
+
parser.add_argument("--tz", default="")
|
|
43
|
+
parser.add_argument("--at", default="")
|
|
44
|
+
return parser
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def maybe_build_cron_request(args: argparse.Namespace) -> dict[str, object] | None:
|
|
48
|
+
explicit_args = argparse.Namespace(
|
|
49
|
+
subject=args.subject,
|
|
50
|
+
watch_kind=args.watch_kind,
|
|
51
|
+
status=args.watch_status,
|
|
52
|
+
severity=args.severity,
|
|
53
|
+
recommended_adapter=args.recommended_adapter,
|
|
54
|
+
next_check_hint=args.next_check_hint,
|
|
55
|
+
message="",
|
|
56
|
+
every_seconds=args.every_seconds,
|
|
57
|
+
cron_expr=args.cron_expr,
|
|
58
|
+
tz=args.tz,
|
|
59
|
+
at=args.at,
|
|
60
|
+
)
|
|
61
|
+
if args.every_seconds > 0 or args.cron_expr or args.at:
|
|
62
|
+
return build_cron_request_payload(explicit_args)["cron_request"]
|
|
63
|
+
if args.schedule_hint and args.schedule_hint != "manual":
|
|
64
|
+
cron_args = argparse.Namespace(
|
|
65
|
+
subject=args.subject,
|
|
66
|
+
watch_kind=args.watch_kind,
|
|
67
|
+
status=args.watch_status,
|
|
68
|
+
severity=args.severity,
|
|
69
|
+
recommended_adapter=args.recommended_adapter,
|
|
70
|
+
next_check_hint=args.next_check_hint,
|
|
71
|
+
message="",
|
|
72
|
+
every_seconds=0,
|
|
73
|
+
cron_expr=args.schedule_hint,
|
|
74
|
+
tz=args.tz,
|
|
75
|
+
at="",
|
|
76
|
+
)
|
|
77
|
+
return build_cron_request_payload(cron_args)["cron_request"]
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def build_watch_status(args: argparse.Namespace) -> dict[str, object]:
|
|
82
|
+
return {
|
|
83
|
+
"version": 1,
|
|
84
|
+
"status": args.watch_status,
|
|
85
|
+
"watch_kind": args.watch_kind,
|
|
86
|
+
"subject": args.subject,
|
|
87
|
+
"trigger_source": args.trigger_source,
|
|
88
|
+
"severity": args.severity,
|
|
89
|
+
"next_check_hint": args.next_check_hint,
|
|
90
|
+
"schedule_hint": args.schedule_hint,
|
|
91
|
+
"recommended_adapter": args.recommended_adapter,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def main() -> int:
|
|
96
|
+
args = build_parser().parse_args()
|
|
97
|
+
operator_bundle = build_watch_bundle(args)
|
|
98
|
+
effective_adapter = args.recommended_adapter or str(operator_bundle["next_adapter"])
|
|
99
|
+
normalized_args = argparse.Namespace(**vars(args))
|
|
100
|
+
normalized_args.recommended_adapter = effective_adapter
|
|
101
|
+
heartbeat_task = build_heartbeat_task(normalized_args)
|
|
102
|
+
cron_request = maybe_build_cron_request(normalized_args)
|
|
103
|
+
payload = {
|
|
104
|
+
"watch_followup_bundle": {
|
|
105
|
+
"version": 1,
|
|
106
|
+
"watch_status": build_watch_status(normalized_args),
|
|
107
|
+
"operator_bundle": operator_bundle,
|
|
108
|
+
"heartbeat_task": heartbeat_task,
|
|
109
|
+
"cron_request": cron_request,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
|
|
113
|
+
sys.stdout.write("\n")
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
raise SystemExit(main())
|