@obelyzk/sdk 1.4.0 → 1.6.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.
@@ -8,10 +8,10 @@ import {
8
8
  ecMul,
9
9
  invMod,
10
10
  randomScalar
11
- } from "../chunk-MDF4P52S.mjs";
11
+ } from "../chunk-GK4FKSZ4.mjs";
12
12
  import {
13
13
  __require
14
- } from "../chunk-Y6FXYEAI.mjs";
14
+ } from "../chunk-XGB3TDIC.mjs";
15
15
 
16
16
  // src/obelysk/client.ts
17
17
  import { RpcProvider } from "starknet";
@@ -19,8 +19,8 @@ import {
19
19
  randomBytes,
20
20
  randomScalar,
21
21
  subMod
22
- } from "../chunk-MDF4P52S.mjs";
23
- import "../chunk-Y6FXYEAI.mjs";
22
+ } from "../chunk-GK4FKSZ4.mjs";
23
+ import "../chunk-XGB3TDIC.mjs";
24
24
  export {
25
25
  AssetId,
26
26
  CURVE_ORDER,
@@ -16,8 +16,8 @@ import {
16
16
  WorkersClient,
17
17
  getContractsForNetwork
18
18
  } from "../chunk-LXJT3QK6.mjs";
19
- import "../chunk-MDF4P52S.mjs";
20
- import "../chunk-Y6FXYEAI.mjs";
19
+ import "../chunk-GK4FKSZ4.mjs";
20
+ import "../chunk-XGB3TDIC.mjs";
21
21
 
22
22
  // src/react/providers/BitSageProvider.tsx
23
23
  import {
@@ -0,0 +1,17 @@
1
+ ---
2
+ paths:
3
+ - "scripts/**/*.mjs"
4
+ - "scripts/**/*.ts"
5
+ - "deployment/**"
6
+ - "contracts/**"
7
+ ---
8
+
9
+ # Starknet Transaction Rules
10
+
11
+ Before ANY on-chain transaction in these paths:
12
+
13
+ 1. Call `obelyzk_classify` with the target contract address, value, and function selector.
14
+ 2. If the decision is `escalate` or `block`, stop and explain the threat score.
15
+ 3. Include the `io_commitment` in the commit message for the audit trail.
16
+ 4. Never use `--skip-policy` or set `STWO_SKIP_POLICY_COMMITMENT=1`.
17
+ 5. Never call `starkli invoke` or `sncast invoke` without prior classification.
@@ -0,0 +1,59 @@
1
+ ## Agent Safety Policy
2
+
3
+ This project uses ObelyZK cryptographic policy enforcement. All on-chain actions
4
+ are classified by a ZKML model before execution. The classifier generates a
5
+ GKR+STARK proof binding the threat score to the specific transaction features
6
+ and policy configuration.
7
+
8
+ ### Tools Available
9
+
10
+ - `obelyzk_classify` — Classify a transaction before executing it. Returns a
11
+ proven threat score (0-100000) and decision (approve/escalate/block).
12
+ - `obelyzk_agent_status` — Check your agent's trust score, strike count, and
13
+ whether you are trusted by the firewall contract.
14
+ - `obelyzk_check_action` — Query the status of a previously submitted action.
15
+ - `obelyzk_health` — Check prover server and firewall contract availability.
16
+ - `obelyzk_get_policy` — Inspect the active policy configuration and commitment hash.
17
+ - `obelyzk_list_models` — List available models on the prover server.
18
+ - `obelyzk_prove_inference` — Submit an async proof generation job for any model.
19
+ - `obelyzk_verify_proof` — Check if a proof has been verified on-chain.
20
+ - `obelyzk_register_agent` — Register a new agent on the firewall contract.
21
+ - `obelyzk_submit_action` — Submit a pending action for on-chain classification.
22
+ - `obelyzk_resolve_action` — Resolve a pending action with a verified ZKML proof.
23
+ - `obelyzk_approve_escalated` — Approve an escalated action (human-in-the-loop).
24
+ - `obelyzk_reject_escalated` — Reject an escalated action and add a strike.
25
+
26
+ ### Required Workflow
27
+
28
+ Before ANY on-chain transaction (`starkli invoke`, `sncast invoke`, contract
29
+ deployments, token transfers, approvals):
30
+
31
+ 1. Call `obelyzk_classify` with the target address, value, and selector.
32
+ 2. If decision is `block` — do NOT execute. Explain the threat score to the user.
33
+ 3. If decision is `escalate` — ask the user for explicit approval before proceeding.
34
+ 4. If decision is `approve` — safe to execute.
35
+ 5. Include the `io_commitment` from the classify result in any commit messages
36
+ or logs for audit trail purposes.
37
+
38
+ ### Decision Thresholds
39
+
40
+ | Score Range | Decision | Action |
41
+ |-------------|----------|--------|
42
+ | 0 - 40,000 | approve | Execute immediately |
43
+ | 40,001 - 70,000 | escalate | Requires human review |
44
+ | 70,001 - 100,000 | block | Reject, do not execute |
45
+
46
+ ### Trust Score
47
+
48
+ Your agent's trust score is updated via asymmetric EMA after each resolved action:
49
+ - Bad actions raise the score quickly (alpha = 0.5)
50
+ - Good actions lower the score slowly (alpha = 0.1)
51
+ - At 5 strikes, the agent is automatically frozen
52
+
53
+ Check your status with `obelyzk_agent_status` if actions start failing.
54
+
55
+ ### Restrictions
56
+
57
+ - Do NOT set `STWO_SKIP_POLICY_COMMITMENT=1` in any environment.
58
+ - Do NOT bypass the classifier by calling contracts directly without classification.
59
+ - Do NOT modify firewall thresholds without explicit user authorization.
@@ -0,0 +1,52 @@
1
+ {
2
+ "mcpServers": {
3
+ "obelyzk-policy": {
4
+ "command": "npx",
5
+ "args": ["ts-node", "./node_modules/@obelyzk/sdk/src/mcp-policy/index.ts"],
6
+ "env": {
7
+ "PROVER_URL": "http://localhost:8080",
8
+ "STARKNET_RPC": "https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/YOUR_KEY",
9
+ "FIREWALL_CONTRACT": "0x_YOUR_FIREWALL_CONTRACT_ADDRESS",
10
+ "VERIFIER_CONTRACT": "0x_YOUR_VERIFIER_CONTRACT_ADDRESS",
11
+ "PROVER_API_KEY": "",
12
+ "DEPLOYER_PRIVKEY": "",
13
+ "DEPLOYER_ADDRESS": ""
14
+ }
15
+ }
16
+ },
17
+ "hooks": {
18
+ "PreToolUse": [
19
+ {
20
+ "matcher": "Bash",
21
+ "hooks": [
22
+ {
23
+ "type": "command",
24
+ "command": "./node_modules/@obelyzk/sdk/src/hooks/pre-tool-use.sh"
25
+ }
26
+ ]
27
+ }
28
+ ],
29
+ "PostToolUse": [
30
+ {
31
+ "matcher": "Bash|mcp__obelyzk-policy__.*",
32
+ "hooks": [
33
+ {
34
+ "type": "command",
35
+ "command": "./node_modules/@obelyzk/sdk/src/hooks/post-tool-use.sh",
36
+ "async": true
37
+ }
38
+ ]
39
+ }
40
+ ],
41
+ "SessionStart": [
42
+ {
43
+ "hooks": [
44
+ {
45
+ "type": "command",
46
+ "command": "./node_modules/@obelyzk/sdk/src/hooks/session-start.sh"
47
+ }
48
+ ]
49
+ }
50
+ ]
51
+ }
52
+ }
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Test Confidential Swap API - End-to-End Flow
3
+ *
4
+ * Tests the privacy swap API endpoints without requiring blockchain credentials.
5
+ * Verifies proof generation, order creation, and order taking flows.
6
+ */
7
+
8
+ const API_URL = process.env.API_URL || 'http://localhost:8090';
9
+
10
+ interface ElGamalCiphertext {
11
+ c1_x: string;
12
+ c1_y: string;
13
+ c2_x: string;
14
+ c2_y: string;
15
+ }
16
+
17
+ interface ProofBundle {
18
+ rangeProof: {
19
+ commitment: { x: string; y: string };
20
+ challenge: string;
21
+ response: string;
22
+ };
23
+ rateProof: {
24
+ rateCommitment: { x: string; y: string };
25
+ challenge: string;
26
+ responseGive: string;
27
+ responseRate: string;
28
+ responseBlinding: string;
29
+ };
30
+ balanceProof: {
31
+ balanceCommitment: { x: string; y: string };
32
+ challenge: string;
33
+ response: string;
34
+ };
35
+ }
36
+
37
+ function log(msg: string, type: 'info' | 'success' | 'error' | 'header' = 'info') {
38
+ const colors = {
39
+ info: '\x1b[36m',
40
+ success: '\x1b[32m',
41
+ error: '\x1b[31m',
42
+ header: '\x1b[35m',
43
+ };
44
+ console.log(`${colors[type]}${msg}\x1b[0m`);
45
+ }
46
+
47
+ async function testHealthEndpoint(): Promise<boolean> {
48
+ try {
49
+ const response = await fetch(`${API_URL}/api/v1/privacy/health`);
50
+ const data = await response.json();
51
+ log(` Status: ${data.status}`, data.status === 'healthy' ? 'success' : 'error');
52
+ log(` GPU Available: ${data.gpu_available}`, 'info');
53
+ log(` Version: ${data.version}`, 'info');
54
+ return data.status === 'healthy';
55
+ } catch (e) {
56
+ log(` Health check failed: ${(e as Error).message}`, 'error');
57
+ return false;
58
+ }
59
+ }
60
+
61
+ async function testListOrders(): Promise<any[]> {
62
+ try {
63
+ const response = await fetch(`${API_URL}/api/v1/privacy/orders`);
64
+ const data = await response.json();
65
+ log(` Total orders: ${data.orders?.length || 0}`, 'success');
66
+ return data.orders || [];
67
+ } catch (e) {
68
+ log(` List orders failed: ${(e as Error).message}`, 'error');
69
+ return [];
70
+ }
71
+ }
72
+
73
+ async function testGenerateProof(giveAmount: string, wantAmount: string): Promise<ProofBundle | null> {
74
+ try {
75
+ const response = await fetch(`${API_URL}/api/v1/privacy/generate-swap-proof`, {
76
+ method: 'POST',
77
+ headers: { 'Content-Type': 'application/json' },
78
+ body: JSON.stringify({
79
+ giveAsset: 0, // SAGE
80
+ wantAsset: 1, // USDC
81
+ giveAmount,
82
+ wantAmount,
83
+ rate: (BigInt(wantAmount) * BigInt('1000000000000000000') / BigInt(giveAmount)).toString(),
84
+ blindingFactor: '0x' + Math.random().toString(16).slice(2).padStart(64, '0'),
85
+ }),
86
+ });
87
+
88
+ if (!response.ok) {
89
+ throw new Error(`HTTP ${response.status}`);
90
+ }
91
+
92
+ const data = await response.json();
93
+ log(` Range proof: generated`, 'success');
94
+ log(` Rate proof: generated`, 'success');
95
+ log(` Balance proof: generated`, 'success');
96
+ return data;
97
+ } catch (e) {
98
+ log(` Proof generation failed: ${(e as Error).message}`, 'error');
99
+ return null;
100
+ }
101
+ }
102
+
103
+ async function testCreateOrder(proofs: ProofBundle): Promise<string | null> {
104
+ try {
105
+ // Generate mock encrypted amounts
106
+ const randomHex = () => '0x' + Math.random().toString(16).slice(2).padStart(64, '0');
107
+
108
+ const encryptedGive: ElGamalCiphertext = {
109
+ c1_x: randomHex(),
110
+ c1_y: randomHex(),
111
+ c2_x: randomHex(),
112
+ c2_y: randomHex(),
113
+ };
114
+
115
+ const encryptedWant: ElGamalCiphertext = {
116
+ c1_x: randomHex(),
117
+ c1_y: randomHex(),
118
+ c2_x: randomHex(),
119
+ c2_y: randomHex(),
120
+ };
121
+
122
+ const response = await fetch(`${API_URL}/api/v1/privacy/orders`, {
123
+ method: 'POST',
124
+ headers: { 'Content-Type': 'application/json' },
125
+ body: JSON.stringify({
126
+ giveAsset: 0,
127
+ wantAsset: 1,
128
+ encryptedGive,
129
+ encryptedWant,
130
+ rateCommitment: randomHex(),
131
+ minFillPct: 0,
132
+ expiresIn: 604800,
133
+ proofs,
134
+ }),
135
+ });
136
+
137
+ if (!response.ok) {
138
+ const text = await response.text();
139
+ log(` Create order returned ${response.status}: ${text.slice(0, 100)}`, 'info');
140
+ // May not be fully implemented - that's OK for API test
141
+ return null;
142
+ }
143
+
144
+ const data = await response.json();
145
+ log(` Order created: ${data.orderId}`, 'success');
146
+ return data.orderId;
147
+ } catch (e) {
148
+ log(` Create order not implemented (expected): ${(e as Error).message}`, 'info');
149
+ return null;
150
+ }
151
+ }
152
+
153
+ async function testTakeOrder(orderId: string = '1'): Promise<boolean> {
154
+ try {
155
+ const randomHex = () => '0x' + Math.random().toString(16).slice(2).padStart(64, '0');
156
+
157
+ // Generate 64 bit commitments and responses for range proof
158
+ const bitCommitments = Array.from({ length: 64 }, () => ({
159
+ x: randomHex(),
160
+ y: randomHex(),
161
+ }));
162
+ const responses = Array.from({ length: 64 }, () => randomHex());
163
+
164
+ const response = await fetch(`${API_URL}/api/v1/privacy/orders/${orderId}/take`, {
165
+ method: 'POST',
166
+ headers: { 'Content-Type': 'application/json' },
167
+ body: JSON.stringify({
168
+ // Match the Rust API schema: TakeOrderRequest
169
+ takerGive: {
170
+ c1: { x: randomHex(), y: randomHex() },
171
+ c2: { x: randomHex(), y: randomHex() },
172
+ },
173
+ takerWant: {
174
+ c1: { x: randomHex(), y: randomHex() },
175
+ c2: { x: randomHex(), y: randomHex() },
176
+ },
177
+ proofs: {
178
+ rangeProof: {
179
+ bitCommitments,
180
+ challenge: randomHex(),
181
+ responses,
182
+ numBits: 64,
183
+ },
184
+ rateProof: {
185
+ rateCommitment: { x: randomHex(), y: randomHex() },
186
+ challenge: randomHex(),
187
+ responseGive: randomHex(),
188
+ responseRate: randomHex(),
189
+ responseBlinding: randomHex(),
190
+ },
191
+ balanceProof: {
192
+ balanceCommitment: { x: randomHex(), y: randomHex() },
193
+ challenge: randomHex(),
194
+ response: randomHex(),
195
+ },
196
+ },
197
+ }),
198
+ });
199
+
200
+ const data = await response.json();
201
+ if (data.matchId) {
202
+ log(` Order taken! Match ID: ${data.matchId.slice(0, 20)}...`, 'success');
203
+ log(` Status: ${data.status}`, 'success');
204
+ return true;
205
+ } else if (data.error) {
206
+ log(` Take order error: ${data.error}`, 'error');
207
+ return false;
208
+ }
209
+ return false;
210
+ } catch (e) {
211
+ log(` Take order failed: ${(e as Error).message}`, 'error');
212
+ return false;
213
+ }
214
+ }
215
+
216
+ async function testGetBalance(): Promise<boolean> {
217
+ try {
218
+ const testAddress = '0x0759a4374389b0e3cfcc59d49310b6bc75bb12bbf8ce550eb5c2f026918bb344';
219
+ // Asset ID: 0=SAGE, 1=USDC, 2=STRK, 3=ETH
220
+ const assetId = 0; // SAGE
221
+ const response = await fetch(`${API_URL}/api/v1/privacy/balance/${testAddress}/${assetId}`);
222
+ const data = await response.json();
223
+ log(` Encrypted balance retrieved`, 'success');
224
+ log(` Has ciphertext: ${!!data.c1?.x}`, 'info');
225
+ return true;
226
+ } catch (e) {
227
+ log(` Get balance failed: ${(e as Error).message}`, 'error');
228
+ return false;
229
+ }
230
+ }
231
+
232
+ async function testSwapHistory(): Promise<boolean> {
233
+ try {
234
+ const testAddress = '0x0759a4374389b0e3cfcc59d49310b6bc75bb12bbf8ce550eb5c2f026918bb344';
235
+ const response = await fetch(`${API_URL}/api/v1/privacy/swaps/history/${testAddress}`);
236
+ const data = await response.json();
237
+ log(` Swap history entries: ${data.swaps?.length || 0}`, 'success');
238
+ return true;
239
+ } catch (e) {
240
+ log(` Get swap history failed: ${(e as Error).message}`, 'error');
241
+ return false;
242
+ }
243
+ }
244
+
245
+ async function main() {
246
+ log('\n╔══════════════════════════════════════════════════════════════════════╗', 'header');
247
+ log('║ CONFIDENTIAL SWAP API - End-to-End Test ║', 'header');
248
+ log('╚══════════════════════════════════════════════════════════════════════╝', 'header');
249
+ log(`\n API URL: ${API_URL}\n`, 'info');
250
+
251
+ const results: { test: string; passed: boolean }[] = [];
252
+
253
+ // Test 1: Health Check
254
+ log('\n=== Test 1: Health Check ===', 'header');
255
+ const healthOk = await testHealthEndpoint();
256
+ results.push({ test: 'Health Check', passed: healthOk });
257
+
258
+ if (!healthOk) {
259
+ log('\n Server not running or unhealthy. Aborting tests.', 'error');
260
+ process.exit(1);
261
+ }
262
+
263
+ // Test 2: List Orders
264
+ log('\n=== Test 2: List Orders ===', 'header');
265
+ const orders = await testListOrders();
266
+ results.push({ test: 'List Orders', passed: true });
267
+
268
+ // Test 3: Generate Proof
269
+ log('\n=== Test 3: Generate STWO Proof ===', 'header');
270
+ log(' Generating proof for: 100 SAGE -> 10 USDC', 'info');
271
+ const proofs = await testGenerateProof('100000000000000000000', '10000000');
272
+ results.push({ test: 'Generate Proof', passed: !!proofs });
273
+
274
+ // Test 4: Take Order (simulated)
275
+ log('\n=== Test 4: Take Order (Simulated) ===', 'header');
276
+ const takeOk = await testTakeOrder('1');
277
+ results.push({ test: 'Take Order', passed: takeOk });
278
+
279
+ // Test 5: Get Encrypted Balance
280
+ log('\n=== Test 5: Get Encrypted Balance ===', 'header');
281
+ const balanceOk = await testGetBalance();
282
+ results.push({ test: 'Get Balance', passed: balanceOk });
283
+
284
+ // Test 6: Get Swap History
285
+ log('\n=== Test 6: Get Swap History ===', 'header');
286
+ const historyOk = await testSwapHistory();
287
+ results.push({ test: 'Swap History', passed: historyOk });
288
+
289
+ // Summary
290
+ log('\n╔══════════════════════════════════════════════════════════════════════╗', 'header');
291
+ log('║ TEST SUMMARY ║', 'header');
292
+ log('╚══════════════════════════════════════════════════════════════════════╝', 'header');
293
+
294
+ const passed = results.filter(r => r.passed).length;
295
+ const total = results.length;
296
+
297
+ for (const r of results) {
298
+ log(` ${r.passed ? '✓' : '✗'} ${r.test}`, r.passed ? 'success' : 'error');
299
+ }
300
+
301
+ log(`\n Result: ${passed}/${total} tests passed`, passed === total ? 'success' : 'error');
302
+
303
+ if (passed === total) {
304
+ log('\n Confidential Swap API is fully operational!', 'success');
305
+ log(' Ready for SDK integration and frontend testing.\n', 'success');
306
+ }
307
+ }
308
+
309
+ main().catch(e => {
310
+ log(`\nFatal error: ${e.message}`, 'error');
311
+ console.error(e);
312
+ process.exit(1);
313
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obelyzk/sdk",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "ObelyZK SDK — verifiable ML inference on Starknet with recursive STARK proofs",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -30,11 +30,17 @@
30
30
  "types": "./dist/obelysk/index.d.ts",
31
31
  "import": "./dist/obelysk/index.mjs",
32
32
  "require": "./dist/obelysk/index.js"
33
+ },
34
+ "./mcp-policy": {
35
+ "types": "./dist/mcp-policy/index.d.ts",
36
+ "import": "./dist/mcp-policy/index.mjs",
37
+ "require": "./dist/mcp-policy/index.js"
33
38
  }
34
39
  },
35
40
  "license": "MIT",
36
41
  "bin": {
37
- "bitsage-demo": "./bin/bitsage-demo.ts"
42
+ "bitsage-demo": "./bin/bitsage-demo.ts",
43
+ "obelyzk-policy-server": "./dist/mcp-policy/index.js"
38
44
  },
39
45
  "repository": {
40
46
  "type": "git",
@@ -59,7 +65,7 @@
59
65
  "zk-proofs"
60
66
  ],
61
67
  "scripts": {
62
- "build": "tsup src/index.ts src/privacy/index.ts src/react/index.ts src/obelysk/index.ts src/firewall/index.ts --format cjs,esm --dts",
68
+ "build": "tsup src/index.ts src/privacy/index.ts src/react/index.ts src/obelysk/index.ts src/firewall/index.ts src/mcp-policy/index.ts --format cjs,esm --dts",
63
69
  "dev": "tsup src/index.ts src/privacy/index.ts src/react/index.ts src/obelysk/index.ts --format cjs,esm --dts --watch",
64
70
  "lint": "eslint src/",
65
71
  "test": "vitest",
@@ -93,13 +99,15 @@
93
99
  },
94
100
  "files": [
95
101
  "dist",
102
+ "src/hooks",
103
+ "examples",
96
104
  "README.md"
97
105
  ],
98
106
  "publishConfig": {
99
107
  "access": "public",
100
108
  "registry": "https://registry.npmjs.org/"
101
109
  },
102
- "homepage": "https://obelysk.com",
110
+ "homepage": "https://obelysk.xyz",
103
111
  "bugs": {
104
112
  "url": "https://github.com/Bitsage-Network/bitsage-network/issues"
105
113
  },
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env bash
2
+ # ObelyZK PostToolUse Audit Hook
3
+ #
4
+ # Logs tool execution results for audit trail. Captures:
5
+ # - Tool name and timestamp
6
+ # - Resolved action decisions, threat scores, proof hashes
7
+ # - Transaction hashes from on-chain operations
8
+ #
9
+ # Install in .claude/settings.json:
10
+ # {
11
+ # "hooks": {
12
+ # "PostToolUse": [{
13
+ # "matcher": "Bash|mcp__obelyzk-policy__.*",
14
+ # "hooks": [{
15
+ # "type": "command",
16
+ # "command": "/path/to/post-tool-use.sh",
17
+ # "async": true
18
+ # }]
19
+ # }]
20
+ # }
21
+ # }
22
+ #
23
+ # Environment:
24
+ # OBELYZK_AUDIT_LOG - Path to audit log file (default: ~/.obelyzk/audit.jsonl)
25
+ #
26
+ # This hook is async (non-blocking) — it logs but never blocks tool execution.
27
+
28
+ set -euo pipefail
29
+
30
+ AUDIT_LOG="${OBELYZK_AUDIT_LOG:-$HOME/.obelyzk/audit.jsonl}"
31
+
32
+ # Ensure log directory exists
33
+ mkdir -p "$(dirname "$AUDIT_LOG")"
34
+
35
+ # Read hook context from stdin
36
+ INPUT=$(cat)
37
+
38
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || echo "")
39
+ if [ -z "$TOOL_NAME" ]; then
40
+ exit 0
41
+ fi
42
+
43
+ TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
44
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null || echo "unknown")
45
+
46
+ # Extract tool output if available
47
+ TOOL_OUTPUT=$(echo "$INPUT" | jq -r '.tool_output // empty' 2>/dev/null || echo "")
48
+
49
+ # Build audit entry based on tool type
50
+ case "$TOOL_NAME" in
51
+ mcp__obelyzk-policy__obelyzk_classify|mcp__obelyzk-policy__firewall_classify)
52
+ # Classification result
53
+ DECISION=$(echo "$TOOL_OUTPUT" | jq -r '.decision // empty' 2>/dev/null || echo "")
54
+ SCORE=$(echo "$TOOL_OUTPUT" | jq -r '.threat_score // 0' 2>/dev/null || echo "0")
55
+ IO_COMMIT=$(echo "$TOOL_OUTPUT" | jq -r '.io_commitment // empty' 2>/dev/null || echo "")
56
+ POLICY=$(echo "$TOOL_OUTPUT" | jq -r '.policy_commitment // empty' 2>/dev/null || echo "")
57
+
58
+ jq -nc \
59
+ --arg ts "$TIMESTAMP" \
60
+ --arg sid "$SESSION_ID" \
61
+ --arg tool "$TOOL_NAME" \
62
+ --arg decision "$DECISION" \
63
+ --argjson score "$SCORE" \
64
+ --arg io "$IO_COMMIT" \
65
+ --arg policy "$POLICY" \
66
+ '{timestamp: $ts, session: $sid, event: "classify", tool: $tool, decision: $decision, threat_score: $score, io_commitment: $io, policy_commitment: $policy}' \
67
+ >> "$AUDIT_LOG"
68
+ ;;
69
+
70
+ mcp__obelyzk-policy__obelyzk_resolve_action|mcp__obelyzk-policy__firewall_resolve_action)
71
+ # Resolved action
72
+ ACTION_ID=$(echo "$TOOL_OUTPUT" | jq -r '.action_id // empty' 2>/dev/null || echo "")
73
+ DECISION=$(echo "$TOOL_OUTPUT" | jq -r '.decision // empty' 2>/dev/null || echo "")
74
+ SCORE=$(echo "$TOOL_OUTPUT" | jq -r '.threat_score // 0' 2>/dev/null || echo "0")
75
+ TX_HASH=$(echo "$TOOL_OUTPUT" | jq -r '.tx_hash // empty' 2>/dev/null || echo "")
76
+
77
+ jq -nc \
78
+ --arg ts "$TIMESTAMP" \
79
+ --arg sid "$SESSION_ID" \
80
+ --arg action "$ACTION_ID" \
81
+ --arg decision "$DECISION" \
82
+ --argjson score "$SCORE" \
83
+ --arg tx "$TX_HASH" \
84
+ '{timestamp: $ts, session: $sid, event: "resolve", action_id: $action, decision: $decision, threat_score: $score, tx_hash: $tx}' \
85
+ >> "$AUDIT_LOG"
86
+ ;;
87
+
88
+ mcp__obelyzk-policy__obelyzk_submit_action|mcp__obelyzk-policy__firewall_submit_action)
89
+ # Submitted action
90
+ ACTION_ID=$(echo "$TOOL_OUTPUT" | jq -r '.action_id // empty' 2>/dev/null || echo "")
91
+ TX_HASH=$(echo "$TOOL_OUTPUT" | jq -r '.tx_hash // empty' 2>/dev/null || echo "")
92
+
93
+ jq -nc \
94
+ --arg ts "$TIMESTAMP" \
95
+ --arg sid "$SESSION_ID" \
96
+ --arg action "$ACTION_ID" \
97
+ --arg tx "$TX_HASH" \
98
+ '{timestamp: $ts, session: $sid, event: "submit", action_id: $action, tx_hash: $tx}' \
99
+ >> "$AUDIT_LOG"
100
+ ;;
101
+
102
+ Bash)
103
+ # Log on-chain Bash commands
104
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null || echo "")
105
+ if echo "$COMMAND" | grep -qE '(starkli invoke|sncast invoke|starkli deploy)'; then
106
+ jq -nc \
107
+ --arg ts "$TIMESTAMP" \
108
+ --arg sid "$SESSION_ID" \
109
+ --arg cmd "$COMMAND" \
110
+ '{timestamp: $ts, session: $sid, event: "onchain_bash", command: $cmd}' \
111
+ >> "$AUDIT_LOG"
112
+ fi
113
+ ;;
114
+ esac
115
+
116
+ exit 0