@wowok/agent-mcp 2.5.4 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -26
- package/dist/config/runtime.js +3 -6
- package/dist/harness/checkpoint.js +2 -2
- package/dist/knowledge/flywheel-loop.js +2 -5
- package/dist/knowledge/overrides-loader.js +2 -4
- package/dist/knowledge/progress-ledger.js +3 -0
- package/dist/knowledge/progress-translation.js +5 -1
- package/dist/knowledge/reward-confirm.d.ts +3 -0
- package/dist/knowledge/reward-confirm.js +30 -0
- package/dist/knowledge/reward-puzzle.d.ts +1 -0
- package/dist/knowledge/reward-puzzle.js +12 -0
- package/dist/knowledge/reward-risk.d.ts +4 -1
- package/dist/knowledge/reward-risk.js +85 -3
- package/dist/knowledge/tools-reference.js +5 -4
- package/dist/loop-engineering/improve.js +2 -4
- package/dist/project/deployment-bridge.d.ts +5 -0
- package/dist/project/deployment-bridge.js +96 -0
- package/dist/project/evaluation.d.ts +53 -0
- package/dist/project/evaluation.js +860 -0
- package/dist/project/graph-builder.d.ts +43 -0
- package/dist/project/graph-builder.js +585 -0
- package/dist/project/handlers.d.ts +154 -0
- package/dist/project/handlers.js +567 -0
- package/dist/project/index.d.ts +1 -0
- package/dist/project/index.js +26 -7
- package/dist/project/project-store.js +2 -5
- package/dist/project/query.d.ts +1 -0
- package/dist/project/query.js +2 -0
- package/dist/project/semantic-assembler.d.ts +93 -0
- package/dist/project/semantic-assembler.js +345 -0
- package/dist/schema/call/base.js +1 -1
- package/dist/schema/config/index.d.ts +12 -12
- package/dist/schema/local/index.d.ts +108 -108
- package/dist/schema/local/index.js +21 -18
- package/dist/schema/messenger/index.d.ts +16 -16
- package/dist/schema/messenger/index.js +2 -2
- package/dist/schema/operations.d.ts +0 -6
- package/dist/schema/project/index.d.ts +1840 -3903
- package/dist/schema/project/index.js +403 -955
- package/dist/schema/query/index.d.ts +10 -5
- package/dist/schema/trust/index.d.ts +6 -6
- package/dist/schemas/account_operation.output.json +7 -21
- package/dist/schemas/account_operation.schema.json +9 -23
- package/dist/schemas/bridge_operation.schema.json +1 -1
- package/dist/schemas/guard2file.schema.json +1 -1
- package/dist/schemas/index.json +2 -2
- package/dist/schemas/machineNode2file.schema.json +1 -1
- package/dist/schemas/messenger_operation.schema.json +6 -6
- package/dist/schemas/onchain_operations.schema.json +5 -5
- package/dist/schemas/onchain_operations_allocation.schema.json +1 -1
- package/dist/schemas/onchain_operations_arbitration.schema.json +1 -1
- package/dist/schemas/onchain_operations_contact.schema.json +1 -1
- package/dist/schemas/onchain_operations_demand.schema.json +1 -1
- package/dist/schemas/onchain_operations_gen_passport.schema.json +2 -2
- package/dist/schemas/onchain_operations_gen_proof.schema.json +2 -2
- package/dist/schemas/onchain_operations_guard.schema.json +1 -1
- package/dist/schemas/onchain_operations_machine.schema.json +1 -1
- package/dist/schemas/onchain_operations_order.schema.json +1 -1
- package/dist/schemas/onchain_operations_payment.schema.json +1 -1
- package/dist/schemas/onchain_operations_permission.schema.json +1 -1
- package/dist/schemas/onchain_operations_personal.schema.json +1 -1
- package/dist/schemas/onchain_operations_progress.schema.json +1 -1
- package/dist/schemas/onchain_operations_proof.schema.json +1 -1
- package/dist/schemas/onchain_operations_repository.schema.json +1 -1
- package/dist/schemas/onchain_operations_reward.schema.json +1 -1
- package/dist/schemas/onchain_operations_service.schema.json +1 -1
- package/dist/schemas/onchain_operations_treasury.schema.json +1 -1
- package/dist/schemas/project_operation.output.json +1178 -2494
- package/dist/schemas/project_operation.schema.json +216 -530
- package/dist/schemas/query_toolkit.output.json +7 -6
- package/dist/schemas/query_toolkit.schema.json +0 -4
- package/dist/telemetry/storage.js +2 -2
- package/dist/tools/handlers/onchain.js +110 -19
- package/dist/tools/handlers/project.js +56 -1046
- package/dist/tools/index.js +85 -56
- package/package.json +2 -2
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
const CRITICAL_BINDINGS = [
|
|
2
|
+
{ sourceType: "Service", targetType: "Machine", fieldName: "machine", critical: true },
|
|
3
|
+
{ sourceType: "Service", targetType: "Permission", fieldName: "permission", critical: true },
|
|
4
|
+
{ sourceType: "Service", targetType: "Guard", fieldName: "buy_guard", critical: false },
|
|
5
|
+
{ sourceType: "Service", targetType: "Treasury", fieldName: "compensation_fund", critical: false },
|
|
6
|
+
{ sourceType: "Service", targetType: "Allocation", fieldName: "order_allocators", critical: false },
|
|
7
|
+
{ sourceType: "Service", targetType: "Arbitration", fieldName: "arbitrations", critical: false },
|
|
8
|
+
{ sourceType: "Machine", targetType: "Permission", fieldName: "permission", critical: true },
|
|
9
|
+
];
|
|
10
|
+
const MERCHANT_REQUIRED_ITEMS = [
|
|
11
|
+
{ id: "R1", objectType: "Permission", fieldName: "", required: true, description: "Governance — who can manage the service" },
|
|
12
|
+
{ id: "R2", objectType: "Service", fieldName: "", required: true, description: "Core offering — the service itself" },
|
|
13
|
+
{ id: "R3", objectType: "Machine", fieldName: "machine", required: true, description: "Workflow — order processing pipeline" },
|
|
14
|
+
{ id: "R4", objectType: "Guard", fieldName: "buy_guard", required: true, description: "Purchase validation — guard on Service.buy()" },
|
|
15
|
+
{ id: "R5", objectType: "Allocation", fieldName: "order_allocators", required: true, description: "Order allocators — fund distribution rules" },
|
|
16
|
+
{ id: "R6", objectType: "Treasury", fieldName: "compensation_fund", required: true, description: "Compensation fund — buyer refund protection" },
|
|
17
|
+
{ id: "R7", objectType: "Arbitration", fieldName: "arbitrations", required: true, description: "Dispute resolution — arbitration configured" },
|
|
18
|
+
{ id: "R8", objectType: "Contact", fieldName: "", required: true, description: "Customer communication — contact info" },
|
|
19
|
+
{ id: "R9", objectType: "Progress", fieldName: "", required: false, description: "Order tracking — progress nodes" },
|
|
20
|
+
{ id: "R10", objectType: "Reward", fieldName: "rewards", required: false, description: "Customer incentives — reward claims" },
|
|
21
|
+
{ id: "R11", objectType: "Repository", fieldName: "repositories", required: false, description: "Documentation — published data" },
|
|
22
|
+
{ id: "R12", objectType: "Demand", fieldName: "", required: false, description: "Market discovery — demand signals" },
|
|
23
|
+
];
|
|
24
|
+
const CONSUMER_DUE_DILIGENCE = [
|
|
25
|
+
{ id: "E1", check: "Service is published (bPublished=true)", severity: "critical", deduction: -30 },
|
|
26
|
+
{ id: "E2", check: "Service is not paused (bPaused=false)", severity: "warning", deduction: -20 },
|
|
27
|
+
{ id: "E3", check: "Machine has workflow nodes", severity: "warning", deduction: -15 },
|
|
28
|
+
{ id: "E4", check: "Service has buy_guard (purchase validation)", severity: "warning", deduction: -15 },
|
|
29
|
+
{ id: "E5", check: "Service has compensation_fund (refund protection)", severity: "warning", deduction: -15 },
|
|
30
|
+
{ id: "E6", check: "Arbitration is configured (dispute resolution)", severity: "critical", deduction: -25 },
|
|
31
|
+
{ id: "E7", check: "Permission is present (governance)", severity: "warning", deduction: -10 },
|
|
32
|
+
{ id: "E8", check: "Contact is available (merchant reachable)", severity: "info", deduction: -5 },
|
|
33
|
+
{ id: "E9", check: "Repository has published data (transparency)", severity: "info", deduction: -5 },
|
|
34
|
+
{ id: "E10", check: "Reward is configured (customer incentives)", severity: "info", deduction: -3 },
|
|
35
|
+
{ id: "E11", check: "Service has description (offering clarity)", severity: "info", deduction: -5 },
|
|
36
|
+
{ 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
|
+
];
|
|
39
|
+
function clampScore(n) {
|
|
40
|
+
return Math.max(0, Math.min(100, Math.round(n)));
|
|
41
|
+
}
|
|
42
|
+
function shortTypeName(objectType) {
|
|
43
|
+
if (!objectType)
|
|
44
|
+
return "unknown";
|
|
45
|
+
const base = objectType.replace(/<.*>/, "").trim();
|
|
46
|
+
const segments = base.split("::");
|
|
47
|
+
return segments[segments.length - 1] || "unknown";
|
|
48
|
+
}
|
|
49
|
+
function parseCacheData(cached) {
|
|
50
|
+
if (!cached)
|
|
51
|
+
return null;
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(cached.data);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isAddress(v) {
|
|
60
|
+
return typeof v === "string" && v.startsWith("0x") && v.length >= 10;
|
|
61
|
+
}
|
|
62
|
+
function countArrayField(data, field) {
|
|
63
|
+
const v = data[field];
|
|
64
|
+
if (Array.isArray(v))
|
|
65
|
+
return v.length;
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
function detectCircularRefs(edges) {
|
|
69
|
+
const adj = new Map();
|
|
70
|
+
for (const e of edges) {
|
|
71
|
+
if (e.edgeSource === "potential")
|
|
72
|
+
continue;
|
|
73
|
+
if (!adj.has(e.sourceObject))
|
|
74
|
+
adj.set(e.sourceObject, []);
|
|
75
|
+
adj.get(e.sourceObject).push(e.targetObject);
|
|
76
|
+
}
|
|
77
|
+
const cycles = [];
|
|
78
|
+
const visited = new Set();
|
|
79
|
+
const recursionStack = new Set();
|
|
80
|
+
const path = [];
|
|
81
|
+
const dfs = (node) => {
|
|
82
|
+
visited.add(node);
|
|
83
|
+
recursionStack.add(node);
|
|
84
|
+
path.push(node);
|
|
85
|
+
const neighbors = adj.get(node) ?? [];
|
|
86
|
+
for (const neighbor of neighbors) {
|
|
87
|
+
if (!visited.has(neighbor)) {
|
|
88
|
+
dfs(neighbor);
|
|
89
|
+
}
|
|
90
|
+
else if (recursionStack.has(neighbor)) {
|
|
91
|
+
const cycleStart = path.indexOf(neighbor);
|
|
92
|
+
const cycle = path.slice(cycleStart).concat(neighbor);
|
|
93
|
+
cycles.push(cycle.join("→"));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
path.pop();
|
|
97
|
+
recursionStack.delete(node);
|
|
98
|
+
};
|
|
99
|
+
for (const node of adj.keys()) {
|
|
100
|
+
if (!visited.has(node))
|
|
101
|
+
dfs(node);
|
|
102
|
+
}
|
|
103
|
+
return cycles;
|
|
104
|
+
}
|
|
105
|
+
function adjustWeightsForPerspective(criterionScores, perspective) {
|
|
106
|
+
if (perspective === "consumer") {
|
|
107
|
+
return criterionScores.map(c => {
|
|
108
|
+
if (c.name === "reputation" || c.name === "arbitration_coverage" || c.name === "consumer_diligence") {
|
|
109
|
+
return { ...c, weight: c.weight * 1.4 };
|
|
110
|
+
}
|
|
111
|
+
if (c.name === "structural_risk" || c.name === "deployment_readiness") {
|
|
112
|
+
return { ...c, weight: c.weight * 0.7 };
|
|
113
|
+
}
|
|
114
|
+
return c;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (perspective === "producer") {
|
|
118
|
+
return criterionScores.map(c => {
|
|
119
|
+
if (c.name === "structural_risk" || c.name === "deployment_readiness" || c.name === "binding_completeness") {
|
|
120
|
+
return { ...c, weight: c.weight * 1.3 };
|
|
121
|
+
}
|
|
122
|
+
if (c.name === "consumer_diligence" || c.name === "transparency") {
|
|
123
|
+
return { ...c, weight: c.weight * 0.7 };
|
|
124
|
+
}
|
|
125
|
+
return c;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return criterionScores;
|
|
129
|
+
}
|
|
130
|
+
function normalizeWeights(criterionScores) {
|
|
131
|
+
const total = criterionScores.reduce((sum, c) => sum + c.weight, 0);
|
|
132
|
+
if (total === 0)
|
|
133
|
+
return criterionScores;
|
|
134
|
+
return criterionScores.map(c => ({ ...c, weight: c.weight / total }));
|
|
135
|
+
}
|
|
136
|
+
function buildBindingIndex(edges) {
|
|
137
|
+
const idx = new Map();
|
|
138
|
+
for (const e of edges) {
|
|
139
|
+
if (e.edgeSource === "potential")
|
|
140
|
+
continue;
|
|
141
|
+
if (!e.fieldName)
|
|
142
|
+
continue;
|
|
143
|
+
const key = `${e.sourceObject}#${e.fieldName}`;
|
|
144
|
+
if (!idx.has(key))
|
|
145
|
+
idx.set(key, new Set());
|
|
146
|
+
idx.get(key).add(e.targetObject);
|
|
147
|
+
}
|
|
148
|
+
return idx;
|
|
149
|
+
}
|
|
150
|
+
function evaluateRisk(input) {
|
|
151
|
+
const findings = [];
|
|
152
|
+
const strengths = [];
|
|
153
|
+
const { objects, edges, objectCache, project } = input;
|
|
154
|
+
const byType = new Map();
|
|
155
|
+
for (const obj of objects) {
|
|
156
|
+
const t = shortTypeName(obj.objectType);
|
|
157
|
+
if (!byType.has(t))
|
|
158
|
+
byType.set(t, []);
|
|
159
|
+
byType.get(t).push(obj);
|
|
160
|
+
}
|
|
161
|
+
const services = byType.get("Service") ?? [];
|
|
162
|
+
const bindingIndex = buildBindingIndex(edges);
|
|
163
|
+
let structuralScore = 100;
|
|
164
|
+
const serviceObj = services[0];
|
|
165
|
+
if (serviceObj) {
|
|
166
|
+
for (const expected of CRITICAL_BINDINGS) {
|
|
167
|
+
if (expected.sourceType !== "Service")
|
|
168
|
+
continue;
|
|
169
|
+
const key = `${serviceObj.objectAddress}#${expected.fieldName}`;
|
|
170
|
+
const bindings = bindingIndex.get(key);
|
|
171
|
+
if (!bindings || bindings.size === 0) {
|
|
172
|
+
const deduction = expected.critical ? -25 : -10;
|
|
173
|
+
structuralScore += deduction;
|
|
174
|
+
findings.push({
|
|
175
|
+
criterion: "structural_risk",
|
|
176
|
+
severity: expected.critical ? "critical" : "warning",
|
|
177
|
+
message: `Service missing ${expected.fieldName} binding (expected ${expected.targetType})`,
|
|
178
|
+
deduction,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
strengths.push(`Service has ${expected.fieldName} binding`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
structuralScore = 30;
|
|
188
|
+
findings.push({
|
|
189
|
+
criterion: "structural_risk",
|
|
190
|
+
severity: "critical",
|
|
191
|
+
message: "No Service object in project — project has no core",
|
|
192
|
+
deduction: -70,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
structuralScore = clampScore(structuralScore);
|
|
196
|
+
let fundScore = 100;
|
|
197
|
+
const treasuries = byType.get("Treasury") ?? [];
|
|
198
|
+
if (treasuries.length === 0) {
|
|
199
|
+
fundScore -= 30;
|
|
200
|
+
findings.push({
|
|
201
|
+
criterion: "fund_safety",
|
|
202
|
+
severity: "warning",
|
|
203
|
+
message: "No Treasury object — no compensation fund for refunds",
|
|
204
|
+
deduction: -30,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
strengths.push(`${treasuries.length} Treasury object(s) present`);
|
|
209
|
+
}
|
|
210
|
+
if (serviceObj) {
|
|
211
|
+
const cfKey = `${serviceObj.objectAddress}#compensation_fund`;
|
|
212
|
+
if (!bindingIndex.get(cfKey)?.size) {
|
|
213
|
+
fundScore -= 20;
|
|
214
|
+
findings.push({
|
|
215
|
+
criterion: "fund_safety",
|
|
216
|
+
severity: "warning",
|
|
217
|
+
message: "Service compensation_fund not bound — buyer refunds unprotected",
|
|
218
|
+
deduction: -20,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
fundScore = clampScore(fundScore);
|
|
223
|
+
let operationalScore = 100;
|
|
224
|
+
if (serviceObj) {
|
|
225
|
+
const data = parseCacheData(objectCache.get(serviceObj.objectAddress));
|
|
226
|
+
if (data) {
|
|
227
|
+
const paused = data.bPaused;
|
|
228
|
+
const published = data.bPublished;
|
|
229
|
+
if (published && paused) {
|
|
230
|
+
operationalScore -= 25;
|
|
231
|
+
findings.push({
|
|
232
|
+
criterion: "operational_risk",
|
|
233
|
+
severity: "warning",
|
|
234
|
+
message: "Service is published but PAUSED — operations halted",
|
|
235
|
+
deduction: -25,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (!published) {
|
|
239
|
+
operationalScore -= 15;
|
|
240
|
+
findings.push({
|
|
241
|
+
criterion: "operational_risk",
|
|
242
|
+
severity: "info",
|
|
243
|
+
message: "Service not yet published — project not live",
|
|
244
|
+
deduction: -15,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const arbitrations = byType.get("Arbitration") ?? [];
|
|
250
|
+
if (arbitrations.length === 0) {
|
|
251
|
+
operationalScore -= 20;
|
|
252
|
+
findings.push({
|
|
253
|
+
criterion: "operational_risk",
|
|
254
|
+
severity: "warning",
|
|
255
|
+
message: "No Arbitration object — disputes cannot be resolved",
|
|
256
|
+
deduction: -20,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
strengths.push(`${arbitrations.length} Arbitration object(s) configured`);
|
|
261
|
+
}
|
|
262
|
+
operationalScore = clampScore(operationalScore);
|
|
263
|
+
let governanceScore = 100;
|
|
264
|
+
const permissions = byType.get("Permission") ?? [];
|
|
265
|
+
if (permissions.length === 0) {
|
|
266
|
+
governanceScore -= 40;
|
|
267
|
+
findings.push({
|
|
268
|
+
criterion: "governance_risk",
|
|
269
|
+
severity: "critical",
|
|
270
|
+
message: "No Permission object — project is ungoverned",
|
|
271
|
+
deduction: -40,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
if (serviceObj) {
|
|
275
|
+
const data = parseCacheData(objectCache.get(serviceObj.objectAddress));
|
|
276
|
+
if (data && !isAddress(data.um)) {
|
|
277
|
+
governanceScore -= 15;
|
|
278
|
+
findings.push({
|
|
279
|
+
criterion: "governance_risk",
|
|
280
|
+
severity: "warning",
|
|
281
|
+
message: "Service has no UM (manager) — cannot be managed on-chain",
|
|
282
|
+
deduction: -15,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
governanceScore = clampScore(governanceScore);
|
|
287
|
+
let coherenceScore = 100;
|
|
288
|
+
const objectAddresses = new Set(objects.map(o => o.objectAddress));
|
|
289
|
+
let danglingCount = 0;
|
|
290
|
+
for (const e of edges) {
|
|
291
|
+
if (e.edgeSource === "potential")
|
|
292
|
+
continue;
|
|
293
|
+
if (!objectAddresses.has(e.targetObject))
|
|
294
|
+
danglingCount++;
|
|
295
|
+
}
|
|
296
|
+
if (danglingCount > 0) {
|
|
297
|
+
const deduction = -Math.min(20, danglingCount * 3);
|
|
298
|
+
coherenceScore += deduction;
|
|
299
|
+
findings.push({
|
|
300
|
+
criterion: "graph_coherence",
|
|
301
|
+
severity: danglingCount >= 3 ? "warning" : "info",
|
|
302
|
+
message: `${danglingCount} edge(s) point to objects outside the project — dangling references`,
|
|
303
|
+
deduction,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
const cycles = detectCircularRefs(edges);
|
|
307
|
+
if (cycles.length > 0) {
|
|
308
|
+
coherenceScore -= 15;
|
|
309
|
+
findings.push({
|
|
310
|
+
criterion: "graph_coherence",
|
|
311
|
+
severity: "warning",
|
|
312
|
+
message: `${cycles.length} circular reference(s) detected: ${cycles.slice(0, 2).join("; ")}${cycles.length > 2 ? "..." : ""}`,
|
|
313
|
+
deduction: -15,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
if (danglingCount === 0 && cycles.length === 0) {
|
|
317
|
+
strengths.push("Graph is coherent — no dangling refs or cycles");
|
|
318
|
+
}
|
|
319
|
+
coherenceScore = clampScore(coherenceScore);
|
|
320
|
+
let criterionScores = [
|
|
321
|
+
{ name: "structural_risk", score: structuralScore, weight: 0.25, rationale: `${CRITICAL_BINDINGS.filter(b => b.sourceType === "Service").length} expected Service bindings checked` },
|
|
322
|
+
{ name: "fund_safety", score: fundScore, weight: 0.2, rationale: `Treasury + compensation_fund coverage` },
|
|
323
|
+
{ name: "operational_risk", score: operationalScore, weight: 0.2, rationale: `Service state + arbitration coverage` },
|
|
324
|
+
{ name: "governance_risk", score: governanceScore, weight: 0.15, rationale: `Permission + UM presence` },
|
|
325
|
+
{ name: "graph_coherence", score: coherenceScore, weight: 0.2, rationale: `${danglingCount} dangling + ${cycles.length} circular refs` },
|
|
326
|
+
];
|
|
327
|
+
criterionScores = normalizeWeights(adjustWeightsForPerspective(criterionScores, project.perspective));
|
|
328
|
+
const overall = clampScore(criterionScores.reduce((sum, c) => sum + c.score * c.weight, 0));
|
|
329
|
+
const criticalCount = findings.filter(f => f.severity === "critical").length;
|
|
330
|
+
const summary = criticalCount > 0
|
|
331
|
+
? `HIGH RISK: ${criticalCount} critical issue(s). ${findings.length} total findings.`
|
|
332
|
+
: `Moderate risk: ${findings.length} finding(s), no critical issues.`;
|
|
333
|
+
return {
|
|
334
|
+
evaluationType: "risk",
|
|
335
|
+
score: overall,
|
|
336
|
+
summary,
|
|
337
|
+
criterionScores,
|
|
338
|
+
findings,
|
|
339
|
+
strengths,
|
|
340
|
+
needsLlm: false,
|
|
341
|
+
evaluatedAt: Date.now(),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function evaluateTrust(input) {
|
|
345
|
+
const findings = [];
|
|
346
|
+
const strengths = [];
|
|
347
|
+
const { objects, edges, objectCache, reputation, project } = input;
|
|
348
|
+
const byType = new Map();
|
|
349
|
+
for (const obj of objects) {
|
|
350
|
+
const t = shortTypeName(obj.objectType);
|
|
351
|
+
if (!byType.has(t))
|
|
352
|
+
byType.set(t, []);
|
|
353
|
+
byType.get(t).push(obj);
|
|
354
|
+
}
|
|
355
|
+
const services = byType.get("Service") ?? [];
|
|
356
|
+
let reputationScore = 50;
|
|
357
|
+
if (reputation && reputation.size > 0 && services[0]) {
|
|
358
|
+
const rep = reputation.get(services[0].objectAddress);
|
|
359
|
+
if (rep) {
|
|
360
|
+
const total = rep.likes + rep.dislikes;
|
|
361
|
+
if (total > 0) {
|
|
362
|
+
const ratio = rep.likes / total;
|
|
363
|
+
reputationScore = clampScore(50 + (ratio - 0.5) * 100);
|
|
364
|
+
if (ratio >= 0.8) {
|
|
365
|
+
strengths.push(`Strong reputation: ${rep.likes} likes vs ${rep.dislikes} dislikes`);
|
|
366
|
+
}
|
|
367
|
+
else if (ratio < 0.4) {
|
|
368
|
+
findings.push({
|
|
369
|
+
criterion: "reputation",
|
|
370
|
+
severity: "warning",
|
|
371
|
+
message: `Poor reputation: ${rep.dislikes} dislikes vs ${rep.likes} likes`,
|
|
372
|
+
deduction: -20,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (rep.affiliations >= 3) {
|
|
377
|
+
strengths.push(`${rep.affiliations} affiliations — trusted by other merchants`);
|
|
378
|
+
reputationScore = clampScore(reputationScore + 10);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
findings.push({
|
|
384
|
+
criterion: "reputation",
|
|
385
|
+
severity: "info",
|
|
386
|
+
message: "No Registrar reputation data — unknown merchant standing",
|
|
387
|
+
deduction: -10,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
reputationScore = clampScore(reputationScore);
|
|
391
|
+
let arbitrationScore = 100;
|
|
392
|
+
const arbitrations = byType.get("Arbitration") ?? [];
|
|
393
|
+
if (arbitrations.length === 0) {
|
|
394
|
+
arbitrationScore = 30;
|
|
395
|
+
findings.push({
|
|
396
|
+
criterion: "arbitration_coverage",
|
|
397
|
+
severity: "critical",
|
|
398
|
+
message: "No Arbitration object — buyers have no dispute resolution path",
|
|
399
|
+
deduction: -70,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
strengths.push(`${arbitrations.length} Arbitration object(s) — dispute resolution available`);
|
|
404
|
+
}
|
|
405
|
+
arbitrationScore = clampScore(arbitrationScore);
|
|
406
|
+
let guardScore = 60;
|
|
407
|
+
const guards = byType.get("Guard") ?? [];
|
|
408
|
+
if (guards.length === 0) {
|
|
409
|
+
guardScore -= 20;
|
|
410
|
+
findings.push({
|
|
411
|
+
criterion: "guard_quality",
|
|
412
|
+
severity: "warning",
|
|
413
|
+
message: "No Guard objects — no validation rules",
|
|
414
|
+
deduction: -20,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
guardScore = 80;
|
|
419
|
+
strengths.push(`${guards.length} Guard object(s) — validation rules configured`);
|
|
420
|
+
}
|
|
421
|
+
if (services[0]) {
|
|
422
|
+
const bindingIndex = buildBindingIndex(edges);
|
|
423
|
+
const bgKey = `${services[0].objectAddress}#buy_guard`;
|
|
424
|
+
if (bindingIndex.get(bgKey)?.size) {
|
|
425
|
+
guardScore = clampScore(guardScore + 15);
|
|
426
|
+
strengths.push("Service has buy_guard — purchase validation active");
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
findings.push({
|
|
430
|
+
criterion: "guard_quality",
|
|
431
|
+
severity: "warning",
|
|
432
|
+
message: "Service buy_guard not bound — unvalidated purchases",
|
|
433
|
+
deduction: -15,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
guardScore = clampScore(guardScore);
|
|
438
|
+
let transparencyScore = 60;
|
|
439
|
+
if (services[0]) {
|
|
440
|
+
const data = parseCacheData(objectCache.get(services[0].objectAddress));
|
|
441
|
+
if (data) {
|
|
442
|
+
const desc = data.description;
|
|
443
|
+
if (typeof desc === "string" && desc.length > 0) {
|
|
444
|
+
transparencyScore += 15;
|
|
445
|
+
strengths.push("Service has description");
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
findings.push({
|
|
449
|
+
criterion: "transparency",
|
|
450
|
+
severity: "info",
|
|
451
|
+
message: "Service has no description — buyers cannot evaluate offering",
|
|
452
|
+
deduction: -10,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const repositories = byType.get("Repository") ?? [];
|
|
458
|
+
if (repositories.length > 0) {
|
|
459
|
+
transparencyScore = clampScore(transparencyScore + 10);
|
|
460
|
+
strengths.push(`${repositories.length} Repository object(s) — supporting data published`);
|
|
461
|
+
}
|
|
462
|
+
const contacts = byType.get("Contact") ?? [];
|
|
463
|
+
if (contacts.length > 0) {
|
|
464
|
+
transparencyScore = clampScore(transparencyScore + 10);
|
|
465
|
+
strengths.push("Contact information available");
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
findings.push({
|
|
469
|
+
criterion: "transparency",
|
|
470
|
+
severity: "info",
|
|
471
|
+
message: "No Contact object — buyers cannot reach merchant",
|
|
472
|
+
deduction: -5,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
transparencyScore = clampScore(transparencyScore);
|
|
476
|
+
let diligenceScore = 100;
|
|
477
|
+
const diligencePassed = [];
|
|
478
|
+
const bindingIndex2 = buildBindingIndex(edges);
|
|
479
|
+
const serviceObj2 = services[0];
|
|
480
|
+
if (serviceObj2) {
|
|
481
|
+
const svcData = parseCacheData(objectCache.get(serviceObj2.objectAddress));
|
|
482
|
+
for (const check of CONSUMER_DUE_DILIGENCE) {
|
|
483
|
+
let passed = false;
|
|
484
|
+
switch (check.id) {
|
|
485
|
+
case "E1":
|
|
486
|
+
passed = !!(svcData?.bPublished);
|
|
487
|
+
break;
|
|
488
|
+
case "E2":
|
|
489
|
+
passed = !!svcData && !svcData.bPaused;
|
|
490
|
+
break;
|
|
491
|
+
case "E3":
|
|
492
|
+
passed = (byType.get("Machine") ?? []).some(m => {
|
|
493
|
+
const d = parseCacheData(objectCache.get(m.objectAddress));
|
|
494
|
+
return d && (countArrayField(d, "nodes") > 0);
|
|
495
|
+
});
|
|
496
|
+
break;
|
|
497
|
+
case "E4":
|
|
498
|
+
passed = !!bindingIndex2.get(`${serviceObj2.objectAddress}#buy_guard`)?.size;
|
|
499
|
+
break;
|
|
500
|
+
case "E5":
|
|
501
|
+
passed = !!bindingIndex2.get(`${serviceObj2.objectAddress}#compensation_fund`)?.size;
|
|
502
|
+
break;
|
|
503
|
+
case "E6":
|
|
504
|
+
passed = (byType.get("Arbitration") ?? []).length > 0;
|
|
505
|
+
break;
|
|
506
|
+
case "E7":
|
|
507
|
+
passed = (byType.get("Permission") ?? []).length > 0;
|
|
508
|
+
break;
|
|
509
|
+
case "E8":
|
|
510
|
+
passed = (byType.get("Contact") ?? []).length > 0;
|
|
511
|
+
break;
|
|
512
|
+
case "E9":
|
|
513
|
+
passed = (byType.get("Repository") ?? []).length > 0;
|
|
514
|
+
break;
|
|
515
|
+
case "E10":
|
|
516
|
+
passed = !!bindingIndex2.get(`${serviceObj2.objectAddress}#rewards`)?.size;
|
|
517
|
+
break;
|
|
518
|
+
case "E11":
|
|
519
|
+
passed = !!(svcData && typeof svcData.description === "string" && svcData.description.length > 0);
|
|
520
|
+
break;
|
|
521
|
+
case "E12":
|
|
522
|
+
if (reputation && reputation.size > 0) {
|
|
523
|
+
const rep = reputation.get(serviceObj2.objectAddress);
|
|
524
|
+
passed = !!rep && rep.likes >= rep.dislikes;
|
|
525
|
+
}
|
|
526
|
+
break;
|
|
527
|
+
case "E13":
|
|
528
|
+
passed = true;
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
if (passed) {
|
|
532
|
+
diligencePassed.push(check.id);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
diligenceScore += check.deduction;
|
|
536
|
+
findings.push({
|
|
537
|
+
criterion: "consumer_diligence",
|
|
538
|
+
severity: check.severity,
|
|
539
|
+
message: `${check.id}: ${check.check} — FAILED`,
|
|
540
|
+
deduction: check.deduction,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const passRate = diligencePassed.length / CONSUMER_DUE_DILIGENCE.length;
|
|
545
|
+
if (passRate >= 0.8) {
|
|
546
|
+
strengths.push(`Consumer due diligence: ${diligencePassed.length}/${CONSUMER_DUE_DILIGENCE.length} checks passed`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
diligenceScore = 20;
|
|
551
|
+
findings.push({
|
|
552
|
+
criterion: "consumer_diligence",
|
|
553
|
+
severity: "critical",
|
|
554
|
+
message: "No Service object — consumer due diligence cannot be performed",
|
|
555
|
+
deduction: -80,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
diligenceScore = clampScore(diligenceScore);
|
|
559
|
+
let criterionScores = [
|
|
560
|
+
{ name: "reputation", score: reputationScore, weight: 0.25, rationale: `Registrar likes/dislikes + affiliations` },
|
|
561
|
+
{ name: "arbitration_coverage", score: arbitrationScore, weight: 0.2, rationale: `Dispute resolution availability` },
|
|
562
|
+
{ name: "guard_quality", score: guardScore, weight: 0.2, rationale: `Validation rules + buy_guard binding` },
|
|
563
|
+
{ name: "transparency", score: transparencyScore, weight: 0.15, rationale: `Description + Repository + Contact presence` },
|
|
564
|
+
{ name: "consumer_diligence", score: diligenceScore, weight: 0.2, rationale: `${diligencePassed.length}/${CONSUMER_DUE_DILIGENCE.length} E-checks passed` },
|
|
565
|
+
];
|
|
566
|
+
criterionScores = normalizeWeights(adjustWeightsForPerspective(criterionScores, project.perspective));
|
|
567
|
+
const overall = clampScore(criterionScores.reduce((sum, c) => sum + c.score * c.weight, 0));
|
|
568
|
+
const summary = overall >= 70
|
|
569
|
+
? `Trusted project (score ${overall}/100). ${strengths.length} strength(s).`
|
|
570
|
+
: `Limited trust signals (score ${overall}/100). ${findings.length} finding(s).`;
|
|
571
|
+
return {
|
|
572
|
+
evaluationType: "trust",
|
|
573
|
+
score: overall,
|
|
574
|
+
summary,
|
|
575
|
+
criterionScores,
|
|
576
|
+
findings,
|
|
577
|
+
strengths,
|
|
578
|
+
needsLlm: false,
|
|
579
|
+
evaluatedAt: Date.now(),
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function evaluateCompleteness(input) {
|
|
583
|
+
const findings = [];
|
|
584
|
+
const strengths = [];
|
|
585
|
+
const { objects, edges, objectCache, project } = input;
|
|
586
|
+
const byType = new Map();
|
|
587
|
+
for (const obj of objects) {
|
|
588
|
+
const t = shortTypeName(obj.objectType);
|
|
589
|
+
if (!byType.has(t))
|
|
590
|
+
byType.set(t, []);
|
|
591
|
+
byType.get(t).push(obj);
|
|
592
|
+
}
|
|
593
|
+
const services = byType.get("Service") ?? [];
|
|
594
|
+
const bindingIndex = buildBindingIndex(edges);
|
|
595
|
+
const requiredItems = MERCHANT_REQUIRED_ITEMS;
|
|
596
|
+
const requiredCount = requiredItems.filter(r => r.required).length;
|
|
597
|
+
const optionalItems = requiredItems.filter(r => !r.required);
|
|
598
|
+
let requiredMet = 0;
|
|
599
|
+
let optionalMet = 0;
|
|
600
|
+
const missingRequired = [];
|
|
601
|
+
const missingOptional = [];
|
|
602
|
+
for (const item of requiredItems) {
|
|
603
|
+
const hasObject = (byType.get(item.objectType)?.length ?? 0) > 0;
|
|
604
|
+
const hasBinding = item.fieldName
|
|
605
|
+
? !!bindingIndex.get(`${services[0]?.objectAddress ?? ""}#${item.fieldName}`)?.size
|
|
606
|
+
: hasObject;
|
|
607
|
+
if (item.required) {
|
|
608
|
+
if (hasBinding || (!item.fieldName && hasObject)) {
|
|
609
|
+
requiredMet++;
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
missingRequired.push(`${item.id}(${item.objectType})`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
if (hasBinding || (!item.fieldName && hasObject)) {
|
|
617
|
+
optionalMet++;
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
missingOptional.push(`${item.id}(${item.objectType})`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const requiredRatio = requiredMet / requiredCount;
|
|
625
|
+
const optionalRatio = optionalItems.length > 0 ? optionalMet / optionalItems.length : 1;
|
|
626
|
+
let coverageScore = clampScore(requiredRatio * 80 + optionalRatio * 20);
|
|
627
|
+
if (missingRequired.length > 0) {
|
|
628
|
+
findings.push({
|
|
629
|
+
criterion: "object_coverage",
|
|
630
|
+
severity: missingRequired.length >= 3 ? "critical" : "warning",
|
|
631
|
+
message: `Missing required items: ${missingRequired.join(", ")} — ${missingRequired.length}/${requiredCount} required items absent`,
|
|
632
|
+
deduction: -15 * missingRequired.length,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
strengths.push(`All ${requiredCount} required items present (R1-R8)`);
|
|
637
|
+
}
|
|
638
|
+
if (missingOptional.length > 0 && missingOptional.length === optionalItems.length) {
|
|
639
|
+
findings.push({
|
|
640
|
+
criterion: "object_coverage",
|
|
641
|
+
severity: "info",
|
|
642
|
+
message: `No optional items configured: ${missingOptional.join(", ")} — recommended for enhanced UX`,
|
|
643
|
+
deduction: -5,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
else if (optionalMet > 0) {
|
|
647
|
+
strengths.push(`${optionalMet}/${optionalItems.length} optional items configured (R9-R12)`);
|
|
648
|
+
}
|
|
649
|
+
coverageScore = clampScore(coverageScore);
|
|
650
|
+
let bindingScore = 100;
|
|
651
|
+
if (services[0]) {
|
|
652
|
+
const serviceAddr = services[0].objectAddress;
|
|
653
|
+
let boundCount = 0;
|
|
654
|
+
let totalCount = 0;
|
|
655
|
+
for (const expected of CRITICAL_BINDINGS) {
|
|
656
|
+
if (expected.sourceType !== "Service")
|
|
657
|
+
continue;
|
|
658
|
+
totalCount++;
|
|
659
|
+
const key = `${serviceAddr}#${expected.fieldName}`;
|
|
660
|
+
if (bindingIndex.get(key)?.size) {
|
|
661
|
+
boundCount++;
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
const deduction = expected.critical ? -20 : -8;
|
|
665
|
+
bindingScore += deduction;
|
|
666
|
+
findings.push({
|
|
667
|
+
criterion: "binding_completeness",
|
|
668
|
+
severity: expected.critical ? "critical" : "warning",
|
|
669
|
+
message: `Service.${expected.fieldName} not bound (expected ${expected.targetType})`,
|
|
670
|
+
deduction,
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (boundCount === totalCount) {
|
|
675
|
+
strengths.push(`All ${totalCount} Service bindings established`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
bindingScore = 0;
|
|
680
|
+
findings.push({
|
|
681
|
+
criterion: "binding_completeness",
|
|
682
|
+
severity: "critical",
|
|
683
|
+
message: "No Service object — cannot evaluate bindings",
|
|
684
|
+
deduction: -100,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
bindingScore = clampScore(bindingScore);
|
|
688
|
+
let workflowScore = 80;
|
|
689
|
+
const machines = byType.get("Machine") ?? [];
|
|
690
|
+
if (machines.length === 0) {
|
|
691
|
+
workflowScore -= 30;
|
|
692
|
+
findings.push({
|
|
693
|
+
criterion: "workflow_rationality",
|
|
694
|
+
severity: "warning",
|
|
695
|
+
message: "No Machine object — no workflow defined",
|
|
696
|
+
deduction: -30,
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
for (const m of machines) {
|
|
701
|
+
const data = parseCacheData(objectCache.get(m.objectAddress));
|
|
702
|
+
if (data) {
|
|
703
|
+
const nodeCount = countArrayField(data, "nodes") || countArrayField(data, "node_count");
|
|
704
|
+
if (nodeCount > 0) {
|
|
705
|
+
strengths.push(`Machine has ${nodeCount} node(s)`);
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
workflowScore -= 10;
|
|
709
|
+
findings.push({
|
|
710
|
+
criterion: "workflow_rationality",
|
|
711
|
+
severity: "info",
|
|
712
|
+
message: `Machine ${m.objectAddress.slice(0, 8)} has no nodes`,
|
|
713
|
+
deduction: -10,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
const objectAddresses = new Set(objects.map(o => o.objectAddress));
|
|
720
|
+
let danglingCount = 0;
|
|
721
|
+
for (const e of edges) {
|
|
722
|
+
if (e.edgeSource === "potential")
|
|
723
|
+
continue;
|
|
724
|
+
if (!objectAddresses.has(e.targetObject))
|
|
725
|
+
danglingCount++;
|
|
726
|
+
}
|
|
727
|
+
if (danglingCount > 0) {
|
|
728
|
+
workflowScore = clampScore(workflowScore - Math.min(20, danglingCount * 2));
|
|
729
|
+
findings.push({
|
|
730
|
+
criterion: "workflow_rationality",
|
|
731
|
+
severity: "info",
|
|
732
|
+
message: `${danglingCount} edge(s) point to objects not in project — dangling references`,
|
|
733
|
+
deduction: -Math.min(20, danglingCount * 2),
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
workflowScore = clampScore(workflowScore);
|
|
737
|
+
let readinessScore = 100;
|
|
738
|
+
if (services[0]) {
|
|
739
|
+
const data = parseCacheData(objectCache.get(services[0].objectAddress));
|
|
740
|
+
if (data) {
|
|
741
|
+
const published = data.bPublished;
|
|
742
|
+
if (published) {
|
|
743
|
+
strengths.push("Service already published — deployment complete");
|
|
744
|
+
readinessScore = 100;
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
const hasMachine = bindingIndex.get(`${services[0].objectAddress}#machine`)?.size;
|
|
748
|
+
const hasPermission = bindingIndex.get(`${services[0].objectAddress}#permission`)?.size;
|
|
749
|
+
if (!hasMachine) {
|
|
750
|
+
readinessScore -= 30;
|
|
751
|
+
findings.push({
|
|
752
|
+
criterion: "deployment_readiness",
|
|
753
|
+
severity: "critical",
|
|
754
|
+
message: "Service has no Machine — cannot publish",
|
|
755
|
+
deduction: -30,
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
if (!hasPermission) {
|
|
759
|
+
readinessScore -= 30;
|
|
760
|
+
findings.push({
|
|
761
|
+
criterion: "deployment_readiness",
|
|
762
|
+
severity: "critical",
|
|
763
|
+
message: "Service has no Permission — cannot publish",
|
|
764
|
+
deduction: -30,
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
if (hasMachine && hasPermission) {
|
|
768
|
+
strengths.push("Service ready to publish (Machine + Permission bound)");
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
readinessScore = 0;
|
|
775
|
+
}
|
|
776
|
+
readinessScore = clampScore(readinessScore);
|
|
777
|
+
let criterionScores = [
|
|
778
|
+
{ name: "object_coverage", score: coverageScore, weight: 0.3, rationale: `R1-R8: ${requiredMet}/${requiredCount} required, R9-R12: ${optionalMet}/${optionalItems.length} optional` },
|
|
779
|
+
{ name: "binding_completeness", score: bindingScore, weight: 0.25, rationale: `Service critical bindings checked` },
|
|
780
|
+
{ name: "workflow_rationality", score: workflowScore, weight: 0.2, rationale: `Machine nodes + dangling edges` },
|
|
781
|
+
{ name: "deployment_readiness", score: readinessScore, weight: 0.25, rationale: `Service publishable state` },
|
|
782
|
+
];
|
|
783
|
+
criterionScores = normalizeWeights(adjustWeightsForPerspective(criterionScores, project.perspective));
|
|
784
|
+
const overall = clampScore(criterionScores.reduce((sum, c) => sum + c.score * c.weight, 0));
|
|
785
|
+
const criticalCount = findings.filter(f => f.severity === "critical").length;
|
|
786
|
+
const summary = criticalCount > 0
|
|
787
|
+
? `Incomplete: ${criticalCount} critical gap(s). Not ready for deployment.`
|
|
788
|
+
: overall >= 80
|
|
789
|
+
? `Complete project (score ${overall}/100). Ready for deployment.`
|
|
790
|
+
: `Partially complete (score ${overall}/100). ${findings.length} gap(s) to address.`;
|
|
791
|
+
return {
|
|
792
|
+
evaluationType: "completeness",
|
|
793
|
+
score: overall,
|
|
794
|
+
summary,
|
|
795
|
+
criterionScores,
|
|
796
|
+
findings,
|
|
797
|
+
strengths,
|
|
798
|
+
needsLlm: false,
|
|
799
|
+
evaluatedAt: Date.now(),
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
function evaluateWorthBuyingStub(input, llmPrompt) {
|
|
803
|
+
return {
|
|
804
|
+
evaluationType: "worth_buying",
|
|
805
|
+
score: 0,
|
|
806
|
+
summary: "Requires LLM evaluation — semantic prompt assembled. Call save_evaluation with the LLM result.",
|
|
807
|
+
criterionScores: [],
|
|
808
|
+
findings: [],
|
|
809
|
+
strengths: [],
|
|
810
|
+
needsLlm: true,
|
|
811
|
+
llmPrompt,
|
|
812
|
+
evaluatedAt: Date.now(),
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function evaluateValueStub(input, llmPrompt) {
|
|
816
|
+
return {
|
|
817
|
+
evaluationType: "value",
|
|
818
|
+
score: 0,
|
|
819
|
+
summary: "Requires LLM evaluation — semantic prompt assembled. Call save_evaluation with the LLM result.",
|
|
820
|
+
criterionScores: [],
|
|
821
|
+
findings: [],
|
|
822
|
+
strengths: [],
|
|
823
|
+
needsLlm: true,
|
|
824
|
+
llmPrompt,
|
|
825
|
+
evaluatedAt: Date.now(),
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
export function runEvaluation(input, evaluationType, llmPrompt) {
|
|
829
|
+
switch (evaluationType) {
|
|
830
|
+
case "risk":
|
|
831
|
+
return evaluateRisk(input);
|
|
832
|
+
case "trust":
|
|
833
|
+
return evaluateTrust(input);
|
|
834
|
+
case "completeness":
|
|
835
|
+
return evaluateCompleteness(input);
|
|
836
|
+
case "worth_buying":
|
|
837
|
+
return evaluateWorthBuyingStub(input, llmPrompt);
|
|
838
|
+
case "value":
|
|
839
|
+
return evaluateValueStub(input, llmPrompt);
|
|
840
|
+
default:
|
|
841
|
+
throw new Error(`Unknown evaluation type: ${evaluationType}`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
export function compareProjects(entries, evaluationType) {
|
|
845
|
+
const ranking = [...entries].sort((a, b) => {
|
|
846
|
+
if (a.score === null && b.score === null)
|
|
847
|
+
return 0;
|
|
848
|
+
if (a.score === null)
|
|
849
|
+
return 1;
|
|
850
|
+
if (b.score === null)
|
|
851
|
+
return -1;
|
|
852
|
+
return b.score - a.score;
|
|
853
|
+
});
|
|
854
|
+
return {
|
|
855
|
+
evaluationType,
|
|
856
|
+
entries,
|
|
857
|
+
ranking,
|
|
858
|
+
bestProjectId: ranking[0]?.score !== null ? ranking[0]?.projectId ?? null : null,
|
|
859
|
+
};
|
|
860
|
+
}
|