@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.
- package/README.md +5 -3
- package/dist/customer/info-puzzle.d.ts +1 -1
- package/dist/customer/info-puzzle.js +4 -2
- package/dist/customer/risk-assessment.js +26 -4
- package/dist/customer/types.d.ts +2 -0
- package/dist/examples/guard-template-balance-check.json +38 -0
- package/dist/examples/guard-template-time-lock.json +39 -0
- package/dist/examples/machine-template-7node-rental.json +114 -0
- package/dist/examples/rental-ziroom-machine-create.json +137 -0
- package/dist/examples/rental-ziroom-permission-create.json +35 -0
- package/dist/examples/rental-ziroom-service-create.json +80 -0
- package/dist/examples/retail-myshop-service-create.json +88 -0
- package/dist/extensions/capability-manifest.d.ts +125 -0
- package/dist/extensions/capability-manifest.js +594 -0
- package/dist/extensions/constraint-registry.d.ts +24 -0
- package/dist/extensions/constraint-registry.js +196 -0
- package/dist/extensions/index.d.ts +12 -0
- package/dist/extensions/index.js +6 -0
- package/dist/extensions/metric-registry.d.ts +26 -0
- package/dist/extensions/metric-registry.js +257 -0
- package/dist/extensions/mode-evaluator.d.ts +15 -0
- package/dist/extensions/mode-evaluator.js +170 -0
- package/dist/extensions/modes.d.ts +2 -0
- package/dist/extensions/modes.js +493 -0
- package/dist/extensions/registry.d.ts +50 -0
- package/dist/extensions/registry.js +662 -0
- package/dist/extensions/types.d.ts +219 -0
- package/dist/extensions/types.js +1 -0
- package/dist/index.js +50 -0
- package/dist/knowledge/deployment-scanner.d.ts +3 -0
- package/dist/knowledge/deployment-scanner.js +64 -3
- package/dist/knowledge/fund-layer.d.ts +72 -0
- package/dist/knowledge/fund-layer.js +420 -0
- package/dist/knowledge/guard-render.d.ts +57 -0
- package/dist/knowledge/guard-render.js +700 -0
- package/dist/knowledge/guard-risk.d.ts +13 -0
- package/dist/knowledge/guard-risk.js +57 -0
- package/dist/knowledge/guard-submission-prompt.d.ts +31 -0
- package/dist/knowledge/guard-submission-prompt.js +171 -0
- package/dist/knowledge/guard-templates.js +278 -0
- package/dist/knowledge/machine-ledger.js +1 -1
- package/dist/knowledge/machine-render.d.ts +41 -0
- package/dist/knowledge/machine-render.js +565 -0
- package/dist/knowledge/machine-templates.js +24 -5
- package/dist/knowledge/reward-confirm.js +2 -2
- package/dist/knowledge/reward-puzzle.js +1 -1
- package/dist/knowledge/reward-risk.js +9 -9
- package/dist/knowledge/reward-templates.js +2 -2
- package/dist/knowledge/service-confirm.d.ts +15 -6
- package/dist/knowledge/service-confirm.js +119 -11
- package/dist/knowledge/service-context.js +1 -1
- package/dist/knowledge/service-ledger.js +1 -1
- package/dist/knowledge/service-risk.d.ts +1 -1
- package/dist/knowledge/service-risk.js +3 -3
- package/dist/knowledge/service-templates.js +2 -2
- package/dist/knowledge/service-translation.d.ts +1 -1
- package/dist/knowledge/service-translation.js +7 -7
- package/dist/knowledge/tool-constraints.js +6 -2
- package/dist/project/deployment-bridge.d.ts +1 -1
- package/dist/project/deployment-bridge.js +31 -7
- package/dist/project/deployment-doc.d.ts +3 -0
- package/dist/project/deployment-doc.js +76 -14
- package/dist/project/edit-planner.d.ts +123 -0
- package/dist/project/edit-planner.js +1371 -0
- package/dist/project/evaluation.d.ts +2 -0
- package/dist/project/evaluation.js +873 -88
- package/dist/project/graph-builder.d.ts +4 -1
- package/dist/project/graph-builder.js +132 -62
- package/dist/project/graph.d.ts +1 -0
- package/dist/project/handlers.d.ts +317 -6
- package/dist/project/handlers.js +1171 -19
- package/dist/project/stage-gate.d.ts +4 -0
- package/dist/project/stage-gate.js +64 -5
- package/dist/project/task-tracker.d.ts +26 -0
- package/dist/project/task-tracker.js +78 -0
- package/dist/safety/preview.js +16 -0
- package/dist/schema/call/allocation.d.ts +16 -16
- package/dist/schema/call/allocation.js +2 -2
- package/dist/schema/call/arbitration.js +19 -6
- package/dist/schema/call/base.d.ts +21 -13
- package/dist/schema/call/base.js +27 -6
- package/dist/schema/call/bridge.d.ts +5 -5
- package/dist/schema/call/bridge.js +3 -1
- package/dist/schema/call/contact.js +2 -2
- package/dist/schema/call/demand.d.ts +23 -31
- package/dist/schema/call/demand.js +2 -2
- package/dist/schema/call/guard.js +2 -2
- package/dist/schema/call/machine.d.ts +1976 -752
- package/dist/schema/call/machine.js +50 -4
- package/dist/schema/call/order.d.ts +149 -228
- package/dist/schema/call/order.js +4 -3
- package/dist/schema/call/payment.d.ts +183 -3
- package/dist/schema/call/payment.js +21 -3
- package/dist/schema/call/permission.js +2 -2
- package/dist/schema/call/personal.d.ts +241 -52
- package/dist/schema/call/progress.d.ts +53 -61
- package/dist/schema/call/progress.js +18 -4
- package/dist/schema/call/repository.d.ts +23 -31
- package/dist/schema/call/repository.js +2 -2
- package/dist/schema/call/reward.js +3 -3
- package/dist/schema/call/semantic.d.ts +1 -1
- package/dist/schema/call/semantic.js +37 -2
- package/dist/schema/call/service.d.ts +275 -127
- package/dist/schema/call/service.js +89 -13
- package/dist/schema/call/treasury.js +3 -3
- package/dist/schema/common/index.d.ts +13 -2
- package/dist/schema/common/index.js +80 -15
- package/dist/schema/local/index.d.ts +32 -35
- package/dist/schema/local/index.js +28 -6
- package/dist/schema/messenger/index.d.ts +274 -46
- package/dist/schema/operations.d.ts +1264 -544
- package/dist/schema/operations.js +65 -7
- package/dist/schema/project/index.d.ts +2846 -167
- package/dist/schema/project/index.js +570 -16
- package/dist/schema/query/index.d.ts +922 -349
- package/dist/schema/query/index.js +205 -42
- package/dist/schema/schema-query/index.d.ts +65 -3
- package/dist/schema/schema-query/index.js +38 -5
- package/dist/schema/utils/node-parser.js +20 -4
- package/dist/schema/utils/object-type-utils.d.ts +12 -0
- package/dist/schema/utils/object-type-utils.js +35 -0
- package/dist/schema/utils/permission-machine-check.d.ts +49 -0
- package/dist/schema/utils/permission-machine-check.js +121 -0
- package/dist/schema/utils/skills-recommendation.d.ts +2 -0
- package/dist/schema/utils/skills-recommendation.js +76 -0
- package/dist/schema-query/index.d.ts +20 -1
- package/dist/schema-query/index.js +306 -4
- package/dist/schemas/account_operation.output.json +7 -1
- package/dist/schemas/account_operation.schema.json +3 -3
- package/dist/schemas/bridge_operation.output.json +6 -0
- package/dist/schemas/bridge_operation.schema.json +1 -1
- package/dist/schemas/guard-templates.json +379 -0
- package/dist/schemas/guard2file.schema.json +1 -1
- package/dist/schemas/index.json +1 -1
- package/dist/schemas/local_info_operation.output.json +6 -0
- package/dist/schemas/local_mark_operation.output.json +7 -1
- package/dist/schemas/local_mark_operation.schema.json +1 -1
- package/dist/schemas/machineNode2file.schema.json +1 -1
- package/dist/schemas/messenger_operation.schema.json +4 -4
- package/dist/schemas/onchain_events.output.json +1 -1
- package/dist/schemas/onchain_operations.output.json +2820 -0
- package/dist/schemas/onchain_operations.schema.json +444 -330
- package/dist/schemas/onchain_operations_allocation.schema.json +37 -28
- package/dist/schemas/onchain_operations_arbitration.schema.json +16 -14
- package/dist/schemas/onchain_operations_contact.schema.json +10 -10
- package/dist/schemas/onchain_operations_demand.schema.json +10 -10
- package/dist/schemas/onchain_operations_gen_passport.schema.json +16 -16
- package/dist/schemas/onchain_operations_gen_proof.schema.json +2 -2
- package/dist/schemas/onchain_operations_guard.schema.json +2 -2
- package/dist/schemas/onchain_operations_machine.schema.json +43 -35
- package/dist/schemas/onchain_operations_order.schema.json +72 -78
- package/dist/schemas/onchain_operations_payment.schema.json +141 -111
- package/dist/schemas/onchain_operations_permission.schema.json +3 -3
- package/dist/schemas/onchain_operations_personal.schema.json +7 -7
- package/dist/schemas/onchain_operations_progress.schema.json +9 -9
- package/dist/schemas/onchain_operations_proof.schema.json +8 -8
- package/dist/schemas/onchain_operations_repository.schema.json +10 -10
- package/dist/schemas/onchain_operations_reward.schema.json +14 -14
- package/dist/schemas/onchain_operations_service.schema.json +119 -48
- package/dist/schemas/onchain_operations_treasury.schema.json +11 -11
- package/dist/schemas/onchain_table_data.output.json +33 -25
- package/dist/schemas/onchain_table_data.schema.json +22 -21
- package/dist/schemas/project_operation.output.json +2194 -23
- package/dist/schemas/project_operation.schema.json +84 -6
- package/dist/schemas/query_toolkit.output.json +108 -80
- package/dist/schemas/query_toolkit.schema.json +12 -12
- package/dist/schemas/schema_query.output.json +70 -3
- package/dist/schemas/schema_query.schema.json +18 -3
- package/dist/schemas/wowok_buildin_info.output.json +81 -8
- package/dist/schemas/wowok_buildin_info.schema.json +46 -2
- package/dist/tools/handlers/local.js +20 -5
- package/dist/tools/handlers/onchain.js +594 -6
- package/dist/tools/handlers/project.js +76 -4
- package/dist/tools/handlers/query.js +27 -0
- package/dist/tools/handlers/schema-query.js +43 -1
- package/dist/tools/handlers/task-status.d.ts +170 -0
- package/dist/tools/handlers/task-status.js +55 -0
- package/dist/tools/handlers/wip.js +47 -1
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +387 -12
- package/dist/tools/retry.d.ts +8 -0
- package/dist/tools/retry.js +85 -0
- package/dist/tools/wip-deploy-assist.d.ts +28 -0
- package/dist/tools/wip-deploy-assist.js +278 -0
- package/package.json +2 -2
- 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: "
|
|
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: "
|
|
15
|
-
{ id: "R5", objectType: "Allocation", fieldName: "order_allocators", required: true, description: "Order allocators —
|
|
16
|
-
{ id: "R6", objectType: "Treasury", fieldName: "
|
|
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: "
|
|
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
|
-
|
|
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
|
|
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
|
|
82
|
-
visited.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
560
|
+
const deduction = effectiveCritical ? -25 : -10;
|
|
173
561
|
structuralScore += deduction;
|
|
174
562
|
findings.push({
|
|
175
563
|
criterion: "structural_risk",
|
|
176
|
-
severity:
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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 (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
218
|
-
deduction: -
|
|
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 -=
|
|
735
|
+
governanceScore -= 5;
|
|
278
736
|
findings.push({
|
|
279
737
|
criterion: "governance_risk",
|
|
280
|
-
severity: "
|
|
281
|
-
message: "Service has no UM (manager) —
|
|
282
|
-
deduction: -
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
539
|
-
message: `${check.id}: ${check.check} —
|
|
540
|
-
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
|
|
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 =
|
|
1315
|
+
const deduction = effectiveCritical ? -20 : -8;
|
|
665
1316
|
bindingScore += deduction;
|
|
666
1317
|
findings.push({
|
|
667
1318
|
criterion: "binding_completeness",
|
|
668
|
-
severity:
|
|
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 -=
|
|
1342
|
+
workflowScore -= 20;
|
|
692
1343
|
findings.push({
|
|
693
1344
|
criterion: "workflow_rationality",
|
|
694
|
-
severity: "
|
|
695
|
-
message: "No Machine object —
|
|
696
|
-
deduction: -
|
|
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 =
|
|
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(
|
|
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 (
|
|
768
|
-
|
|
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
|
|
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(
|
|
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(
|
|
1560
|
+
return evaluateRisk(filtered);
|
|
832
1561
|
case "trust":
|
|
833
|
-
return evaluateTrust(
|
|
1562
|
+
return evaluateTrust(filtered);
|
|
834
1563
|
case "completeness":
|
|
835
|
-
return evaluateCompleteness(
|
|
1564
|
+
return evaluateCompleteness(filtered);
|
|
836
1565
|
case "worth_buying":
|
|
837
|
-
return evaluateWorthBuyingStub(
|
|
1566
|
+
return evaluateWorthBuyingStub(filtered, llmPrompt);
|
|
838
1567
|
case "value":
|
|
839
|
-
return evaluateValueStub(
|
|
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)
|