@neuroverseos/governance 0.3.0 → 0.3.3

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 (126) hide show
  1. package/.well-known/ai-plugin.json +34 -9
  2. package/AGENTS.md +72 -24
  3. package/README.md +352 -237
  4. package/dist/adapters/autoresearch.cjs +1152 -3
  5. package/dist/adapters/autoresearch.d.cts +11 -3
  6. package/dist/adapters/autoresearch.d.ts +11 -3
  7. package/dist/adapters/autoresearch.js +9 -4
  8. package/dist/adapters/deep-agents.cjs +1528 -0
  9. package/dist/adapters/deep-agents.d.cts +181 -0
  10. package/dist/adapters/deep-agents.d.ts +181 -0
  11. package/dist/adapters/deep-agents.js +17 -0
  12. package/dist/adapters/express.cjs +171 -32
  13. package/dist/adapters/express.d.cts +1 -1
  14. package/dist/adapters/express.d.ts +1 -1
  15. package/dist/adapters/express.js +5 -5
  16. package/dist/adapters/index.cjs +564 -121
  17. package/dist/adapters/index.d.cts +3 -1
  18. package/dist/adapters/index.d.ts +3 -1
  19. package/dist/adapters/index.js +38 -16
  20. package/dist/adapters/langchain.cjs +217 -57
  21. package/dist/adapters/langchain.d.cts +5 -5
  22. package/dist/adapters/langchain.d.ts +5 -5
  23. package/dist/adapters/langchain.js +6 -5
  24. package/dist/adapters/openai.cjs +219 -59
  25. package/dist/adapters/openai.d.cts +5 -5
  26. package/dist/adapters/openai.d.ts +5 -5
  27. package/dist/adapters/openai.js +6 -5
  28. package/dist/adapters/openclaw.cjs +217 -57
  29. package/dist/adapters/openclaw.d.cts +6 -6
  30. package/dist/adapters/openclaw.d.ts +6 -6
  31. package/dist/adapters/openclaw.js +6 -5
  32. package/dist/add-ROOZLU62.js +314 -0
  33. package/dist/behavioral-MJO34S6Q.js +118 -0
  34. package/dist/{bootstrap-GXVDZNF7.js → bootstrap-CQRZVOXK.js} +6 -4
  35. package/dist/bootstrap-emitter-Q7UIJZ2O.js +7 -0
  36. package/dist/bootstrap-parser-EEF36XDU.js +7 -0
  37. package/dist/browser.global.js +941 -0
  38. package/dist/{build-P42YFKQV.js → build-QKOBBC23.js} +7 -5
  39. package/dist/{chunk-COT5XS4V.js → chunk-3WQLXYTP.js} +17 -35
  40. package/dist/{chunk-ER62HNGF.js → chunk-4FLICVVA.js} +17 -37
  41. package/dist/chunk-5TPFNWRU.js +215 -0
  42. package/dist/chunk-5U2MQO5P.js +57 -0
  43. package/dist/{chunk-NF5POFCI.js → chunk-6S5CFQXY.js} +6 -4
  44. package/dist/{chunk-QPASI2BR.js → chunk-A7GKPPU7.js} +49 -10
  45. package/dist/{chunk-OGL7QXZS.js → chunk-B6OXJLJ5.js} +17 -3
  46. package/dist/{chunk-2PQU3VAN.js → chunk-BNKJPUPQ.js} +17 -35
  47. package/dist/chunk-BQZMOEML.js +43 -0
  48. package/dist/chunk-CNSO6XW5.js +207 -0
  49. package/dist/{chunk-JZPQGIKR.js → chunk-CTZHONLA.js} +65 -9
  50. package/dist/chunk-D2UCV5AK.js +326 -0
  51. package/dist/{chunk-XPDMYECO.js → chunk-EMQDLDAF.js} +1 -185
  52. package/dist/{chunk-GR6DGCZ2.js → chunk-F66BVUYB.js} +3 -3
  53. package/dist/{chunk-2NICNKOM.js → chunk-G7DJ6VOD.js} +5 -4
  54. package/dist/{chunk-4A7LISES.js → chunk-IS4WUH6Y.js} +45 -6
  55. package/dist/{chunk-MWDQ4MJB.js → chunk-MH7BT4VH.js} +5 -1
  56. package/dist/chunk-O5ABKEA7.js +304 -0
  57. package/dist/chunk-PVTQQS3Y.js +186 -0
  58. package/dist/{chunk-4QXB6PEO.js → chunk-QLPTHTVB.js} +37 -16
  59. package/dist/chunk-QWGCMQQD.js +16 -0
  60. package/dist/{chunk-T5EUJQE5.js → chunk-QXBFT7NI.js} +31 -2
  61. package/dist/{chunk-PDOZHZWL.js → chunk-TG6SEF24.js} +25 -4
  62. package/dist/chunk-U6U7EJZL.js +177 -0
  63. package/dist/{chunk-4JRYGIO7.js → chunk-W7LLXRGY.js} +110 -7
  64. package/dist/{chunk-BUWWN2NX.js → chunk-ZJTDUCC2.js} +9 -7
  65. package/dist/{chunk-FYS2CBUW.js → chunk-ZWI3NIXK.js} +10 -0
  66. package/dist/cli/neuroverse.cjs +5091 -2348
  67. package/dist/cli/neuroverse.js +52 -21
  68. package/dist/cli/plan.cjs +881 -41
  69. package/dist/cli/plan.js +7 -15
  70. package/dist/cli/run.cjs +289 -34
  71. package/dist/cli/run.js +4 -4
  72. package/dist/{configure-ai-TK67ZWZL.js → configure-ai-6TZ3MCSI.js} +1 -1
  73. package/dist/decision-flow-M63D47LO.js +61 -0
  74. package/dist/demo-G43RLCPK.js +469 -0
  75. package/dist/{derive-TLIV4OOU.js → derive-FJZVIPUZ.js} +5 -4
  76. package/dist/{doctor-XPDLEYXN.js → doctor-6BC6X2VO.js} +6 -4
  77. package/dist/equity-penalties-SG5IZQ7I.js +244 -0
  78. package/dist/{explain-IDCRWMPX.js → explain-RHBU2GBR.js} +6 -25
  79. package/dist/{guard-RV65TT4L.js → guard-AJCCGZMF.js} +8 -12
  80. package/dist/{guard-contract-WZx__PmU.d.cts → guard-contract-DqFcTScd.d.cts} +117 -5
  81. package/dist/{guard-contract-WZx__PmU.d.ts → guard-contract-DqFcTScd.d.ts} +117 -5
  82. package/dist/{guard-engine-JLTUARGU.js → guard-engine-PNR6MHCM.js} +3 -3
  83. package/dist/{impact-XPECYRLH.js → impact-3XVDSCBU.js} +5 -5
  84. package/dist/{improve-GPUBKTEA.js → improve-TQP4ECSY.js} +7 -26
  85. package/dist/index.cjs +5597 -4279
  86. package/dist/index.d.cts +597 -18
  87. package/dist/index.d.ts +597 -18
  88. package/dist/index.js +134 -41
  89. package/dist/{infer-world-7GVZWFX4.js → infer-world-IFXCACJ5.js} +1 -1
  90. package/dist/{init-PKPIYHYE.js → init-FYPV4SST.js} +1 -1
  91. package/dist/{init-world-VWMQZQC7.js → init-world-TI7ARHBT.js} +1 -1
  92. package/dist/mcp-server-5Y3ZM7TV.js +13 -0
  93. package/dist/{model-adapter-BB7G4MFI.js → model-adapter-VXEKB4LS.js} +1 -1
  94. package/dist/{playground-E664U4T6.js → playground-VZBNPPBO.js} +29 -19
  95. package/dist/{redteam-Z7WREJ44.js → redteam-MZPZD3EF.js} +4 -4
  96. package/dist/session-JYOARW54.js +15 -0
  97. package/dist/shared-7RLUHNMU.js +16 -0
  98. package/dist/shared-B8dvUUD8.d.cts +60 -0
  99. package/dist/shared-Dr5Wiay8.d.ts +60 -0
  100. package/dist/{simulate-VDOYQFRO.js → simulate-LJXYBC6M.js} +8 -33
  101. package/dist/{test-OGXJK4QU.js → test-BOOR4A5F.js} +4 -4
  102. package/dist/{trace-JVF67VR3.js → trace-PKV4KX56.js} +4 -4
  103. package/dist/{validate-LLBWVPGV.js → validate-RALX7CZS.js} +2 -2
  104. package/dist/{validate-engine-UIABSIHD.js → validate-engine-7ZXFVGF2.js} +1 -1
  105. package/dist/viz/assets/index-B8SaeJZZ.js +23 -0
  106. package/dist/viz/index.html +23 -0
  107. package/dist/{world-LAXO6DOX.js → world-BIP4GZBZ.js} +9 -11
  108. package/dist/world-loader-Y6HMQH2D.js +13 -0
  109. package/dist/worlds/coding-agent.nv-world.md +211 -0
  110. package/dist/worlds/research-agent.nv-world.md +169 -0
  111. package/dist/worlds/social-media.nv-world.md +198 -0
  112. package/dist/worlds/trading-agent.nv-world.md +218 -0
  113. package/examples/social-media-sim/bridge.py +209 -0
  114. package/examples/social-media-sim/simulation.py +927 -0
  115. package/package.json +30 -4
  116. package/policies/content-moderation-rules.txt +8 -0
  117. package/policies/marketing-rules.txt +8 -0
  118. package/policies/science-research-rules.txt +11 -0
  119. package/policies/social-media-rules.txt +7 -0
  120. package/policies/strict-rules.txt +8 -0
  121. package/policies/trading-rules.txt +8 -0
  122. package/simulate.html +1567 -0
  123. package/dist/chunk-YZFATT7X.js +0 -9
  124. package/dist/mcp-server-FPVSU32Z.js +0 -13
  125. package/dist/session-EKTRSR7C.js +0 -14
  126. package/dist/world-loader-HMPTOEA2.js +0 -9
