@wowok/agent-mcp 2.5.2 → 2.5.4
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/dist/customer/index.d.ts +2 -2
- package/dist/customer/index.js +1 -1
- package/dist/customer/info-puzzle.d.ts +39 -2
- package/dist/customer/info-puzzle.js +370 -29
- package/dist/customer/risk-assessment.js +51 -14
- package/dist/customer/types.d.ts +85 -0
- package/dist/index.js +46 -6
- package/dist/knowledge/guard-design-patterns.js +104 -1
- package/dist/knowledge/guard-ledger.js +3 -3
- package/dist/knowledge/guard-lint.js +3 -3
- package/dist/knowledge/guard-risk.js +23 -15
- package/dist/knowledge/guard-templates.js +174 -2
- package/dist/knowledge/guard-translation.js +3 -3
- package/dist/knowledge/index.d.ts +3 -0
- package/dist/knowledge/index.js +9 -0
- package/dist/knowledge/machine-context.d.ts +1 -1
- package/dist/knowledge/machine-context.js +21 -0
- package/dist/knowledge/machine-ledger.d.ts +1 -0
- package/dist/knowledge/machine-ledger.js +60 -37
- package/dist/knowledge/machine-risk.js +23 -0
- package/dist/knowledge/machine-templates.js +150 -164
- package/dist/knowledge/overrides-loader.d.ts +13 -0
- package/dist/knowledge/overrides-loader.js +67 -0
- package/dist/knowledge/payment-risk.d.ts +4 -0
- package/dist/knowledge/payment-risk.js +67 -0
- package/dist/knowledge/repository-confirm.d.ts +4 -0
- package/dist/knowledge/repository-confirm.js +44 -0
- package/dist/knowledge/repository-risk.d.ts +8 -2
- package/dist/knowledge/repository-risk.js +61 -0
- package/dist/knowledge/repository-translation.js +23 -0
- package/dist/knowledge/reward-templates.d.ts +1 -1
- package/dist/knowledge/reward-templates.js +77 -5
- package/dist/knowledge/scenario-modes.js +22 -5
- package/dist/knowledge/service-templates.js +38 -0
- package/dist/knowledge/treasury-templates.js +42 -0
- package/dist/knowledge/trust-metrics.d.ts +4 -0
- package/dist/knowledge/trust-metrics.js +67 -7
- package/dist/loop-engineering/context-collect.d.ts +7 -0
- package/dist/loop-engineering/context-collect.js +83 -0
- package/dist/loop-engineering/improve.d.ts +29 -0
- package/dist/loop-engineering/improve.js +178 -0
- package/dist/project/context-assembly.d.ts +16 -0
- package/dist/project/context-assembly.js +130 -0
- package/dist/project/deployment-doc.d.ts +115 -0
- package/dist/project/deployment-doc.js +595 -0
- package/dist/project/index.js +37 -1
- package/dist/project/migration.d.ts +22 -0
- package/dist/project/migration.js +125 -0
- package/dist/project/project-store.d.ts +110 -0
- package/dist/project/project-store.js +425 -0
- package/dist/project/rollback-policy.d.ts +45 -0
- package/dist/project/rollback-policy.js +290 -0
- package/dist/project/stage-gate.d.ts +49 -0
- package/dist/project/stage-gate.js +310 -0
- package/dist/schema/call/allocation.d.ts +5 -0
- package/dist/schema/call/arbitration.d.ts +59 -54
- package/dist/schema/call/base.d.ts +3 -0
- package/dist/schema/call/base.js +1 -0
- package/dist/schema/call/bridge.d.ts +24 -0
- package/dist/schema/call/contact.d.ts +5 -0
- package/dist/schema/call/demand.d.ts +53 -48
- package/dist/schema/call/guard.d.ts +15 -0
- package/dist/schema/call/machine.d.ts +10 -0
- package/dist/schema/call/order.d.ts +5 -0
- package/dist/schema/call/payment.d.ts +5 -0
- package/dist/schema/call/permission.d.ts +5 -0
- package/dist/schema/call/personal.d.ts +5 -0
- package/dist/schema/call/progress.d.ts +5 -0
- package/dist/schema/call/proof.d.ts +10 -0
- package/dist/schema/call/repository.d.ts +5 -0
- package/dist/schema/call/reward.d.ts +5 -0
- package/dist/schema/call/service.d.ts +5 -0
- package/dist/schema/call/treasury.d.ts +5 -0
- package/dist/schema/operations.d.ts +286 -76
- package/dist/schema/operations.js +24 -0
- package/dist/schema/project/index.d.ts +2633 -120
- package/dist/schema/project/index.js +622 -2
- package/dist/schema/query/index.d.ts +601 -0
- package/dist/schema/query/index.js +61 -0
- package/dist/schema/trust/index.d.ts +10 -10
- package/dist/schemas/bridge_operation.schema.json +4 -0
- package/dist/schemas/guard2file.schema.json +4 -0
- package/dist/schemas/index.json +1 -1
- package/dist/schemas/machineNode2file.schema.json +4 -0
- package/dist/schemas/onchain_operations.schema.json +20 -0
- package/dist/schemas/onchain_operations_allocation.schema.json +4 -0
- package/dist/schemas/onchain_operations_arbitration.schema.json +4 -0
- package/dist/schemas/onchain_operations_contact.schema.json +4 -0
- package/dist/schemas/onchain_operations_demand.schema.json +4 -0
- package/dist/schemas/onchain_operations_gen_passport.schema.json +8 -0
- package/dist/schemas/onchain_operations_gen_proof.schema.json +8 -0
- package/dist/schemas/onchain_operations_guard.schema.json +4 -0
- package/dist/schemas/onchain_operations_machine.schema.json +4 -0
- package/dist/schemas/onchain_operations_order.schema.json +4 -0
- package/dist/schemas/onchain_operations_payment.schema.json +4 -0
- package/dist/schemas/onchain_operations_permission.schema.json +4 -0
- package/dist/schemas/onchain_operations_personal.schema.json +4 -0
- package/dist/schemas/onchain_operations_progress.schema.json +4 -0
- package/dist/schemas/onchain_operations_proof.schema.json +4 -0
- package/dist/schemas/onchain_operations_repository.schema.json +4 -0
- package/dist/schemas/onchain_operations_reward.schema.json +4 -0
- package/dist/schemas/onchain_operations_service.schema.json +4 -0
- package/dist/schemas/onchain_operations_treasury.schema.json +4 -0
- package/dist/schemas/project_operation.output.json +1798 -0
- package/dist/schemas/project_operation.schema.json +132 -2
- package/dist/schemas/query_toolkit.output.json +407 -1
- package/dist/schemas/query_toolkit.schema.json +132 -0
- package/dist/tools/handlers/project.js +742 -1
- package/dist/tools/handlers/query.js +38 -2
- package/package.json +2 -2
package/dist/customer/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { CUSTOMER_INTELLIGENCE_VERSION, RISK_LEVELS, RISK_DIMENSION_MAX, type PuzzleDepth, type ServiceBasics, type WorkflowAnalysis, type FundSafety, type TrustSignals, type MerchantBehavior, type MarketContext, type InfoPuzzle, type RiskDimensionName, type RiskCheck, type RiskDimensionScore, type RiskLevel, type RiskScore, type UserPreferences, type ReminderPriority, type Reminder, type GameScenario, type GameStrategy, type CustomerAdvice, } from "./types.js";
|
|
2
|
-
export { assembleServiceBasics, assembleWorkflowAnalysis, assembleFundSafety, assembleTrustSignals, assembleMerchantBehavior, assembleMarketContext, assembleInfoPuzzle, type ServiceRawData, type MachineNodeRaw, type ReviewRaw, type EventRaw, type PuzzleRawData, } from "./info-puzzle.js";
|
|
1
|
+
export { CUSTOMER_INTELLIGENCE_VERSION, RISK_LEVELS, RISK_DIMENSION_MAX, type PuzzleDepth, type ServiceBasics, type WorkflowAnalysis, type FundSafety, type TrustSignals, type MerchantBehavior, type MarketContext, type InfoPuzzle, type RiskDimensionName, type RiskCheck, type RiskDimensionScore, type RiskLevel, type RiskScore, type UserPreferences, type ReminderPriority, type Reminder, type GameScenario, type GameStrategy, type CustomerAdvice, type UserMetricEvaluatorType, type UserMetric, type UserMetricsConfig, type UserMetricResult, type UserMetricsResult, type ServiceNestingEvaluation, } from "./types.js";
|
|
2
|
+
export { assembleServiceBasics, assembleWorkflowAnalysis, assembleFundSafety, assembleTrustSignals, assembleMerchantBehavior, assembleMarketContext, assembleInfoPuzzle, assembleUserMetrics, evaluateServiceNesting, type ServiceRawData, type MachineNodeRaw, type ReviewRaw, type EventRaw, type PuzzleRawData, type ServiceNestingRawData, } from "./info-puzzle.js";
|
|
3
3
|
export { riskLevelFromScore, riskLevelLabel, riskLevelEmoji, assessRisk, passesRiskThreshold, } from "./risk-assessment.js";
|
|
4
4
|
export { getIndustryRiskChecks, getIndustryNeeds, checkIndustryNeeds, assessIndustryRisks, inferIndustry, type IndustryNeedTier, type IndustryNeed, type IndustryRiskCheck, type IndustryRiskResult, } from "./industry-risks.js";
|
|
5
5
|
export { getDefaultPreferences, getIndustryTemplate, inferPreferences, matchServiceToPreferences, mergePreferences, validatePreferences, SUPPORTED_INDUSTRIES, type Industry, type IndustryTemplate, type OrderHistoryEntry, type PreferenceInference, type ServiceForMatching, type PreferenceMatchResult, } from "./user-preferences.js";
|
package/dist/customer/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { CUSTOMER_INTELLIGENCE_VERSION, RISK_LEVELS, RISK_DIMENSION_MAX, } from "./types.js";
|
|
2
|
-
export { assembleServiceBasics, assembleWorkflowAnalysis, assembleFundSafety, assembleTrustSignals, assembleMerchantBehavior, assembleMarketContext, assembleInfoPuzzle, } from "./info-puzzle.js";
|
|
2
|
+
export { assembleServiceBasics, assembleWorkflowAnalysis, assembleFundSafety, assembleTrustSignals, assembleMerchantBehavior, assembleMarketContext, assembleInfoPuzzle, assembleUserMetrics, evaluateServiceNesting, } from "./info-puzzle.js";
|
|
3
3
|
export { riskLevelFromScore, riskLevelLabel, riskLevelEmoji, assessRisk, passesRiskThreshold, } from "./risk-assessment.js";
|
|
4
4
|
export { getIndustryRiskChecks, getIndustryNeeds, checkIndustryNeeds, assessIndustryRisks, inferIndustry, } from "./industry-risks.js";
|
|
5
5
|
export { getDefaultPreferences, getIndustryTemplate, inferPreferences, matchServiceToPreferences, mergePreferences, validatePreferences, SUPPORTED_INDUSTRIES, } from "./user-preferences.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InfoPuzzle, ServiceBasics, WorkflowAnalysis, FundSafety, TrustSignals, MerchantBehavior, MarketContext, PuzzleDepth } from "./types.js";
|
|
1
|
+
import type { InfoPuzzle, ServiceBasics, WorkflowAnalysis, FundSafety, TrustSignals, MerchantBehavior, MarketContext, PuzzleDepth, UserMetricsConfig, UserMetricsResult, ServiceNestingEvaluation } from "./types.js";
|
|
2
2
|
import { CUSTOMER_INTELLIGENCE_VERSION } from "./types.js";
|
|
3
3
|
export interface ServiceRawData {
|
|
4
4
|
name?: string;
|
|
@@ -60,7 +60,7 @@ export interface EventRaw {
|
|
|
60
60
|
data?: Record<string, unknown>;
|
|
61
61
|
}
|
|
62
62
|
export declare function assembleServiceBasics(serviceId: string, raw: ServiceRawData, wipVerified?: boolean): ServiceBasics;
|
|
63
|
-
export declare function assembleWorkflowAnalysis(nodes: MachineNodeRaw[], isPublished: boolean, isPaused: boolean): WorkflowAnalysis;
|
|
63
|
+
export declare function assembleWorkflowAnalysis(nodes: MachineNodeRaw[], isPublished: boolean, isPaused: boolean, serviceRaw?: ServiceRawData): WorkflowAnalysis;
|
|
64
64
|
export declare function assembleFundSafety(raw: ServiceRawData, orderAmount: bigint): FundSafety;
|
|
65
65
|
export declare function assembleTrustSignals(service: ServiceRawData, reviews: ReviewRaw[], trustScore: {
|
|
66
66
|
score: number;
|
|
@@ -97,6 +97,43 @@ export interface PuzzleRawData {
|
|
|
97
97
|
has_dispute: boolean;
|
|
98
98
|
is_repeat: boolean;
|
|
99
99
|
}>;
|
|
100
|
+
user_metrics_config?: UserMetricsConfig;
|
|
101
|
+
user_metrics_context?: Record<string, unknown>;
|
|
102
|
+
service_nesting_raw?: ServiceNestingRawData;
|
|
103
|
+
service_nesting?: ServiceNestingEvaluation;
|
|
100
104
|
}
|
|
101
105
|
export declare function assembleInfoPuzzle(serviceId: string, raw: PuzzleRawData, depth?: PuzzleDepth): InfoPuzzle;
|
|
106
|
+
export declare function assembleUserMetrics(config: UserMetricsConfig, context: Record<string, unknown>): UserMetricsResult;
|
|
107
|
+
export interface ServiceNestingRawData {
|
|
108
|
+
service_id: string;
|
|
109
|
+
depth?: number;
|
|
110
|
+
maxDepth?: number;
|
|
111
|
+
machine?: {
|
|
112
|
+
object_id: string;
|
|
113
|
+
published: boolean;
|
|
114
|
+
paused: boolean;
|
|
115
|
+
has_dispute_anti_pattern?: boolean;
|
|
116
|
+
has_no_entry?: boolean;
|
|
117
|
+
has_no_terminal?: boolean;
|
|
118
|
+
structural_issue_count?: number;
|
|
119
|
+
};
|
|
120
|
+
arbitrations?: Array<{
|
|
121
|
+
object_id: string;
|
|
122
|
+
voting_guard_count: number;
|
|
123
|
+
fee_balance: bigint;
|
|
124
|
+
paused: boolean;
|
|
125
|
+
}>;
|
|
126
|
+
rewards?: Array<{
|
|
127
|
+
object_id: string;
|
|
128
|
+
balance: bigint;
|
|
129
|
+
guard_count: number;
|
|
130
|
+
expiration_time?: number;
|
|
131
|
+
}>;
|
|
132
|
+
repositories?: Array<{
|
|
133
|
+
object_id: string;
|
|
134
|
+
has_write_guard: boolean;
|
|
135
|
+
is_empty: boolean;
|
|
136
|
+
}>;
|
|
137
|
+
}
|
|
138
|
+
export declare function evaluateServiceNesting(raw: ServiceNestingRawData): ServiceNestingEvaluation | undefined;
|
|
102
139
|
export { CUSTOMER_INTELLIGENCE_VERSION };
|
|
@@ -22,17 +22,24 @@ export function assembleServiceBasics(serviceId, raw, wipVerified = false) {
|
|
|
22
22
|
wip_verified: wipVerified,
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
export function assembleWorkflowAnalysis(nodes, isPublished, isPaused) {
|
|
25
|
+
export function assembleWorkflowAnalysis(nodes, isPublished, isPaused, serviceRaw) {
|
|
26
26
|
const riskSignals = [];
|
|
27
27
|
const terminalNodes = [];
|
|
28
28
|
const deadEndNodes = [];
|
|
29
29
|
let userOperable = 0;
|
|
30
30
|
let providerOnly = 0;
|
|
31
31
|
let guardGated = 0;
|
|
32
|
-
let hasRefundPath = false;
|
|
33
|
-
let hasArbitrationPath = false;
|
|
34
32
|
let hasMilestone = false;
|
|
33
|
+
let hasWonderNode = false;
|
|
34
|
+
let hasDisputeAntiPattern = false;
|
|
35
|
+
const antiPatternNodeRegex = /^(disputed|refunded|arb|arbitration|dispute|refund)$/i;
|
|
36
|
+
const wonderNodeRegex = /^(wonder|no_wonder|praise|complain|thumbs_up|thumbs_down|好评|差评)$/i;
|
|
35
37
|
const totalForwards = nodes.reduce((sum, n) => sum + (n.forwards?.length ?? 0), 0);
|
|
38
|
+
for (const node of nodes) {
|
|
39
|
+
if (antiPatternNodeRegex.test(node.name.trim())) {
|
|
40
|
+
hasDisputeAntiPattern = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
36
43
|
for (const node of nodes) {
|
|
37
44
|
const outForwards = node.forwards ?? [];
|
|
38
45
|
if (outForwards.length === 0) {
|
|
@@ -55,15 +62,11 @@ export function assembleWorkflowAnalysis(nodes, isPublished, isPaused) {
|
|
|
55
62
|
else {
|
|
56
63
|
providerOnly++;
|
|
57
64
|
}
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
hasRefundPath = true;
|
|
62
|
-
}
|
|
63
|
-
if (fwdName.includes("arb") || target.includes("arb")) {
|
|
64
|
-
hasArbitrationPath = true;
|
|
65
|
+
const target = (fwd.to ?? "").trim().toLowerCase();
|
|
66
|
+
if (wonderNodeRegex.test(target)) {
|
|
67
|
+
hasWonderNode = true;
|
|
65
68
|
}
|
|
66
|
-
if (
|
|
69
|
+
if (target.includes("milestone")) {
|
|
67
70
|
hasMilestone = true;
|
|
68
71
|
}
|
|
69
72
|
}
|
|
@@ -77,10 +80,18 @@ export function assembleWorkflowAnalysis(nodes, isPublished, isPaused) {
|
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
}
|
|
83
|
+
const hasArbitrationBinding = (serviceRaw?.arbitrations?.length ?? 0) > 0;
|
|
84
|
+
const compBalance = serviceRaw?.compensation_fund?.balance ?? 0n;
|
|
85
|
+
const hasArbRefundPath = hasArbitrationBinding && compBalance > 0n;
|
|
86
|
+
const allocators = serviceRaw?.order_allocators?.[0]?.allocators ?? [];
|
|
87
|
+
const hasRefundAllocator = allocators.some((a) => a.surplus_to === "order" || a.surplus_to === "Order");
|
|
88
|
+
const hasRefundPath = hasArbRefundPath || hasRefundAllocator;
|
|
80
89
|
if (!hasRefundPath)
|
|
81
90
|
riskSignals.push("no_refund_path");
|
|
82
|
-
if (!
|
|
83
|
-
riskSignals.push("
|
|
91
|
+
if (!hasArbitrationBinding)
|
|
92
|
+
riskSignals.push("no_arbitration_binding");
|
|
93
|
+
if (hasDisputeAntiPattern)
|
|
94
|
+
riskSignals.push("dispute_anti_pattern");
|
|
84
95
|
if (userOperable === 0)
|
|
85
96
|
riskSignals.push("no_user_operable_path");
|
|
86
97
|
if (deadEndNodes.length > 0)
|
|
@@ -94,7 +105,10 @@ export function assembleWorkflowAnalysis(nodes, isPublished, isPaused) {
|
|
|
94
105
|
provider_only_forwards: providerOnly,
|
|
95
106
|
guard_gated_forwards: guardGated,
|
|
96
107
|
has_refund_path: hasRefundPath,
|
|
97
|
-
has_arbitration_path:
|
|
108
|
+
has_arbitration_path: hasArbitrationBinding,
|
|
109
|
+
has_arbitration_binding: hasArbitrationBinding,
|
|
110
|
+
has_wonder_node: hasWonderNode,
|
|
111
|
+
has_dispute_anti_pattern: hasDisputeAntiPattern,
|
|
98
112
|
has_milestone_nodes: hasMilestone,
|
|
99
113
|
dead_end_nodes: deadEndNodes,
|
|
100
114
|
terminal_nodes: terminalNodes,
|
|
@@ -251,7 +265,7 @@ export function assembleInfoPuzzle(serviceId, raw, depth = "evaluate") {
|
|
|
251
265
|
let trustSignals;
|
|
252
266
|
if (depth === "evaluate" || depth === "preorder" || depth === "monitor") {
|
|
253
267
|
if (raw.machine_nodes) {
|
|
254
|
-
workflowAnalysis = assembleWorkflowAnalysis(raw.machine_nodes, raw.machine_published ?? false, raw.machine_paused ?? false);
|
|
268
|
+
workflowAnalysis = assembleWorkflowAnalysis(raw.machine_nodes, raw.machine_published ?? false, raw.machine_paused ?? false, raw.service);
|
|
255
269
|
}
|
|
256
270
|
else {
|
|
257
271
|
gaps.push("machine_workflow");
|
|
@@ -283,19 +297,14 @@ export function assembleInfoPuzzle(serviceId, raw, depth = "evaluate") {
|
|
|
283
297
|
gaps.push("market_context");
|
|
284
298
|
}
|
|
285
299
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
trustSignals,
|
|
295
|
-
merchantBehavior,
|
|
296
|
-
marketContext,
|
|
297
|
-
].filter((d) => d !== undefined && d !== null).length;
|
|
298
|
-
const completeness = Math.min(1, actualDimensions / expectedDimensions);
|
|
300
|
+
let userMetrics;
|
|
301
|
+
if (raw.user_metrics_config && raw.user_metrics_config.metrics.length > 0) {
|
|
302
|
+
userMetrics = assembleUserMetrics(raw.user_metrics_config, raw.user_metrics_context ?? {});
|
|
303
|
+
}
|
|
304
|
+
let serviceNesting = raw.service_nesting;
|
|
305
|
+
if (!serviceNesting && raw.service_nesting_raw) {
|
|
306
|
+
serviceNesting = evaluateServiceNesting(raw.service_nesting_raw);
|
|
307
|
+
}
|
|
299
308
|
return {
|
|
300
309
|
service_id: serviceId,
|
|
301
310
|
service_basics: serviceBasics,
|
|
@@ -304,9 +313,341 @@ export function assembleInfoPuzzle(serviceId, raw, depth = "evaluate") {
|
|
|
304
313
|
trust_signals: trustSignals,
|
|
305
314
|
merchant_behavior: merchantBehavior,
|
|
306
315
|
market_context: marketContext,
|
|
307
|
-
|
|
316
|
+
user_metrics: userMetrics,
|
|
317
|
+
service_nesting: serviceNesting,
|
|
318
|
+
completeness: computeCompleteness(depth, [
|
|
319
|
+
serviceBasics,
|
|
320
|
+
workflowAnalysis,
|
|
321
|
+
fundSafety,
|
|
322
|
+
trustSignals,
|
|
323
|
+
merchantBehavior,
|
|
324
|
+
marketContext,
|
|
325
|
+
userMetrics,
|
|
326
|
+
serviceNesting,
|
|
327
|
+
]),
|
|
308
328
|
gaps,
|
|
309
329
|
assembled_at: Date.now(),
|
|
310
330
|
};
|
|
311
331
|
}
|
|
332
|
+
export function assembleUserMetrics(config, context) {
|
|
333
|
+
const cap = config.adjustment_cap ?? 10;
|
|
334
|
+
const results = [];
|
|
335
|
+
const totalWeight = config.metrics.reduce((sum, m) => sum + Math.max(0, m.weight), 0);
|
|
336
|
+
for (const metric of config.metrics) {
|
|
337
|
+
const normalizedWeight = totalWeight > 0 ? Math.max(0, metric.weight) / totalWeight : 0;
|
|
338
|
+
const observed = resolvePath(context, metric.params.path ?? metric.name);
|
|
339
|
+
const { passed, score, observed_value } = evaluateMetric(metric, observed);
|
|
340
|
+
results.push({
|
|
341
|
+
name: metric.name,
|
|
342
|
+
label: metric.label,
|
|
343
|
+
passed,
|
|
344
|
+
score,
|
|
345
|
+
observed_value: formatObservedValue(observed),
|
|
346
|
+
weight: normalizedWeight,
|
|
347
|
+
advice: passed
|
|
348
|
+
? metric.advice_on_pass
|
|
349
|
+
: metric.advice_on_fail,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
const aggregated = results.reduce((sum, r) => sum + r.score * r.weight, 0);
|
|
353
|
+
const score = Math.round(aggregated * 100);
|
|
354
|
+
const adjustment = Math.round(((score - 50) / 50) * cap);
|
|
355
|
+
return {
|
|
356
|
+
metrics: results,
|
|
357
|
+
score,
|
|
358
|
+
adjustment,
|
|
359
|
+
source_file: config.source_file,
|
|
360
|
+
evaluated_at: Date.now(),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function resolvePath(obj, path) {
|
|
364
|
+
if (!path)
|
|
365
|
+
return undefined;
|
|
366
|
+
const parts = path.split(".");
|
|
367
|
+
let current = obj;
|
|
368
|
+
for (const part of parts) {
|
|
369
|
+
if (current === null || current === undefined)
|
|
370
|
+
return undefined;
|
|
371
|
+
if (typeof current !== "object")
|
|
372
|
+
return undefined;
|
|
373
|
+
current = current[part];
|
|
374
|
+
}
|
|
375
|
+
return current;
|
|
376
|
+
}
|
|
377
|
+
function evaluateMetric(metric, observed) {
|
|
378
|
+
const p = metric.params;
|
|
379
|
+
switch (metric.evaluator) {
|
|
380
|
+
case "threshold": {
|
|
381
|
+
const v = typeof observed === "number" ? observed
|
|
382
|
+
: typeof observed === "string" ? Number(observed)
|
|
383
|
+
: typeof observed === "bigint" ? Number(observed)
|
|
384
|
+
: NaN;
|
|
385
|
+
const threshold = typeof p.threshold === "number" ? p.threshold
|
|
386
|
+
: typeof p.threshold === "string" ? Number(p.threshold)
|
|
387
|
+
: NaN;
|
|
388
|
+
if (isNaN(v) || isNaN(threshold)) {
|
|
389
|
+
return { passed: false, score: 0, observed_value: observed };
|
|
390
|
+
}
|
|
391
|
+
const passed = p.invert ? v <= threshold : v >= threshold;
|
|
392
|
+
const margin = Math.abs(threshold) * 0.5 || 1;
|
|
393
|
+
const rawScore = p.invert
|
|
394
|
+
? (threshold - v) / margin + 1
|
|
395
|
+
: (v - threshold) / margin + 1;
|
|
396
|
+
const score = Math.max(0, Math.min(1, rawScore));
|
|
397
|
+
return { passed, score, observed_value: observed };
|
|
398
|
+
}
|
|
399
|
+
case "ratio": {
|
|
400
|
+
const v = typeof observed === "number" ? observed
|
|
401
|
+
: typeof observed === "string" ? Number(observed)
|
|
402
|
+
: NaN;
|
|
403
|
+
const min = p.min ?? 0;
|
|
404
|
+
const max = p.max ?? 1;
|
|
405
|
+
if (isNaN(v)) {
|
|
406
|
+
return { passed: false, score: 0, observed_value: observed };
|
|
407
|
+
}
|
|
408
|
+
const passed = v >= min && v <= max;
|
|
409
|
+
const range = max - min;
|
|
410
|
+
const mid = (min + max) / 2;
|
|
411
|
+
const score = range > 0 ? Math.max(0, 1 - Math.abs(v - mid) / (range / 2)) : 1;
|
|
412
|
+
return { passed, score, observed_value: observed };
|
|
413
|
+
}
|
|
414
|
+
case "boolean": {
|
|
415
|
+
const passed = !!observed;
|
|
416
|
+
return { passed, score: passed ? 1 : 0, observed_value: observed };
|
|
417
|
+
}
|
|
418
|
+
case "presence": {
|
|
419
|
+
const present = observed !== undefined && observed !== null && observed !== "";
|
|
420
|
+
return { passed: present, score: present ? 1 : 0, observed_value: observed };
|
|
421
|
+
}
|
|
422
|
+
case "regex_match": {
|
|
423
|
+
if (typeof observed !== "string" || !p.pattern) {
|
|
424
|
+
return { passed: false, score: 0, observed_value: observed };
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
const regex = new RegExp(p.pattern);
|
|
428
|
+
const passed = regex.test(observed);
|
|
429
|
+
return { passed, score: passed ? 1 : 0, observed_value: observed };
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
return { passed: false, score: 0, observed_value: observed };
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
case "custom_query": {
|
|
436
|
+
return { passed: false, score: 0.5, observed_value: observed };
|
|
437
|
+
}
|
|
438
|
+
default:
|
|
439
|
+
return { passed: false, score: 0, observed_value: observed };
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function formatObservedValue(v) {
|
|
443
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean")
|
|
444
|
+
return v;
|
|
445
|
+
if (typeof v === "bigint")
|
|
446
|
+
return v.toString();
|
|
447
|
+
if (v === undefined || v === null)
|
|
448
|
+
return undefined;
|
|
449
|
+
try {
|
|
450
|
+
return JSON.stringify(v);
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
return String(v);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function computeCompleteness(depth, dimensions) {
|
|
457
|
+
const requiredCount = depth === "browse" ? 1
|
|
458
|
+
: depth === "evaluate" ? 4
|
|
459
|
+
: depth === "preorder" ? 6
|
|
460
|
+
: 5;
|
|
461
|
+
const requiredDimensions = dimensions.slice(0, requiredCount);
|
|
462
|
+
const presentCount = requiredDimensions.filter((d) => d !== undefined && d !== null).length;
|
|
463
|
+
return Math.min(1, presentCount / requiredCount);
|
|
464
|
+
}
|
|
465
|
+
export function evaluateServiceNesting(raw) {
|
|
466
|
+
const depth = raw.depth ?? 0;
|
|
467
|
+
const maxDepth = raw.maxDepth ?? 3;
|
|
468
|
+
const truncated = depth >= maxDepth;
|
|
469
|
+
const bindings = {
|
|
470
|
+
arbitrations: [],
|
|
471
|
+
rewards: [],
|
|
472
|
+
repositories: [],
|
|
473
|
+
};
|
|
474
|
+
let totalEvaluated = 0;
|
|
475
|
+
if (raw.machine) {
|
|
476
|
+
const m = raw.machine;
|
|
477
|
+
const issues = [];
|
|
478
|
+
let score = 0;
|
|
479
|
+
if (m.published)
|
|
480
|
+
score += 10;
|
|
481
|
+
else
|
|
482
|
+
issues.push("Machine not published");
|
|
483
|
+
if (m.paused) {
|
|
484
|
+
score += 40;
|
|
485
|
+
issues.push("Machine paused");
|
|
486
|
+
}
|
|
487
|
+
if (m.has_no_entry) {
|
|
488
|
+
score += 50;
|
|
489
|
+
issues.push("No entry point (R-M1-01)");
|
|
490
|
+
}
|
|
491
|
+
if (m.has_no_terminal) {
|
|
492
|
+
score += 50;
|
|
493
|
+
issues.push("No terminal node (R-M1-02)");
|
|
494
|
+
}
|
|
495
|
+
if (m.has_dispute_anti_pattern) {
|
|
496
|
+
score += 60;
|
|
497
|
+
issues.push("Dispute anti-pattern (R-M1-11): machine illegally models dispute flow");
|
|
498
|
+
}
|
|
499
|
+
const structIssues = m.structural_issue_count ?? 0;
|
|
500
|
+
if (structIssues > 0) {
|
|
501
|
+
score += structIssues * 15;
|
|
502
|
+
issues.push(`${structIssues} structural issue(s) detected (cycles/dead-ends/orphans)`);
|
|
503
|
+
}
|
|
504
|
+
score = Math.min(100, score);
|
|
505
|
+
bindings.machine = {
|
|
506
|
+
object_id: m.object_id,
|
|
507
|
+
published: m.published,
|
|
508
|
+
risk_level: scoreToNestingLevel(score),
|
|
509
|
+
issues,
|
|
510
|
+
};
|
|
511
|
+
totalEvaluated++;
|
|
512
|
+
}
|
|
513
|
+
for (const arb of raw.arbitrations ?? []) {
|
|
514
|
+
const issues = [];
|
|
515
|
+
let score = 0;
|
|
516
|
+
if (arb.voting_guard_count === 0) {
|
|
517
|
+
score += 50;
|
|
518
|
+
issues.push("No voting_guard configured — no arbitrators can vote");
|
|
519
|
+
}
|
|
520
|
+
else if (arb.voting_guard_count <= 2) {
|
|
521
|
+
score += 30;
|
|
522
|
+
issues.push("Few arbitrators (<=2) — limited dispute resolution capacity");
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
score += 10;
|
|
526
|
+
}
|
|
527
|
+
if (arb.paused) {
|
|
528
|
+
score += 40;
|
|
529
|
+
issues.push("Arbitration paused — disputes cannot be filed");
|
|
530
|
+
}
|
|
531
|
+
if (arb.fee_balance === 0n) {
|
|
532
|
+
score += 30;
|
|
533
|
+
issues.push("No compensation fund balance — rulings cannot pay out");
|
|
534
|
+
}
|
|
535
|
+
score = Math.min(100, score);
|
|
536
|
+
bindings.arbitrations.push({
|
|
537
|
+
object_id: arb.object_id,
|
|
538
|
+
voting_guard_count: arb.voting_guard_count,
|
|
539
|
+
fee_balance: arb.fee_balance.toString(),
|
|
540
|
+
paused: arb.paused,
|
|
541
|
+
risk_level: scoreToNestingLevel(score),
|
|
542
|
+
issues,
|
|
543
|
+
});
|
|
544
|
+
totalEvaluated++;
|
|
545
|
+
}
|
|
546
|
+
for (const r of raw.rewards ?? []) {
|
|
547
|
+
const issues = [];
|
|
548
|
+
let score = 0;
|
|
549
|
+
if (r.balance === 0n) {
|
|
550
|
+
score += 60;
|
|
551
|
+
issues.push("Reward fund empty — claims will fail");
|
|
552
|
+
}
|
|
553
|
+
if (r.guard_count === 0) {
|
|
554
|
+
score += 50;
|
|
555
|
+
issues.push("No claim_guard — anyone could claim (or no one can, depending on default)");
|
|
556
|
+
}
|
|
557
|
+
if (r.expiration_time && r.expiration_time > 0 && r.expiration_time < Date.now()) {
|
|
558
|
+
score += 40;
|
|
559
|
+
issues.push("Reward expired — claims no longer possible");
|
|
560
|
+
}
|
|
561
|
+
score = Math.min(100, score);
|
|
562
|
+
bindings.rewards.push({
|
|
563
|
+
object_id: r.object_id,
|
|
564
|
+
balance: r.balance.toString(),
|
|
565
|
+
guard_count: r.guard_count,
|
|
566
|
+
risk_level: scoreToNestingLevel(score),
|
|
567
|
+
issues,
|
|
568
|
+
});
|
|
569
|
+
totalEvaluated++;
|
|
570
|
+
}
|
|
571
|
+
for (const repo of raw.repositories ?? []) {
|
|
572
|
+
const issues = [];
|
|
573
|
+
let score = 0;
|
|
574
|
+
if (!repo.has_write_guard) {
|
|
575
|
+
score += 40;
|
|
576
|
+
issues.push("No write_guard — Repository is open to arbitrary writes");
|
|
577
|
+
}
|
|
578
|
+
if (repo.is_empty) {
|
|
579
|
+
score += 20;
|
|
580
|
+
issues.push("Repository empty — no historical data for evaluation");
|
|
581
|
+
}
|
|
582
|
+
score = Math.min(100, score);
|
|
583
|
+
bindings.repositories.push({
|
|
584
|
+
object_id: repo.object_id,
|
|
585
|
+
risk_level: scoreToNestingLevel(score),
|
|
586
|
+
issues,
|
|
587
|
+
});
|
|
588
|
+
totalEvaluated++;
|
|
589
|
+
}
|
|
590
|
+
if (totalEvaluated === 0) {
|
|
591
|
+
return undefined;
|
|
592
|
+
}
|
|
593
|
+
const machineScore = bindings.machine ? nestingLevelToScore(bindings.machine.risk_level) : 0;
|
|
594
|
+
const machineWeight = bindings.machine ? 35 : 0;
|
|
595
|
+
const arbScore = bindings.arbitrations.length > 0
|
|
596
|
+
? Math.max(...bindings.arbitrations.map((a) => nestingLevelToScore(a.risk_level)))
|
|
597
|
+
: 0;
|
|
598
|
+
const arbWeight = bindings.arbitrations.length > 0 ? 30 : 0;
|
|
599
|
+
const rewardScore = bindings.rewards.length > 0
|
|
600
|
+
? Math.max(...bindings.rewards.map((r) => nestingLevelToScore(r.risk_level)))
|
|
601
|
+
: 0;
|
|
602
|
+
const rewardWeight = bindings.rewards.length > 0 ? 20 : 0;
|
|
603
|
+
const repoScore = bindings.repositories.length > 0
|
|
604
|
+
? Math.max(...bindings.repositories.map((r) => nestingLevelToScore(r.risk_level)))
|
|
605
|
+
: 0;
|
|
606
|
+
const repoWeight = bindings.repositories.length > 0 ? 15 : 0;
|
|
607
|
+
const totalWeight = machineWeight + arbWeight + rewardWeight + repoWeight;
|
|
608
|
+
const overallScore = totalWeight > 0
|
|
609
|
+
? Math.round((machineScore * machineWeight + arbScore * arbWeight
|
|
610
|
+
+ rewardScore * rewardWeight + repoScore * repoWeight) / totalWeight)
|
|
611
|
+
: 0;
|
|
612
|
+
const allLevels = [];
|
|
613
|
+
if (bindings.machine)
|
|
614
|
+
allLevels.push(bindings.machine.risk_level);
|
|
615
|
+
for (const a of bindings.arbitrations)
|
|
616
|
+
allLevels.push(a.risk_level);
|
|
617
|
+
for (const r of bindings.rewards)
|
|
618
|
+
allLevels.push(r.risk_level);
|
|
619
|
+
for (const r of bindings.repositories)
|
|
620
|
+
allLevels.push(r.risk_level);
|
|
621
|
+
const overallLevel = allLevels.reduce(highestLevel, "low");
|
|
622
|
+
return {
|
|
623
|
+
depth_reached: depth,
|
|
624
|
+
truncated,
|
|
625
|
+
bindings,
|
|
626
|
+
overall_risk_level: overallLevel,
|
|
627
|
+
overall_risk_score: overallScore,
|
|
628
|
+
total_bindings_evaluated: totalEvaluated,
|
|
629
|
+
evaluated_at: Date.now(),
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function scoreToNestingLevel(score) {
|
|
633
|
+
if (score >= 75)
|
|
634
|
+
return "critical";
|
|
635
|
+
if (score >= 50)
|
|
636
|
+
return "high";
|
|
637
|
+
if (score >= 25)
|
|
638
|
+
return "medium";
|
|
639
|
+
return "low";
|
|
640
|
+
}
|
|
641
|
+
function nestingLevelToScore(level) {
|
|
642
|
+
switch (level) {
|
|
643
|
+
case "critical": return 90;
|
|
644
|
+
case "high": return 65;
|
|
645
|
+
case "medium": return 40;
|
|
646
|
+
case "low": return 10;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function highestLevel(a, b) {
|
|
650
|
+
const order = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
651
|
+
return order[a] >= order[b] ? a : b;
|
|
652
|
+
}
|
|
312
653
|
export { CUSTOMER_INTELLIGENCE_VERSION };
|
|
@@ -14,23 +14,23 @@ export function riskLevelEmoji(level) {
|
|
|
14
14
|
function assessWorkflowRisk(puzzle) {
|
|
15
15
|
const wf = puzzle.workflow_analysis;
|
|
16
16
|
const checks = [];
|
|
17
|
-
const
|
|
17
|
+
const hasArb = wf?.has_arbitration_binding ?? wf?.has_arbitration_path ?? false;
|
|
18
18
|
checks.push({
|
|
19
|
-
id: "
|
|
19
|
+
id: "wf_arbitration_binding",
|
|
20
20
|
dimension: "workflow",
|
|
21
|
-
description: "
|
|
21
|
+
description: "Service has Arbitration object bound (dispute independence)",
|
|
22
22
|
weight: 10,
|
|
23
|
-
passed:
|
|
24
|
-
advice:
|
|
23
|
+
passed: hasArb,
|
|
24
|
+
advice: hasArb ? undefined : "⚠️ No Arbitration binding: disputes cannot be escalated to third-party arbitration. Recommend requiring the merchant to bind an Arbitration service (with voting_guard config) before ordering. Dispute is filed via arbitration::dispute(order, arbitration), independent of the machine workflow.",
|
|
25
25
|
});
|
|
26
|
-
const
|
|
26
|
+
const hasRefund = wf?.has_refund_path ?? false;
|
|
27
27
|
checks.push({
|
|
28
|
-
id: "
|
|
28
|
+
id: "wf_refund_path",
|
|
29
29
|
dimension: "workflow",
|
|
30
|
-
description: "
|
|
30
|
+
description: "Refund path exists (Arbitration+compensation_fund OR refund Allocator)",
|
|
31
31
|
weight: 10,
|
|
32
|
-
passed:
|
|
33
|
-
advice:
|
|
32
|
+
passed: hasRefund,
|
|
33
|
+
advice: hasRefund ? undefined : "⚠️ No refund path: if delivery fails, funds may not be recoverable. Either (a) bind Arbitration AND deposit compensation_fund, or (b) configure a refund-to-order Allocator. Refund via arbitration flows: dispute → arbitration() ruling with indemnity → arb_withdraw by order holder.",
|
|
34
34
|
});
|
|
35
35
|
const userOps = wf?.user_operable_forwards ?? 0;
|
|
36
36
|
checks.push({
|
|
@@ -51,14 +51,19 @@ function assessWorkflowRisk(puzzle) {
|
|
|
51
51
|
passed: noDeadEnds,
|
|
52
52
|
advice: noDeadEnds ? undefined : `⚠️ Detected ${deadEnds.length} potential dead-end node(s): ${deadEnds.join(", ")}. All exits of these nodes depend on merchant action.`,
|
|
53
53
|
});
|
|
54
|
-
const machineUsable = wf
|
|
54
|
+
const machineUsable = wf
|
|
55
|
+
? (wf.machine_published && !wf.machine_paused && !wf.has_dispute_anti_pattern)
|
|
56
|
+
: false;
|
|
55
57
|
checks.push({
|
|
56
58
|
id: "wf_machine_usable",
|
|
57
59
|
dimension: "workflow",
|
|
58
|
-
description: "Machine
|
|
60
|
+
description: "Machine published, not paused, no dispute anti-pattern",
|
|
59
61
|
weight: 3,
|
|
60
62
|
passed: machineUsable,
|
|
61
|
-
advice: machineUsable ? undefined
|
|
63
|
+
advice: machineUsable ? undefined
|
|
64
|
+
: wf?.has_dispute_anti_pattern
|
|
65
|
+
? "🔴 Machine contains dispute anti-pattern nodes (disputed/refunded/arb/arbitration). Dispute is INDEPENDENT of machine — these nodes create dead-end paths because arbitrators are in Arbitration.voting_guard, not Permission. Recommend removing these nodes and binding an Arbitration object to the Service instead."
|
|
66
|
+
: "⚠️ Machine is not published or is paused: the workflow may not be in effect or has been actively paused by the merchant.",
|
|
62
67
|
});
|
|
63
68
|
const score = checks.reduce((sum, c) => sum + (c.passed ? c.weight : 0), 0);
|
|
64
69
|
return { name: "workflow", score, max_score: RISK_DIMENSION_MAX.workflow, checks };
|
|
@@ -264,7 +269,12 @@ export function assessRisk(puzzle) {
|
|
|
264
269
|
assessTrustRisk(puzzle),
|
|
265
270
|
assessBehaviorRisk(puzzle),
|
|
266
271
|
];
|
|
267
|
-
const
|
|
272
|
+
const baseTotal = dimensions.reduce((sum, d) => sum + d.score, 0);
|
|
273
|
+
const userMetricsAdjustment = puzzle.user_metrics?.adjustment ?? 0;
|
|
274
|
+
const serviceNestingRisk = puzzle.service_nesting?.overall_risk_score;
|
|
275
|
+
const serviceNestingLevel = puzzle.service_nesting?.overall_risk_level;
|
|
276
|
+
const wonderBonus = puzzle.workflow_analysis?.has_wonder_node ? 2 : 0;
|
|
277
|
+
const total = Math.max(0, Math.min(100, baseTotal + userMetricsAdjustment + wonderBonus));
|
|
268
278
|
const level = riskLevelFromScore(total);
|
|
269
279
|
const redFlags = [];
|
|
270
280
|
for (const dim of dimensions) {
|
|
@@ -284,11 +294,18 @@ export function assessRisk(puzzle) {
|
|
|
284
294
|
redFlags.push(`[behavior_flag] ${f}`);
|
|
285
295
|
}
|
|
286
296
|
}
|
|
297
|
+
if (serviceNestingLevel === "high" || serviceNestingLevel === "critical") {
|
|
298
|
+
redFlags.push(`[service_nesting] overall nesting risk: ${serviceNestingLevel} (score ${serviceNestingRisk})`);
|
|
299
|
+
}
|
|
287
300
|
const advice = generateAdvice(puzzle, total, level, dimensions);
|
|
288
301
|
return {
|
|
289
302
|
total,
|
|
303
|
+
base_total: baseTotal,
|
|
290
304
|
level,
|
|
291
305
|
dimensions,
|
|
306
|
+
user_metrics_adjustment: userMetricsAdjustment,
|
|
307
|
+
service_nesting_risk: serviceNestingRisk,
|
|
308
|
+
service_nesting_level: serviceNestingLevel,
|
|
292
309
|
red_flags: redFlags,
|
|
293
310
|
advice,
|
|
294
311
|
};
|
|
@@ -326,6 +343,26 @@ function generateAdvice(puzzle, total, level, dimensions) {
|
|
|
326
343
|
if (puzzle.completeness < 0.8) {
|
|
327
344
|
advice.push(`ℹ️ Information completeness is only ${Math.round(puzzle.completeness * 100)}%; missing: ${puzzle.gaps.join(", ") || "none"}. Recommend supplementing data before making a decision.`);
|
|
328
345
|
}
|
|
346
|
+
if (puzzle.workflow_analysis?.has_wonder_node) {
|
|
347
|
+
advice.push("🌟 Machine implements wonder/no-wonder praise design: customers can advance Progress to 'wonder' (positive review) or 'no_wonder' (negative review) after completion. Reward objects can claim against Progress.current == 'wonder' to incentivize positive outcomes.");
|
|
348
|
+
}
|
|
349
|
+
if (puzzle.workflow_analysis?.has_dispute_anti_pattern) {
|
|
350
|
+
advice.push("🔴 Machine contains dispute anti-pattern nodes (disputed/refunded/arb/arbitration). Dispute is INDEPENDENT of machine — it flows through Order → Arbitration.dispute() → Arb → arbitration() → arb_withdraw. Arbitrators are configured in Arbitration.voting_guard, NOT in Permission. Recommend the merchant remove these nodes and bind an Arbitration object instead.");
|
|
351
|
+
}
|
|
352
|
+
if (puzzle.user_metrics) {
|
|
353
|
+
const um = puzzle.user_metrics;
|
|
354
|
+
const passedCount = um.metrics.filter((m) => m.passed).length;
|
|
355
|
+
advice.push(`📊 User-customized metrics: ${passedCount}/${um.metrics.length} passed (score ${um.score}/100, adjustment ${um.adjustment >= 0 ? "+" : ""}${um.adjustment}).${um.source_file ? ` Source: ${um.source_file}` : ""}`);
|
|
356
|
+
for (const m of um.metrics) {
|
|
357
|
+
if (!m.passed && m.advice) {
|
|
358
|
+
advice.push(` • ${m.label}: ${m.advice}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (puzzle.service_nesting) {
|
|
363
|
+
const sn = puzzle.service_nesting;
|
|
364
|
+
advice.push(`🔗 Service nesting evaluation: ${sn.total_bindings_evaluated} bound objects, overall risk ${sn.overall_risk_level} (score ${sn.overall_risk_score}/100).${sn.truncated ? " Recursion truncated at depth limit." : ""}`);
|
|
365
|
+
}
|
|
329
366
|
return advice;
|
|
330
367
|
}
|
|
331
368
|
export function passesRiskThreshold(score, minLevel = "medium_high") {
|