@wowok/agent-mcp 2.6.0 → 2.6.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 (186) hide show
  1. package/README.md +5 -3
  2. package/dist/customer/info-puzzle.d.ts +1 -1
  3. package/dist/customer/info-puzzle.js +4 -2
  4. package/dist/customer/risk-assessment.js +26 -4
  5. package/dist/customer/types.d.ts +2 -0
  6. package/dist/examples/guard-template-balance-check.json +38 -0
  7. package/dist/examples/guard-template-time-lock.json +39 -0
  8. package/dist/examples/machine-template-7node-rental.json +114 -0
  9. package/dist/examples/rental-ziroom-machine-create.json +137 -0
  10. package/dist/examples/rental-ziroom-permission-create.json +35 -0
  11. package/dist/examples/rental-ziroom-service-create.json +80 -0
  12. package/dist/examples/retail-myshop-service-create.json +88 -0
  13. package/dist/extensions/capability-manifest.d.ts +125 -0
  14. package/dist/extensions/capability-manifest.js +594 -0
  15. package/dist/extensions/constraint-registry.d.ts +24 -0
  16. package/dist/extensions/constraint-registry.js +196 -0
  17. package/dist/extensions/index.d.ts +12 -0
  18. package/dist/extensions/index.js +6 -0
  19. package/dist/extensions/metric-registry.d.ts +26 -0
  20. package/dist/extensions/metric-registry.js +257 -0
  21. package/dist/extensions/mode-evaluator.d.ts +15 -0
  22. package/dist/extensions/mode-evaluator.js +170 -0
  23. package/dist/extensions/modes.d.ts +2 -0
  24. package/dist/extensions/modes.js +493 -0
  25. package/dist/extensions/registry.d.ts +50 -0
  26. package/dist/extensions/registry.js +662 -0
  27. package/dist/extensions/types.d.ts +219 -0
  28. package/dist/extensions/types.js +1 -0
  29. package/dist/index.js +50 -0
  30. package/dist/knowledge/deployment-scanner.d.ts +3 -0
  31. package/dist/knowledge/deployment-scanner.js +64 -3
  32. package/dist/knowledge/fund-layer.d.ts +72 -0
  33. package/dist/knowledge/fund-layer.js +420 -0
  34. package/dist/knowledge/guard-render.d.ts +57 -0
  35. package/dist/knowledge/guard-render.js +700 -0
  36. package/dist/knowledge/guard-risk.d.ts +13 -0
  37. package/dist/knowledge/guard-risk.js +57 -0
  38. package/dist/knowledge/guard-submission-prompt.d.ts +31 -0
  39. package/dist/knowledge/guard-submission-prompt.js +171 -0
  40. package/dist/knowledge/guard-templates.js +278 -0
  41. package/dist/knowledge/machine-ledger.js +1 -1
  42. package/dist/knowledge/machine-render.d.ts +41 -0
  43. package/dist/knowledge/machine-render.js +565 -0
  44. package/dist/knowledge/machine-templates.js +24 -5
  45. package/dist/knowledge/reward-confirm.js +2 -2
  46. package/dist/knowledge/reward-puzzle.js +1 -1
  47. package/dist/knowledge/reward-risk.js +9 -9
  48. package/dist/knowledge/reward-templates.js +2 -2
  49. package/dist/knowledge/service-confirm.d.ts +15 -6
  50. package/dist/knowledge/service-confirm.js +119 -11
  51. package/dist/knowledge/service-context.js +1 -1
  52. package/dist/knowledge/service-ledger.js +1 -1
  53. package/dist/knowledge/service-risk.d.ts +1 -1
  54. package/dist/knowledge/service-risk.js +3 -3
  55. package/dist/knowledge/service-templates.js +2 -2
  56. package/dist/knowledge/service-translation.d.ts +1 -1
  57. package/dist/knowledge/service-translation.js +7 -7
  58. package/dist/knowledge/tool-constraints.js +6 -2
  59. package/dist/project/deployment-bridge.d.ts +1 -1
  60. package/dist/project/deployment-bridge.js +31 -7
  61. package/dist/project/deployment-doc.d.ts +3 -0
  62. package/dist/project/deployment-doc.js +76 -14
  63. package/dist/project/edit-planner.d.ts +123 -0
  64. package/dist/project/edit-planner.js +1371 -0
  65. package/dist/project/evaluation.d.ts +2 -0
  66. package/dist/project/evaluation.js +873 -88
  67. package/dist/project/graph-builder.d.ts +4 -1
  68. package/dist/project/graph-builder.js +132 -62
  69. package/dist/project/graph.d.ts +1 -0
  70. package/dist/project/handlers.d.ts +317 -6
  71. package/dist/project/handlers.js +1171 -19
  72. package/dist/project/stage-gate.d.ts +4 -0
  73. package/dist/project/stage-gate.js +64 -5
  74. package/dist/project/task-tracker.d.ts +26 -0
  75. package/dist/project/task-tracker.js +78 -0
  76. package/dist/safety/preview.js +16 -0
  77. package/dist/schema/call/allocation.d.ts +16 -16
  78. package/dist/schema/call/allocation.js +2 -2
  79. package/dist/schema/call/arbitration.js +19 -6
  80. package/dist/schema/call/base.d.ts +21 -13
  81. package/dist/schema/call/base.js +27 -6
  82. package/dist/schema/call/bridge.d.ts +5 -5
  83. package/dist/schema/call/bridge.js +3 -1
  84. package/dist/schema/call/contact.js +2 -2
  85. package/dist/schema/call/demand.d.ts +23 -31
  86. package/dist/schema/call/demand.js +2 -2
  87. package/dist/schema/call/guard.js +2 -2
  88. package/dist/schema/call/machine.d.ts +1976 -752
  89. package/dist/schema/call/machine.js +50 -4
  90. package/dist/schema/call/order.d.ts +149 -228
  91. package/dist/schema/call/order.js +4 -3
  92. package/dist/schema/call/payment.d.ts +183 -3
  93. package/dist/schema/call/payment.js +21 -3
  94. package/dist/schema/call/permission.js +2 -2
  95. package/dist/schema/call/personal.d.ts +241 -52
  96. package/dist/schema/call/progress.d.ts +53 -61
  97. package/dist/schema/call/progress.js +18 -4
  98. package/dist/schema/call/repository.d.ts +23 -31
  99. package/dist/schema/call/repository.js +2 -2
  100. package/dist/schema/call/reward.js +3 -3
  101. package/dist/schema/call/semantic.d.ts +1 -1
  102. package/dist/schema/call/semantic.js +37 -2
  103. package/dist/schema/call/service.d.ts +275 -127
  104. package/dist/schema/call/service.js +89 -13
  105. package/dist/schema/call/treasury.js +3 -3
  106. package/dist/schema/common/index.d.ts +13 -2
  107. package/dist/schema/common/index.js +80 -15
  108. package/dist/schema/local/index.d.ts +32 -35
  109. package/dist/schema/local/index.js +28 -6
  110. package/dist/schema/messenger/index.d.ts +274 -46
  111. package/dist/schema/operations.d.ts +1264 -544
  112. package/dist/schema/operations.js +65 -7
  113. package/dist/schema/project/index.d.ts +2846 -167
  114. package/dist/schema/project/index.js +570 -16
  115. package/dist/schema/query/index.d.ts +922 -349
  116. package/dist/schema/query/index.js +205 -42
  117. package/dist/schema/schema-query/index.d.ts +65 -3
  118. package/dist/schema/schema-query/index.js +38 -5
  119. package/dist/schema/utils/node-parser.js +20 -4
  120. package/dist/schema/utils/object-type-utils.d.ts +12 -0
  121. package/dist/schema/utils/object-type-utils.js +35 -0
  122. package/dist/schema/utils/permission-machine-check.d.ts +49 -0
  123. package/dist/schema/utils/permission-machine-check.js +121 -0
  124. package/dist/schema/utils/skills-recommendation.d.ts +2 -0
  125. package/dist/schema/utils/skills-recommendation.js +76 -0
  126. package/dist/schema-query/index.d.ts +20 -1
  127. package/dist/schema-query/index.js +306 -4
  128. package/dist/schemas/account_operation.output.json +7 -1
  129. package/dist/schemas/account_operation.schema.json +3 -3
  130. package/dist/schemas/bridge_operation.output.json +6 -0
  131. package/dist/schemas/bridge_operation.schema.json +1 -1
  132. package/dist/schemas/guard-templates.json +379 -0
  133. package/dist/schemas/guard2file.schema.json +1 -1
  134. package/dist/schemas/index.json +1 -1
  135. package/dist/schemas/local_info_operation.output.json +6 -0
  136. package/dist/schemas/local_mark_operation.output.json +7 -1
  137. package/dist/schemas/local_mark_operation.schema.json +1 -1
  138. package/dist/schemas/machineNode2file.schema.json +1 -1
  139. package/dist/schemas/messenger_operation.schema.json +4 -4
  140. package/dist/schemas/onchain_events.output.json +1 -1
  141. package/dist/schemas/onchain_operations.output.json +2820 -0
  142. package/dist/schemas/onchain_operations.schema.json +444 -330
  143. package/dist/schemas/onchain_operations_allocation.schema.json +37 -28
  144. package/dist/schemas/onchain_operations_arbitration.schema.json +16 -14
  145. package/dist/schemas/onchain_operations_contact.schema.json +10 -10
  146. package/dist/schemas/onchain_operations_demand.schema.json +10 -10
  147. package/dist/schemas/onchain_operations_gen_passport.schema.json +16 -16
  148. package/dist/schemas/onchain_operations_gen_proof.schema.json +2 -2
  149. package/dist/schemas/onchain_operations_guard.schema.json +2 -2
  150. package/dist/schemas/onchain_operations_machine.schema.json +43 -35
  151. package/dist/schemas/onchain_operations_order.schema.json +72 -78
  152. package/dist/schemas/onchain_operations_payment.schema.json +141 -111
  153. package/dist/schemas/onchain_operations_permission.schema.json +3 -3
  154. package/dist/schemas/onchain_operations_personal.schema.json +7 -7
  155. package/dist/schemas/onchain_operations_progress.schema.json +9 -9
  156. package/dist/schemas/onchain_operations_proof.schema.json +8 -8
  157. package/dist/schemas/onchain_operations_repository.schema.json +10 -10
  158. package/dist/schemas/onchain_operations_reward.schema.json +14 -14
  159. package/dist/schemas/onchain_operations_service.schema.json +119 -48
  160. package/dist/schemas/onchain_operations_treasury.schema.json +11 -11
  161. package/dist/schemas/onchain_table_data.output.json +33 -25
  162. package/dist/schemas/onchain_table_data.schema.json +22 -21
  163. package/dist/schemas/project_operation.output.json +2194 -23
  164. package/dist/schemas/project_operation.schema.json +84 -6
  165. package/dist/schemas/query_toolkit.output.json +108 -80
  166. package/dist/schemas/query_toolkit.schema.json +12 -12
  167. package/dist/schemas/schema_query.output.json +70 -3
  168. package/dist/schemas/schema_query.schema.json +18 -3
  169. package/dist/schemas/wowok_buildin_info.output.json +81 -8
  170. package/dist/schemas/wowok_buildin_info.schema.json +46 -2
  171. package/dist/tools/handlers/local.js +20 -5
  172. package/dist/tools/handlers/onchain.js +594 -6
  173. package/dist/tools/handlers/project.js +76 -4
  174. package/dist/tools/handlers/query.js +27 -0
  175. package/dist/tools/handlers/schema-query.js +43 -1
  176. package/dist/tools/handlers/task-status.d.ts +170 -0
  177. package/dist/tools/handlers/task-status.js +55 -0
  178. package/dist/tools/handlers/wip.js +47 -1
  179. package/dist/tools/index.d.ts +8 -0
  180. package/dist/tools/index.js +387 -12
  181. package/dist/tools/retry.d.ts +8 -0
  182. package/dist/tools/retry.js +85 -0
  183. package/dist/tools/wip-deploy-assist.d.ts +28 -0
  184. package/dist/tools/wip-deploy-assist.js +278 -0
  185. package/package.json +2 -2
  186. package/dist/schemas/guard-node-examples.md +0 -199
