@wowok/agent-mcp 2.5.3 → 2.5.5

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 (69) hide show
  1. package/README.md +33 -26
  2. package/dist/customer/index.d.ts +2 -2
  3. package/dist/customer/index.js +1 -1
  4. package/dist/customer/info-puzzle.d.ts +39 -2
  5. package/dist/customer/info-puzzle.js +370 -29
  6. package/dist/customer/risk-assessment.js +51 -14
  7. package/dist/customer/types.d.ts +85 -0
  8. package/dist/knowledge/machine-ledger.js +32 -29
  9. package/dist/knowledge/machine-risk.js +23 -0
  10. package/dist/knowledge/machine-templates.js +118 -86
  11. package/dist/knowledge/reward-confirm.d.ts +3 -0
  12. package/dist/knowledge/reward-confirm.js +30 -0
  13. package/dist/knowledge/reward-puzzle.d.ts +1 -0
  14. package/dist/knowledge/reward-puzzle.js +12 -0
  15. package/dist/knowledge/reward-risk.d.ts +4 -1
  16. package/dist/knowledge/reward-risk.js +85 -3
  17. package/dist/knowledge/reward-templates.d.ts +1 -1
  18. package/dist/knowledge/reward-templates.js +77 -5
  19. package/dist/knowledge/tools-reference.js +5 -4
  20. package/dist/knowledge/trust-metrics.d.ts +4 -0
  21. package/dist/knowledge/trust-metrics.js +67 -7
  22. package/dist/project/evaluation.d.ts +53 -0
  23. package/dist/project/evaluation.js +853 -0
  24. package/dist/project/graph-builder.d.ts +43 -0
  25. package/dist/project/graph-builder.js +572 -0
  26. package/dist/project/handlers.d.ts +152 -0
  27. package/dist/project/handlers.js +458 -0
  28. package/dist/project/index.d.ts +1 -0
  29. package/dist/project/index.js +24 -1
  30. package/dist/project/query.d.ts +1 -0
  31. package/dist/project/query.js +2 -0
  32. package/dist/project/semantic-assembler.d.ts +93 -0
  33. package/dist/project/semantic-assembler.js +345 -0
  34. package/dist/schema/call/arbitration.d.ts +54 -54
  35. package/dist/schema/call/base.js +1 -1
  36. package/dist/schema/call/demand.d.ts +48 -48
  37. package/dist/schema/operations.d.ts +76 -76
  38. package/dist/schema/project/index.d.ts +1558 -3885
  39. package/dist/schema/project/index.js +357 -961
  40. package/dist/schema/trust/index.d.ts +24 -24
  41. package/dist/schemas/bridge_operation.schema.json +1 -1
  42. package/dist/schemas/guard2file.schema.json +1 -1
  43. package/dist/schemas/index.json +2 -2
  44. package/dist/schemas/machineNode2file.schema.json +1 -1
  45. package/dist/schemas/onchain_operations.schema.json +5 -5
  46. package/dist/schemas/onchain_operations_allocation.schema.json +1 -1
  47. package/dist/schemas/onchain_operations_arbitration.schema.json +1 -1
  48. package/dist/schemas/onchain_operations_contact.schema.json +1 -1
  49. package/dist/schemas/onchain_operations_demand.schema.json +1 -1
  50. package/dist/schemas/onchain_operations_gen_passport.schema.json +2 -2
  51. package/dist/schemas/onchain_operations_gen_proof.schema.json +2 -2
  52. package/dist/schemas/onchain_operations_guard.schema.json +1 -1
  53. package/dist/schemas/onchain_operations_machine.schema.json +1 -1
  54. package/dist/schemas/onchain_operations_order.schema.json +1 -1
  55. package/dist/schemas/onchain_operations_payment.schema.json +1 -1
  56. package/dist/schemas/onchain_operations_permission.schema.json +1 -1
  57. package/dist/schemas/onchain_operations_personal.schema.json +1 -1
  58. package/dist/schemas/onchain_operations_progress.schema.json +1 -1
  59. package/dist/schemas/onchain_operations_proof.schema.json +1 -1
  60. package/dist/schemas/onchain_operations_repository.schema.json +1 -1
  61. package/dist/schemas/onchain_operations_reward.schema.json +1 -1
  62. package/dist/schemas/onchain_operations_service.schema.json +1 -1
  63. package/dist/schemas/onchain_operations_treasury.schema.json +1 -1
  64. package/dist/schemas/project_operation.output.json +975 -2471
  65. package/dist/schemas/project_operation.schema.json +207 -534
  66. package/dist/tools/handlers/onchain.js +97 -19
  67. package/dist/tools/handlers/project.js +34 -1046
  68. package/dist/tools/index.js +84 -55
  69. package/package.json +2 -2
