@wowok/agent-mcp 2.4.1 → 2.4.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 (74) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.js +11 -1624
  3. package/dist/knowledge/guard-ledger.js +14 -0
  4. package/dist/knowledge/guard-lint.d.ts +1 -0
  5. package/dist/knowledge/guard-lint.js +83 -3
  6. package/dist/knowledge/guard-migration.d.ts +48 -0
  7. package/dist/knowledge/guard-migration.js +228 -0
  8. package/dist/knowledge/guard-risk.d.ts +2 -0
  9. package/dist/knowledge/guard-risk.js +827 -57
  10. package/dist/knowledge/guard-templates.d.ts +1 -0
  11. package/dist/knowledge/guard-templates.js +278 -2
  12. package/dist/project/graph.d.ts +1 -0
  13. package/dist/project/graph.js +27 -1
  14. package/dist/project/index.d.ts +1 -0
  15. package/dist/project/index.js +39 -2
  16. package/dist/project/namespace.d.ts +11 -1
  17. package/dist/project/namespace.js +27 -2
  18. package/dist/project/query.d.ts +2 -0
  19. package/dist/project/query.js +56 -15
  20. package/dist/rules.d.ts +12 -0
  21. package/dist/rules.js +9 -0
  22. package/dist/schema/call/allocation.d.ts +10 -10
  23. package/dist/schema/call/base.js +3 -3
  24. package/dist/schema/call/machine.d.ts +38 -38
  25. package/dist/schema/call/semantic.js +1 -1
  26. package/dist/schema/call/service.d.ts +7 -7
  27. package/dist/schema/operations.d.ts +48 -48
  28. package/dist/schema/query/index.d.ts +146 -146
  29. package/dist/schema/query/index.js +15 -9
  30. package/dist/schema/utils/guard-query-utils.d.ts +7 -0
  31. package/dist/schema/utils/guard-query-utils.js +14 -1
  32. package/dist/schemas/bridge_operation.output.json +1 -1
  33. package/dist/schemas/index.json +1 -1
  34. package/dist/schemas/messenger_operation.output.json +1 -1
  35. package/dist/schemas/onchain_events.output.json +1 -1
  36. package/dist/schemas/onchain_operations.output.json +2 -2
  37. package/dist/schemas/onchain_operations.schema.json +11 -20
  38. package/dist/schemas/onchain_operations_allocation.schema.json +7 -10
  39. package/dist/schemas/onchain_operations_machine.schema.json +2 -5
  40. package/dist/schemas/onchain_operations_service.schema.json +2 -5
  41. package/dist/schemas/onchain_table_data.output.json +6 -12
  42. package/dist/tools/handlers/bridge.d.ts +1 -0
  43. package/dist/tools/handlers/bridge.js +1 -0
  44. package/dist/tools/handlers/config.d.ts +2 -0
  45. package/dist/tools/handlers/config.js +71 -0
  46. package/dist/tools/handlers/file-export.d.ts +3 -0
  47. package/dist/tools/handlers/file-export.js +90 -0
  48. package/dist/tools/handlers/local.d.ts +30 -0
  49. package/dist/tools/handlers/local.js +27 -0
  50. package/dist/tools/handlers/messenger.d.ts +16 -0
  51. package/dist/tools/handlers/messenger.js +187 -0
  52. package/dist/tools/handlers/onchain.d.ts +2 -0
  53. package/dist/tools/handlers/onchain.js +246 -0
  54. package/dist/tools/handlers/project.d.ts +2 -0
  55. package/dist/tools/handlers/project.js +53 -0
  56. package/dist/tools/handlers/query.d.ts +5 -0
  57. package/dist/tools/handlers/query.js +256 -0
  58. package/dist/tools/handlers/schema-query.d.ts +2 -0
  59. package/dist/tools/handlers/schema-query.js +92 -0
  60. package/dist/tools/handlers/trust.d.ts +2 -0
  61. package/dist/tools/handlers/trust.js +194 -0
  62. package/dist/tools/handlers/wip.d.ts +2 -0
  63. package/dist/tools/handlers/wip.js +44 -0
  64. package/dist/tools/index.d.ts +13 -0
  65. package/dist/tools/index.js +479 -0
  66. package/dist/tools/rules-hook.d.ts +2 -0
  67. package/dist/tools/rules-hook.js +22 -0
  68. package/dist/tools/shared.d.ts +29 -0
  69. package/dist/tools/shared.js +130 -0
  70. package/dist/tools/types.d.ts +35 -0
  71. package/dist/tools/types.js +1 -0
  72. package/dist/tools/wrap.d.ts +6 -0
  73. package/dist/tools/wrap.js +55 -0
  74. package/package.json +19 -5