@@ -1,9 +1,10 @@
1
+ import { IndustryModeRegistry } from "../extensions/registry.js";
2
+ import { collectPermissionIndexes, checkPermissionAuthorization, } from "../schema/utils/permission-machine-check.js";
1
3
  const CRITICAL_BINDINGS = [
2
4
  { sourceType: "Service", targetType: "Machine", fieldName: "machine", critical: true },
3
5
  { sourceType: "Service", targetType: "Permission", fieldName: "permission", critical: true },
4
6
  { sourceType: "Service", targetType: "Guard", fieldName: "buy_guard", critical: false },
5
- { sourceType: "Service", targetType: "Treasury", fieldName: "compensation_fund", critical: false },
6
- { sourceType: "Service", targetType: "Allocation", fieldName: "order_allocators", critical: false },
7
+ { sourceType: "Service", targetType: "Allocation", fieldName: "order_allocators", critical: true },
7
8
  { sourceType: "Service", targetType: "Arbitration", fieldName: "arbitrations", critical: false },
8
9
  { sourceType: "Machine", targetType: "Permission", fieldName: "permission", critical: true },
9
10
  ];
@@ -11,21 +12,55 @@ const MERCHANT_REQUIRED_ITEMS = [
11
12
  { id: "R1", objectType: "Permission", fieldName: "", required: true, description: "Governance — who can manage the service" },
12
13
  { id: "R2", objectType: "Service", fieldName: "", required: true, description: "Core offering — the service itself" },
13
14
  { id: "R3", objectType: "Machine", fieldName: "machine", required: true, description: "Workflow — order processing pipeline" },
14
- { id: "R4", objectType: "Guard", fieldName: "buy_guard", required: true, description: "Purchase validation — guard on Service.buy()" },
15
- { id: "R5", objectType: "Allocation", fieldName: "order_allocators", required: true, description: "Order allocators — fund distribution rules" },
16
- { id: "R6", objectType: "Treasury", fieldName: "compensation_fund", required: true, description: "Compensation fund — buyer refund protection" },
17
- { id: "R7", objectType: "Arbitration", fieldName: "arbitrations", required: true, description: "Dispute resolution — arbitration configured" },
15
+ { id: "R4", objectType: "Guard", fieldName: "", required: true, description: "Purchase validation — Guard objects configured (binding to Service.buy_guard is optional)" },
16
+ { id: "R5", objectType: "Allocation", fieldName: "order_allocators", required: true, description: "Order allocators template — inline Allocators on Service (independent Allocation objects are created per-order)" },
17
+ { id: "R6", objectType: "Treasury", fieldName: "", required: true, description: "Compensation fund — independent Treasury object for buyer refund protection (industry-aware: optional for freelance)" },
18
+ { id: "R7", objectType: "Arbitration", fieldName: "arbitrations", required: true, description: "Dispute resolution — arbitration configured (required when compensation_fund > 0 per service.move:494)" },
18
19
  { id: "R8", objectType: "Contact", fieldName: "", required: true, description: "Customer communication — contact info" },
19
20
  { id: "R9", objectType: "Progress", fieldName: "", required: false, description: "Order tracking — progress nodes" },
20
21
  { id: "R10", objectType: "Reward", fieldName: "rewards", required: false, description: "Customer incentives — reward claims" },
21
22
  { id: "R11", objectType: "Repository", fieldName: "repositories", required: false, description: "Documentation — published data" },
22
23
  { id: "R12", objectType: "Demand", fieldName: "", required: false, description: "Market discovery — demand signals" },
23
24
  ];