@@ -14,23 +14,23 @@ export function riskLevelEmoji(level) {
14
14
  function assessWorkflowRisk(puzzle) {
15
15
  const wf = puzzle.workflow_analysis;
16
16
  const checks = [];
17
- const hasRefund = wf?.has_refund_path ?? false;
17
+ const hasArb = wf?.has_arbitration_binding ?? wf?.has_arbitration_path ?? false;
18
18
  checks.push({
19
- id: "wf_refund_path",
19
+ id: "wf_arbitration_binding",
20
20
  dimension: "workflow",
21
- description: "Refund path exists (100%→Order Allocator)",
21
+ description: "Service has Arbitration object bound (dispute independence)",
22
22
  weight: 10,
23
- passed: hasRefund,
24
- advice: hasRefund ? undefined : "⚠️ No refund path: if delivery fails, funds may not be recoverable. Recommend negotiating refund terms with the merchant via Messenger and writing them into the WTS before ordering.",
23
+ passed: hasArb,
24
+ advice: hasArb ? undefined : "⚠️ No Arbitration binding: disputes cannot be escalated to third-party arbitration. Recommend requiring the merchant to bind an Arbitration service (with voting_guard config) before ordering. Dispute is filed via arbitration::dispute(order, arbitration), independent of the machine workflow.",
25
25
  });
26
- const hasArb = wf?.has_arbitration_path ?? false;
26
+ const hasRefund = wf?.has_refund_path ?? false;
27
27
  checks.push({
28
- id: "wf_arbitration_path",
28
+ id: "wf_refund_path",
29
29
  dimension: "workflow",
30
- description: "Arbitration path exists",
30
+ description: "Refund path exists (Arbitration+compensation_fund OR refund Allocator)",
31
31
  weight: 10,
32
- passed: hasArb,
33
- advice: hasArb ? undefined : "⚠️ No arbitration path: disputes cannot be escalated to third-party arbitration. Recommend requiring the merchant to bind an Arbitration service before ordering.",
32
+ passed: hasRefund,
33
+ advice: hasRefund ? undefined : "⚠️ No refund path: if delivery fails, funds may not be recoverable. Either (a) bind Arbitration AND deposit compensation_fund, or (b) configure a refund-to-order Allocator. Refund via arbitration flows: dispute → arbitration() ruling with indemnity → arb_withdraw by order holder.",
34
34
  });
35
35
  const userOps = wf?.user_operable_forwards ?? 0;
36
36
  checks.push({
@@ -51,14 +51,19 @@ function assessWorkflowRisk(puzzle) {
51
51
  passed: noDeadEnds,
52
52
  advice: noDeadEnds ? undefined : `⚠️ Detected ${deadEnds.length} potential dead-end node(s): ${deadEnds.join(", ")}. All exits of these nodes depend on merchant action.`,
53
53
  });
54
- const machineUsable = wf ? (wf.machine_published && !wf.machine_paused) : false;
54
+ const machineUsable = wf
55
+ ? (wf.machine_published && !wf.machine_paused && !wf.has_dispute_anti_pattern)
56
+ : false;
55
57
  checks.push({
56
58
  id: "wf_machine_usable",
57
59
  dimension: "workflow",
58
- description: "Machine is published and not paused",
60
+ description: "Machine published, not paused, no dispute anti-pattern",
59
61
  weight: 3,
60
62
  passed: machineUsable,
61
- advice: machineUsable ? undefined : "⚠️ Machine is not published or is paused: the workflow may not be in effect or has been actively paused by the merchant.",
63
+ advice: machineUsable ? undefined
64
+ : wf?.has_dispute_anti_pattern
65
+ ? "🔴 Machine contains dispute anti-pattern nodes (disputed/refunded/arb/arbitration). Dispute is INDEPENDENT of machine — these nodes create dead-end paths because arbitrators are in Arbitration.voting_guard, not Permission. Recommend removing these nodes and binding an Arbitration object to the Service instead."
66
+ : "⚠️ Machine is not published or is paused: the workflow may not be in effect or has been actively paused by the merchant.",
62
67
  });
63
68
  const score = checks.reduce((sum, c) => sum + (c.passed ? c.weight : 0), 0);
64
69
  return { name: "workflow", score, max_score: RISK_DIMENSION_MAX.workflow, checks };
@@ -264,7 +269,12 @@ export function assessRisk(puzzle) {
264
269
  assessTrustRisk(puzzle),
265
270
  assessBehaviorRisk(puzzle),
266
271
  ];
267
- const total = dimensions.reduce((sum, d) => sum + d.score, 0);
272
+ const baseTotal = dimensions.reduce((sum, d) => sum + d.score, 0);
273
+ const userMetricsAdjustment = puzzle.user_metrics?.adjustment ?? 0;
274
+ const serviceNestingRisk = puzzle.service_nesting?.overall_risk_score;
275
+ const serviceNestingLevel = puzzle.service_nesting?.overall_risk_level;
276
+ const wonderBonus = puzzle.workflow_analysis?.has_wonder_node ? 2 : 0;
277
+ const total = Math.max(0, Math.min(100, baseTotal + userMetricsAdjustment + wonderBonus));
268
278
  const level = riskLevelFromScore(total);
269
279
  const redFlags = [];
270
280
  for (const dim of dimensions) {
@@ -284,11 +294,18 @@ export function assessRisk(puzzle) {
284
294
  redFlags.push(`[behavior_flag] ${f}`);
285
295
  }
286
296
  }
297
+ if (serviceNestingLevel === "high" || serviceNestingLevel === "critical") {
298
+ redFlags.push(`[service_nesting] overall nesting risk: ${serviceNestingLevel} (score ${serviceNestingRisk})`);
299
+ }
287
300
  const advice = generateAdvice(puzzle, total, level, dimensions);
288
301
  return {
289
302
  total,
303
+ base_total: baseTotal,
290
304
  level,
291
305
  dimensions,
306
+ user_metrics_adjustment: userMetricsAdjustment,
307
+ service_nesting_risk: serviceNestingRisk,
308
+ service_nesting_level: serviceNestingLevel,
292
309
  red_flags: redFlags,
293
310
  advice,
294
311
  };
@@ -326,6 +343,26 @@ function generateAdvice(puzzle, total, level, dimensions) {
326
343
  if (puzzle.completeness < 0.8) {
327
344
  advice.push(`ℹ️ Information completeness is only ${Math.round(puzzle.completeness * 100)}%; missing: ${puzzle.gaps.join(", ") || "none"}. Recommend supplementing data before making a decision.`);
328
345
  }
346
+ if (puzzle.workflow_analysis?.has_wonder_node) {
347
+ advice.push("🌟 Machine implements wonder/no-wonder praise design: customers can advance Progress to 'wonder' (positive review) or 'no_wonder' (negative review) after completion. Reward objects can claim against Progress.current == 'wonder' to incentivize positive outcomes.");
348
+ }
349
+ if (puzzle.workflow_analysis?.has_dispute_anti_pattern) {
350
+ advice.push("🔴 Machine contains dispute anti-pattern nodes (disputed/refunded/arb/arbitration). Dispute is INDEPENDENT of machine — it flows through Order → Arbitration.dispute() → Arb → arbitration() → arb_withdraw. Arbitrators are configured in Arbitration.voting_guard, NOT in Permission. Recommend the merchant remove these nodes and bind an Arbitration object instead.");
351
+ }
352
+ if (puzzle.user_metrics) {
353
+ const um = puzzle.user_metrics;
354
+ const passedCount = um.metrics.filter((m) => m.passed).length;
355
+ advice.push(`📊 User-customized metrics: ${passedCount}/${um.metrics.length} passed (score ${um.score}/100, adjustment ${um.adjustment >= 0 ? "+" : ""}${um.adjustment}).${um.source_file ? ` Source: ${um.source_file}` : ""}`);
356
+ for (const m of um.metrics) {
357
+ if (!m.passed && m.advice) {
358
+ advice.push(` • ${m.label}: ${m.advice}`);
359
+ }
360
+ }
361
+ }
362
+ if (puzzle.service_nesting) {
363
+ const sn = puzzle.service_nesting;
364
+ advice.push(`🔗 Service nesting evaluation: ${sn.total_bindings_evaluated} bound objects, overall risk ${sn.overall_risk_level} (score ${sn.overall_risk_score}/100).${sn.truncated ? " Recursion truncated at depth limit." : ""}`);
365
+ }
329
366
  return advice;
330
367
  }
331
368
  export function passesRiskThreshold(score, minLevel = "medium_high") {
@@ -28,6 +28,9 @@ export interface WorkflowAnalysis {
28
28
  guard_gated_forwards: number;
29
29
  has_refund_path: boolean;
30
30
  has_arbitration_path: boolean;
31
+ has_arbitration_binding: boolean;
32
+ has_wonder_node: boolean;
33
+ has_dispute_anti_pattern: boolean;
31
34
  has_milestone_nodes: boolean;
32
35
  dead_end_nodes: string[];
33
36
  terminal_nodes: string[];
@@ -77,6 +80,82 @@ export interface MarketContext {
77
80
  price_percentile: number;
78
81
  supply_demand_ratio: number;
79
82
  }
83
+ export type UserMetricEvaluatorType = "threshold" | "ratio" | "boolean" | "presence" | "regex_match" | "custom_query";
84
+ export interface UserMetric {
85
+ name: string;
86
+ label: string;
87
+ evaluator: UserMetricEvaluatorType;
88
+ params: {
89
+ path?: string;
90
+ threshold?: number | string;
91
+ invert?: boolean;
92
+ min?: number;
93
+ max?: number;
94
+ pattern?: string;
95
+ query_key?: string;
96
+ };
97
+ weight: number;
98
+ advice_on_fail?: string;
99
+ advice_on_pass?: string;
100
+ }
101
+ export interface UserMetricsConfig {
102
+ version: number;
103
+ source_file?: string;
104
+ metrics: UserMetric[];
105
+ adjustment_cap?: number;
106
+ }
107
+ export interface UserMetricResult {
108
+ name: string;
109
+ label: string;
110
+ passed: boolean;
111
+ score: number;
112
+ observed_value?: string | number | boolean;
113
+ weight: number;
114
+ advice?: string;
115
+ }
116
+ export interface UserMetricsResult {
117
+ metrics: UserMetricResult[];
118
+ score: number;
119
+ adjustment: number;
120
+ source_file?: string;
121
+ evaluated_at: number;
122
+ }
123
+ export interface ServiceNestingEvaluation {
124
+ depth_reached: number;
125
+ truncated: boolean;
126
+ bindings: {
127
+ machine?: {
128
+ object_id: string;
129
+ published: boolean;
130
+ risk_level: "low" | "medium" | "high" | "critical";
131
+ issues: string[];
132
+ };
133
+ arbitrations: Array<{
134
+ object_id: string;
135
+ voting_guard_count: number;
136
+ fee_balance: string;
137
+ paused: boolean;
138
+ risk_level: "low" | "medium" | "high" | "critical";
139
+ issues: string[];
140
+ }>;
141
+ rewards: Array<{
142
+ object_id: string;
143
+ balance: string;
144
+ guard_count: number;
145
+ risk_level: "low" | "medium" | "high" | "critical";
146
+ issues: string[];
147
+ }>;
148
+ repositories: Array<{
149
+ object_id: string;
150
+ risk_level: "low" | "medium" | "high" | "critical";
151
+ issues: string[];
152
+ }>;
153
+ };
154
+ overall_risk_level: "low" | "medium" | "high" | "critical";
155
+ overall_risk_score: number;
156
+ total_bindings_evaluated: number;
157
+ evaluated_at: number;
158
+ }
80
159
  export interface InfoPuzzle {
81
160
  service_id: string;
82
161
  service_basics: ServiceBasics;
@@ -85,6 +164,8 @@ export interface InfoPuzzle {
85
164
  trust_signals?: TrustSignals;
86
165
  merchant_behavior?: MerchantBehavior;
87
166
  market_context?: MarketContext;
167
+ user_metrics?: UserMetricsResult;
168
+ service_nesting?: ServiceNestingEvaluation;
88
169
  completeness: number;
89
170
  gaps: string[];
90
171
  assembled_at: number;
@@ -107,8 +188,12 @@ export interface RiskDimensionScore {
107
188
  export type RiskLevel = "low" | "medium_low" | "medium_high" | "high";
108
189
  export interface RiskScore {
109
190
  total: number;
191
+ base_total: number;
110
192
  level: RiskLevel;
111
193
  dimensions: RiskDimensionScore[];
194
+ user_metrics_adjustment?: number;
195
+ service_nesting_risk?: number;
196
+ service_nesting_level?: "low" | "medium" | "high" | "critical";
112
197
  red_flags: string[];
113
198
  advice: string[];
114
199
  }
@@ -29,25 +29,24 @@ export const MACHINE_SCENES = [
29
29
  id: "ecommerce_with_exceptions",
30
30
  name: "E-commerce Flow with Exception Branches",
31
31
  industry: ["ecommerce"],
32
- scenario: "Paid (entry) → Ship → {Receive → Complete | Lost → Arbitration | Return → Refund}",
32
+ scenario: "Paid (entry) → Ship → {Receive → Complete | Lost (terminal) | Return → Refund}",
33
33
  participants: [
34
- { role: "customer", permission_type: "OrderHolder", involved_in: ["Receive", "Return"] },
35
- { role: "merchant", permission_type: "Permission", involved_in: ["Ship", "Refund"] },
36
- { role: "arbitrator", permission_type: "NamedOperator", involved_in: ["Arbitration"] },
34
+ { role: "customer", permission_type: "OrderHolder", involved_in: ["Receive", "Return", "Lost"] },
35
+ { role: "merchant", permission_type: "Permission", involved_in: ["Ship", "Refund", "Lost"] },
37
36
  ],
38
37
  recommended_patterns: ["P-M-SEQ", "P-M-COMPETING"],
39
38
  recommended_topology: ["P-M-LINEAR", "P-M-COMPETING"],
40
39
  guard_requirements: [
41
40
  { forward: "Lost", scene: "machine_forward_guard", purpose: "Dual-sig to confirm lost" },
42
- { forward: "Arbitration", scene: "arbitration_usage_guard", purpose: "Verify arbitration eligibility" },
43
41
  ],
44
- allocation_integration: "Complete->merchant | Refund->customer | Arb->arbitrator",
42
+ allocation_integration: "Complete->merchant | Refund->customer | Lost->escrow or partial refund (Allocator only). Disputes are NOT modeled here — they flow through Order + Arbitration object off-Machine.",
45
43
  special_constraints: [
46
44
  "First node 'Paid' represents post-payment state — buy() completes payment atomically before Progress is created",
47
45
  "No separate 'Order' or 'Pay' nodes needed — payment is handled by Service.buy(), not Machine",
48
- "Lost node threshold=2 (customer + merchant dual-sig)",
49
- "Competing Pair: Receive vs Lost (first-wins)",
50
- "Arbitration flow independent of main flow",
46
+ "Lost node threshold=2 (customer + merchant dual-sig) — Lost is a TERMINAL node, not a path to arbitration",
47
+ "Competing Pair: Receive vs Lost vs Return (first-wins)",
48
+ "DISPUTE INDEPENDENCE: arbitration is NOT a Machine node. If a Lost-terminal order later needs dispute resolution, the customer files arbitration::dispute(order, arbitration) against the Arbitration object bound to the Service — completely off-Machine. Arbitrators live in Arbitration.voting_guard, never in Permission or NamedOperator.",
49
+ "Refund Allocation must use sharing.who=OrderHolder (refund to customer, NOT merchant)",
51
50
  ],
52
51
  first_node_semantics: "Paid — order has been created and paid via Service.buy(); Machine starts at post-payment state",
53
52
  mutable_after_publish: false,
@@ -207,31 +206,32 @@ export const MACHINE_SCENES = [
207
206
  m_rounds_focus: ["M1", "M3", "M8"],
208
207
  },
209
208
  {
210
- id: "arbitration_flow",
211
- name: "Arbitration Flow",
212
- industry: ["ecommerce", "service", "rental"],
213
- scenario: "Dispute → Submit → Vote → Ruling → Execute",
209
+ id: "wonder_feedback",
210
+ name: "Wonder Feedback Flow (Post-Completion Rating)",
211
+ industry: ["ecommerce", "service", "rental", "subscription", "education"],
212
+ scenario: "Paid (entry) → ... → Completed → {Wonder | NoWonder} (rating terminals)",
214
213
  participants: [
215
- { role: "disputant", permission_type: "OrderHolder", involved_in: ["Submit Dispute"] },
216
- { role: "arbitrator", permission_type: "NamedOperator", involved_in: ["Vote"] },
217
- { role: "system", permission_type: "Permission", involved_in: ["Execute Ruling"] },
214
+ { role: "customer", permission_type: "OrderHolder", involved_in: ["Wonder", "NoWonder"] },
215
+ { role: "merchant", permission_type: "Permission", involved_in: ["Completed"] },
218
216
  ],
219
- recommended_patterns: ["P-M-VOTE", "P-M-SEQ"],
220
- recommended_topology: ["P-M-LINEAR"],
217
+ recommended_patterns: ["P-M-SEQ", "P-M-COMPETING"],
218
+ recommended_topology: ["P-M-LINEAR", "P-M-COMPETING"],
221
219
  guard_requirements: [
222
- { forward: "Submit Dispute", scene: "arbitration_usage_guard", purpose: "Verify dispute eligibility + prevent double-dispute" },
223
- { forward: "Vote", scene: "arbitration_voting_guard", purpose: "Verify voting eligibility + extract weight" },
220
+ { forward: "Praise", scene: "machine_forward_guard", purpose: "Verify Progress.current == 'Completed' (rating only after completion)" },
221
+ { forward: "Complain", scene: "machine_forward_guard", purpose: "Verify Progress.current == 'Completed' (rating only after completion)" },
224
222
  ],
225
- allocation_integration: "After ruling -> distribute funds according to ruling",
223
+ allocation_integration: "Completed -> merchant allocation (terminal); Wonder/NoWonder are reputation terminals — no fund allocation. Reward object (independent) claims fire when Progress.current == 'wonder'",
226
224
  special_constraints: [
227
- "Arbitration object must be already created",
228
- "Prevent double-dispute (R-X1-14 HIGH)",
229
- "Prevent double-vote (R-X1-14 CRITICAL)",
230
- "MAX_DISPUTE_COUNT=10 per order",
225
+ "Dispute MUST NOT be modeled as machine nodes — disputes flow through Order + Arbitration object, independent of this Machine",
226
+ "Wonder/NoWonder are POST-completion terminals: order is already settled when customer rates",
227
+ "Both Wonder and NoWonder forwards have named_operator='' (OrderHolder) — rating is a customer-only action",
228
+ "Reward object (if bound) MUST use a Guard verifying Progress.current == 'wonder' AND order.holder == signer — no Permission needed for reward claim",
229
+ "Competing Pair: Praise vs Complain (first-wins; one rating per order)",
230
+ "No dispute/refund/arbitration nodes in this Machine — those flows live off-Machine",
231
231
  ],
232
- first_node_semantics: "DisputeOpened — order has been created and paid via Service.buy(); Machine starts at post-payment dispute state",
232
+ first_node_semantics: "Paid — order has been created and paid via Service.buy(); Machine starts at post-payment state, same as e-commerce baseline",
233
233
  mutable_after_publish: false,
234
- m_rounds_focus: ["M1", "M3", "M4", "M5", "M8"],
234
+ m_rounds_focus: ["M1", "M2", "M3", "M5", "M8"],
235
235
  },
236
236
  {
237
237
  id: "subscription",
@@ -281,8 +281,11 @@ export function inferSceneFromFlow(description) {
281
281
  if (lower.includes("supply chain") || lower.includes("cross-machine")) {
282
282
  return findScene("cross_machine_supply_chain");
283
283
  }
284
- if (lower.includes("arbitration") || lower.includes("dispute")) {
285
- return findScene("arbitration_flow");
284
+ if (lower.includes("wonder") || lower.includes("praise") || lower.includes("complain")
285
+ || lower.includes("thumbs_up") || lower.includes("thumbs_down")
286
+ || lower.includes("rating") || lower.includes("feedback")
287
+ || lower.includes("好评") || lower.includes("差评")) {
288
+ return findScene("wonder_feedback");
286
289
  }
287
290
  if (lower.includes("reward") || lower.includes("incentive")) {
288
291
  return findScene("reward_incentive");
@@ -109,6 +109,29 @@ export const MACHINE_RISK_RULES = [
109
109
  auto_fixable: false,
110
110
  related_constraint: "MC-C-04",
111
111
  },
112
+ {
113
+ id: "R-M1-11",
114
+ name: "Dispute anti-pattern — machine illegally models dispute flow",
115
+ category: "structural",
116
+ severity: "critical",
117
+ description: "Machine contains nodes named 'disputed', 'refunded', 'arb', 'arbitration', " +
118
+ "'dispute', or 'refund'. This is a CRITICAL design anti-pattern because " +
119
+ "dispute is INDEPENDENT of the machine workflow. Dispute flows through: " +
120
+ "Order → Arbitration.dispute() → Arb object → arbitration() ruling → " +
121
+ "arb_withdraw by order holder. Arbitrators are configured in " +
122
+ "Arbitration.voting_guard, NOT in Permission. Merchants who model " +
123
+ "dispute in the machine (e.g., 'disputed → refunded' forward guarded " +
124
+ "by Permission index 1500) create dead-end paths because no one has " +
125
+ "Permission 1500 — arbitrators are NOT Permission holders.",
126
+ detection: "Match node names against regex /^(disputed|refunded|arb|arbitration|dispute|refund)$/i",
127
+ recommendation: "Remove all dispute/refund nodes from the machine. Instead: (1) bind an " +
128
+ "Arbitration object to the Service via Service.arbitrations, (2) configure " +
129
+ "arbitrators via Arbitration.voting_guard_add, (3) deposit compensation_fund " +
130
+ "to the Service. Disputes are then filed via arbitration::dispute(order, " +
131
+ "arbitration) — completely independent of the machine workflow.",
132
+ auto_fixable: false,
133
+ related_constraint: "dispute_independence_principle",
134
+ },
112
135
  {
113
136
  id: "R-M2-01",
114
137
  name: "Guard not created",
@@ -993,142 +993,174 @@ export const MACHINE_TEMPLATES = [
993
993
  ],
994
994
  },
995
995
  {
996
- id: "tpl_machine_arbitration",
997
- scene_id: "arbitration_flow",
998
- name: "Arbitration Flow",
999
- description: "Dispute -> Submit -> Vote -> Ruling -> Execute. Uses arbitration_usage_guard + arbitration_voting_guard. " +
1000
- "MAX_DISPUTE_COUNT=10 per order. Dual-vote prevention mandatory.",
1001
- recommended_patterns: ["P-M-VOTE", "P-M-SEQ"],
1002
- recommended_topology: ["P-M-LINEAR"],
996
+ id: "tpl_machine_wonder_feedback",
997
+ scene_id: "wonder_feedback",
998
+ name: "Wonder Feedback Flow (Post-Completion Rating)",
999
+ description: "Post-completion rating flow: Paid (entry) → Shipped → Received → Completed → {Wonder | NoWonder}. " +
1000
+ "After the order reaches 'Completed' (terminal business state, funds already allocated to merchant), " +
1001
+ "the OrderHolder (namedOperator='') can advance Progress to 'wonder' (positive review) or 'no_wonder' " +
1002
+ "(negative review). A bound Reward object's claim_guard verifies Progress.current == 'wonder' before " +
1003
+ "paying out — reward claim needs NO Permission. Dispute is NEVER modeled here: it flows through " +
1004
+ "Order → Arbitration.dispute() → Arb → arbitration() ruling → arb_withdraw, fully off-Machine.",
1005
+ recommended_patterns: ["P-M-SEQ", "P-M-COMPETING"],
1006
+ recommended_topology: ["P-M-LINEAR", "P-M-COMPETING"],
1003
1007
  parameters: [
1004
1008
  {
1005
- name: "arbitration_ref",
1006
- type: "arbitration_ref",
1009
+ name: "merchant_permission",
1010
+ type: "permission_ref",
1007
1011
  required: true,
1008
- description: "Arbitration object address",
1012
+ description: "Merchant Permission object (index 2 = merchant, index 3 = admin)",
1009
1013
  },
1010
1014
  {
1011
- name: "max_disputes",
1012
- type: "number",
1015
+ name: "wonder_node_name",
1016
+ type: "string",
1013
1017
  required: true,
1014
- default: "10",
1015
- description: "Hard limit: MAX_DISPUTE_COUNT=10 per order",
1018
+ default: "wonder",
1019
+ description: "Node name for positive feedback (好评) terminal",
1020
+ constraint: "Must match /^(wonder|praise|thumbs_up)$/i OR be a custom name; must NOT match dispute anti-pattern regex",
1016
1021
  },
1017
1022
  {
1018
- name: "voting_threshold",
1019
- type: "number",
1023
+ name: "no_wonder_node_name",
1024
+ type: "string",
1020
1025
  required: true,
1021
- default: "100",
1022
- description: "Weight threshold required for a voting decision to pass",
1026
+ default: "no_wonder",
1027
+ description: "Node name for negative feedback (差评) terminal",
1028
+ constraint: "Must match /^(no_wonder|complain|thumbs_down)$/i OR be a custom name; must NOT match dispute anti-pattern regex",
1029
+ },
1030
+ {
1031
+ name: "reward_ref",
1032
+ type: "reward_ref",
1033
+ required: false,
1034
+ description: "Reward object address (optional). If bound, its claim_guard should verify Progress.current == wonder_node_name AND order.holder == signer. Use wonder_praise or wonder_feedback reward template.",
1035
+ },
1036
+ {
1037
+ name: "rating_time_limit_days",
1038
+ type: "number",
1039
+ required: false,
1040
+ default: "30",
1041
+ description: "Days after completion during which the customer can rate (enforced by Guard on Praise/Complain forwards via context(Clock))",
1042
+ constraint: "Must be > 0; reasonable range 7-90",
1023
1043
  },
1024
1044
  ],
1025
1045
  example_nodes: [
1026
- { name: "DisputeOpened" },
1027
- { name: "EvidenceSubmitted" },
1028
- { name: "Voting" },
1029
- { name: "Ruling" },
1030
- { name: "Executed" },
1046
+ { name: "Paid", description: "Entry: order created and paid via Service.buy(); Machine starts here" },
1047
+ { name: "Shipped", description: "Merchant dispatched goods/service" },
1048
+ { name: "Received", description: "Customer confirmed receipt" },
1049
+ { name: "Completed", description: "Terminal business state — triggers merchant Allocation. Funds settled." },
1050
+ { name: "${wonder_node_name}", description: "Positive feedback terminal (好评). OrderHolder-only. Reward-eligible." },
1051
+ { name: "${no_wonder_node_name}", description: "Negative feedback terminal (差评). OrderHolder-only. Optional Repository write for analytics." },
1031
1052
  ],
1032
1053
  example_pairs: [
1033
1054
  {
1034
1055
  prev_node: "",
1035
- next_node: "DisputeOpened",
1056
+ next_node: "Paid",
1036
1057
  threshold: 1,
1037
- forwards: [
1038
- {
1039
- name: "OpenDispute",
1040
- weight: 1,
1041
- named_operator: "",
1042
- guard: "arb_usage_guard",
1043
- retained_submission: "dispute_record",
1044
- },
1045
- ],
1058
+ forwards: [{ name: "Enter", weight: 1, named_operator: "" }],
1046
1059
  },
1047
1060
  {
1048
- prev_node: "DisputeOpened",
1049
- next_node: "EvidenceSubmitted",
1061
+ prev_node: "Paid",
1062
+ next_node: "Shipped",
1050
1063
  threshold: 1,
1051
- forwards: [{ name: "SubmitEvidence", weight: 1, named_operator: "" }],
1064
+ forwards: [{ name: "Ship", weight: 1, permission_index: 2, named_operator: "" }],
1052
1065
  },
1053
1066
  {
1054
- prev_node: "EvidenceSubmitted",
1055
- next_node: "Voting",
1067
+ prev_node: "Shipped",
1068
+ next_node: "Received",
1069
+ threshold: 1,
1070
+ forwards: [{ name: "ConfirmReceive", weight: 1, named_operator: "" }],
1071
+ },
1072
+ {
1073
+ prev_node: "Received",
1074
+ next_node: "Completed",
1056
1075
  threshold: 1,
1057
- forwards: [{ name: "StartVote", weight: 1, permission_index: 3 }],
1076
+ forwards: [{ name: "Finish", weight: 1, permission_index: 2, named_operator: "" }],
1058
1077
  },
1059
1078
  {
1060
- prev_node: "Voting",
1061
- next_node: "Ruling",
1062
- threshold: "${voting_threshold}",
1079
+ prev_node: "Completed",
1080
+ next_node: "${wonder_node_name}",
1081
+ threshold: 1,
1063
1082
  forwards: [
1064
1083
  {
1065
- name: "ChiefVote",
1066
- weight: 60,
1067
- permission_index: 2,
1068
- guard: "arb_voting_guard",
1069
- },
1070
- {
1071
- name: "MemberVote",
1072
- weight: 8,
1073
- named_operator: "arbitrator",
1074
- guard: "arb_voting_guard",
1084
+ name: "Praise",
1085
+ weight: 1,
1086
+ named_operator: "",
1087
+ guard: "rating_window_guard",
1088
+ retained_submission: "rating_record",
1075
1089
  },
1076
1090
  ],
1077
1091
  },
1078
1092
  {
1079
- prev_node: "Ruling",
1080
- next_node: "Executed",
1093
+ prev_node: "Completed",
1094
+ next_node: "${no_wonder_node_name}",
1081
1095
  threshold: 1,
1082
- forwards: [{ name: "Execute", weight: 1, permission_index: 3 }],
1096
+ forwards: [
1097
+ {
1098
+ name: "Complain",
1099
+ weight: 1,
1100
+ named_operator: "",
1101
+ guard: "rating_window_guard",
1102
+ retained_submission: "rating_record",
1103
+ },
1104
+ ],
1083
1105
  },
1084
1106
  ],
1085
1107
  required_guards: [
1086
1108
  {
1087
- guard_template_id: "tpl_usage_guard_threshold",
1088
- bound_to_forward: "OpenDispute",
1089
- purpose: "Verify dispute count < MAX_DISPUTE_COUNT (10)",
1090
- },
1091
- {
1092
- guard_template_id: "tpl_voting_guard_numeric_weight",
1093
- bound_to_forward: "ChiefVote",
1094
- purpose: "Verify voting eligibility + weight extraction",
1109
+ guard_template_id: "tpl_forward_node_check",
1110
+ bound_to_forward: "Praise",
1111
+ purpose: "Verify Progress.current == 'Completed' (rating only allowed after completion) AND context(Clock) <= progress.entry_time + rating_time_limit_days",
1095
1112
  },
1096
1113
  {
1097
- guard_template_id: "tpl_voting_guard_numeric_weight",
1098
- bound_to_forward: "MemberVote",
1099
- purpose: "Verify voting eligibility + prevent double-vote (R-X1-14 CRITICAL)",
1114
+ guard_template_id: "tpl_forward_node_check",
1115
+ bound_to_forward: "Complain",
1116
+ purpose: "Verify Progress.current == 'Completed' (rating only allowed after completion) AND context(Clock) <= progress.entry_time + rating_time_limit_days",
1100
1117
  },
1101
1118
  ],
1102
1119
  required_permissions: [
1103
- { index: 2, name: "chief_arbitrator", description: "High-weight voter" },
1104
- { index: 3, name: "admin", description: "Vote starter / executor" },
1120
+ { index: 2, name: "merchant", description: "Merchant role for Ship + Finish (NOT for rating — rating is OrderHolder-only via named_operator='')" },
1121
+ { index: 3, name: "admin", description: "Admin override (optional)" },
1105
1122
  ],
1106
1123
  required_allocators: [
1107
1124
  {
1108
- name: "RulingAllocation",
1109
- trigger_node: "Executed",
1110
- description: "Allocation based on ruling outcome",
1125
+ name: "MerchantAllocation",
1126
+ trigger_node: "Completed",
1127
+ description: "100% to merchant Treasury or personal address. Fires at Completion — NOT at Wonder/NoWonder (those are reputation-only terminals with no fund flow).",
1111
1128
  },
1112
1129
  ],
1113
- forkable_fields: ["voting_threshold", "voter_count", "evidence_steps"],
1114
- applicable_industries: ["arbitration", "ecommerce", "service", "rental"],
1130
+ forkable_fields: ["wonder_node_name", "no_wonder_node_name", "rating_time_limit_days", "additional_rating_nodes"],
1131
+ applicable_industries: ["ecommerce", "service", "rental", "subscription", "education"],
1115
1132
  relevant_risks: [
1116
- "R-M1-08", "R-M2-05", "R-M3-04", "R-M5-04",
1117
- "R-X1-14",
1133
+ "R-M1-01", "R-M1-02", "R-M1-08", "R-M1-11",
1134
+ "R-M2-01", "R-M2-05",
1135
+ "R-M3-02", "R-M3-04",
1136
+ "R-M4-01", "R-M5-03",
1118
1137
  ],
1119
1138
  creation_notes: [
1120
- "Arbitration object MUST be created before this Machine",
1121
- "Anti-double-dispute: query arb.dispute_count < 10 (R-X1-14 HIGH)",
1122
- "Anti-double-vote: query arb.voted has [Signer] (R-X1-14 CRITICAL)",
1123
- "MAX_DISPUTE_COUNT=10 hard limit per order (on-chain constant)",
1124
- ],
1125
- example_use_case: "E-commerce dispute: customer claims item not as described. Arbitration panel (1 chief + 3 members) " +
1126
- "votes to rule. Up to 10 disputes per order allowed.",
1139
+ "DISPUTE INDEPENDENCE: This Machine MUST NOT contain 'disputed', 'refunded', 'arb', 'arbitration' nodes (R-M1-11 critical). Disputes are filed off-Machine via arbitration::dispute(order, arbitration).",
1140
+ "Build order: Permission → Machine (unpublished) → Guards → Bind guards → Publish → Bind to Service → Bind Reward (optional)",
1141
+ "First node 'Paid' represents post-payment state — Service.buy() completes payment atomically before Progress is created",
1142
+ "No separate 'Order' or 'Pay' nodes needed — payment is handled by Service.buy(), not Machine",
1143
+ "Wonder/NoWonder forwards have named_operator='' (empty) — this delegates to the OrderHolder (customer). No Permission index needed for rating.",
1144
+ "Completed node triggers MerchantAllocation — funds are settled BEFORE the rating window opens. Rating does NOT affect fund flow.",
1145
+ "Competing Pair: Praise vs Complain (first-wins) — one rating per order, enforced by Guard + retained_submission audit trail.",
1146
+ "Reward object (if bound) is INDEPENDENT of Allocator: Reward.claim() requires Guard+passport, not Permission. Guard checks Progress.current == 'wonder' AND order.holder == signer.",
1147
+ "rating_window_guard enforces time limit: context(Clock) <= progress.entry_time + rating_time_limit_days. After window closes, no rating possible (Completed becomes true terminal).",
1148
+ "Optional: bind a Repository to record feedback node for analytics. Repository write happens via Guard's data extraction (NOT auto-passed — see 06 §14.4).",
1149
+ ],
1150
+ example_use_case: "Photography service: Paid → Shipped (booking confirmed) → Received (session delivered) → Completed " +
1151
+ "(photos delivered, funds released to photographer) → customer advances to 'wonder' (loved the work) " +
1152
+ "or 'no_wonder' (didn't love it). Bound Reward pays 1 WOW to customers who advance to 'wonder'. " +
1153
+ "Merchant's wonder/no_wonder ratio becomes an on-chain reputation signal. Disputes (if any) are " +
1154
+ "filed separately via Arbitration object — never enter this Machine.",
1127
1155
  publish_checklist: [
1128
- "Arbitration object created and tested",
1129
- "Both anti-double Guards tested (dispute + vote)",
1130
- "Voting threshold verified with different weight distributions",
1131
- "MAX_DISPUTE_COUNT boundary tested (10th dispute OK, 11th fails)",
1156
+ "All 6 nodes verified: no 'disputed'/'refunded'/'arb'/'arbitration' names (R-M1-11 PASS)",
1157
+ "Entry Pair exists (prev_node='' targets 'Paid')",
1158
+ "Competing Pair tested: Praise vs Complain — first-wins semantics verified on testnet",
1159
+ "rating_window_guard tested: rating blocked after time limit expires",
1160
+ "MerchantAllocation fires ONLY at Completed (NOT at Wonder/NoWonder)",
1161
+ "Reward object (if bound): claim_guard tested — Progress.current == 'wonder' required, claim fails at 'no_wonder' or 'Completed'",
1162
+ "named_operator='' on Praise/Complain forwards verified (OrderHolder-only operation)",
1163
+ "machineNode2file export as backup",
1132
1164
  ],
1133
1165
  },
1134
1166
  {