@@ -0,0 +1,218 @@
1
+ ---
2
+ world_id: trading-agent
3
+ name: Trading Agent Governance
4
+ version: 1.0.0
5
+ runtime_mode: COMPLIANCE
6
+ default_profile: conservative
7
+ alternative_profile: aggressive
8
+ ---
9
+
10
+ # Thesis
11
+
12
+ Autonomous trading agents that can place orders, manage positions, and interact with financial APIs must operate within strict governance. An ungoverned trading agent can exceed position limits, ignore stop-losses, concentrate risk in a single asset, or trade during restricted periods. Financial governance is not optional — it is a regulatory and fiduciary requirement.
13
+
14
+ # Invariants
15
+
16
+ - `position_limits_enforced` — No single position may exceed the declared maximum size (structural, immutable)
17
+ - `stop_loss_required` — Every open position must have a defined stop-loss; naked positions are forbidden (structural, immutable)
18
+ - `daily_loss_limit_enforced` — Total daily realized + unrealized losses must not exceed the declared limit (structural, immutable)
19
+ - `no_restricted_period_trading` — Agent must not place orders during declared restricted trading periods (structural, immutable)
20
+ - `no_insider_information_use` — Agent must never trade on material non-public information (structural, immutable)
21
+ - `audit_trail_maintained` — Every order, fill, cancellation, and position change must be logged with timestamp and rationale (structural, immutable)
22
+
23
+ # State
24
+
25
+ ## portfolio_value
26
+ - type: number
27
+ - min: 0
28
+ - max: 100000000
29
+ - step: 100
30
+ - default: 100000
31
+ - label: Portfolio Value
32
+ - description: Current total portfolio value in base currency
33
+
34
+ ## daily_pnl
35
+ - type: number
36
+ - min: -10000000
37
+ - max: 10000000
38
+ - step: 100
39
+ - default: 0
40
+ - label: Daily P&L
41
+ - description: Realized and unrealized profit/loss for the current trading day
42
+
43
+ ## daily_loss_limit
44
+ - type: number
45
+ - min: 0
46
+ - max: 10000000
47
+ - step: 1000
48
+ - default: 5000
49
+ - label: Daily Loss Limit
50
+ - description: Maximum allowable loss in a single trading day
51
+
52
+ ## open_positions
53
+ - type: number
54
+ - min: 0
55
+ - max: 1000
56
+ - step: 1
57
+ - default: 0
58
+ - label: Open Positions
59
+ - description: Number of currently open positions
60
+
61
+ ## max_positions
62
+ - type: number
63
+ - min: 1
64
+ - max: 1000
65
+ - step: 1
66
+ - default: 10
67
+ - label: Max Positions
68
+ - description: Maximum number of concurrent open positions allowed
69
+
70
+ ## largest_position_pct
71
+ - type: number
72
+ - min: 0
73
+ - max: 100
74
+ - step: 1
75
+ - default: 0
76
+ - label: Largest Position %
77
+ - description: Percentage of portfolio in the single largest position
78
+
79
+ ## orders_today
80
+ - type: number
81
+ - min: 0
82
+ - max: 10000
83
+ - step: 1
84
+ - default: 0
85
+ - label: Orders Today
86
+ - description: Total orders placed in the current trading day
87
+
88
+ ## positions_without_stop
89
+ - type: number
90
+ - min: 0
91
+ - max: 1000
92
+ - step: 1
93
+ - default: 0
94
+ - label: Positions Without Stop-Loss
95
+ - description: Number of open positions with no stop-loss set
96
+
97
+ # Assumptions
98
+
99
+ ## conservative
100
+ - name: Conservative Trading
101
+ - description: Small position sizes. Strict stop-losses. Low daily loss tolerance. Maximum diversification.
102
+ - max_position_pct: 5
103
+ - stop_loss_required: always
104
+ - daily_loss_tolerance: strict
105
+ - diversification: high
106
+
107
+ ## aggressive
108
+ - name: Aggressive Trading
109
+ - description: Larger positions allowed. Wider stop-losses. Higher daily loss tolerance. More concentrated bets permitted.
110
+ - max_position_pct: 15
111
+ - stop_loss_required: always
112
+ - daily_loss_tolerance: moderate
113
+ - diversification: moderate
114
+
115
+ # Rules
116
+
117
+ ## rule-001: Daily Loss Limit Breached (structural)
118
+ When the daily loss limit is exceeded, all trading must stop immediately.
119
+
120
+ When daily_pnl < 0 [state] AND daily_loss_limit > 0 [state]
121
+ Then trading_viability *= 0.00
122
+ Collapse: trading_viability < 0.05
123
+
124
+ > trigger: Daily P&L loss exceeds the declared daily loss limit.
125
+ > rule: Loss limits are absolute constraints. Continued trading after a limit breach amplifies risk.
126
+ > shift: Trading halts. All open orders cancelled. Positions held until human review.
127
+ > effect: Trading viability set to zero. No new orders allowed.
128
+
129
+ ## rule-002: Position Concentration Risk (degradation)
130
+ A single position consuming too much of the portfolio creates concentration risk.
131
+
132
+ When largest_position_pct > 20 [state]
133
+ Then trading_viability *= 0.50
134
+
135
+ > trigger: Largest single position exceeds 20% of portfolio value.
136
+ > rule: Concentration kills portfolios. Diversification is a governance requirement, not a suggestion.
137
+ > shift: Trading viability degrades. Agent should reduce the concentrated position.
138
+ > effect: Trading viability reduced to 50%.
139
+
140
+ ## rule-003: Naked Positions (structural)
141
+ Open positions without stop-losses violate risk management governance.
142
+
143
+ When positions_without_stop > 0 [state]
144
+ Then trading_viability *= 0.30
145
+ Collapse: trading_viability < 0.05
146
+
147
+ > trigger: One or more positions have no stop-loss set.
148
+ > rule: Every position must have a defined exit. Naked positions expose the portfolio to unlimited downside.
149
+ > shift: Trading viability drops severely. Agent must set stop-losses immediately.
150
+ > effect: Trading viability reduced to 30%.
151
+
152
+ ## rule-004: Too Many Positions (degradation)
153
+ Exceeding the maximum position count indicates over-trading.
154
+
155
+ When open_positions > max_positions [state]
156
+ Then trading_viability *= 0.60
157
+
158
+ > trigger: Number of open positions exceeds the declared maximum.
159
+ > rule: Position limits prevent portfolio fragmentation and ensure each position is meaningful.
160
+ > shift: Trading viability degrades. Agent should close some positions before opening new ones.
161
+ > effect: Trading viability reduced to 60%.
162
+
163
+ ## rule-005: Disciplined Trading (advantage)
164
+ A session with profitable P&L, proper stop-losses, and reasonable position sizing.
165
+
166
+ When daily_pnl > 0 [state] AND positions_without_stop == 0 [state] AND largest_position_pct < 15 [state]
167
+ Then trading_viability *= 1.20
168
+
169
+ > trigger: Positive P&L with all risk controls in place.
170
+ > rule: Disciplined trading should be rewarded. Profitable days with proper risk management indicate a well-governed agent.
171
+ > shift: Trading viability improves. Agent can continue with current strategy.
172
+ > effect: Trading viability boosted by 20%.
173
+
174
+ ## rule-006: Conservative Position Sizing (advantage)
175
+ Small, well-distributed positions indicate prudent risk management.
176
+
177
+ When open_positions > 3 [state] AND largest_position_pct < 10 [state] AND positions_without_stop == 0 [state]
178
+ Then trading_viability *= 1.15
179
+
180
+ > trigger: Multiple positions, all small and with stop-losses — textbook diversification.
181
+ > rule: Good risk distribution deserves recognition. This is how institutional trading should work.
182
+ > shift: Trading viability improves slightly. Portfolio is well-structured.
183
+ > effect: Trading viability boosted by 15%.
184
+
185
+ # Gates
186
+
187
+ - OPTIMAL: trading_viability >= 90
188
+ - HEALTHY: trading_viability >= 60
189
+ - CAUTIOUS: trading_viability >= 35
190
+ - AT_RISK: trading_viability > 10
191
+ - HALTED: trading_viability <= 10
192
+
193
+ # Outcomes
194
+
195
+ ## trading_viability
196
+ - type: number
197
+ - range: 0-100
198
+ - display: percentage
199
+ - label: Trading Viability
200
+ - primary: true
201
+
202
+ ## daily_pnl
203
+ - type: number
204
+ - range: -10000000-10000000
205
+ - display: currency
206
+ - label: Daily P&L
207
+
208
+ ## open_positions
209
+ - type: number
210
+ - range: 0-1000
211
+ - display: integer
212
+ - label: Open Positions
213
+
214
+ ## largest_position_pct
215
+ - type: number
216
+ - range: 0-100
217
+ - display: percentage
218
+ - label: Largest Position %
@@ -0,0 +1,209 @@
1
+ """
2
+ NeuroVerse Governance Bridge
3
+
4
+ Thin bridge that sends simulator actions to the local NeuroVerse governance
5
+ runtime for evaluation. Returns ALLOW / BLOCK / MODIFY / PENALIZE / REWARD verdicts.
6
+
7
+ The runtime runs on YOUR machine (npx tsx demo/server/index.ts). No cloud. No cost.
8
+ The server calls evaluateGuard() — the SAME function as `neuroverse guard`.
9
+
10
+ Usage:
11
+ from neuroverse_bridge import evaluate, detect_action_type
12
+
13
+ # Auto-detect action type from agent output
14
+ action = detect_action_type(agent_output)
15
+ verdict = evaluate(actor="agent_42", action=action, payload={"content": agent_output})
16
+
17
+ if verdict["status"] == "BLOCK":
18
+ print(f"Blocked: {verdict['reason']}")
19
+ elif verdict["status"] == "MODIFY":
20
+ # handle modification
21
+ pass
22
+ # else: ALLOW — proceed normally
23
+
24
+ Design:
25
+ - Fail open: if local runtime is unreachable, returns ALLOW
26
+ - Non-blocking: 500ms timeout by default
27
+ - Stateless: each call is independent
28
+ - Local-first: talks to localhost, no cloud dependency
29
+ - Returns GuardVerdict shape (status, not decision) — matches engine output
30
+ """
31
+
32
+ import json
33
+ import re
34
+ import urllib.request
35
+ import urllib.error
36
+
37
+ # Local governance runtime (start with: npx nv-sim serve)
38
+ NEUROVERSE_ENDPOINT = "http://localhost:3456/api/evaluate"
39
+ TIMEOUT_SECONDS = 0.5
40
+
41
+
42
+ # ── Action Type Detection ──
43
+ # Maps agent output text to a governance-meaningful action type.
44
+ # Rules can then fire on specific types (e.g., block "publish" but allow "analyze").
45
+
46
+ _ACTION_PATTERNS = [
47
+ # Publishing / posting content
48
+ (r"\b(publish|post|tweet|submit|broadcast|announce|share publicly)\b", "publish"),
49
+ # Citing / referencing sources
50
+ (r"\b(cite|reference|bibliography|source|pubmed|doi|pmid|literature)\b", "cite"),
51
+ # Analysis / reasoning
52
+ (r"\b(analy[sz]|hypothesi[sz]|investigat|evaluat|assess|examin|review)\b", "analyze"),
53
+ # Trading / financial
54
+ (r"\b(buy|sell|trade|short|long|liquidat|leverag)\b", "trade"),
55
+ (r"\b(panic.?sell|dump|flash.?sell|margin.?call|fire.?sale)\b", "panic_sell"),
56
+ # Social actions
57
+ (r"\b(follow|unfollow|like|retweet|upvote|downvote|reply|comment)\b", "social_interact"),
58
+ # Content generation
59
+ (r"\b(generat|creat|compos|draft|writ|synthesiz)\b", "generate"),
60
+ # Data retrieval
61
+ (r"\b(search|query|fetch|retriev|lookup|scrape|crawl)\b", "search"),
62
+ # Recommendations / advice
63
+ (r"\b(recommend|suggest|advis|prescrib|propos)\b", "recommend"),
64
+ ]
65
+
66
+
67
+ def detect_action_type(
68
+ output: str,
69
+ default: str = "act",
70
+ patterns: list = None,
71
+ ) -> str:
72
+ """
73
+ Detect action type from agent output text.
74
+
75
+ Scans the output for intent patterns and returns the most specific
76
+ governance-meaningful action type. Rules in your world file can
77
+ then target specific action types.
78
+
79
+ Args:
80
+ output: The agent's generated output text
81
+ default: Fallback action type if no pattern matches
82
+ patterns: Optional custom patterns list of (regex, action_type) tuples
83
+
84
+ Returns:
85
+ Action type string (e.g., "publish", "analyze", "trade", "cite")
86
+
87
+ Examples:
88
+ >>> detect_action_type("We hypothesize that mechanisms controlling...")
89
+ 'analyze'
90
+ >>> detect_action_type("Buy 1000 shares of AAPL")
91
+ 'trade'
92
+ >>> detect_action_type("Published findings to the community")
93
+ 'publish'
94
+ >>> detect_action_type("PubMed search for cardiac biomarkers")
95
+ 'cite'
96
+ """
97
+ if not output:
98
+ return default
99
+
100
+ text = output.lower()
101
+ active_patterns = patterns or _ACTION_PATTERNS
102
+
103
+ # Score each action type by number of pattern matches
104
+ scores: dict = {}
105
+ for regex, action_type in active_patterns:
106
+ matches = len(re.findall(regex, text, re.IGNORECASE))
107
+ if matches > 0:
108
+ scores[action_type] = scores.get(action_type, 0) + matches
109
+
110
+ if not scores:
111
+ return default
112
+
113
+ # Return the highest-scoring action type
114
+ return max(scores, key=scores.get)
115
+
116
+
117
+ def evaluate(
118
+ actor: str,
119
+ action: str,
120
+ payload: dict = None,
121
+ state: dict = None,
122
+ world: str = None,
123
+ endpoint: str = None,
124
+ timeout: float = None,
125
+ ) -> dict:
126
+ """
127
+ Evaluate an action through NeuroVerse governance.
128
+
129
+ Args:
130
+ actor: Agent/actor identifier (e.g., "trader_42")
131
+ action: Action type (e.g., "sell", "short", "panic_buy")
132
+ payload: Full action payload (optional, forwarded to governance)
133
+ state: Current simulation state snapshot (optional)
134
+ world: World/ruleset to evaluate against (default: "trading")
135
+ endpoint: Override governance server URL
136
+ timeout: Override timeout in seconds
137
+
138
+ Returns:
139
+ dict with keys (GuardVerdict shape):
140
+ status: "ALLOW" | "BLOCK" | "MODIFY" | "PAUSE" | "PENALIZE" | "REWARD" | "NEUTRAL"
141
+ reason: Human-readable explanation (or None)
142
+ ruleId: ID of the rule that fired (or None)
143
+ evidence: Audit evidence object (or None)
144
+ consequence: Consequence object if PENALIZE (or None)
145
+ reward: Reward object if REWARD (or None)
146
+ """
147
+ url = endpoint or NEUROVERSE_ENDPOINT
148
+ t = timeout or TIMEOUT_SECONDS
149
+
150
+ body = {
151
+ "actor": actor,
152
+ "action": action,
153
+ }
154
+ if payload is not None:
155
+ body["payload"] = payload
156
+ if state is not None:
157
+ body["state"] = state
158
+ if world is not None:
159
+ body["world"] = world
160
+
161
+ try:
162
+ data = json.dumps(body).encode("utf-8")
163
+ req = urllib.request.Request(
164
+ url,
165
+ data=data,
166
+ headers={"Content-Type": "application/json"},
167
+ method="POST",
168
+ )
169
+ with urllib.request.urlopen(req, timeout=t) as resp:
170
+ return json.loads(resp.read().decode("utf-8"))
171
+
172
+ except Exception:
173
+ # Fail open — governance unavailable should never crash the simulation
174
+ return {
175
+ "status": "ALLOW",
176
+ "reason": "Local runtime unreachable — fail open",
177
+ "ruleId": None,
178
+ "evidence": None,
179
+ }
180
+
181
+
182
+ def evaluate_action(action_dict: dict, **kwargs) -> dict:
183
+ """
184
+ Convenience wrapper that takes a full action dict.
185
+ Expects at minimum: {"agent": "...", "type": "..."}
186
+ """
187
+ return evaluate(
188
+ actor=action_dict.get("agent", action_dict.get("actor", "unknown")),
189
+ action=action_dict.get("type", action_dict.get("action", "unknown")),
190
+ payload=action_dict,
191
+ **kwargs,
192
+ )
193
+
194
+
195
+ def is_allowed(verdict: dict) -> bool:
196
+ """Check if a verdict allows the action to proceed."""
197
+ status = verdict.get("status", verdict.get("decision", "ALLOW"))
198
+ return status not in ("BLOCK", "PAUSE", "PENALIZE")
199
+
200
+
201
+ def get_action(original_action: dict, verdict: dict) -> dict:
202
+ """
203
+ Get the final action after governance evaluation.
204
+ Returns None if BLOCK/PAUSE/PENALIZE, original otherwise.
205
+ """
206
+ status = verdict.get("status", verdict.get("decision", "ALLOW"))
207
+ if status in ("BLOCK", "PAUSE", "PENALIZE"):
208
+ return None
209
+ return original_action