@@ -13,6 +13,7 @@ export const GUARD_SCENES = [
13
13
  "The name of b_submission=true entries must be a natural-language description (the contract the customer sees)",
14
14
  "If the Guard references the Service itself, use the circular reference pattern (CREATE Service → CREATE Guard → MODIFY Service)",
15
15
  "buy_guard can still be modified after Service is published (via MODIFY Service)",
16
+ "Reentrancy: LOW — Each purchase creates a fresh Order; no existing Order state is modified. Default: no reentrancy risk. IF business intent is 'limit one purchase per user' (limited-edition goods, whitelist single-buy), an anti-reentrancy check is REQUIRED but no direct GUARDQUERY primitive exists for per-user purchase history — use a Repository to record prior purchases and add `repository.data has` (query 1166) + `logic_not` to root, or revoke EntityRegistrar membership after first buy",
16
17
  ],
17
18
  common_patterns: [
18
19
  "Identity check: context(Signer) + logic_equal (single address) / vec_contains_address (whitelist)",
@@ -39,12 +40,18 @@ export const GUARD_SCENES = [
39
40
  "Guard usually queries Order/Progress state to determine allocation conditions",
40
41
  "Common queries: order.amount, progress.current_node, order.completed, etc.",
41
42
  "GuardIdentifier can be used for dynamic amounts (extract u64 value from table as allocation ratio)",
43
+ "Guard+sharing coupling: Guard determines IF allocation happens; sharing.who determines WHERE funds go. If sharing.who=Signer, the caller receives funds — the Guard MUST bind Signer (context(Signer)==authorized) to prevent theft. PREFERRED: use sharing.who=Entity (Treasury or fixed address) so funds always go to the legitimate recipient regardless of caller.",
44
+ "allocators require unique guard addresses: no two allocators may reference the same Guard object (create separate Guard objects with identical root/table but different descriptions if needed).",
45
+ "First-match-wins: only the first allocator whose Guard passes executes; subsequent allocators are skipped. Multiple allocators represent alternative strategies, not a simultaneous split.",
46
+ "If sharing.who=Entity references a Treasury object, verify the Treasury's permission is consistent with the Service's permission (different permissions = different permission organizations = minor risk).",
47
+ "Reentrancy: LOW — Protocol-level protection: order::on_allocation (order.move L116-119) asserts `allocation` field is None (E_ALLOCATION_ALREADY_SPECIFIED), enforcing ONE Allocation object per Order, ever. After alloc() executes, typical Rate-mode allocators (sum=10000, validated at allocation.move L162-163) and any Surplus-mode allocator drain balance to 0 via balance::withdraw_all (L254), making a second alloc() fail at `assert!(balance >= threshold)` (L193). EDGE CASE: Amount-mode-only allocators (no Surplus, total < full balance) leave remainder; a second alloc() could extract more — mitigated by preferring Rate/Surplus modes. External deposit via receive() (L291) is possible but irrational (attacker funds the Allocation they attack). Optional anti-reentrancy primitive: `order.allocation some` (query 1568) + logic_not",
42
48
  ],
43
49
  common_patterns: [
44
50
  "Node state check: query(Progress.current_node) + logic_equal('Completed')",
45
51
  "Amount threshold: query(Order.amount) + logic_as_u256_greater_or_equal(threshold)",
46
52
  "Multi-condition combination: logic_and (node state + amount + time)",
47
53
  "GuardIdentifier dynamic ratio: table[u64] extracts allocation ratio",
54
+ "Treasury+personal 2-allocator pattern: 2 Guard objects (identical root/table, different descriptions) + 2 allocators (sharing.who=Entity Treasury / Entity personal) — 2 alternative merchant collection approaches; first-match-wins means only the first executes.",
48
55
  ],
49
56
  mutable_after_publish: false,
50
57
  r_rounds_focus: ["R1", "R2", "R3", "R5", "R6", "R7"],
@@ -64,6 +71,7 @@ export const GUARD_SCENES = [
64
71
  "retained_submission indices must correspond to the Guard table",
65
72
  "Common queries: progress.current_node, progress.session_forward, order.service, etc.",
66
73
  "Time lock: context(Clock) + calc_number_add(progress.entry_time, duration) tested with 1000ms",
74
+ "Reentrancy: ALLOWED — By design, Forward moves progress.current_node to next_node (progress.move L443), naturally invalidating the Guard at the old node. Re-submission at the SAME node (before forward completes) is intentional: users may fix prior submission data or accumulate session weight toward threshold. No anti-reentrancy primitive needed. Optional observability queries: `progress.session_forward` (1262), `progress.history` (1273), `query_progress_history_find` (1274)",
67
75
  ],
68
76
  common_patterns: [
69
77
  "Identity check: context(Signer) + logic_equal (only specific parties can advance)",
@@ -88,6 +96,7 @@ export const GUARD_SCENES = [
88
96
  "Works in concert with Forward.guard: Forward.guard validates eligibility, submission guard validates data",
89
97
  "retained_submission stores submission data in Progress session for audit",
90
98
  "Common queries: progress.session_forward, progress.session_weight, etc.",
99
+ "Reentrancy: ALLOWED — Multiple submissions accumulate session weight toward threshold by design (progress.move session_accomplish_imp). Re-submission is the intended mechanism for reaching threshold; no anti-reentrancy primitive needed",
91
100
  ],
92
101
  common_patterns: [
93
102
  "Signature validation: identifier(signature_submission) + logic_equal(expected)",
@@ -110,6 +119,7 @@ export const GUARD_SCENES = [
110
119
  "Common query_reward_record_count prevents duplicate claims (one-time claim)",
111
120
  "Guard usually queries history records + time conditions + identity validation",
112
121
  "If the Reward references a Service, use the circular reference pattern",
122
+ "Reentrancy: CRITICAL — Guard pass distributes reward funds to the Claimant AND creates a reward_record, but the Guard conditions (identity, time, node state) do NOT naturally invalidate after a claim. Without an explicit anti-reentrancy primitive, the same Claimant can repeatedly pass the Guard and DRAIN THE ENTIRE POOL. This is the most severe reentrancy scene. REQUIRED anti-reentrancy primitive (any one): `reward.record has` (query 1613, Bool) + `logic_not`; OR `reward.record count` (query 1612, U64) + `logic_equal(0)`; OR `reward.record.find_first` (query 1626, U64) + `logic_not`/`logic_equal(0)`. Reference template: MyShop_Advanced Guard 7 condition 4 — `logic_not(query_reward_record_exists(where.storeFromId=order_id))`",
113
123
  ],
114
124
  common_patterns: [
115
125
  "One-time claim: query_reward_record_count(recipient) + logic_equal(0)",
@@ -134,6 +144,7 @@ export const GUARD_SCENES = [
134
144
  "The table entry referenced by data_from_submission must match the Repository's value_type",
135
145
  "Type mismatch leads to write failure or data corruption",
136
146
  "Commonly used for oracle data writes, third-party credential attestation, etc.",
147
+ "Reentrancy: LOW — Default writes are idempotent (same ID overwrites, different ID appends); no fund flow. IF business intent is 'one write per ID' (e.g. voting, immutable credential, single-attestation), an anti-reentrancy check is needed. Optional primitive: `repository.data has` (query 1166, Bool) + `logic_not`",
137
148
  ],
138
149
  common_patterns: [
139
150
  "Authorized writer: context(Signer) + vec_contains_address(authorized_writers)",
@@ -157,6 +168,7 @@ export const GUARD_SCENES = [
157
168
  "The customer must first generate a Passport (gen_passport passes usage_guard)",
158
169
  "Common queries: order.completed, order.service, entity.has, etc.",
159
170
  "Guard validates customer identity + order status + dispute eligibility",
171
+ "Reentrancy: HIGH — Guard pass creates a new Arb case. Without anti-reentrancy, a customer can create multiple Arb cases for the SAME Order (up to MAX_DISPUTE_COUNT=10 per order.move L96), harassing the counterparty and inflating arbitration system load. Not direct fund loss, but severe system abuse. Note: order::dispute (order.move L93-99) deduplicates by Arb address but does NOT prevent multiple distinct Arbs for the same Order. REQUIRED anti-reentrancy primitive (any one): `order.dispute has` (query 1565, Bool, per-Arb check) + `logic_not`; OR `order.dispute count` (query 1564, U64) + `logic_equal(0)` (strict: block any prior dispute) or `logic_as_u256_less_or_equal(N)` (lenient: allow up to N disputes)",
160
172
  ],
161
173
  common_patterns: [
162
174
  "Order status validation: query(Order.status) + logic_equal('completed')",
@@ -182,6 +194,7 @@ export const GUARD_SCENES = [
182
194
  "b_submission must be true (weight is submitted at runtime by the voter)",
183
195
  "Multiple voting_guards can be configured, each corresponding to a different voter group",
184
196
  "When Arbitration bPaused=true, voting is blocked",
197
+ "Reentrancy: CRITICAL — Guard pass counts the vote with the extracted weight. Without anti-reentrancy, the same voter can vote MULTIPLE times, multiplying their weight and skewing the arbitration result (which directly determines fund ownership via compensation). The voter's eligibility (identity, reputation) does NOT naturally invalidate after voting. REQUIRED anti-reentrancy primitive (any one): `arb.voted has` (query 1404, Bool, per-voter) + `logic_not`; OR `arb.voted.agree has` (query 1406, Bool, per-voter-per-proposition) + `logic_not`; OR `arb.voted.agree count` (query 1405, U64, per-voter) + `logic_equal(0)`. Note: `arb.voted_count` (1403) is total voter count (NOT per-voter) and does NOT serve as an anti-reentrancy primitive for individual voters",
185
198
  ],
186
199
  common_patterns: [
187
200
  "Reputation score weight: query(EntityRegistrar.records[reputation]) + GuardIdentifier(u64)",
@@ -208,6 +221,7 @@ export const GUARD_SCENES = [
208
221
  "Guard can be created standalone (not bound to a specific Host Object)",
209
222
  "Guard can be depended on by other Guards via rely (requires rep=true)",
210
223
  "The Passport generated by gen_passport can be used as submission data for subsequent operations",
224
+ "Reentrancy: LOW — Each gen_passport call generates an independent Passport (one Passport per operation). Multiple Passports for the same user are usually valid (each serves a different subsequent operation). IF business intent is 'one active Passport per user', an anti-reentrancy check is needed but no direct GUARDQUERY primitive exists — use off-chain tracking or a Repository to record issued Passport IDs",
211
225
  ],
212
226
  common_patterns: [
213
227
  "Identity validation: context(Signer) + logic_equal (single address/whitelist)",
@@ -34,6 +34,7 @@ export interface GuardJsonInput {
34
34
  host_object?: string;
35
35
  binding_field?: string;
36
36
  action?: string;
37
+ sharing_recipients?: ("Signer" | "Entity" | "GuardIdentifier")[];
37
38
  };
38
39
  }
39
40
  export interface AutoFixAction {
@@ -81,10 +81,15 @@ async function checkSdkSyntax(json) {
81
81
  function checkSemanticErrors(json) {
82
82
  const diagnostics = [];
83
83
  const { table, root, binding_hint } = json;
84
+ const scene = binding_hint ? findScene(binding_hint.host_object ?? "", binding_hint.binding_field ?? "") : undefined;
84
85
  const submissionEntries = getSubmissionEntries(table);
85
86
  if (submissionEntries.length > 0 && root && !hasSignerContext(root)) {
86
87
  const hasAddressSubmission = submissionEntries.some((e) => e.value_type === "Address");
87
- if (hasAddressSubmission) {
88
+ const isAllocatorsEntitySharing = scene?.id === "service_order_allocators_guard" &&
89
+ binding_hint?.sharing_recipients !== undefined &&
90
+ binding_hint.sharing_recipients.length > 0 &&
91
+ !binding_hint.sharing_recipients.includes("Signer");
92
+ if (hasAddressSubmission && !isAllocatorsEntitySharing) {
88
93
  const indices = submissionEntries
89
94
  .filter((e) => e.value_type === "Address")
90
95
  .map((e) => table.indexOf(e));
@@ -101,7 +106,6 @@ function checkSemanticErrors(json) {
101
106
  });
102
107
  }
103
108
  }
104
- const scene = binding_hint ? findScene(binding_hint.host_object ?? "", binding_hint.binding_field ?? "") : undefined;
105
109
  if (scene?.id === "arbitration_voting_guard") {
106
110
  const hasWeightSubmission = submissionEntries.some((e) => ["U8", "U16", "U32", "U64", "U128", "U256"].includes(e.value_type ?? ""));
107
111
  if (hasWeightSubmission) {
@@ -238,6 +242,22 @@ function checkSyntaxHints(json) {
238
242
  });
239
243
  }
240
244
  }
245
+ for (let i = 0; i < table.length; i++) {
246
+ const entry = table[i];
247
+ if (entry.b_submission === true && entry.value !== undefined && entry.value !== null) {
248
+ diagnostics.push({
249
+ code: "SH-11",
250
+ message: `table[${i}] has a redundant value field on b_submission=true entry (the verifier supplies the value at runtime; this value is ignored by BCS encoding and wastes gas)`,
251
+ severity: "warning",
252
+ source: "lint",
253
+ location: { path: `/table/${i}/value` },
254
+ fix: {
255
+ description: "Remove the value field — it is ignored for b_submission=true entries",
256
+ auto_fixable: true,
257
+ },
258
+ });
259
+ }
260
+ }
241
261
  return diagnostics;
242
262
  }
243
263
  function checkSuggestions(json) {
@@ -324,6 +344,46 @@ function checkSuggestions(json) {
324
344
  });
325
345
  }
326
346
  }
347
+ if (root) {
348
+ const rootStr = rootToString(root);
349
+ const hasProofSigner = rootStr.includes("proof.signer");
350
+ const hasProofTime = rootStr.includes("proof.time");
351
+ const hasStringLength = rootStr.includes("calc_string_length");
352
+ const hasStringSubmission = submissionEntries.some((e) => e.value_type === "String");
353
+ if (hasProofSigner && hasProofTime) {
354
+ diagnostics.push({
355
+ code: "SG-04",
356
+ message: "Strict-mode privacy-delivery guard detected (proof.signer + proof.time). " +
357
+ "Recommended three conditions: Signer==proof.signer (submitter accountability), " +
358
+ "proof.time>order.time (freshness), order.service==service (project binding). " +
359
+ "No content verification — only submission responsibility ('whoever benefits bears the burden of proof').",
360
+ severity: "info",
361
+ source: "lint",
362
+ location: { path: "/root" },
363
+ fix: {
364
+ description: "Ensure all three conditions are present (logic_and). Refer to template tpl_forward_privacy_delivery_proof. " +
365
+ "Query IDs: proof.signer=1958, proof.time=1960, order.time=1574, order.service=1563.",
366
+ auto_fixable: false,
367
+ },
368
+ });
369
+ }
370
+ else if (hasStringLength && hasStringSubmission) {
371
+ diagnostics.push({
372
+ code: "SG-04",
373
+ message: "Broad-mode privacy-delivery guard detected (String submission + length check, e.g. Merkle Root length == 66). " +
374
+ "This trusts the submitter's honesty; the counterparty can dispute by checking their own Messenger conversation. " +
375
+ "Suitable as an arbitration basis, not a fund-flow gate.",
376
+ severity: "info",
377
+ source: "lint",
378
+ location: { path: "/root" },
379
+ fix: {
380
+ description: "For stronger assurance, consider strict mode: submit a Proof object address (from messenger.submitChainProof) + " +
381
+ "Order address, then verify proof.signer/proof.time/order.service. See template tpl_forward_privacy_delivery_proof.",
382
+ auto_fixable: false,
383
+ },
384
+ });
385
+ }
386
+ }
327
387
  return diagnostics;
328
388
  }
329
389
  export function autoFixGuard(json) {
@@ -433,6 +493,20 @@ export function autoFixGuard(json) {
433
493
  fixed.rely.guards = unique;
434
494
  }
435
495
  }
496
+ for (let i = 0; i < fixed.table.length; i++) {
497
+ const entry = fixed.table[i];
498
+ if (entry.b_submission === true && entry.value !== undefined && entry.value !== null) {
499
+ const before = JSON.stringify(entry.value);
500
+ delete entry.value;
501
+ actions.push({
502
+ diagnostic_code: "SH-11",
503
+ description: `table[${i}] remove redundant value on b_submission=true entry`,
504
+ before,
505
+ after: "(removed)",
506
+ path: `/table/${i}/value`,
507
+ });
508
+ }
509
+ }
436
510
  return { fixed, actions };
437
511
  }
438
512
  export async function lintGuard(json, options) {
@@ -467,7 +541,13 @@ export async function lintGuard(json, options) {
467
541
  name: e.name,
468
542
  object_type: e.object_type,
469
543
  }));
470
- risk_assessment = assessGuardRisks({ table: mappedTable, root, rely, scene });
544
+ risk_assessment = assessGuardRisks({
545
+ table: mappedTable,
546
+ root,
547
+ rely,
548
+ scene,
549
+ sharing_recipients: binding_hint?.sharing_recipients,
550
+ });
471
551
  for (const r of risk_assessment.risks) {
472
552
  const severity = r.level === "critical" || r.level === "high" ? "error" :
473
553
  r.level === "medium" ? "warning" :
@@ -0,0 +1,48 @@
1
+ export type GuardNodeType = 'logic_or' | 'logic_and' | 'logic_not' | 'logic_equal' | 'logic_string_nocase_equal' | 'logic_as_u256_equal' | 'logic_string_contains' | 'logic_string_nocase_contains' | 'logic_as_u256_greater' | 'logic_as_u256_lesser' | 'logic_as_u256_greater_or_equal' | 'logic_as_u256_lesser_or_equal' | 'vec_contains_string' | 'vec_contains_string_nocase' | 'vec_contains_address' | 'vec_contains_number' | 'vec_contains_bool' | 'identifier' | 'query' | 'context' | 'calc_number_add' | 'calc_number_subtract' | 'calc_number_multiply' | 'calc_number_divide' | 'calc_number_mod' | string;
2
+ export interface GuardNode {
3
+ type: GuardNodeType;
4
+ nodes?: GuardNode[];
5
+ node?: GuardNode;
6
+ identifier?: number;
7
+ query?: string | number;
8
+ context?: string;
9
+ object?: {
10
+ identifier?: number;
11
+ convert_witness?: string | number;
12
+ };
13
+ parameters?: unknown[];
14
+ }
15
+ export interface GuardTableEntry {
16
+ identifier: number;
17
+ b_submission?: boolean;
18
+ value_type?: string | number;
19
+ value?: unknown;
20
+ name?: string;
21
+ }
22
+ export type VecContainsTarget = 'vec_contains_string' | 'vec_contains_string_nocase' | 'vec_contains_address' | 'vec_contains_number' | 'vec_contains_bool';
23
+ export type VecValueType = 'VecString' | 'VecAddress' | 'VecU64' | 'VecU128' | 'VecU256' | 'VecU8' | 'VecU16' | 'VecU32' | 'VecBool';
24
+ export interface ConversionResult {
25
+ convertible: boolean;
26
+ targetOperator?: VecContainsTarget;
27
+ vectorValueType?: VecValueType;
28
+ sharedNode?: GuardNode;
29
+ constants?: Array<{
30
+ identifier: number;
31
+ value: unknown;
32
+ value_type: string;
33
+ }>;
34
+ reasons?: string[];
35
+ }
36
+ export declare function analyzeLogicOrConversion(orNode: GuardNode, table: GuardTableEntry[]): ConversionResult;
37
+ export interface ShorthandResult {
38
+ newRoot: GuardNode;
39
+ removeIdentifiers: number[];
40
+ addEntry: {
41
+ identifier: number;
42
+ b_submission: false;
43
+ value_type: VecValueType;
44
+ value: unknown[];
45
+ name: string;
46
+ };
47
+ }
48
+ export declare function buildShorthand(orNode: GuardNode, result: ConversionResult, newIdentifier: number): ShorthandResult | null;
@@ -0,0 +1,228 @@
1
+ const EQUAL_OPERATORS = new Set([
2
+ 'logic_equal',
3
+ 'logic_string_nocase_equal',
4
+ 'logic_as_u256_equal',
5
+ ]);
6
+ function mapOperatorToTarget(operator, valueType) {
7
+ if (valueType === 'String' || valueType === 2) {
8
+ if (operator === 'logic_string_nocase_equal') {
9
+ return { target: 'vec_contains_string_nocase', vecType: 'VecString' };
10
+ }
11
+ if (operator === 'logic_equal') {
12
+ return { target: 'vec_contains_string', vecType: 'VecString' };
13
+ }
14
+ return null;
15
+ }
16
+ if (valueType === 'Address' || valueType === 1) {
17
+ if (operator === 'logic_equal') {
18
+ return { target: 'vec_contains_address', vecType: 'VecAddress' };
19
+ }
20
+ return null;
21
+ }
22
+ if (valueType === 'Bool' || valueType === 0) {
23
+ if (operator === 'logic_equal') {
24
+ return { target: 'vec_contains_bool', vecType: 'VecBool' };
25
+ }
26
+ return null;
27
+ }
28
+ const numericTypes = new Set([
29
+ 'U8', 'U16', 'U32', 'U64', 'U128', 'U256',
30
+ 3, 4, 5, 6, 7, 8,
31
+ ]);
32
+ if (numericTypes.has(valueType)) {
33
+ if (operator === 'logic_equal' || operator === 'logic_as_u256_equal') {
34
+ return { target: 'vec_contains_number', vecType: 'VecU256' };
35
+ }
36
+ return null;
37
+ }
38
+ return null;
39
+ }
40
+ export function analyzeLogicOrConversion(orNode, table) {
41
+ const reasons = [];
42
+ if (orNode.type !== 'logic_or') {
43
+ return {
44
+ convertible: false,
45
+ reasons: [`Node type is '${orNode.type}', not 'logic_or'. Only logic_or nodes are candidates.`],
46
+ };
47
+ }
48
+ const children = orNode.nodes ?? [];
49
+ if (children.length < 2) {
50
+ return {
51
+ convertible: false,
52
+ reasons: [`logic_or has ${children.length} children; need at least 2 to convert.`],
53
+ };
54
+ }
55
+ if (children.length > 7) {
56
+ return {
57
+ convertible: false,
58
+ reasons: [
59
+ `logic_or has ${children.length} children; vec_contains_* supports at most 7 needles (1 haystack + 7 needles = 8 node limit). Keep the logic_or.`,
60
+ ],
61
+ };
62
+ }
63
+ const childOperators = new Set();
64
+ for (const child of children) {
65
+ if (!EQUAL_OPERATORS.has(child.type)) {
66
+ if (child.type === 'logic_string_contains' || child.type === 'logic_string_nocase_contains') {
67
+ reasons.push(`Child uses '${child.type}' (substring match). vec_contains_* is whole-element membership, NOT substring. Cannot convert.`);
68
+ }
69
+ else if (child.type?.startsWith('logic_as_u256_greater') || child.type?.startsWith('logic_as_u256_lesser')) {
70
+ reasons.push(`Child uses '${child.type}' (ordering comparison). vec_contains_* is membership, not ordering. Cannot convert.`);
71
+ }
72
+ else if (child.type === 'logic_and' || child.type === 'logic_or' || child.type === 'logic_not') {
73
+ reasons.push(`Child is a nested logic node ('${child.type}'). Only leaf equal-style comparisons are convertible.`);
74
+ }
75
+ else {
76
+ reasons.push(`Child uses '${child.type}' which is not an equal-style operator (logic_equal / logic_string_nocase_equal / logic_as_u256_equal).`);
77
+ }
78
+ return { convertible: false, reasons };
79
+ }
80
+ childOperators.add(child.type);
81
+ }
82
+ if (childOperators.size > 1) {
83
+ const opList = Array.from(childOperators).join(', ');
84
+ return {
85
+ convertible: false,
86
+ reasons: [
87
+ `Children use mixed operators: [${opList}]. Mixing case-sensitive (logic_equal) and case-insensitive (logic_string_nocase_equal) comparisons would change semantics. Keep the logic_or.`,
88
+ ],
89
+ };
90
+ }
91
+ const sharedOperator = children[0].type;
92
+ for (let i = 0; i < children.length; i++) {
93
+ const childNodes = children[i].nodes ?? [];
94
+ if (childNodes.length !== 2) {
95
+ return {
96
+ convertible: false,
97
+ reasons: [
98
+ `Child ${i} ('${sharedOperator}') has ${childNodes.length} sub-nodes; expected exactly 2 (one constant side + one shared query side).`,
99
+ ],
100
+ };
101
+ }
102
+ }
103
+ const constants = [];
104
+ let sharedNode = null;
105
+ let sharedNodeSerial = null;
106
+ for (let i = 0; i < children.length; i++) {
107
+ const childNodes = children[i].nodes;
108
+ const left = childNodes[0];
109
+ const right = childNodes[1];
110
+ const leftIsConstId = left.type === 'identifier' && typeof left.identifier === 'number';
111
+ const rightIsConstId = right.type === 'identifier' && typeof right.identifier === 'number';
112
+ if (!leftIsConstId && !rightIsConstId) {
113
+ return {
114
+ convertible: false,
115
+ reasons: [
116
+ `Child ${i} has no constant identifier side (both sub-nodes are non-identifier). Conversion requires one side to be a constant table entry.`,
117
+ ],
118
+ };
119
+ }
120
+ if (leftIsConstId && rightIsConstId) {
121
+ return {
122
+ convertible: false,
123
+ reasons: [
124
+ `Child ${i} has two identifier sides. Conversion requires one side to be a non-constant (query/context/calc) node shared across all children.`,
125
+ ],
126
+ };
127
+ }
128
+ const constIdNode = leftIsConstId ? left : right;
129
+ const nonConstNode = leftIsConstId ? right : left;
130
+ const tableEntry = table.find((e) => e.identifier === constIdNode.identifier);
131
+ if (!tableEntry) {
132
+ return {
133
+ convertible: false,
134
+ reasons: [
135
+ `Child ${i} references identifier[${constIdNode.identifier}] which is not in the table.`,
136
+ ],
137
+ };
138
+ }
139
+ if (tableEntry.b_submission !== false) {
140
+ return {
141
+ convertible: false,
142
+ reasons: [
143
+ `Child ${i} references identifier[${constIdNode.identifier}] with b_submission=${tableEntry.b_submission}. Conversion requires compile-time constants (b_submission=false).`,
144
+ ],
145
+ };
146
+ }
147
+ if (tableEntry.value === undefined || tableEntry.value === null) {
148
+ return {
149
+ convertible: false,
150
+ reasons: [
151
+ `Child ${i} references identifier[${constIdNode.identifier}] which has no 'value' field. Conversion requires explicit constant values.`,
152
+ ],
153
+ };
154
+ }
155
+ const nonConstSerial = JSON.stringify(nonConstNode);
156
+ if (sharedNodeSerial === null) {
157
+ sharedNodeSerial = nonConstSerial;
158
+ sharedNode = nonConstNode;
159
+ }
160
+ else if (sharedNodeSerial !== nonConstSerial) {
161
+ return {
162
+ convertible: false,
163
+ reasons: [
164
+ `Child ${i}'s non-constant side differs from child 0's. All children must share the SAME non-constant node (same query, same convert_witness, same parameters). Keep the logic_or.`,
165
+ ],
166
+ };
167
+ }
168
+ constants.push({
169
+ identifier: constIdNode.identifier,
170
+ value: tableEntry.value,
171
+ value_type: String(tableEntry.value_type ?? ''),
172
+ });
173
+ }
174
+ const valueTypes = new Set(constants.map((c) => c.value_type));
175
+ if (valueTypes.size > 1) {
176
+ return {
177
+ convertible: false,
178
+ reasons: [
179
+ `Constants have mixed value_types: [${Array.from(valueTypes).join(', ')}]. All constants must share the same type to form a single VecXxx.`,
180
+ ],
181
+ };
182
+ }
183
+ const sharedValueType = constants[0].value_type;
184
+ const mapping = mapOperatorToTarget(sharedOperator, sharedValueType);
185
+ if (!mapping) {
186
+ return {
187
+ convertible: false,
188
+ reasons: [
189
+ `No vec_contains_* variant maps to operator='${sharedOperator}' with value_type='${sharedValueType}'. ` +
190
+ `Mappable combinations: (logic_string_nocase_equal, String)→vec_contains_string_nocase; ` +
191
+ `(logic_equal, String)→vec_contains_string; (logic_equal, Address)→vec_contains_address; ` +
192
+ `(logic_equal, Bool)→vec_contains_bool; (logic_equal|logic_as_u256_equal, numeric)→vec_contains_number.`,
193
+ ],
194
+ };
195
+ }
196
+ return {
197
+ convertible: true,
198
+ targetOperator: mapping.target,
199
+ vectorValueType: mapping.vecType,
200
+ sharedNode: sharedNode ?? undefined,
201
+ constants,
202
+ };
203
+ }
204
+ export function buildShorthand(orNode, result, newIdentifier) {
205
+ if (!result.convertible || !result.targetOperator || !result.vectorValueType || !result.sharedNode || !result.constants) {
206
+ return null;
207
+ }
208
+ const values = result.constants.map((c) => c.value);
209
+ const removeIdentifiers = result.constants.map((c) => c.identifier);
210
+ const newRoot = {
211
+ type: result.targetOperator,
212
+ nodes: [
213
+ { type: 'identifier', identifier: newIdentifier },
214
+ result.sharedNode,
215
+ ],
216
+ };
217
+ return {
218
+ newRoot,
219
+ removeIdentifiers,
220
+ addEntry: {
221
+ identifier: newIdentifier,
222
+ b_submission: false,
223
+ value_type: result.vectorValueType,
224
+ value: values,
225
+ name: 'converted_constants',
226
+ },
227
+ };
228
+ }
@@ -46,6 +46,7 @@ export interface RiskCheckContext {
46
46
  };
47
47
  scene?: GuardScene;
48
48
  operation_type?: string;
49
+ sharing_recipients?: ("Signer" | "Entity" | "GuardIdentifier")[];
49
50
  }
50
51
  export declare function assessGuardRisks(params: {
51
52
  table: RiskCheckContext["table"];
@@ -53,6 +54,7 @@ export declare function assessGuardRisks(params: {
53
54
  rely?: RiskCheckContext["rely"];
54
55
  scene?: GuardScene;
55
56
  operation_type?: string;
57
+ sharing_recipients?: RiskCheckContext["sharing_recipients"];
56
58
  }): GuardRiskAssessment;
57
59
  export declare function assessGuardRisksForOperation(operation_type: string, data: any): GuardRiskAssessment | null;
58
60
  export declare function getRiskSummary(assessment: GuardRiskAssessment): {