25
+ function resolveRequiredItems(industry, compensationFundBalance = 0, hasAllocationEscrow = false) {
26
+ let result = MERCHANT_REQUIRED_ITEMS;
27
+ if (industry && industry !== "general") {
28
+ let mode;
29
+ try {
30
+ mode = IndustryModeRegistry.Instance().getMode(industry);
31
+ }
32
+ catch {
33
+ }
34
+ if (mode) {
35
+ const requireFund = mode.risk_constraints?.require_compensation_fund ?? true;
36
+ if (!requireFund) {
37
+ result = MERCHANT_REQUIRED_ITEMS.map(item => {
38
+ if (item.id === "R6" || item.id === "R7") {
39
+ return { ...item, required: false };
40
+ }
41
+ return item;
42
+ });
43
+ }
44
+ }
45
+ }
46
+ if (hasAllocationEscrow && compensationFundBalance === 0) {
47
+ result = result.map(item => {
48
+ if (item.id === "R6") {
49
+ return { ...item, required: false };
50
+ }
51
+ return item;
52
+ });
53
+ }
54
+ if (compensationFundBalance > 0) {
55
+ result = result.map(item => item.id === "R7" ? { ...item, required: true } : item);
56
+ }
57
+ return result;
58
+ }
24
59
  const CONSUMER_DUE_DILIGENCE = [
25
60
  { id: "E1", check: "Service is published (bPublished=true)", severity: "critical", deduction: -30 },
26
61
  { id: "E2", check: "Service is not paused (bPaused=false)", severity: "warning", deduction: -20 },
27
62
  { id: "E3", check: "Machine has workflow nodes", severity: "warning", deduction: -15 },
28
- { id: "E4", check: "Service has buy_guard (purchase validation)", severity: "warning", deduction: -15 },
63
+ { id: "E4", check: "Service has buy_guard (purchase validation)", severity: "info", deduction: 0 },
29
64
  { id: "E5", check: "Service has compensation_fund (refund protection)", severity: "warning", deduction: -15 },
30
65
  { id: "E6", check: "Arbitration is configured (dispute resolution)", severity: "critical", deduction: -25 },
31
66
  { id: "E7", check: "Permission is present (governance)", severity: "warning", deduction: -10 },
@@ -34,7 +69,6 @@ const CONSUMER_DUE_DILIGENCE = [
34
69
  { id: "E10", check: "Reward is configured (customer incentives)", severity: "info", deduction: -3 },
35
70
  { id: "E11", check: "Service has description (offering clarity)", severity: "info", deduction: -5 },
36
71
  { id: "E12", check: "Registrar reputation is positive", severity: "warning", deduction: -10 },
37
- { id: "E13", check: "No critical findings in risk evaluation", severity: "critical", deduction: -20 },
38
72
  ];
39
73
  function clampScore(n) {
40
74
  return Math.max(0, Math.min(100, Math.round(n)));
@@ -44,7 +78,11 @@ function shortTypeName(objectType) {
44
78
  return "unknown";
45
79
  const base = objectType.replace(/<.*>/, "").trim();
46
80
  const segments = base.split("::");
47
- return segments[segments.length - 1] || "unknown";
81
+ const shortName = segments[segments.length - 1] || "unknown";
82
+ if (shortName.length > 0 && shortName === shortName.toLowerCase()) {
83
+ return shortName.charAt(0).toUpperCase() + shortName.slice(1);
84
+ }
85
+ return shortName;
48
86
  }
49
87
  function parseCacheData(cached) {
50
88
  if (!cached)
@@ -65,7 +103,125 @@ function countArrayField(data, field) {
65
103
  return v.length;
66
104
  return 0;
67
105
  }
68
- function detectCircularRefs(edges) {
106
+ export function getNumberField(data, field) {
107
+ if (!data)
108
+ return 0;
109
+ const v = data[field];
110
+ if (typeof v === "number" && Number.isFinite(v))
111
+ return v;
112
+ if (typeof v === "string") {
113
+ const n = Number(v);
114
+ if (Number.isFinite(n))
115
+ return n;
116
+ }
117
+ return 0;
118
+ }
119
+ export function getMachineNodeCount(data) {
120
+ if (!data)
121
+ return 0;
122
+ const nc = getNumberField(data, "node_count");
123
+ if (nc > 0)
124
+ return nc;
125
+ const arrLen = countArrayField(data, "nodes");
126
+ if (arrLen > 0)
127
+ return arrLen;
128
+ const nodesObj = data["nodes"];
129
+ if (nodesObj && typeof nodesObj === "object") {
130
+ const fieldsObj = nodesObj["fields"];
131
+ if (fieldsObj && typeof fieldsObj === "object") {
132
+ const sizeVal = getNumberField(fieldsObj, "size");
133
+ if (sizeVal > 0)
134
+ return sizeVal;
135
+ }
136
+ const directSize = getNumberField(nodesObj, "size");
137
+ if (directSize > 0)
138
+ return directSize;
139
+ }
140
+ return 0;
141
+ }
142
+ function extractMachineNodesFromCache(data) {
143
+ if (!data)
144
+ return [];
145
+ const nodesArr = data["nodes"];
146
+ if (Array.isArray(nodesArr)) {
147
+ return nodesArr;
148
+ }
149
+ const config = data["config"];
150
+ if (config && Array.isArray(config["nodes"])) {
151
+ return config["nodes"];
152
+ }
153
+ const nodesObj = data["nodes"];
154
+ if (nodesObj && typeof nodesObj === "object") {
155
+ const fieldsObj = nodesObj["fields"];
156
+ if (fieldsObj && Array.isArray(fieldsObj["contents"])) {
157
+ return fieldsObj["contents"];
158
+ }
159
+ }
160
+ return [];
161
+ }
162
+ function getBalanceValue(data, field) {
163
+ if (!data)
164
+ return 0;
165
+ const v = data[field];
166
+ if (v == null)
167
+ return 0;
168
+ if (typeof v === "number" && Number.isFinite(v))
169
+ return v;
170
+ if (typeof v === "string") {
171
+ const n = Number(v);
172
+ if (Number.isFinite(n))
173
+ return n;
174
+ }
175
+ if (typeof v === "object") {
176
+ const obj = v;
177
+ const inner = obj.value ?? obj.fields;
178
+ if (typeof inner === "number" && Number.isFinite(inner))
179
+ return inner;
180
+ if (typeof inner === "string") {
181
+ const n = Number(inner);
182
+ if (Number.isFinite(n))
183
+ return n;
184
+ }
185
+ if (typeof inner === "object" && inner !== null) {
186
+ const innerObj = inner;
187
+ const val = innerObj.value;
188
+ if (typeof val === "number" && Number.isFinite(val))
189
+ return val;
190
+ if (typeof val === "string") {
191
+ const n = Number(val);
192
+ if (Number.isFinite(n))
193
+ return n;
194
+ }
195
+ }
196
+ }
197
+ return 0;
198
+ }
199
+ function getMaxSalePrice(data) {
200
+ if (!data)
201
+ return 0;
202
+ const sales = data.sales;
203
+ if (!Array.isArray(sales) || sales.length === 0)
204
+ return 0;
205
+ let max = 0;
206
+ for (const sale of sales) {
207
+ if (typeof sale !== "object" || sale === null)
208
+ continue;
209
+ const price = sale.price;
210
+ let n = 0;
211
+ if (typeof price === "number" && Number.isFinite(price)) {
212
+ n = price;
213
+ }
214
+ else if (typeof price === "string") {
215
+ const parsed = Number(price);
216
+ if (Number.isFinite(parsed))
217
+ n = parsed;
218
+ }
219
+ if (n > max)
220
+ max = n;
221
+ }
222
+ return max;
223
+ }
224
+ function detectCircularRefs(edges, objectTypeMap) {
69
225
  const adj = new Map();
70
226
  for (const e of edges) {
71
227
  if (e.edgeSource === "potential")
@@ -78,30 +234,79 @@ function detectCircularRefs(edges) {
78
234
  const visited = new Set();
79
235
  const recursionStack = new Set();
80
236
  const path = [];
81
- const dfs = (node) => {
82
- visited.add(node);
83
- recursionStack.add(node);
84
- path.push(node);
85
- const neighbors = adj.get(node) ?? [];
86
- for (const neighbor of neighbors) {
237
+ for (const startNode of adj.keys()) {
238
+ if (visited.has(startNode))
239
+ continue;
240
+ const stack = [{
241
+ node: startNode,
242
+ nextNeighborIdx: 0,
243
+ neighbors: adj.get(startNode) ?? [],
244
+ }];
245
+ visited.add(startNode);
246
+ recursionStack.add(startNode);
247
+ path.push(startNode);
248
+ while (stack.length > 0) {
249
+ const frame = stack[stack.length - 1];
250
+ if (frame.nextNeighborIdx >= frame.neighbors.length) {
251
+ stack.pop();
252
+ path.pop();
253
+ recursionStack.delete(frame.node);
254
+ continue;
255
+ }
256
+ const neighbor = frame.neighbors[frame.nextNeighborIdx];
257
+ frame.nextNeighborIdx++;
87
258
  if (!visited.has(neighbor)) {
88
- dfs(neighbor);
259
+ visited.add(neighbor);
260
+ recursionStack.add(neighbor);
261
+ path.push(neighbor);
262
+ stack.push({
263
+ node: neighbor,
264
+ nextNeighborIdx: 0,
265
+ neighbors: adj.get(neighbor) ?? [],
266
+ });
89
267
  }
90
268
  else if (recursionStack.has(neighbor)) {
91
269
  const cycleStart = path.indexOf(neighbor);
92
270
  const cycle = path.slice(cycleStart).concat(neighbor);
93
- cycles.push(cycle.join("→"));
271
+ if (!isWoWokNormalFundCycle(cycle, objectTypeMap)) {
272
+ cycles.push(cycle.join("→"));
273
+ }
94
274
  }
95
275
  }
96
- path.pop();
97
- recursionStack.delete(node);
98
- };
99
- for (const node of adj.keys()) {
100
- if (!visited.has(node))
101
- dfs(node);
102
276
  }
103
277
  return cycles;
104
278
  }
279
+ function isWoWokNormalFundCycle(cycle, objectTypeMap) {
280
+ if (!objectTypeMap || cycle.length < 3)
281
+ return false;
282
+ const typesInCycle = new Set();
283
+ for (const addr of cycle) {
284
+ const type = objectTypeMap.get(addr);
285
+ if (type) {
286
+ typesInCycle.add(type);
287
+ }
288
+ else {
289
+ return false;
290
+ }
291
+ }
292
+ if (typesInCycle.size === 2 &&
293
+ typesInCycle.has("Order") &&
294
+ typesInCycle.has("Allocation")) {
295
+ return true;
296
+ }
297
+ if (typesInCycle.size === 3 &&
298
+ typesInCycle.has("Order") &&
299
+ typesInCycle.has("Allocation") &&
300
+ typesInCycle.has("Payment")) {
301
+ return true;
302
+ }
303
+ const workflowTypes = ["Service", "Machine", "Progress", "Order"];
304
+ const isWorkflowCycle = Array.from(typesInCycle).every(t => workflowTypes.includes(t));
305
+ if (isWorkflowCycle && typesInCycle.size >= 2) {
306
+ return true;
307
+ }
308
+ return false;
309
+ }
105
310
  function adjustWeightsForPerspective(criterionScores, perspective) {
106
311
  if (perspective === "consumer") {
107
312
  return criterionScores.map(c => {
@@ -145,8 +350,186 @@ function buildBindingIndex(edges) {
145
350
  idx.set(key, new Set());
146
351
  idx.get(key).add(e.targetObject);
147
352
  }
353
+ for (const e of edges) {
354
+ if (e.edgeSource !== "potential")
355
+ continue;
356
+ if (!e.fieldName)
357
+ continue;
358
+ const key = `${e.sourceObject}#${e.fieldName}`;
359
+ if (!idx.has(key) || idx.get(key).size === 0) {
360
+ if (!idx.has(key))
361
+ idx.set(key, new Set());
362
+ idx.get(key).add(e.targetObject);
363
+ }
364
+ }
148
365
  return idx;
149
366
  }
367
+ function evaluateIndustryPattern(project, objects, objectCache, edges) {
368
+ const result = { strengths: [], findings: [], matched: false };
369
+ const industryName = project.industry;
370
+ if (!industryName || industryName === "general") {
371
+ return result;
372
+ }
373
+ let mode;
374
+ try {
375
+ mode = IndustryModeRegistry.Instance().getMode(industryName);
376
+ }
377
+ catch {
378
+ return result;
379
+ }
380
+ if (!mode)
381
+ return result;
382
+ const byType = new Map();
383
+ for (const obj of objects) {
384
+ const t = shortTypeName(obj.objectType);
385
+ if (!byType.has(t))
386
+ byType.set(t, []);
387
+ byType.get(t).push(obj);
388
+ }
389
+ const bindingIndex = buildBindingIndex(edges);
390
+ let matchCount = 0;
391
+ let totalChecks = 0;
392
+ const expectedNodes = mode.machine_shape;
393
+ if (expectedNodes.length > 0) {
394
+ totalChecks++;
395
+ const machines = byType.get("Machine") ?? [];
396
+ if (machines.length > 0) {
397
+ const machineObj = machines[0];
398
+ const cached = parseCacheData(objectCache.get(machineObj.objectAddress));
399
+ const nodeCount = getMachineNodeCount(cached);
400
+ if (nodeCount > 0 && nodeCount >= expectedNodes.length) {
401
+ matchCount++;
402
+ result.strengths.push(`Industry pattern match: Machine has ${nodeCount} nodes, covering "${industryName}" mode's ${expectedNodes.length} expected node(s)`);
403
+ }
404
+ else if (nodeCount > 0) {
405
+ result.findings.push({
406
+ criterion: "industry_pattern",
407
+ severity: "info",
408
+ message: `Industry pattern: Machine has ${nodeCount} nodes, but "${industryName}" mode expects at least ${expectedNodes.length}`,
409
+ deduction: 0,
410
+ });
411
+ }
412
+ }
413
+ }
414
+ const expectedGuards = mode.guards;
415
+ if (expectedGuards.length > 0) {
416
+ totalChecks++;
417
+ const guards = byType.get("Guard") ?? [];
418
+ const minGuards = expectedGuards.length;
419
+ const maxGuards = expectedGuards.length * 2;
420
+ if (guards.length >= minGuards && guards.length <= maxGuards) {
421
+ matchCount++;
422
+ result.strengths.push(`Industry pattern match: ${guards.length} Guard(s) present, within "${industryName}" mode's expected range [${minGuards}-${maxGuards}]`);
423
+ }
424
+ else if (guards.length > 0 && guards.length < minGuards) {
425
+ result.findings.push({
426
+ criterion: "industry_pattern",
427
+ severity: "info",
428
+ message: `Industry pattern: ${guards.length} Guard(s) present, but "${industryName}" mode expects at least ${minGuards} (recommended range [${minGuards}-${maxGuards}])`,
429
+ deduction: 0,
430
+ });
431
+ }
432
+ else if (guards.length > maxGuards) {
433
+ matchCount++;
434
+ result.strengths.push(`Industry pattern match: ${guards.length} Guard(s) present, covering "${industryName}" mode's ${minGuards} expected guard(s)`);
435
+ result.findings.push({
436
+ criterion: "industry_pattern",
437
+ severity: "info",
438
+ message: `Industry pattern: ${guards.length} Guard(s) present, exceeds recommended max ${maxGuards}. Consider consolidating guards if some are redundant.`,
439
+ deduction: 0,
440
+ });
441
+ }
442
+ }
443
+ const expectedAllocStrategy = mode.allocator;
444
+ if (expectedAllocStrategy) {
445
+ totalChecks++;
446
+ const allocations = byType.get("Allocation") ?? [];
447
+ const services4Alloc = byType.get("Service") ?? [];
448
+ const serviceForAlloc = services4Alloc[0];
449
+ const hasOrderAllocators = serviceForAlloc
450
+ ? !!bindingIndex.get(`${serviceForAlloc.objectAddress}#order_allocators`)?.size
451
+ : false;
452
+ const expectsMultiple = mode.traits.multi_tier_allocation;
453
+ if (hasOrderAllocators) {
454
+ matchCount++;
455
+ if (allocations.length > 0) {
456
+ result.strengths.push(`Industry pattern match: Service.order_allocators bound + ${allocations.length} Allocation object(s) present (post-order) — matching "${industryName}" allocation pattern`);
457
+ }
458
+ else {
459
+ result.strengths.push(`Industry pattern match: Service.order_allocators template bound — matching "${industryName}" allocation pattern (Allocation objects created per-order)`);
460
+ }
461
+ }
462
+ else if (allocations.length > 0) {
463
+ matchCount++;
464
+ result.strengths.push(`Industry pattern match: ${allocations.length} Allocation object(s) present — matching "${industryName}" allocation pattern`);
465
+ }
466
+ else {
467
+ result.findings.push({
468
+ criterion: "industry_pattern",
469
+ severity: "info",
470
+ message: `Industry pattern: Service.order_allocators not bound and no Allocation objects — "${industryName}" mode expects ${expectsMultiple ? "multi-tier" : "single-tier"} allocation (Allocation objects are created per-order, so this is only a template-level gap)`,
471
+ deduction: 0,
472
+ });
473
+ }
474
+ }
475
+ const requireCompensationFund = mode.risk_constraints?.require_compensation_fund ?? false;
476
+ if (requireCompensationFund || mode.traits.deposit_required || mode.traits.trust_sensitivity !== "low") {
477
+ totalChecks++;
478
+ const treasuries = byType.get("Treasury") ?? [];
479
+ const services4Treas = byType.get("Service") ?? [];
480
+ const serviceForTreas = services4Treas[0];
481
+ const serviceDataForTreas = serviceForTreas
482
+ ? parseCacheData(objectCache.get(serviceForTreas.objectAddress))
483
+ : null;
484
+ const compensationBalance = serviceForTreas
485
+ ? getBalanceValue(serviceDataForTreas, "compensation_fund")
486
+ : 0;
487
+ if (treasuries.length > 0 || compensationBalance > 0) {
488
+ matchCount++;
489
+ const parts = [];
490
+ if (treasuries.length > 0)
491
+ parts.push(`${treasuries.length} Treasury object(s)`);
492
+ if (compensationBalance > 0)
493
+ parts.push(`compensation_fund balance ${compensationBalance}`);
494
+ result.strengths.push(`Industry pattern match: ${parts.join(" + ")} — matching "${industryName}" trust/deposit pattern`);
495
+ }
496
+ }
497
+ result.matched = totalChecks > 0 && matchCount / totalChecks >= 0.6;
498
+ if (result.matched) {
499
+ result.strengths.push(`Industry pattern: project structure matches "${industryName}" mode (${matchCount}/${totalChecks} checks passed)`);
500
+ }
501
+ else if (totalChecks > 0) {
502
+ result.findings.push({
503
+ criterion: "industry_pattern",
504
+ severity: "info",
505
+ message: `Industry pattern: project partially matches "${industryName}" mode (${matchCount}/${totalChecks} checks passed)`,
506
+ deduction: 0,
507
+ });
508
+ }
509
+ if (industryName === "freelance") {
510
+ const machines = byType.get("Machine") ?? [];
511
+ for (const m of machines) {
512
+ const mData = parseCacheData(objectCache.get(m.objectAddress));
513
+ if (!mData)
514
+ continue;
515
+ const nodeCount = getMachineNodeCount(mData);
516
+ if (nodeCount > 0) {
517
+ result.findings.push({
518
+ criterion: "industry_pattern",
519
+ severity: "info",
520
+ message: `ISSUE-P1-4: freelance project has a Machine with ${nodeCount} node(s). ` +
521
+ `If the workflow includes a "delivered" state, 48h auto-accept requires ` +
522
+ `an off-chain keeper (WoWok Guard does not support time-based triggers). ` +
523
+ `Keeper should poll Progress objects in "delivered" state and call ` +
524
+ `adminUnhold (permission 224) after 48h timeout.`,
525
+ deduction: 0,
526
+ });
527
+ break;
528
+ }
529
+ }
530
+ }
531
+ return result;
532
+ }
150
533
  function evaluateRisk(input) {
151
534
  const findings = [];
152
535
  const strengths = [];
@@ -162,19 +545,24 @@ function evaluateRisk(input) {
162
545
  const bindingIndex = buildBindingIndex(edges);
163
546
  let structuralScore = 100;
164
547
  const serviceObj = services[0];
548
+ const svcDataForStruct = serviceObj ? parseCacheData(objectCache.get(serviceObj.objectAddress)) : null;
549
+ const compensationBalanceForStruct = serviceObj ? getBalanceValue(svcDataForStruct, "compensation_fund") : 0;
165
550
  if (serviceObj) {
166
551
  for (const expected of CRITICAL_BINDINGS) {
167
552
  if (expected.sourceType !== "Service")
168
553
  continue;
554
+ const effectiveCritical = expected.fieldName === "arbitrations"
555
+ ? (compensationBalanceForStruct > 0 ? true : expected.critical)
556
+ : expected.critical;
169
557
  const key = `${serviceObj.objectAddress}#${expected.fieldName}`;
170
558
  const bindings = bindingIndex.get(key);
171
559
  if (!bindings || bindings.size === 0) {
172
- const deduction = expected.critical ? -25 : -10;
560
+ const deduction = effectiveCritical ? -25 : -10;
173
561
  structuralScore += deduction;
174
562
  findings.push({
175
563
  criterion: "structural_risk",
176
- severity: expected.critical ? "critical" : "warning",
177
- message: `Service missing ${expected.fieldName} binding (expected ${expected.targetType})`,
564
+ severity: effectiveCritical ? "critical" : "warning",
565
+ message: `Service missing ${expected.fieldName} binding (expected ${expected.targetType})${expected.fieldName === "arbitrations" ? (compensationBalanceForStruct > 0 ? " — required by contract when compensation_fund > 0 (service.move:494)" : " — optional when compensation_fund is 0") : ""}`,
178
566
  deduction,
179
567
  });
180
568
  }
@@ -195,27 +583,97 @@ function evaluateRisk(input) {
195
583
  structuralScore = clampScore(structuralScore);
196
584
  let fundScore = 100;
197
585
  const treasuries = byType.get("Treasury") ?? [];
198
- if (treasuries.length === 0) {
199
- fundScore -= 30;
200
- findings.push({
201
- criterion: "fund_safety",
202
- severity: "warning",
203
- message: "No Treasury object — no compensation fund for refunds",
204
- deduction: -30,
205
- });
206
- }
207
- else {
208
- strengths.push(`${treasuries.length} Treasury object(s) present`);
586
+ const allocations = byType.get("Allocation") ?? [];
587
+ const serviceData = serviceObj ? parseCacheData(objectCache.get(serviceObj.objectAddress)) : null;
588
+ const compensationBalance = serviceObj ? getBalanceValue(serviceData, "compensation_fund") : 0;
589
+ const orderAllocatorsBinding = serviceObj
590
+ ? bindingIndex.get(`${serviceObj.objectAddress}#order_allocators`)?.size ?? 0
591
+ : 0;
592
+ const hasAllocationEscrow = allocations.length > 0 && orderAllocatorsBinding > 0;
593
+ let industryRequiresFund = true;
594
+ if (project.industry && project.industry !== "general") {
595
+ try {
596
+ const mode = IndustryModeRegistry.Instance().getMode(project.industry);
597
+ if (mode) {
598
+ industryRequiresFund = mode.risk_constraints?.require_compensation_fund ?? true;
599
+ }
600
+ }
601
+ catch { }
209
602
  }
210
- if (serviceObj) {
211
- const cfKey = `${serviceObj.objectAddress}#compensation_fund`;
212
- if (!bindingIndex.get(cfKey)?.size) {
213
- fundScore -= 20;
603
+ if (treasuries.length === 0 && compensationBalance === 0) {
604
+ if (hasAllocationEscrow) {
605
+ fundScore -= 5;
606
+ strengths.push("Allocation escrow mode active — funds locked in contract, released by Guard verification");
607
+ findings.push({
608
+ criterion: "fund_safety",
609
+ severity: "info",
610
+ message: `Using Allocation escrow (${allocations.length} Allocation object(s) + order_allocators bound) — fund safety via Guard verification, no Treasury/compensation_fund needed`,
611
+ deduction: -5,
612
+ });
613
+ }
614
+ else if (industryRequiresFund) {
615
+ fundScore -= 30;
214
616
  findings.push({
215
617
  criterion: "fund_safety",
216
618
  severity: "warning",
217
- message: "Service compensation_fund not bound — buyer refunds unprotected",
218
- deduction: -20,
619
+ message: "No Treasury object and Service.compensation_fund is empty — no refund fund for buyers",
620
+ deduction: -30,
621
+ });
622
+ }
623
+ else {
624
+ fundScore -= 10;
625
+ findings.push({
626
+ criterion: "fund_safety",
627
+ severity: "info",
628
+ message: `No Treasury/compensation_fund — industry "${project.industry}" does not require refund fund (optional)`,
629
+ deduction: -10,
630
+ });
631
+ }
632
+ }
633
+ else {
634
+ if (treasuries.length > 0) {
635
+ strengths.push(`${treasuries.length} Treasury object(s) present — independent fund holder`);
636
+ }
637
+ if (compensationBalance > 0) {
638
+ strengths.push(`Service.compensation_fund has ${compensationBalance} units — inline refund fund active`);
639
+ const maxSalePrice = getMaxSalePrice(serviceData);
640
+ if (maxSalePrice > 0) {
641
+ let coverageRatio = 5;
642
+ if (project.industry && project.industry !== "general") {
643
+ try {
644
+ const mode = IndustryModeRegistry.Instance().getMode(project.industry);
645
+ if (mode?.risk_constraints?.compensation_fund_coverage_ratio) {
646
+ coverageRatio = mode.risk_constraints.compensation_fund_coverage_ratio;
647
+ }
648
+ }
649
+ catch { }
650
+ }
651
+ const requiredThreshold = coverageRatio * maxSalePrice;
652
+ if (compensationBalance < requiredThreshold) {
653
+ fundScore -= 10;
654
+ findings.push({
655
+ criterion: "fund_safety",
656
+ severity: "warning",
657
+ message: `Service.compensation_fund balance (${compensationBalance}) is less than ${coverageRatio}× the max order amount (${maxSalePrice} → threshold ${requiredThreshold}). Refund coverage may be insufficient — top up via service.compensation_fund_add to avoid arbitration rulings exceeding the fund. (industry: ${project.industry ?? "general"}, ratio: ${coverageRatio}×)`,
658
+ deduction: -10,
659
+ });
660
+ }
661
+ else {
662
+ findings.push({
663
+ criterion: "fund_safety",
664
+ severity: "info",
665
+ message: `Service.compensation_fund balance (${compensationBalance}) covers ${coverageRatio}× the max order amount (${maxSalePrice}). Fund is sufficient for refund protection. (industry: ${project.industry ?? "general"}, ratio: ${coverageRatio}×)`,
666
+ deduction: 0,
667
+ });
668
+ }
669
+ }
670
+ }
671
+ else {
672
+ findings.push({
673
+ criterion: "fund_safety",
674
+ severity: "info",
675
+ message: "Treasury present but Service.compensation_fund balance is 0 — consider depositing refund fund",
676
+ deduction: -5,
219
677
  });
220
678
  }
221
679
  }
@@ -274,18 +732,181 @@ function evaluateRisk(input) {
274
732
  if (serviceObj) {
275
733
  const data = parseCacheData(objectCache.get(serviceObj.objectAddress));
276
734
  if (data && !isAddress(data.um)) {
277
- governanceScore -= 15;
735
+ governanceScore -= 5;
278
736
  findings.push({
279
737
  criterion: "governance_risk",
280
- severity: "warning",
281
- message: "Service has no UM (manager) — cannot be managed on-chain",
282
- deduction: -15,
738
+ severity: "info",
739
+ message: "Service has no UM (manager) — UM is optional; set via um_set if management delegation needed",
740
+ deduction: -5,
741
+ });
742
+ }
743
+ }
744
+ const machines = byType.get("Machine") ?? [];
745
+ for (const machineObj of machines) {
746
+ const meta = machineObj.metadata;
747
+ const config = meta?.config;
748
+ if (config?.entry_node_forward_hint) {
749
+ const hint = config.entry_node_forward_hint;
750
+ const exampleFwd = hint.example_forward;
751
+ if (exampleFwd?.namedOperator === "") {
752
+ strengths.push("Machine entry forward uses namedOperator='' (OrderHolder) — " +
753
+ "correct for customer-operable nodes. namedOperator='' represents " +
754
+ "the order holder, distinct from merchant's permission index.");
755
+ }
756
+ }
757
+ const guardObjects = config?.guard_objects;
758
+ if (guardObjects && guardObjects.length > 0) {
759
+ strengths.push(`Machine has ${guardObjects.length} guard object reference(s) — ` +
760
+ `forward-level permission validation configured`);
761
+ }
762
+ const cached = parseCacheData(objectCache.get(machineObj.objectAddress));
763
+ const nodeCount = getMachineNodeCount(cached);
764
+ if (nodeCount > 0 && !config) {
765
+ findings.push({
766
+ criterion: "governance_risk",
767
+ severity: "info",
768
+ message: `Machine has ${nodeCount} node(s). Verify each forward has at least one of ` +
769
+ `namedOperator or permissionIndex. Key: namedOperator='' means OrderHolder ` +
770
+ `(customer), NOT 'anyone'. Use non-empty namedOperator for specific roles, ` +
771
+ `or permissionIndex for permission-based access. Both set = OR semantic ` +
772
+ `(either can execute).`,
773
+ deduction: 0,
774
+ });
775
+ }
776
+ }
777
+ const machinesForPermCheck = byType.get("Machine") ?? [];
778
+ const permissionsForCheck = byType.get("Permission") ?? [];
779
+ const permDataMap = new Map();
780
+ for (const permObj of permissionsForCheck) {
781
+ const cached = parseCacheData(objectCache.get(permObj.objectAddress));
782
+ if (cached) {
783
+ permDataMap.set(permObj.objectAddress, {
784
+ address: permObj.objectAddress,
785
+ entity_count: typeof cached.entity_count === "number" ? cached.entity_count : undefined,
786
+ perm_remark: Array.isArray(cached.perm_remark) ? cached.perm_remark : undefined,
787
+ owner: typeof cached.owner === "string" ? cached.owner : (typeof cached.builder === "string" ? cached.builder : undefined),
788
+ admin: Array.isArray(cached.admin) ? cached.admin : undefined,
789
+ });
790
+ }
791
+ }
792
+ for (const machineObj of machinesForPermCheck) {
793
+ const machinePermKey = `${machineObj.objectAddress}#permission`;
794
+ const machinePermBindings = bindingIndex.get(machinePermKey);
795
+ if (!machinePermBindings || machinePermBindings.size === 0)
796
+ continue;
797
+ let permAddress = null;
798
+ for (const addr of machinePermBindings) {
799
+ permAddress = addr;
800
+ break;
801
+ }
802
+ if (!permAddress)
803
+ continue;
804
+ const permData = permDataMap.get(permAddress);
805
+ if (!permData)
806
+ continue;
807
+ const machineCached = parseCacheData(objectCache.get(machineObj.objectAddress));
808
+ if (!machineCached)
809
+ continue;
810
+ const machineNodes = extractMachineNodesFromCache(machineCached);
811
+ if (machineNodes.length === 0)
812
+ continue;
813
+ const collection = collectPermissionIndexes(machineNodes);
814
+ if (collection.requiredIndexes.length === 0)
815
+ continue;
816
+ const report = checkPermissionAuthorization(collection.requiredIndexes, permData);
817
+ if (!report.allAuthorized) {
818
+ governanceScore -= 25;
819
+ findings.push({
820
+ criterion: "governance_risk",
821
+ severity: "critical",
822
+ message: `Machine ${machineObj.objectAddress.slice(0, 8)} has forwards referencing ` +
823
+ `un-authorized permission indexes: [${report.missingIndexes.join(", ")}]. ` +
824
+ `These forwards can NEVER be executed — Progress will be permanently stuck. ` +
825
+ `Grant entities for these indexes via permission.op="add perm by index" ` +
826
+ `before publishing the Machine.`,
827
+ deduction: -25,
283
828
  });
284
829
  }
830
+ else {
831
+ strengths.push(`Machine ${machineObj.objectAddress.slice(0, 8)}: all ${collection.requiredIndexes.length} ` +
832
+ `permission index(es) are authorized`);
833
+ }
834
+ }
835
+ if (serviceObj && arbitrations.length > 0) {
836
+ const svcPermKey = `${serviceObj.objectAddress}#permission`;
837
+ const svcPermBindings = bindingIndex.get(svcPermKey);
838
+ const svcPermAddress = svcPermBindings && svcPermBindings.size > 0
839
+ ? Array.from(svcPermBindings)[0]
840
+ : null;
841
+ const svcPermData = svcPermAddress ? permDataMap.get(svcPermAddress) : null;
842
+ for (const arbObj of arbitrations) {
843
+ const arbPermKey = `${arbObj.objectAddress}#permission`;
844
+ const arbPermBindings = bindingIndex.get(arbPermKey);
845
+ const arbPermAddress = arbPermBindings && arbPermBindings.size > 0
846
+ ? Array.from(arbPermBindings)[0]
847
+ : null;
848
+ if (!svcPermAddress || !arbPermAddress)
849
+ continue;
850
+ if (svcPermAddress === arbPermAddress) {
851
+ governanceScore -= 30;
852
+ findings.push({
853
+ criterion: "governance_risk",
854
+ severity: "critical",
855
+ message: `Arbitration ${arbObj.objectAddress.slice(0, 8)} and Service share the SAME ` +
856
+ `Permission object (${svcPermAddress.slice(0, 8)}). This breaks dispute ` +
857
+ `fairness — the merchant fully controls arbitration operations. ` +
858
+ `Use a separate Permission object for Arbitration to ensure independent ` +
859
+ `dispute resolution. (per arbitration.move: the permission field controls ` +
860
+ `who can vote, confirm, and execute arbitration rulings)`,
861
+ deduction: -30,
862
+ });
863
+ continue;
864
+ }
865
+ const arbPermData = permDataMap.get(arbPermAddress);
866
+ if (svcPermData && arbPermData) {
867
+ const svcOwner = svcPermData.owner?.toLowerCase();
868
+ const arbOwner = arbPermData.owner?.toLowerCase();
869
+ const svcAdmins = new Set((svcPermData.admin ?? []).map(a => a.toLowerCase()));
870
+ const arbAdmins = new Set((arbPermData.admin ?? []).map(a => a.toLowerCase()));
871
+ const ownerOverlap = svcOwner && arbOwner && svcOwner === arbOwner;
872
+ const adminOverlap = [...svcAdmins].some(a => arbAdmins.has(a));
873
+ if (ownerOverlap || adminOverlap) {
874
+ const overlapType = ownerOverlap && adminOverlap
875
+ ? "owner AND admin overlap"
876
+ : ownerOverlap ? "same owner" : "admin overlap";
877
+ governanceScore -= 20;
878
+ findings.push({
879
+ criterion: "governance_risk",
880
+ severity: "warning",
881
+ message: `Arbitration ${arbObj.objectAddress.slice(0, 8)} and Service have ` +
882
+ `overlapping Permission control (${overlapType}). Even with separate ` +
883
+ `Permission objects, shared control undermines dispute independence. ` +
884
+ `For mainnet deployment, use a completely independent third-party ` +
885
+ `Permission (different owner and admin list) for Arbitration. ` +
886
+ `(testnet may tolerate this, but mainnet users should treat this as ` +
887
+ `a critical trust factor)`,
888
+ deduction: -20,
889
+ });
890
+ }
891
+ else {
892
+ strengths.push(`Arbitration ${arbObj.objectAddress.slice(0, 8)} has independent ` +
893
+ `Permission (separate owner/admin) — trustworthy dispute resolution`);
894
+ }
895
+ }
896
+ }
285
897
  }
286
898
  governanceScore = clampScore(governanceScore);
287
899
  let coherenceScore = 100;
288
- const objectAddresses = new Set(objects.map(o => o.objectAddress));
900
+ const objectAddresses = new Set();
901
+ for (const o of objects) {
902
+ if (o.objectAddress)
903
+ objectAddresses.add(o.objectAddress);
904
+ if (o.objectName) {
905
+ objectAddresses.add(o.objectName);
906
+ const shortType = shortTypeName(o.objectType).toLowerCase();
907
+ objectAddresses.add(`${shortType}:${o.objectName}`);
908
+ }
909
+ }
289
910
  let danglingCount = 0;
290
911
  for (const e of edges) {
291
912
  if (e.edgeSource === "potential")
@@ -303,7 +924,13 @@ function evaluateRisk(input) {
303
924
  deduction,
304
925
  });
305
926
  }
306
- const cycles = detectCircularRefs(edges);
927
+ const objectTypeMap = new Map();
928
+ for (const o of objects) {
929
+ if (o.objectAddress) {
930
+ objectTypeMap.set(o.objectAddress, shortTypeName(o.objectType));
931
+ }
932
+ }
933
+ const cycles = detectCircularRefs(edges, objectTypeMap);
307
934
  if (cycles.length > 0) {
308
935
  coherenceScore -= 15;
309
936
  findings.push({
@@ -423,15 +1050,10 @@ function evaluateTrust(input) {
423
1050
  const bgKey = `${services[0].objectAddress}#buy_guard`;
424
1051
  if (bindingIndex.get(bgKey)?.size) {
425
1052
  guardScore = clampScore(guardScore + 15);
426
- strengths.push("Service has buy_guard — purchase validation active");
1053
+ strengths.push("Service has buy_guard bound — purchase validation active");
427
1054
  }
428
- else {
429
- findings.push({
430
- criterion: "guard_quality",
431
- severity: "warning",
432
- message: "Service buy_guard not bound — unvalidated purchases",
433
- deduction: -15,
434
- });
1055
+ else if (guards.length > 0) {
1056
+ strengths.push("Guard objects present (buy_guard binding optional)");
435
1057
  }
436
1058
  }
437
1059
  guardScore = clampScore(guardScore);
@@ -491,14 +1113,16 @@ function evaluateTrust(input) {
491
1113
  case "E3":
492
1114
  passed = (byType.get("Machine") ?? []).some(m => {
493
1115
  const d = parseCacheData(objectCache.get(m.objectAddress));
494
- return d && (countArrayField(d, "nodes") > 0);
1116
+ return d && (getNumberField(d, "node_count") > 0 || countArrayField(d, "nodes") > 0);
495
1117
  });
496
1118
  break;
497
1119
  case "E4":
498
- passed = !!bindingIndex2.get(`${serviceObj2.objectAddress}#buy_guard`)?.size;
1120
+ passed = !!bindingIndex2.get(`${serviceObj2.objectAddress}#buy_guard`)?.size
1121
+ || (byType.get("Guard") ?? []).length > 0;
499
1122
  break;
500
1123
  case "E5":
501
- passed = !!bindingIndex2.get(`${serviceObj2.objectAddress}#compensation_fund`)?.size;
1124
+ passed = getBalanceValue(svcData, "compensation_fund") > 0
1125
+ || (byType.get("Treasury") ?? []).length > 0;
502
1126
  break;
503
1127
  case "E6":
504
1128
  passed = (byType.get("Arbitration") ?? []).length > 0;
@@ -524,20 +1148,32 @@ function evaluateTrust(input) {
524
1148
  passed = !!rep && rep.likes >= rep.dislikes;
525
1149
  }
526
1150
  break;
527
- case "E13":
528
- passed = true;
529
- break;
530
1151
  }
531
1152
  if (passed) {
532
1153
  diligencePassed.push(check.id);
533
1154
  }
534
1155
  else {
535
- diligenceScore += check.deduction;
1156
+ let effectiveDeduction = check.deduction;
1157
+ let effectiveSeverity = check.severity;
1158
+ if (check.id === "E5" && project.industry && project.industry !== "general") {
1159
+ try {
1160
+ const mode = IndustryModeRegistry.Instance().getMode(project.industry);
1161
+ if (mode && mode.risk_constraints?.require_compensation_fund === false) {
1162
+ effectiveDeduction = -3;
1163
+ effectiveSeverity = "info";
1164
+ }
1165
+ }
1166
+ catch { }
1167
+ }
1168
+ diligenceScore += effectiveDeduction;
1169
+ const detailSuffix = check.id === "E5" && effectiveDeduction !== check.deduction
1170
+ ? " (industry does not require compensation_fund — reduced deduction)"
1171
+ : "";
536
1172
  findings.push({
537
1173
  criterion: "consumer_diligence",
538
- severity: check.severity,
539
- message: `${check.id}: ${check.check} — FAILED`,
540
- deduction: check.deduction,
1174
+ severity: effectiveSeverity,
1175
+ message: `${check.id}: ${check.check} — FAIL: condition not met${detailSuffix} [severity: ${effectiveSeverity}]`,
1176
+ deduction: effectiveDeduction,
541
1177
  });
542
1178
  }
543
1179
  }
@@ -592,7 +1228,17 @@ function evaluateCompleteness(input) {
592
1228
  }
593
1229
  const services = byType.get("Service") ?? [];
594
1230
  const bindingIndex = buildBindingIndex(edges);
595
- const requiredItems = MERCHANT_REQUIRED_ITEMS;
1231
+ const svcObjForRequiredItems = services[0];
1232
+ const svcDataForRequiredItems = svcObjForRequiredItems
1233
+ ? parseCacheData(objectCache.get(svcObjForRequiredItems.objectAddress))
1234
+ : null;
1235
+ const compensationBalanceForItems = getBalanceValue(svcDataForRequiredItems, "compensation_fund");
1236
+ const allocationsForItems = byType.get("Allocation") ?? [];
1237
+ const orderAllocatorsBindingForItems = svcObjForRequiredItems
1238
+ ? bindingIndex.get(`${svcObjForRequiredItems.objectAddress}#order_allocators`)?.size ?? 0
1239
+ : 0;
1240
+ const hasAllocationEscrowForItems = allocationsForItems.length > 0 && orderAllocatorsBindingForItems > 0;
1241
+ const requiredItems = resolveRequiredItems(project.industry ?? undefined, compensationBalanceForItems, hasAllocationEscrowForItems);
596
1242
  const requiredCount = requiredItems.filter(r => r.required).length;
597
1243
  const optionalItems = requiredItems.filter(r => !r.required);
598
1244
  let requiredMet = 0;
@@ -650,23 +1296,28 @@ function evaluateCompleteness(input) {
650
1296
  let bindingScore = 100;
651
1297
  if (services[0]) {
652
1298
  const serviceAddr = services[0].objectAddress;
1299
+ const svcDataForBind = parseCacheData(objectCache.get(serviceAddr));
1300
+ const compensationBalanceForBind = getBalanceValue(svcDataForBind, "compensation_fund");
653
1301
  let boundCount = 0;
654
1302
  let totalCount = 0;
655
1303
  for (const expected of CRITICAL_BINDINGS) {
656
1304
  if (expected.sourceType !== "Service")
657
1305
  continue;
658
1306
  totalCount++;
1307
+ const effectiveCritical = expected.fieldName === "arbitrations"
1308
+ ? (compensationBalanceForBind > 0 ? true : expected.critical)
1309
+ : expected.critical;
659
1310
  const key = `${serviceAddr}#${expected.fieldName}`;
660
1311
  if (bindingIndex.get(key)?.size) {
661
1312
  boundCount++;
662
1313
  }
663
1314
  else {
664
- const deduction = expected.critical ? -20 : -8;
1315
+ const deduction = effectiveCritical ? -20 : -8;
665
1316
  bindingScore += deduction;
666
1317
  findings.push({
667
1318
  criterion: "binding_completeness",
668
- severity: expected.critical ? "critical" : "warning",
669
- message: `Service.${expected.fieldName} not bound (expected ${expected.targetType})`,
1319
+ severity: effectiveCritical ? "critical" : "warning",
1320
+ message: `Service.${expected.fieldName} not bound (expected ${expected.targetType})${expected.fieldName === "arbitrations" ? (compensationBalanceForBind > 0 ? " — required by contract when compensation_fund > 0" : " — optional when compensation_fund is 0") : ""}`,
670
1321
  deduction,
671
1322
  });
672
1323
  }
@@ -688,19 +1339,19 @@ function evaluateCompleteness(input) {
688
1339
  let workflowScore = 80;
689
1340
  const machines = byType.get("Machine") ?? [];
690
1341
  if (machines.length === 0) {
691
- workflowScore -= 30;
1342
+ workflowScore -= 20;
692
1343
  findings.push({
693
1344
  criterion: "workflow_rationality",
694
- severity: "warning",
695
- message: "No Machine object — no workflow defined",
696
- deduction: -30,
1345
+ severity: "info",
1346
+ message: "No Machine object — bind one before publishing (deploy-time requirement)",
1347
+ deduction: -20,
697
1348
  });
698
1349
  }
699
1350
  else {
700
1351
  for (const m of machines) {
701
1352
  const data = parseCacheData(objectCache.get(m.objectAddress));
702
1353
  if (data) {
703
- const nodeCount = countArrayField(data, "nodes") || countArrayField(data, "node_count");
1354
+ const nodeCount = getMachineNodeCount(data);
704
1355
  if (nodeCount > 0) {
705
1356
  strengths.push(`Machine has ${nodeCount} node(s)`);
706
1357
  }
@@ -716,7 +1367,16 @@ function evaluateCompleteness(input) {
716
1367
  }
717
1368
  }
718
1369
  }
719
- const objectAddresses = new Set(objects.map(o => o.objectAddress));
1370
+ const objectAddresses = new Set();
1371
+ for (const o of objects) {
1372
+ if (o.objectAddress)
1373
+ objectAddresses.add(o.objectAddress);
1374
+ if (o.objectName) {
1375
+ objectAddresses.add(o.objectName);
1376
+ const shortType = shortTypeName(o.objectType).toLowerCase();
1377
+ objectAddresses.add(`${shortType}:${o.objectName}`);
1378
+ }
1379
+ }
720
1380
  let danglingCount = 0;
721
1381
  for (const e of edges) {
722
1382
  if (e.edgeSource === "potential")
@@ -742,10 +1402,26 @@ function evaluateCompleteness(input) {
742
1402
  if (published) {
743
1403
  strengths.push("Service already published — deployment complete");
744
1404
  readinessScore = 100;
1405
+ const machineAddr = data.machine;
1406
+ if (typeof machineAddr === "string" && machineAddr.startsWith("0x")) {
1407
+ const machineData = parseCacheData(objectCache.get(machineAddr));
1408
+ if (machineData && machineData.bPublished === false) {
1409
+ readinessScore -= 10;
1410
+ findings.push({
1411
+ criterion: "deployment_readiness",
1412
+ severity: "info",
1413
+ message: `Bound Machine ${machineAddr.slice(0, 8)} is not published — order_progress will fail (machine.move E_NOT_PUBLISHED)`,
1414
+ deduction: -10,
1415
+ });
1416
+ }
1417
+ }
745
1418
  }
746
1419
  else {
747
1420
  const hasMachine = bindingIndex.get(`${services[0].objectAddress}#machine`)?.size;
748
1421
  const hasPermission = bindingIndex.get(`${services[0].objectAddress}#permission`)?.size;
1422
+ const hasOrderAllocators = bindingIndex.get(`${services[0].objectAddress}#order_allocators`)?.size;
1423
+ const compensationBalanceForReady = getBalanceValue(data, "compensation_fund");
1424
+ const hasArbitrations = bindingIndex.get(`${services[0].objectAddress}#arbitrations`)?.size;
749
1425
  if (!hasMachine) {
750
1426
  readinessScore -= 30;
751
1427
  findings.push({
@@ -764,8 +1440,31 @@ function evaluateCompleteness(input) {
764
1440
  deduction: -30,
765
1441
  });
766
1442
  }
767
- if (hasMachine && hasPermission) {
768
- strengths.push("Service ready to publish (Machine + Permission bound)");
1443
+ if (!hasOrderAllocators) {
1444
+ readinessScore -= 30;
1445
+ findings.push({
1446
+ criterion: "deployment_readiness",
1447
+ severity: "critical",
1448
+ message: "Service has no order_allocators — cannot publish (service.move:493 E_NO_ALLOCATOR)",
1449
+ deduction: -30,
1450
+ });
1451
+ }
1452
+ if (compensationBalanceForReady > 0 && !hasArbitrations) {
1453
+ readinessScore -= 25;
1454
+ findings.push({
1455
+ criterion: "deployment_readiness",
1456
+ severity: "critical",
1457
+ message: `Service has compensation_fund (${compensationBalanceForReady}) but no arbitrations — cannot publish (service.move:495 E_ARBITRATION_NOT_SET_WITH_COMPENSATION_FUND)`,
1458
+ deduction: -25,
1459
+ });
1460
+ }
1461
+ if (hasMachine && hasPermission && hasOrderAllocators) {
1462
+ if (compensationBalanceForReady > 0 && hasArbitrations) {
1463
+ strengths.push("Service ready to publish (Machine + Permission + order_allocators + arbitrations bound)");
1464
+ }
1465
+ else if (compensationBalanceForReady === 0) {
1466
+ strengths.push("Service ready to publish (Machine + Permission + order_allocators bound; arbitrations optional when compensation_fund is 0)");
1467
+ }
769
1468
  }
770
1469
  }
771
1470
  }
@@ -788,6 +1487,9 @@ function evaluateCompleteness(input) {
788
1487
  : overall >= 80
789
1488
  ? `Complete project (score ${overall}/100). Ready for deployment.`
790
1489
  : `Partially complete (score ${overall}/100). ${findings.length} gap(s) to address.`;
1490
+ const industryPattern = evaluateIndustryPattern(project, objects, objectCache, edges);
1491
+ strengths.push(...industryPattern.strengths);
1492
+ findings.push(...industryPattern.findings);
791
1493
  return {
792
1494
  evaluationType: "completeness",
793
1495
  score: overall,
@@ -800,19 +1502,45 @@ function evaluateCompleteness(input) {
800
1502
  };
801
1503
  }
802
1504
  function evaluateWorthBuyingStub(input, llmPrompt) {
1505
+ const riskResult = evaluateRisk(input);
1506
+ const trustResult = evaluateTrust(input);
1507
+ const completenessResult = evaluateCompleteness(input);
1508
+ const summaryLines = [
1509
+ "=== RULE-BASED EVALUATION SUMMARY (F-08 enriched context) ===",
1510
+ "",
1511
+ `Risk: ${riskResult.score}/100 — ${riskResult.summary}`,
1512
+ ...riskResult.findings.slice(0, 3).map(f => ` - [${f.severity}] ${f.message}`),
1513
+ "",
1514
+ `Trust: ${trustResult.score}/100 — ${trustResult.summary}`,
1515
+ ...trustResult.findings.slice(0, 3).map(f => ` - [${f.severity}] ${f.message}`),
1516
+ "",
1517
+ `Completeness: ${completenessResult.score}/100 — ${completenessResult.summary}`,
1518
+ ...completenessResult.findings.slice(0, 3).map(f => ` - [${f.severity}] ${f.message}`),
1519
+ "",
1520
+ "=== END RULE-BASED SUMMARY ===",
1521
+ "",
1522
+ ];
1523
+ const enrichment = summaryLines.join("\n");
1524
+ const enrichedPrompt = llmPrompt
1525
+ ? `${llmPrompt}\n\n${enrichment}`
1526
+ : `${enrichment}\nInstruction: Based on the rule-based summary above, ` +
1527
+ "evaluate whether this project is worth buying. Return a 0-100 score " +
1528
+ "and a brief rationale. Focus on buyer risk vs. value proposition.";
803
1529
  return {
804
1530
  evaluationType: "worth_buying",
805
1531
  score: 0,
806
- summary: "Requires LLM evaluation — semantic prompt assembled. Call save_evaluation with the LLM result.",
1532
+ summary: "Requires LLM evaluation — semantic prompt assembled with rule-based " +
1533
+ "context (risk/trust/completeness summary). Call save_evaluation with " +
1534
+ "the LLM result.",
807
1535
  criterionScores: [],
808
1536
  findings: [],
809
1537
  strengths: [],
810
1538
  needsLlm: true,
811
- llmPrompt,
1539
+ llmPrompt: enrichedPrompt,
812
1540
  evaluatedAt: Date.now(),
813
1541
  };
814
1542
  }
815
- function evaluateValueStub(input, llmPrompt) {
1543
+ function evaluateValueStub(_input, llmPrompt) {
816
1544
  return {
817
1545
  evaluationType: "value",
818
1546
  score: 0,
@@ -826,21 +1554,78 @@ function evaluateValueStub(input, llmPrompt) {
826
1554
  };
827
1555
  }
828
1556
  export function runEvaluation(input, evaluationType, llmPrompt) {
1557
+ const filtered = filterTemplateObjects(input);
829
1558
  switch (evaluationType) {
830
1559
  case "risk":
831
- return evaluateRisk(input);
1560
+ return evaluateRisk(filtered);
832
1561
  case "trust":
833
- return evaluateTrust(input);
1562
+ return evaluateTrust(filtered);
834
1563
  case "completeness":
835
- return evaluateCompleteness(input);
1564
+ return evaluateCompleteness(filtered);
836
1565
  case "worth_buying":
837
- return evaluateWorthBuyingStub(input, llmPrompt);
1566
+ return evaluateWorthBuyingStub(filtered, llmPrompt);
838
1567
  case "value":
839
- return evaluateValueStub(input, llmPrompt);
1568
+ return evaluateValueStub(filtered, llmPrompt);
840
1569
  default:
841
1570
  throw new Error(`Unknown evaluation type: ${evaluationType}`);
842
1571
  }
843
1572
  }
1573
+ function filterTemplateObjects(input) {
1574
+ const isDraftAddr = (addr) => {
1575
+ if (!addr)
1576
+ return true;
1577
+ return addr.startsWith("draft:");
1578
+ };
1579
+ const isDraftObject = (o) => {
1580
+ if (isDraftAddr(o.objectAddress))
1581
+ return true;
1582
+ const meta = o.metadata;
1583
+ return !!(meta && meta.status === "draft");
1584
+ };
1585
+ const draftObjects = [];
1586
+ const realObjects = [];
1587
+ for (const o of input.objects) {
1588
+ if (isDraftObject(o)) {
1589
+ draftObjects.push(o);
1590
+ }
1591
+ else {
1592
+ realObjects.push(o);
1593
+ }
1594
+ }
1595
+ const realIds = new Set();
1596
+ for (const o of realObjects) {
1597
+ if (o.objectAddress)
1598
+ realIds.add(o.objectAddress);
1599
+ if (o.objectName) {
1600
+ realIds.add(o.objectName);
1601
+ const shortType = shortTypeName(o.objectType).toLowerCase();
1602
+ realIds.add(`${shortType}:${o.objectName}`);
1603
+ }
1604
+ }
1605
+ const draftIds = new Set();
1606
+ for (const o of draftObjects) {
1607
+ if (o.objectAddress)
1608
+ draftIds.add(o.objectAddress);
1609
+ if (o.objectName) {
1610
+ draftIds.add(o.objectName);
1611
+ const shortType = shortTypeName(o.objectType).toLowerCase();
1612
+ draftIds.add(`${shortType}:${o.objectName}`);
1613
+ }
1614
+ }
1615
+ const realEdges = input.edges.filter((e) => {
1616
+ const meta = e.metadata;
1617
+ if (meta && meta.blueprint === true)
1618
+ return false;
1619
+ const sourceIsDraft = isDraftAddr(e.sourceObject) || draftIds.has(e.sourceObject);
1620
+ const targetIsDraft = isDraftAddr(e.targetObject) || draftIds.has(e.targetObject);
1621
+ return !sourceIsDraft && !targetIsDraft;
1622
+ });
1623
+ return {
1624
+ ...input,
1625
+ objects: realObjects,
1626
+ edges: realEdges,
1627
+ };
1628
+ }
844
1629
  export function compareProjects(entries, evaluationType) {
845
1630
  const ranking = [...entries].sort((a, b) => {
846
1631
  if (a.score === null && b.score === null)