@wowok/agent-mcp 2.5.5 → 2.6.1
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/config/runtime.js +3 -6
- 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 +407 -0
- package/dist/extensions/registry.d.ts +48 -0
- package/dist/extensions/registry.js +629 -0
- package/dist/extensions/types.d.ts +218 -0
- package/dist/extensions/types.js +1 -0
- package/dist/harness/checkpoint.js +2 -2
- package/dist/knowledge/deployment-scanner.d.ts +3 -0
- package/dist/knowledge/deployment-scanner.js +64 -3
- package/dist/knowledge/flywheel-loop.js +2 -5
- package/dist/knowledge/guard-risk.d.ts +13 -0
- package/dist/knowledge/guard-risk.js +57 -0
- package/dist/knowledge/guard-templates.js +278 -0
- package/dist/knowledge/machine-templates.js +20 -1
- 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/service-confirm.d.ts +14 -5
- package/dist/knowledge/service-confirm.js +116 -8
- package/dist/knowledge/tool-constraints.js +6 -2
- package/dist/loop-engineering/improve.js +2 -4
- package/dist/project/deployment-bridge.d.ts +5 -0
- package/dist/project/deployment-bridge.js +119 -0
- package/dist/project/deployment-doc.d.ts +3 -0
- package/dist/project/deployment-doc.js +72 -10
- package/dist/project/evaluation.d.ts +2 -0
- package/dist/project/evaluation.js +578 -86
- package/dist/project/graph-builder.d.ts +4 -1
- package/dist/project/graph-builder.js +141 -63
- package/dist/project/graph.d.ts +1 -0
- package/dist/project/handlers.d.ts +221 -5
- package/dist/project/handlers.js +933 -14
- package/dist/project/index.js +2 -6
- package/dist/project/project-store.js +2 -5
- 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/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/demand.d.ts +23 -31
- package/dist/schema/call/guard.js +1 -1
- package/dist/schema/call/machine.d.ts +402 -376
- package/dist/schema/call/order.d.ts +149 -228
- package/dist/schema/call/order.js +7 -3
- package/dist/schema/call/payment.d.ts +183 -3
- package/dist/schema/call/payment.js +21 -3
- 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/semantic.d.ts +1 -1
- package/dist/schema/call/semantic.js +30 -1
- package/dist/schema/call/service.d.ts +95 -119
- package/dist/schema/call/service.js +22 -1
- package/dist/schema/common/index.d.ts +11 -2
- package/dist/schema/common/index.js +43 -14
- package/dist/schema/config/index.d.ts +12 -12
- package/dist/schema/local/index.d.ts +140 -143
- package/dist/schema/local/index.js +44 -23
- package/dist/schema/messenger/index.d.ts +290 -62
- package/dist/schema/messenger/index.js +2 -2
- package/dist/schema/operations.d.ts +680 -545
- package/dist/schema/operations.js +22 -0
- package/dist/schema/project/index.d.ts +2064 -84
- package/dist/schema/project/index.js +354 -12
- package/dist/schema/query/index.d.ts +715 -354
- package/dist/schema/query/index.js +164 -31
- package/dist/schema/schema-query/index.d.ts +15 -3
- package/dist/schema/schema-query/index.js +23 -5
- package/dist/schema/trust/index.d.ts +8 -8
- package/dist/schema/utils/node-parser.js +7 -4
- package/dist/schema-query/index.d.ts +7 -1
- package/dist/schema-query/index.js +204 -4
- package/dist/schemas/account_operation.output.json +14 -22
- package/dist/schemas/account_operation.schema.json +11 -25
- 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 +10 -10
- package/dist/schemas/onchain_events.output.json +1 -1
- package/dist/schemas/onchain_operations.schema.json +348 -296
- package/dist/schemas/onchain_operations_allocation.schema.json +34 -25
- package/dist/schemas/onchain_operations_arbitration.schema.json +8 -8
- package/dist/schemas/onchain_operations_contact.schema.json +8 -8
- package/dist/schemas/onchain_operations_demand.schema.json +8 -8
- package/dist/schemas/onchain_operations_gen_passport.schema.json +14 -14
- 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 +41 -33
- package/dist/schemas/onchain_operations_order.schema.json +71 -77
- package/dist/schemas/onchain_operations_payment.schema.json +141 -111
- package/dist/schemas/onchain_operations_permission.schema.json +2 -2
- package/dist/schemas/onchain_operations_personal.schema.json +7 -7
- package/dist/schemas/onchain_operations_progress.schema.json +8 -8
- package/dist/schemas/onchain_operations_proof.schema.json +7 -7
- package/dist/schemas/onchain_operations_repository.schema.json +8 -8
- package/dist/schemas/onchain_operations_reward.schema.json +10 -10
- package/dist/schemas/onchain_operations_service.schema.json +46 -35
- package/dist/schemas/onchain_operations_treasury.schema.json +8 -8
- package/dist/schemas/onchain_table_data.output.json +33 -25
- package/dist/schemas/onchain_table_data.schema.json +12 -12
- package/dist/schemas/project_operation.output.json +1352 -20
- package/dist/schemas/project_operation.schema.json +53 -4
- package/dist/schemas/query_toolkit.output.json +111 -82
- package/dist/schemas/query_toolkit.schema.json +9 -13
- package/dist/schemas/schema_query.output.json +7 -3
- package/dist/schemas/schema_query.schema.json +17 -3
- package/dist/telemetry/storage.js +2 -2
- package/dist/tools/handlers/local.js +20 -5
- package/dist/tools/handlers/onchain.js +36 -0
- package/dist/tools/handlers/project.js +72 -1
- package/dist/tools/handlers/query.js +27 -0
- package/dist/tools/handlers/schema-query.js +19 -0
- 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.js +212 -8
- 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
package/dist/project/handlers.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { LocalProject, LocalMark, query_objects, } from "@wowok/wowok";
|
|
1
|
+
import { LocalProject, LocalMark, query_objects, query_table, } from "@wowok/wowok";
|
|
2
2
|
import { GraphBuilder } from "./graph-builder.js";
|
|
3
3
|
import { SemanticAssembler } from "./semantic-assembler.js";
|
|
4
4
|
import { runEvaluation, compareProjects, } from "./evaluation.js";
|
|
5
|
+
import { TaskTracker } from "./task-tracker.js";
|
|
5
6
|
const localProject = LocalProject.Instance();
|
|
6
7
|
function requireField(value, name) {
|
|
7
8
|
if (value === undefined || value === null) {
|
|
@@ -21,8 +22,17 @@ async function resolveProjectId(v) {
|
|
|
21
22
|
throw new Error("Either 'project_id' or 'project' (prefix) is required for this action");
|
|
22
23
|
}
|
|
23
24
|
async function resolveObjectAddress(v) {
|
|
24
|
-
if (v.object_address)
|
|
25
|
-
|
|
25
|
+
if (v.object_address) {
|
|
26
|
+
const addr = v.object_address;
|
|
27
|
+
const isHexAddress = /^0x[0-9a-fA-F]{32,64}$/.test(addr);
|
|
28
|
+
const isDraftSentinel = addr.startsWith("draft:");
|
|
29
|
+
if (!isHexAddress && !isDraftSentinel) {
|
|
30
|
+
throw new Error(`Invalid object_address format: '${addr.slice(0, 40)}${addr.length > 40 ? "..." : ""}'. ` +
|
|
31
|
+
`Expected a valid Sui address (0x + 32-64 hex chars) or 'draft:<name>' sentinel. ` +
|
|
32
|
+
`To reference an object by name, use 'object_name_add' instead of 'object_address'.`);
|
|
33
|
+
}
|
|
34
|
+
return addr;
|
|
35
|
+
}
|
|
26
36
|
if (v.object_name_add) {
|
|
27
37
|
const mark = await LocalMark.Instance().get(v.object_name_add);
|
|
28
38
|
if (mark?.address)
|
|
@@ -41,15 +51,30 @@ export async function handleListProjects(v) {
|
|
|
41
51
|
filter.network = v.filter_network;
|
|
42
52
|
const projects = await localProject.listProjects(Object.keys(filter).length > 0 ? filter : undefined);
|
|
43
53
|
const items = await Promise.all(projects.map(async (p) => {
|
|
44
|
-
const objs = await
|
|
54
|
+
const [objs, evals] = await Promise.all([
|
|
55
|
+
localProject.listObjects(p.id),
|
|
56
|
+
localProject.listEvaluations(p.id),
|
|
57
|
+
]);
|
|
45
58
|
const types = new Set();
|
|
46
59
|
for (const o of objs) {
|
|
47
60
|
if (o.objectType)
|
|
48
61
|
types.add(o.objectType);
|
|
49
62
|
}
|
|
63
|
+
if (p.status === "draft" && objs.length > 0) {
|
|
64
|
+
const updated = await localProject.updateProject(p.id, { status: "active" });
|
|
65
|
+
if (updated) {
|
|
66
|
+
return {
|
|
67
|
+
...updated,
|
|
68
|
+
objectCount: objs.length,
|
|
69
|
+
evaluationCount: evals.length,
|
|
70
|
+
objectTypes: Array.from(types),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
50
74
|
return {
|
|
51
75
|
...p,
|
|
52
76
|
objectCount: objs.length,
|
|
77
|
+
evaluationCount: evals.length,
|
|
53
78
|
objectTypes: Array.from(types),
|
|
54
79
|
};
|
|
55
80
|
}));
|
|
@@ -78,7 +103,128 @@ export async function handleCreateProject(v) {
|
|
|
78
103
|
network: v.project_network ?? "testnet",
|
|
79
104
|
};
|
|
80
105
|
const project = await localProject.createProject(create);
|
|
81
|
-
|
|
106
|
+
const industry = v.project_industry;
|
|
107
|
+
const skipTemplates = v.skip_templates === true;
|
|
108
|
+
if (industry && industry !== "general" && !skipTemplates) {
|
|
109
|
+
try {
|
|
110
|
+
const { IndustryModeRegistry } = await import("../extensions/index.js");
|
|
111
|
+
const registry = IndustryModeRegistry.Instance();
|
|
112
|
+
const context = {
|
|
113
|
+
prefix,
|
|
114
|
+
version: project.currentVersion ?? "v1",
|
|
115
|
+
network: (project.network ?? "testnet"),
|
|
116
|
+
business_intent: v.project_description ?? name,
|
|
117
|
+
industry,
|
|
118
|
+
};
|
|
119
|
+
const blueprint = registry.applyTemplate(industry, context.business_intent, context);
|
|
120
|
+
const draftObjects = [];
|
|
121
|
+
for (const obj of blueprint.objects) {
|
|
122
|
+
const projectObj = await localProject.addObject(project.id, {
|
|
123
|
+
objectAddress: `draft:${obj.name}`,
|
|
124
|
+
objectType: obj.object_type,
|
|
125
|
+
objectName: obj.name,
|
|
126
|
+
role: obj.object_type === "service" ? "core" : "dependency",
|
|
127
|
+
metadata: {
|
|
128
|
+
status: "draft",
|
|
129
|
+
config: obj.config,
|
|
130
|
+
publish_required: obj.publish_required,
|
|
131
|
+
description: obj.description,
|
|
132
|
+
entry_id: obj.entry_id,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
draftObjects.push(projectObj);
|
|
136
|
+
}
|
|
137
|
+
const graphEdges = blueprint.edges.map((e) => ({
|
|
138
|
+
projectId: project.id,
|
|
139
|
+
sourceObject: e.from,
|
|
140
|
+
targetObject: e.to,
|
|
141
|
+
edgeSource: "internal",
|
|
142
|
+
relationshipType: mapEdgeKindToRelationshipType(e.edge_kind),
|
|
143
|
+
fieldName: e.field_path,
|
|
144
|
+
depth: 0,
|
|
145
|
+
verified: false,
|
|
146
|
+
weight: 1.0,
|
|
147
|
+
metadata: {
|
|
148
|
+
blueprint: true,
|
|
149
|
+
required_at_publish: e.required_at_publish,
|
|
150
|
+
},
|
|
151
|
+
}));
|
|
152
|
+
await localProject.saveGraphEdges(project.id, graphEdges);
|
|
153
|
+
return {
|
|
154
|
+
action: "create_project",
|
|
155
|
+
project,
|
|
156
|
+
blueprint: {
|
|
157
|
+
objects_count: draftObjects.length,
|
|
158
|
+
edges_count: graphEdges.length,
|
|
159
|
+
industry_source: blueprint.industry_source,
|
|
160
|
+
applied_template: blueprint.applied_template,
|
|
161
|
+
steps_count: blueprint.steps.length,
|
|
162
|
+
scenarios_count: blueprint.scenarios?.length ?? 0,
|
|
163
|
+
key_risk: registry.getMode(industry)?.key_risk,
|
|
164
|
+
},
|
|
165
|
+
recommended_creation_order: getRecommendedCreationOrder(industry),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
return {
|
|
170
|
+
action: "create_project",
|
|
171
|
+
project,
|
|
172
|
+
blueprint_warning: `Industry template application failed: ${err.message}. Project created without blueprint — use add_object to manually add objects.`,
|
|
173
|
+
recommended_creation_order: getRecommendedCreationOrder(industry),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (skipTemplates) {
|
|
178
|
+
return {
|
|
179
|
+
action: "create_project",
|
|
180
|
+
project,
|
|
181
|
+
blueprint_skipped: true,
|
|
182
|
+
hint: "skip_templates=true — no industry blueprint objects were created. Use add_object to manually add objects to this project.",
|
|
183
|
+
recommended_creation_order: getRecommendedCreationOrder(industry),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
action: "create_project",
|
|
188
|
+
project,
|
|
189
|
+
recommended_creation_order: getRecommendedCreationOrder(industry),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function getRecommendedCreationOrder(industry) {
|
|
193
|
+
const industryNote = industry && industry !== "general"
|
|
194
|
+
? ` (industry="${industry}" — see IndustryMode.build_notes for overrides)`
|
|
195
|
+
: "";
|
|
196
|
+
return [
|
|
197
|
+
{ step: 1, object: "Permission", reason: "Base permission object, referenced by all other objects.", required: true },
|
|
198
|
+
{ step: 2, object: "Treasury", reason: "Standalone object, no dependencies. Optional but recommended for fund management.", required: false },
|
|
199
|
+
{ step: 3, object: "Machine", reason: `Requires Permission. publish:true locks nodes/pairs/forwards permanently.${industryNote}`, required: true },
|
|
200
|
+
{ step: 4, object: "Contact", reason: "Requires Permission. Used as Service.um (user manual / contact info).", required: true },
|
|
201
|
+
{ step: 5, object: "Arbitration", reason: "Requires Permission. Optional but recommended for dispute resolution.", required: false },
|
|
202
|
+
{ step: 6, object: "Service (unpublished)", reason: "Requires Permission + Machine + Contact + Arbitration. Create WITHOUT publish:true first.", required: true },
|
|
203
|
+
{ step: 7, object: "Guard x N", reason: "Can reference Service via LocalMark. Create all Guards (buy_guard, allocator_guard, etc.) before publishing Service.", required: true },
|
|
204
|
+
{ step: 8, object: "Service update + publish", reason: "Add order_allocators referencing Guards, then publish:true. After publish, machine/order_allocators are immutable.", required: true },
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
function mapEdgeKindToRelationshipType(edgeKind) {
|
|
208
|
+
switch (edgeKind) {
|
|
209
|
+
case "machine":
|
|
210
|
+
case "compensation_fund":
|
|
211
|
+
case "parent":
|
|
212
|
+
return "bind";
|
|
213
|
+
case "order_allocators":
|
|
214
|
+
return "create";
|
|
215
|
+
case "arbitrations":
|
|
216
|
+
case "rewards":
|
|
217
|
+
return "trigger";
|
|
218
|
+
case "buy_guard":
|
|
219
|
+
case "permission":
|
|
220
|
+
case "guard":
|
|
221
|
+
return "validate";
|
|
222
|
+
case "service":
|
|
223
|
+
case "progress":
|
|
224
|
+
return "consume";
|
|
225
|
+
default:
|
|
226
|
+
return "bind";
|
|
227
|
+
}
|
|
82
228
|
}
|
|
83
229
|
export async function handleCreateProjectFromOnchain(v) {
|
|
84
230
|
const serviceAddress = requireField(v.service_address, "service_address");
|
|
@@ -97,6 +243,31 @@ export async function handleCreateProjectFromOnchain(v) {
|
|
|
97
243
|
}
|
|
98
244
|
prefix = `${prefix}_${suffix}`;
|
|
99
245
|
}
|
|
246
|
+
const otherNetwork = network === "testnet" ? "mainnet" : "testnet";
|
|
247
|
+
let rootType = null;
|
|
248
|
+
try {
|
|
249
|
+
const rootQuery = await query_objects({
|
|
250
|
+
objects: [serviceAddress],
|
|
251
|
+
network,
|
|
252
|
+
});
|
|
253
|
+
const rootObj = rootQuery?.objects?.[0];
|
|
254
|
+
if (rootObj && rootObj.object) {
|
|
255
|
+
rootType = rootObj.type ?? null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
throw new Error(`Failed to query ${network} for the Service object: ` +
|
|
260
|
+
`${e instanceof Error ? e.message : String(e)}. ` +
|
|
261
|
+
`Check the network connection or try the ${otherNetwork} network.`);
|
|
262
|
+
}
|
|
263
|
+
if (!rootType) {
|
|
264
|
+
throw new Error(`Service object not found on ${network}: the address ${serviceAddress} does not exist on this network. ` +
|
|
265
|
+
`Switch to the ${otherNetwork} network and try again.`);
|
|
266
|
+
}
|
|
267
|
+
if (!rootType.includes("::service::Service")) {
|
|
268
|
+
throw new Error(`Object ${serviceAddress} is not a Service (its on-chain type is "${rootType}"). ` +
|
|
269
|
+
`Only on-chain Service objects can be imported as a project root.`);
|
|
270
|
+
}
|
|
100
271
|
const project = await localProject.createProject({
|
|
101
272
|
prefix,
|
|
102
273
|
name,
|
|
@@ -108,7 +279,7 @@ export async function handleCreateProjectFromOnchain(v) {
|
|
|
108
279
|
network: network,
|
|
109
280
|
});
|
|
110
281
|
const builder = new GraphBuilder({
|
|
111
|
-
maxDepth: v.max_depth ?? 3,
|
|
282
|
+
maxDepth: v.max_depth ?? v.depth ?? 3,
|
|
112
283
|
enableRegistrar: v.enable_registrar ?? true,
|
|
113
284
|
enablePotential: v.enable_potential ?? false,
|
|
114
285
|
projectId: project.id,
|
|
@@ -126,9 +297,10 @@ export async function handleCreateProjectFromOnchain(v) {
|
|
|
126
297
|
});
|
|
127
298
|
objects.push(obj);
|
|
128
299
|
}
|
|
300
|
+
const updatedProject = await localProject.updateProject(project.id, { status: "active" });
|
|
129
301
|
return {
|
|
130
302
|
action: "create_project_from_onchain",
|
|
131
|
-
project,
|
|
303
|
+
project: updatedProject ?? project,
|
|
132
304
|
objects,
|
|
133
305
|
graphEdgeCount: graphResult.graph.edges.length,
|
|
134
306
|
};
|
|
@@ -156,16 +328,126 @@ export async function handleGetProjectDetail(v) {
|
|
|
156
328
|
const edges = await localProject.getGraph(id);
|
|
157
329
|
const evaluations = await localProject.listEvaluations(id);
|
|
158
330
|
const versions = await localProject.listVersions(id);
|
|
331
|
+
let effectiveProject = project;
|
|
332
|
+
if (project.status === "draft" && objects.length > 0) {
|
|
333
|
+
const updated = await localProject.updateProject(id, { status: "active" });
|
|
334
|
+
if (updated)
|
|
335
|
+
effectiveProject = updated;
|
|
336
|
+
}
|
|
337
|
+
const needsNameFix = objects.some(o => !o.objectName);
|
|
338
|
+
const needsRoleFix = objects.some(o => o.role === "reference");
|
|
339
|
+
if (needsNameFix || needsRoleFix) {
|
|
340
|
+
const allMarks = await LocalMark.Instance().list(null);
|
|
341
|
+
const addressToName = new Map();
|
|
342
|
+
for (const m of allMarks) {
|
|
343
|
+
if (m.name && m.address) {
|
|
344
|
+
addressToName.set(m.address.toLowerCase(), m.name);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
for (const obj of objects) {
|
|
348
|
+
const updates = {};
|
|
349
|
+
if (!obj.objectName) {
|
|
350
|
+
const name = addressToName.get(obj.objectAddress.toLowerCase());
|
|
351
|
+
if (name) {
|
|
352
|
+
updates.objectName = name;
|
|
353
|
+
obj.objectName = name;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (obj.role === "reference" && obj.objectType) {
|
|
357
|
+
const t = obj.objectType;
|
|
358
|
+
if (t.includes("service::Service")) {
|
|
359
|
+
updates.role = "core";
|
|
360
|
+
obj.role = "core";
|
|
361
|
+
}
|
|
362
|
+
else if (t.includes("permission::Permission") ||
|
|
363
|
+
t.includes("machine::Machine") ||
|
|
364
|
+
t.includes("guard::Guard") ||
|
|
365
|
+
t.includes("reward::Reward") ||
|
|
366
|
+
t.includes("treasury::Treasury") ||
|
|
367
|
+
t.includes("allocation::Allocation") ||
|
|
368
|
+
t.includes("progress::Progress") ||
|
|
369
|
+
t.includes("arbitration::Arbitration") ||
|
|
370
|
+
t.includes("demand::Demand") ||
|
|
371
|
+
t.includes("order::Order") ||
|
|
372
|
+
t.includes("repository::Repository")) {
|
|
373
|
+
updates.role = "dependency";
|
|
374
|
+
obj.role = "dependency";
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (effectiveProject.serviceAddress &&
|
|
378
|
+
obj.objectAddress === effectiveProject.serviceAddress &&
|
|
379
|
+
obj.role !== "core") {
|
|
380
|
+
updates.role = "core";
|
|
381
|
+
obj.role = "core";
|
|
382
|
+
}
|
|
383
|
+
if (Object.keys(updates).length > 0) {
|
|
384
|
+
await localProject.updateObject(obj.id, updates).catch(() => { });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
let serviceStatus = null;
|
|
389
|
+
if (effectiveProject.serviceAddress && effectiveProject.network) {
|
|
390
|
+
try {
|
|
391
|
+
const svcQuery = await query_objects({
|
|
392
|
+
objects: [effectiveProject.serviceAddress],
|
|
393
|
+
network: effectiveProject.network,
|
|
394
|
+
});
|
|
395
|
+
const svcData = svcQuery?.objects?.[0];
|
|
396
|
+
if (svcData) {
|
|
397
|
+
const pub = svcData.bPublished ?? svcData.published ?? svcData.b_publish;
|
|
398
|
+
const pau = svcData.bPaused ?? svcData.paused ?? svcData.b_pause;
|
|
399
|
+
serviceStatus = {
|
|
400
|
+
published: typeof pub === "boolean" ? pub : null,
|
|
401
|
+
paused: typeof pau === "boolean" ? pau : null,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const VALID_ROLES = new Set(["core", "dependency", "reference"]);
|
|
409
|
+
const core = objects.filter(o => o.role === "core");
|
|
410
|
+
const dependency = objects.filter(o => o.role === "dependency");
|
|
411
|
+
const reference = objects.filter(o => o.role === "reference");
|
|
412
|
+
const other = objects.filter(o => !o.role || !VALID_ROLES.has(o.role));
|
|
413
|
+
if (other.length > 0) {
|
|
414
|
+
console.warn(`[handleGetProjectDetail] ${other.length} object(s) have invalid or missing role ` +
|
|
415
|
+
`(expected one of: ${Array.from(VALID_ROLES).join(", ")}). ` +
|
|
416
|
+
`They are grouped into 'other' for visibility. ` +
|
|
417
|
+
`Object names: ${other.map(o => o.objectName ?? o.objectAddress).slice(0, 5).join(", ")}${other.length > 5 ? "..." : ""}`);
|
|
418
|
+
}
|
|
419
|
+
const objectGroups = {
|
|
420
|
+
core,
|
|
421
|
+
dependency,
|
|
422
|
+
reference,
|
|
423
|
+
other,
|
|
424
|
+
stats: {
|
|
425
|
+
total: objects.length,
|
|
426
|
+
core_count: core.length,
|
|
427
|
+
dependency_count: dependency.length,
|
|
428
|
+
reference_count: reference.length,
|
|
429
|
+
other_count: other.length,
|
|
430
|
+
by_type: objects.reduce((acc, o) => {
|
|
431
|
+
const t = o.objectType ?? "unknown";
|
|
432
|
+
const prefix = t.includes("::") ? t.split("::")[0].toLowerCase() : t.toLowerCase();
|
|
433
|
+
acc[prefix] = (acc[prefix] ?? 0) + 1;
|
|
434
|
+
return acc;
|
|
435
|
+
}, {}),
|
|
436
|
+
},
|
|
437
|
+
};
|
|
159
438
|
return {
|
|
160
439
|
action: "get_project_detail",
|
|
161
|
-
project,
|
|
440
|
+
project: effectiveProject,
|
|
162
441
|
objects,
|
|
442
|
+
objectGroups,
|
|
163
443
|
graphEdgeCount: edges.length,
|
|
444
|
+
serviceStatus,
|
|
164
445
|
evaluations: evaluations.map(e => ({
|
|
165
446
|
id: e.id,
|
|
166
447
|
evalType: e.evalType,
|
|
167
448
|
score: e.score,
|
|
168
449
|
summary: e.summary,
|
|
450
|
+
details: e.details,
|
|
169
451
|
evaluatedAt: e.evaluatedAt,
|
|
170
452
|
})),
|
|
171
453
|
versions: versions.map(v => ({
|
|
@@ -175,16 +457,72 @@ export async function handleGetProjectDetail(v) {
|
|
|
175
457
|
})),
|
|
176
458
|
};
|
|
177
459
|
}
|
|
460
|
+
const SHORT_TO_FULL_TYPE = {
|
|
461
|
+
"Service": "0x2::service::Service",
|
|
462
|
+
"Machine": "0x2::machine::Machine",
|
|
463
|
+
"Guard": "0x2::guard::Guard",
|
|
464
|
+
"Permission": "0x2::permission::Permission",
|
|
465
|
+
"Reward": "0x2::reward::Reward",
|
|
466
|
+
"Arbitration": "0x2::arbitration::Arbitration",
|
|
467
|
+
"Progress": "0x2::progress::Progress",
|
|
468
|
+
"Treasury": "0x2::treasury::Treasury",
|
|
469
|
+
"Demand": "0x2::demand::Demand",
|
|
470
|
+
"Repository": "0x2::repository::Repository",
|
|
471
|
+
"Contact": "0x2::contact::Contact",
|
|
472
|
+
"Order": "0x2::order::Order",
|
|
473
|
+
"Allocation": "0x2::allocation::Allocation",
|
|
474
|
+
"Personal": "0x2::personal::Personal",
|
|
475
|
+
"Payment": "0x2::payment::Payment",
|
|
476
|
+
"Proof": "0x2::proof::Proof",
|
|
477
|
+
};
|
|
178
478
|
export async function handleAddObject(v) {
|
|
179
479
|
const projectId = await resolveProjectId(v);
|
|
180
480
|
const objectAddress = await resolveObjectAddress(v);
|
|
481
|
+
const rawType = v.object_type_add;
|
|
482
|
+
const objectType = rawType
|
|
483
|
+
? (rawType.includes("::") ? rawType : (SHORT_TO_FULL_TYPE[rawType] ?? rawType))
|
|
484
|
+
: undefined;
|
|
485
|
+
let objectName = v.object_name_add;
|
|
486
|
+
let role = v.object_role;
|
|
487
|
+
if (!objectName) {
|
|
488
|
+
const marks = await LocalMark.Instance().list({ address: objectAddress });
|
|
489
|
+
if (marks.length > 0 && marks[0].name) {
|
|
490
|
+
objectName = marks[0].name;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (!role) {
|
|
494
|
+
const objType = objectType ?? "";
|
|
495
|
+
if (objType.includes("service::Service")) {
|
|
496
|
+
role = "core";
|
|
497
|
+
}
|
|
498
|
+
else if (objType.includes("::") && (objType.includes("permission::") ||
|
|
499
|
+
objType.includes("machine::") ||
|
|
500
|
+
objType.includes("guard::") ||
|
|
501
|
+
objType.includes("reward::") ||
|
|
502
|
+
objType.includes("treasury::") ||
|
|
503
|
+
objType.includes("allocation::") ||
|
|
504
|
+
objType.includes("progress::") ||
|
|
505
|
+
objType.includes("arbitration::") ||
|
|
506
|
+
objType.includes("demand::") ||
|
|
507
|
+
objType.includes("order::") ||
|
|
508
|
+
objType.includes("repository::"))) {
|
|
509
|
+
role = "dependency";
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
role = "reference";
|
|
513
|
+
}
|
|
514
|
+
}
|
|
181
515
|
const obj = await localProject.addObject(projectId, {
|
|
182
516
|
objectAddress,
|
|
183
|
-
objectType
|
|
184
|
-
objectName
|
|
185
|
-
role
|
|
517
|
+
objectType,
|
|
518
|
+
objectName,
|
|
519
|
+
role,
|
|
186
520
|
tags: v.object_tags_add,
|
|
187
521
|
});
|
|
522
|
+
const project = await localProject.getProject(projectId);
|
|
523
|
+
if (project && project.status === "draft") {
|
|
524
|
+
await localProject.updateProject(projectId, { status: "active" });
|
|
525
|
+
}
|
|
188
526
|
return { action: "add_object", object: obj };
|
|
189
527
|
}
|
|
190
528
|
export async function handleRemoveObject(v) {
|
|
@@ -227,20 +565,43 @@ export async function handleBuildGraph(v) {
|
|
|
227
565
|
const project = await localProject.getProject(projectId);
|
|
228
566
|
if (!project)
|
|
229
567
|
throw new Error(`Project not found: ${projectId}`);
|
|
568
|
+
if (v.async_mode === true) {
|
|
569
|
+
const taskId = TaskTracker.create("build_graph", projectId);
|
|
570
|
+
(async () => {
|
|
571
|
+
try {
|
|
572
|
+
TaskTracker.update(taskId, 10, "Resolving root address");
|
|
573
|
+
const result = await buildGraphSync(projectId, project, v);
|
|
574
|
+
TaskTracker.complete(taskId, result);
|
|
575
|
+
}
|
|
576
|
+
catch (err) {
|
|
577
|
+
TaskTracker.fail(taskId, err.message ?? String(err));
|
|
578
|
+
}
|
|
579
|
+
})();
|
|
580
|
+
return {
|
|
581
|
+
action: "build_graph",
|
|
582
|
+
async_mode: true,
|
|
583
|
+
task_id: taskId,
|
|
584
|
+
hint: "Graph is building in the background. Call query_task_status with task_id to check progress and retrieve the result.",
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
return buildGraphSync(projectId, project, v);
|
|
588
|
+
}
|
|
589
|
+
async function buildGraphSync(projectId, project, v) {
|
|
230
590
|
let rootAddress = project.serviceAddress;
|
|
231
591
|
if (!rootAddress) {
|
|
232
592
|
const objects = await localProject.listObjects(projectId);
|
|
233
593
|
if (objects.length === 0) {
|
|
234
594
|
throw new Error("Cannot build graph: project has no objects and no service_address");
|
|
235
595
|
}
|
|
236
|
-
|
|
596
|
+
const coreObj = objects.find(o => o.role === "core");
|
|
597
|
+
rootAddress = (coreObj ?? objects[0]).objectAddress;
|
|
237
598
|
}
|
|
238
599
|
const builder = new GraphBuilder({
|
|
239
|
-
maxDepth: v.max_depth ?? 3,
|
|
600
|
+
maxDepth: v.max_depth ?? v.depth ?? 3,
|
|
240
601
|
enableRegistrar: v.enable_registrar ?? true,
|
|
241
602
|
enablePotential: v.enable_potential ?? false,
|
|
242
603
|
projectId,
|
|
243
|
-
network: project.network,
|
|
604
|
+
network: project.network ?? undefined,
|
|
244
605
|
});
|
|
245
606
|
const result = await builder.buildRelationshipGraph(rootAddress);
|
|
246
607
|
const edgesBySource = {};
|
|
@@ -248,6 +609,19 @@ export async function handleBuildGraph(v) {
|
|
|
248
609
|
const src = edge.edgeSource ?? "unknown";
|
|
249
610
|
edgesBySource[src] = (edgesBySource[src] ?? 0) + 1;
|
|
250
611
|
}
|
|
612
|
+
const topEdges = result.graph.edges
|
|
613
|
+
.slice()
|
|
614
|
+
.sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0))
|
|
615
|
+
.slice(0, 5)
|
|
616
|
+
.map(e => ({
|
|
617
|
+
source: e.sourceObject,
|
|
618
|
+
target: e.targetObject,
|
|
619
|
+
fieldName: e.fieldName,
|
|
620
|
+
relationshipType: e.relationshipType,
|
|
621
|
+
weight: e.weight,
|
|
622
|
+
verified: e.verified,
|
|
623
|
+
}));
|
|
624
|
+
const visualization = topEdges.map(e => ` ${shortLabel(e.source)} --${e.fieldName ?? e.relationshipType ?? ""}--> ${shortLabel(e.target)}`).join("\n");
|
|
251
625
|
return {
|
|
252
626
|
action: "build_graph",
|
|
253
627
|
nodeCount: result.graph.nodes.length,
|
|
@@ -255,8 +629,74 @@ export async function handleBuildGraph(v) {
|
|
|
255
629
|
edgesBySource,
|
|
256
630
|
reputationCount: result.reputation.size,
|
|
257
631
|
builtAt: result.graph.builtAt,
|
|
632
|
+
topEdges,
|
|
633
|
+
visualization: visualization || " (no edges to display)",
|
|
634
|
+
hint: `Showing top ${topEdges.length} critical edges. Call project_operation with action='get_graph' to list all ${result.graph.edges.length} edges.`,
|
|
635
|
+
binding_verification: await verifyServiceBindings(project.serviceAddress, project.network),
|
|
258
636
|
};
|
|
259
637
|
}
|
|
638
|
+
async function verifyServiceBindings(serviceAddress, network) {
|
|
639
|
+
if (!serviceAddress)
|
|
640
|
+
return null;
|
|
641
|
+
try {
|
|
642
|
+
const response = await query_objects({
|
|
643
|
+
objects: [serviceAddress],
|
|
644
|
+
network: network,
|
|
645
|
+
});
|
|
646
|
+
const svc = response?.objects?.[0];
|
|
647
|
+
if (!svc)
|
|
648
|
+
return null;
|
|
649
|
+
const allocCount = svc.order_allocators?.allocators?.length ?? 0;
|
|
650
|
+
const arbCount = Array.isArray(svc.arbitrations) ? svc.arbitrations.length : 0;
|
|
651
|
+
const bindings = {
|
|
652
|
+
"Service.machine": {
|
|
653
|
+
expected: "Machine",
|
|
654
|
+
actual: svc.machine ?? null,
|
|
655
|
+
verified: !!svc.machine,
|
|
656
|
+
},
|
|
657
|
+
"Service.permission": {
|
|
658
|
+
expected: "Permission",
|
|
659
|
+
actual: svc.permission ?? null,
|
|
660
|
+
verified: !!svc.permission,
|
|
661
|
+
},
|
|
662
|
+
"Service.buy_guard": {
|
|
663
|
+
expected: "Guard",
|
|
664
|
+
actual: svc.buy_guard ?? null,
|
|
665
|
+
verified: !!svc.buy_guard,
|
|
666
|
+
},
|
|
667
|
+
"Service.order_allocators": {
|
|
668
|
+
expected: "Guard[]",
|
|
669
|
+
actual: null,
|
|
670
|
+
count: allocCount,
|
|
671
|
+
verified: allocCount > 0,
|
|
672
|
+
},
|
|
673
|
+
"Service.arbitrations": {
|
|
674
|
+
expected: "Arbitration[]",
|
|
675
|
+
actual: null,
|
|
676
|
+
count: arbCount,
|
|
677
|
+
verified: arbCount > 0,
|
|
678
|
+
},
|
|
679
|
+
"Service.um": {
|
|
680
|
+
expected: "Contact",
|
|
681
|
+
actual: svc.um ?? null,
|
|
682
|
+
verified: !!svc.um,
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
const all_verified = Object.values(bindings).every((b) => b.verified);
|
|
686
|
+
return { service: serviceAddress, bindings, all_verified };
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function shortLabel(addrOrName) {
|
|
693
|
+
if (!addrOrName)
|
|
694
|
+
return "(empty)";
|
|
695
|
+
if (!addrOrName.startsWith("0x")) {
|
|
696
|
+
return addrOrName.length <= 24 ? addrOrName : addrOrName.slice(0, 21) + "...";
|
|
697
|
+
}
|
|
698
|
+
return addrOrName.length <= 12 ? addrOrName : addrOrName.slice(0, 10) + "..";
|
|
699
|
+
}
|
|
260
700
|
export async function handleGetGraph(v) {
|
|
261
701
|
const projectId = await resolveProjectId(v);
|
|
262
702
|
const edges = await localProject.getGraph(projectId);
|
|
@@ -438,6 +878,58 @@ export async function handleDeleteProject(v) {
|
|
|
438
878
|
await localProject.deleteProject(id);
|
|
439
879
|
return { action: "delete_project", deleted: true };
|
|
440
880
|
}
|
|
881
|
+
export async function handleCloneProjectToNetwork(v) {
|
|
882
|
+
const sourceProjectId = await resolveProjectId(v);
|
|
883
|
+
const sourceProject = await localProject.getProject(sourceProjectId);
|
|
884
|
+
if (!sourceProject) {
|
|
885
|
+
throw new Error(`Source project not found: ${sourceProjectId}`);
|
|
886
|
+
}
|
|
887
|
+
const targetNetwork = requireField(v.target_network, "target_network");
|
|
888
|
+
if (!["testnet", "mainnet"].includes(targetNetwork)) {
|
|
889
|
+
throw new Error("target_network must be 'testnet' or 'mainnet'");
|
|
890
|
+
}
|
|
891
|
+
const actionName = (v.action === "migrate_network" ? "migrate_network" : "clone_project_to_network");
|
|
892
|
+
const newPrefix = `${sourceProject.prefix}_${targetNetwork}`;
|
|
893
|
+
const existing = await localProject.getProjectByPrefix(newPrefix);
|
|
894
|
+
if (existing) {
|
|
895
|
+
throw new Error(`Target project prefix '${newPrefix}' already exists (project: '${existing.name}', id: '${existing.id}'). ` +
|
|
896
|
+
`Use update_project on the existing target or delete it first.`);
|
|
897
|
+
}
|
|
898
|
+
const newProject = await localProject.createProject({
|
|
899
|
+
prefix: newPrefix,
|
|
900
|
+
name: `${sourceProject.name} (${targetNetwork})`,
|
|
901
|
+
description: sourceProject.description ?? undefined,
|
|
902
|
+
industry: sourceProject.industry ?? undefined,
|
|
903
|
+
perspective: sourceProject.perspective ?? "producer",
|
|
904
|
+
labels: sourceProject.labels ?? undefined,
|
|
905
|
+
network: targetNetwork,
|
|
906
|
+
});
|
|
907
|
+
const sourceObjects = await localProject.listObjects(sourceProjectId);
|
|
908
|
+
let clonedCount = 0;
|
|
909
|
+
for (const obj of sourceObjects) {
|
|
910
|
+
await localProject.addObject(newProject.id, {
|
|
911
|
+
objectAddress: `draft:${obj.objectName ?? "unnamed"}`,
|
|
912
|
+
objectType: obj.objectType !== null ? obj.objectType : undefined,
|
|
913
|
+
objectName: obj.objectName !== null ? obj.objectName : undefined,
|
|
914
|
+
role: obj.role ?? "dependency",
|
|
915
|
+
metadata: {
|
|
916
|
+
status: "draft",
|
|
917
|
+
cloned_from: sourceProjectId,
|
|
918
|
+
cloned_at: Date.now(),
|
|
919
|
+
},
|
|
920
|
+
});
|
|
921
|
+
clonedCount++;
|
|
922
|
+
}
|
|
923
|
+
return {
|
|
924
|
+
action: actionName,
|
|
925
|
+
new_project_id: newProject.id,
|
|
926
|
+
new_prefix: newPrefix,
|
|
927
|
+
target_network: targetNetwork,
|
|
928
|
+
cloned_objects_count: clonedCount,
|
|
929
|
+
message: `Project '${sourceProject.prefix}' cloned to ${targetNetwork} as '${newPrefix}'. ${clonedCount} object(s) copied as drafts — object addresses are empty pending on-chain deployment.`,
|
|
930
|
+
next_steps: `Execute the same onchain_operations sequence with env.network='${targetNetwork}' to deploy objects. Then call update_project to set service_address, and build_graph to construct the new project's relationship graph.`,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
441
933
|
export async function handleGetObjectDiff(v) {
|
|
442
934
|
const address = requireField(v.diff_address, "diff_address");
|
|
443
935
|
const fromTime = requireField(v.diff_from_time, "diff_from_time");
|
|
@@ -456,3 +948,430 @@ export async function handleGetObjectDiff(v) {
|
|
|
456
948
|
})),
|
|
457
949
|
};
|
|
458
950
|
}
|
|
951
|
+
function normalizeForwards(raw) {
|
|
952
|
+
if (!Array.isArray(raw))
|
|
953
|
+
return [];
|
|
954
|
+
return raw.map((f) => {
|
|
955
|
+
const guardObj = f?.guard;
|
|
956
|
+
const isGuardObj = guardObj && typeof guardObj === "object" && typeof guardObj.guard === "string";
|
|
957
|
+
const guardAddress = isGuardObj
|
|
958
|
+
? guardObj.guard
|
|
959
|
+
: typeof guardObj === "string" ? guardObj : null;
|
|
960
|
+
const retainedSubmission = isGuardObj && Array.isArray(guardObj.retained_submission)
|
|
961
|
+
? guardObj.retained_submission
|
|
962
|
+
: null;
|
|
963
|
+
return {
|
|
964
|
+
name: typeof f?.name === "string" ? f.name : "",
|
|
965
|
+
weight: typeof f?.weight === "number" ? f.weight : 0,
|
|
966
|
+
guard: guardAddress,
|
|
967
|
+
retained_submission: retainedSubmission,
|
|
968
|
+
named_operator: typeof f?.namedOperator === "string" ? f.namedOperator : null,
|
|
969
|
+
permission_index: typeof f?.permissionIndex === "number" ? f.permissionIndex : null,
|
|
970
|
+
};
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
export async function handleGetMachineGraph(v) {
|
|
974
|
+
const projectId = await resolveProjectId(v);
|
|
975
|
+
const machineAddress = await resolveObjectAddress(v);
|
|
976
|
+
const project = await localProject.getProject(projectId);
|
|
977
|
+
if (!project)
|
|
978
|
+
throw new Error(`Project not found (id: ${projectId})`);
|
|
979
|
+
const objects = await localProject.listObjects(projectId);
|
|
980
|
+
const machineObj = objects.find(o => o.objectAddress === machineAddress);
|
|
981
|
+
if (!machineObj) {
|
|
982
|
+
throw new Error(`Object ${machineAddress} is not part of project '${project.prefix}'. ` +
|
|
983
|
+
`Add it via add_object first.`);
|
|
984
|
+
}
|
|
985
|
+
const network = (project.network ?? "testnet");
|
|
986
|
+
let machineData = null;
|
|
987
|
+
try {
|
|
988
|
+
const queryResult = await query_objects({
|
|
989
|
+
objects: [machineAddress],
|
|
990
|
+
network,
|
|
991
|
+
});
|
|
992
|
+
machineData = queryResult?.objects?.[0] ?? null;
|
|
993
|
+
}
|
|
994
|
+
catch (e) {
|
|
995
|
+
throw new Error(`Failed to query Machine on ${network}: ${e instanceof Error ? e.message : String(e)}`);
|
|
996
|
+
}
|
|
997
|
+
if (!machineData) {
|
|
998
|
+
throw new Error(`Machine ${machineAddress} not found on ${network}. ` +
|
|
999
|
+
`It may not exist on this network — try switching networks.`);
|
|
1000
|
+
}
|
|
1001
|
+
let machineName = null;
|
|
1002
|
+
const marks = await LocalMark.Instance().list({ address: machineAddress });
|
|
1003
|
+
if (marks.length > 0 && marks[0].name) {
|
|
1004
|
+
machineName = marks[0].name;
|
|
1005
|
+
}
|
|
1006
|
+
const published = typeof machineData.bPublished === "boolean" ? machineData.bPublished : null;
|
|
1007
|
+
const paused = typeof machineData.bPaused === "boolean" ? machineData.bPaused : null;
|
|
1008
|
+
const nodeMap = new Map();
|
|
1009
|
+
const edgeList = [];
|
|
1010
|
+
let rawNodes = [];
|
|
1011
|
+
try {
|
|
1012
|
+
const allFieldObjectIds = [];
|
|
1013
|
+
let cursor = undefined;
|
|
1014
|
+
for (;;) {
|
|
1015
|
+
const tableResult = await query_table({
|
|
1016
|
+
parent: machineAddress,
|
|
1017
|
+
cursor: cursor ?? undefined,
|
|
1018
|
+
network,
|
|
1019
|
+
});
|
|
1020
|
+
if (!tableResult?.items || tableResult.items.length === 0)
|
|
1021
|
+
break;
|
|
1022
|
+
for (const item of tableResult.items) {
|
|
1023
|
+
if (item.object)
|
|
1024
|
+
allFieldObjectIds.push(item.object);
|
|
1025
|
+
}
|
|
1026
|
+
if (!tableResult.hasNextPage || !tableResult.nextCursor)
|
|
1027
|
+
break;
|
|
1028
|
+
cursor = tableResult.nextCursor;
|
|
1029
|
+
}
|
|
1030
|
+
if (allFieldObjectIds.length > 0) {
|
|
1031
|
+
const BATCH = 50;
|
|
1032
|
+
for (let i = 0; i < allFieldObjectIds.length; i += BATCH) {
|
|
1033
|
+
const batch = allFieldObjectIds.slice(i, i + BATCH);
|
|
1034
|
+
const nodeQueryResult = await query_objects({
|
|
1035
|
+
objects: batch,
|
|
1036
|
+
network,
|
|
1037
|
+
});
|
|
1038
|
+
for (const obj of nodeQueryResult?.objects ?? []) {
|
|
1039
|
+
if (obj.type === "TableItem_MachineNode") {
|
|
1040
|
+
rawNodes.push(obj);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
catch (e) {
|
|
1047
|
+
throw new Error(`Failed to query Machine nodes on ${network}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1048
|
+
}
|
|
1049
|
+
for (const nodeObj of rawNodes) {
|
|
1050
|
+
const nodeName = nodeObj.name;
|
|
1051
|
+
if (!nodeName)
|
|
1052
|
+
continue;
|
|
1053
|
+
if (!nodeMap.has(nodeName)) {
|
|
1054
|
+
nodeMap.set(nodeName, { name: nodeName, description: null });
|
|
1055
|
+
}
|
|
1056
|
+
const pairs = Array.isArray(nodeObj.value) ? nodeObj.value : [];
|
|
1057
|
+
for (const pair of pairs) {
|
|
1058
|
+
if (!pair || typeof pair !== "object")
|
|
1059
|
+
continue;
|
|
1060
|
+
const from = typeof pair.prev_node === "string" ? pair.prev_node : "";
|
|
1061
|
+
if (!from)
|
|
1062
|
+
continue;
|
|
1063
|
+
const to = nodeName;
|
|
1064
|
+
const threshold = typeof pair.threshold === "number" ? pair.threshold : 1;
|
|
1065
|
+
const forwards = normalizeForwards(pair.forwards);
|
|
1066
|
+
edgeList.push({ from, to, threshold, forwards });
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
for (const edge of edgeList) {
|
|
1070
|
+
if (!nodeMap.has(edge.from)) {
|
|
1071
|
+
nodeMap.set(edge.from, { name: edge.from, description: null });
|
|
1072
|
+
}
|
|
1073
|
+
if (!nodeMap.has(edge.to)) {
|
|
1074
|
+
nodeMap.set(edge.to, { name: edge.to, description: null });
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return {
|
|
1078
|
+
action: "get_machine_graph",
|
|
1079
|
+
machineAddress,
|
|
1080
|
+
machineName,
|
|
1081
|
+
nodes: Array.from(nodeMap.values()),
|
|
1082
|
+
edges: edgeList,
|
|
1083
|
+
published,
|
|
1084
|
+
paused,
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
export async function handlePreEvaluateCheck(v) {
|
|
1088
|
+
const projectId = await resolveProjectId(v);
|
|
1089
|
+
const project = await localProject.getProject(projectId);
|
|
1090
|
+
if (!project)
|
|
1091
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
1092
|
+
const missing = [];
|
|
1093
|
+
const warnings = [];
|
|
1094
|
+
const objects = await localProject.listObjects(projectId);
|
|
1095
|
+
if (objects.length === 0) {
|
|
1096
|
+
missing.push({
|
|
1097
|
+
id: "PE-01",
|
|
1098
|
+
description: "Project has no objects (project_objects table is empty).",
|
|
1099
|
+
impact: "Evaluator will return a neutral score with no findings. " +
|
|
1100
|
+
"Call add_object or create_project_from_onchain first.",
|
|
1101
|
+
});
|
|
1102
|
+
return {
|
|
1103
|
+
action: "pre_evaluate_check",
|
|
1104
|
+
ready: false,
|
|
1105
|
+
missing,
|
|
1106
|
+
warnings,
|
|
1107
|
+
summary: "Project is empty — add objects before evaluating.",
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
const edges = await localProject.getGraph(projectId);
|
|
1111
|
+
if (edges.length === 0) {
|
|
1112
|
+
warnings.push({
|
|
1113
|
+
id: "PE-02",
|
|
1114
|
+
description: "Project has no graph edges (graph_edges table is empty).",
|
|
1115
|
+
impact: "Evaluator's binding-based checks (machine, permission, " +
|
|
1116
|
+
"buy_guard, order_allocators) will return 0. Call build_graph " +
|
|
1117
|
+
"before evaluate_project to populate edges.",
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
const addresses = objects.map(o => o.objectAddress);
|
|
1121
|
+
const cacheEntries = await localProject.getCachedObjects(addresses);
|
|
1122
|
+
const uncached = [];
|
|
1123
|
+
let staleCount = 0;
|
|
1124
|
+
const STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
|
|
1125
|
+
const now = Date.now();
|
|
1126
|
+
for (const o of objects) {
|
|
1127
|
+
const entry = cacheEntries.get(o.objectAddress);
|
|
1128
|
+
if (!entry) {
|
|
1129
|
+
uncached.push(o.objectAddress);
|
|
1130
|
+
}
|
|
1131
|
+
else {
|
|
1132
|
+
const fetchedAt = entry?.fetchedAt;
|
|
1133
|
+
if (typeof fetchedAt === "number" && (now - fetchedAt) > STALE_THRESHOLD_MS) {
|
|
1134
|
+
staleCount++;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
if (uncached.length > 0) {
|
|
1139
|
+
warnings.push({
|
|
1140
|
+
id: "PE-03",
|
|
1141
|
+
description: `${uncached.length}/${objects.length} project objects have no cached on-chain data.`,
|
|
1142
|
+
impact: "Evaluator will see empty object data for these objects. " +
|
|
1143
|
+
"Call refresh_objects before evaluate_project to fetch fresh data.",
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
if (staleCount > 0) {
|
|
1147
|
+
warnings.push({
|
|
1148
|
+
id: "PE-04",
|
|
1149
|
+
description: `${staleCount}/${objects.length} project objects have cache older than 24 hours.`,
|
|
1150
|
+
impact: "Evaluator results may be based on stale state. " +
|
|
1151
|
+
"Call refresh_objects to fetch fresh data.",
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
const knownAddresses = new Set(objects.map(o => o.objectAddress));
|
|
1155
|
+
const knownNames = new Set(objects.map(o => o.objectName).filter(Boolean));
|
|
1156
|
+
const danglingEdges = edges.filter((e) => !knownAddresses.has(e.sourceObject) && !knownNames.has(e.sourceObject)
|
|
1157
|
+
&& !knownAddresses.has(e.targetObject) && !knownNames.has(e.targetObject));
|
|
1158
|
+
if (danglingEdges.length > 0) {
|
|
1159
|
+
warnings.push({
|
|
1160
|
+
id: "PE-05",
|
|
1161
|
+
description: `${danglingEdges.length} graph edges reference objects not in project_objects.`,
|
|
1162
|
+
impact: "Evaluator's binding index may include stale/dangling entries. " +
|
|
1163
|
+
"Call build_graph again to refresh edges, or remove orphan objects.",
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
if (project.perspective === "producer") {
|
|
1167
|
+
const hasService = objects.some(o => o.objectType === "service");
|
|
1168
|
+
if (!hasService) {
|
|
1169
|
+
missing.push({
|
|
1170
|
+
id: "PE-06",
|
|
1171
|
+
description: "Producer project has no Service-type object.",
|
|
1172
|
+
impact: "Evaluator's service-centric checks (publish state, " +
|
|
1173
|
+
"machine binding, order_allocators, etc.) will all return 0. " +
|
|
1174
|
+
"Add a Service object or import one via create_project_from_onchain.",
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
const draftCount = objects.filter((o) => {
|
|
1179
|
+
const meta = (o.metadata ?? {});
|
|
1180
|
+
return meta.status === "draft" || o.objectAddress.startsWith("draft:");
|
|
1181
|
+
}).length;
|
|
1182
|
+
if (draftCount === objects.length && objects.length > 0) {
|
|
1183
|
+
warnings.push({
|
|
1184
|
+
id: "PE-07",
|
|
1185
|
+
description: `All ${objects.length} objects are draft (not yet on-chain).`,
|
|
1186
|
+
impact: "Evaluator will assess the blueprint as if it were on-chain. " +
|
|
1187
|
+
"Real on-chain state may differ once deployed.",
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
const ready = missing.length === 0;
|
|
1191
|
+
const summary = ready
|
|
1192
|
+
? (warnings.length === 0
|
|
1193
|
+
? `Ready to evaluate. ${objects.length} objects, ${edges.length} edges.`
|
|
1194
|
+
: `Ready to evaluate with ${warnings.length} warning(s). ${objects.length} objects, ${edges.length} edges.`)
|
|
1195
|
+
: `Not ready: ${missing.length} blocking issue(s) found.`;
|
|
1196
|
+
return {
|
|
1197
|
+
action: "pre_evaluate_check",
|
|
1198
|
+
ready,
|
|
1199
|
+
missing,
|
|
1200
|
+
warnings,
|
|
1201
|
+
summary,
|
|
1202
|
+
counts: {
|
|
1203
|
+
objects: objects.length,
|
|
1204
|
+
edges: edges.length,
|
|
1205
|
+
uncached: uncached.length,
|
|
1206
|
+
stale: staleCount,
|
|
1207
|
+
dangling_edges: danglingEdges.length,
|
|
1208
|
+
drafts: draftCount,
|
|
1209
|
+
},
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
export async function handleVerifyDeployment(v) {
|
|
1213
|
+
const projectId = await resolveProjectId(v);
|
|
1214
|
+
const project = await localProject.getProject(projectId);
|
|
1215
|
+
if (!project)
|
|
1216
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
1217
|
+
const objects = await localProject.listObjects(projectId);
|
|
1218
|
+
if (objects.length === 0) {
|
|
1219
|
+
return {
|
|
1220
|
+
action: "verify_deployment",
|
|
1221
|
+
verifiedCount: 0,
|
|
1222
|
+
matched: 0,
|
|
1223
|
+
mismatched: 0,
|
|
1224
|
+
missing: 0,
|
|
1225
|
+
unreachable: 0,
|
|
1226
|
+
results: [],
|
|
1227
|
+
summary: "Project has no objects to verify.",
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
const network = (project.network ?? "testnet");
|
|
1231
|
+
const deployedObjects = objects.filter((o) => !o.objectAddress.startsWith("draft:") && o.objectAddress.startsWith("0x"));
|
|
1232
|
+
if (deployedObjects.length === 0) {
|
|
1233
|
+
return {
|
|
1234
|
+
action: "verify_deployment",
|
|
1235
|
+
verifiedCount: 0,
|
|
1236
|
+
matched: 0,
|
|
1237
|
+
mismatched: 0,
|
|
1238
|
+
missing: 0,
|
|
1239
|
+
unreachable: 0,
|
|
1240
|
+
results: [],
|
|
1241
|
+
summary: `All ${objects.length} objects are draft or non-address — nothing to verify on-chain.`,
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
const batchSize = Math.min(deployedObjects.length, 50);
|
|
1245
|
+
const addressesToQuery = deployedObjects.slice(0, batchSize).map(o => o.objectAddress);
|
|
1246
|
+
let chainResult;
|
|
1247
|
+
try {
|
|
1248
|
+
chainResult = await query_objects({
|
|
1249
|
+
objects: addressesToQuery,
|
|
1250
|
+
network,
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
catch (e) {
|
|
1254
|
+
return {
|
|
1255
|
+
action: "verify_deployment",
|
|
1256
|
+
verifiedCount: 0,
|
|
1257
|
+
matched: 0,
|
|
1258
|
+
mismatched: 0,
|
|
1259
|
+
missing: 0,
|
|
1260
|
+
unreachable: deployedObjects.length,
|
|
1261
|
+
results: deployedObjects.map(o => ({
|
|
1262
|
+
objectAddress: o.objectAddress,
|
|
1263
|
+
objectName: o.objectName,
|
|
1264
|
+
objectType: o.objectType,
|
|
1265
|
+
status: "unreachable",
|
|
1266
|
+
error: e instanceof Error ? e.message : String(e),
|
|
1267
|
+
})),
|
|
1268
|
+
summary: `Failed to query on-chain state: ${e instanceof Error ? e.message : String(e)}`,
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
const chainObjects = chainResult?.objects ?? [];
|
|
1272
|
+
const chainByAddress = new Map();
|
|
1273
|
+
for (const co of chainObjects) {
|
|
1274
|
+
if (co && typeof co === "object" && "objectId" in co) {
|
|
1275
|
+
chainByAddress.set(co.objectId, co);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
const results = [];
|
|
1279
|
+
let matched = 0, mismatched = 0, missing = 0;
|
|
1280
|
+
for (const obj of deployedObjects.slice(0, batchSize)) {
|
|
1281
|
+
const chainObj = chainByAddress.get(obj.objectAddress);
|
|
1282
|
+
if (!chainObj) {
|
|
1283
|
+
missing++;
|
|
1284
|
+
results.push({
|
|
1285
|
+
objectAddress: obj.objectAddress,
|
|
1286
|
+
objectName: obj.objectName,
|
|
1287
|
+
objectType: obj.objectType,
|
|
1288
|
+
status: "missing",
|
|
1289
|
+
});
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
const chainData = chainObj;
|
|
1293
|
+
const sqliteData = (await localProject.getCachedObjects([obj.objectAddress])).get(obj.objectAddress);
|
|
1294
|
+
const sqliteFields = sqliteData?.data ?? {};
|
|
1295
|
+
const differences = [];
|
|
1296
|
+
let bindings;
|
|
1297
|
+
if (obj.objectType === "service") {
|
|
1298
|
+
const fieldsToCompare = ["machine", "permission", "buy_guard", "arbitrations", "rewards", "repositories"];
|
|
1299
|
+
for (const f of fieldsToCompare) {
|
|
1300
|
+
const sqliteVal = sqliteFields[f];
|
|
1301
|
+
const chainVal = chainData[f];
|
|
1302
|
+
const norm = (v) => (v === null || v === undefined || v === "") ? null : v;
|
|
1303
|
+
if (JSON.stringify(norm(sqliteVal)) !== JSON.stringify(norm(chainVal))) {
|
|
1304
|
+
differences.push({ field: f, sqlite: sqliteVal, onchain: chainVal });
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
bindings = {
|
|
1308
|
+
machine: !!chainData.machine,
|
|
1309
|
+
permission: !!chainData.permission,
|
|
1310
|
+
buy_guard: !!chainData.buy_guard,
|
|
1311
|
+
order_allocators: !!chainData.order_allocators,
|
|
1312
|
+
};
|
|
1313
|
+
if (sqliteFields.bPublished !== undefined && chainData.bPublished !== undefined
|
|
1314
|
+
&& sqliteFields.bPublished !== chainData.bPublished) {
|
|
1315
|
+
differences.push({
|
|
1316
|
+
field: "bPublished",
|
|
1317
|
+
sqlite: sqliteFields.bPublished,
|
|
1318
|
+
onchain: chainData.bPublished,
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
else if (obj.objectType === "machine") {
|
|
1323
|
+
if (sqliteFields.bPublished !== undefined && chainData.bPublished !== undefined
|
|
1324
|
+
&& sqliteFields.bPublished !== chainData.bPublished) {
|
|
1325
|
+
differences.push({
|
|
1326
|
+
field: "bPublished",
|
|
1327
|
+
sqlite: sqliteFields.bPublished,
|
|
1328
|
+
onchain: chainData.bPublished,
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
const sqliteNodeCount = sqliteFields.node_count;
|
|
1332
|
+
const chainNodeCount = chainData.node_count;
|
|
1333
|
+
if (sqliteNodeCount !== undefined && chainNodeCount !== undefined
|
|
1334
|
+
&& sqliteNodeCount !== chainNodeCount) {
|
|
1335
|
+
differences.push({
|
|
1336
|
+
field: "node_count",
|
|
1337
|
+
sqlite: sqliteNodeCount,
|
|
1338
|
+
onchain: chainNodeCount,
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
if (differences.length > 0) {
|
|
1343
|
+
mismatched++;
|
|
1344
|
+
results.push({
|
|
1345
|
+
objectAddress: obj.objectAddress,
|
|
1346
|
+
objectName: obj.objectName,
|
|
1347
|
+
objectType: obj.objectType,
|
|
1348
|
+
status: "mismatched",
|
|
1349
|
+
differences,
|
|
1350
|
+
bindings,
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
else {
|
|
1354
|
+
matched++;
|
|
1355
|
+
results.push({
|
|
1356
|
+
objectAddress: obj.objectAddress,
|
|
1357
|
+
objectName: obj.objectName,
|
|
1358
|
+
objectType: obj.objectType,
|
|
1359
|
+
status: "matched",
|
|
1360
|
+
bindings,
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
const summary = matched === deployedObjects.length
|
|
1365
|
+
? `All ${matched} deployed objects verified.`
|
|
1366
|
+
: `${matched} matched, ${mismatched} mismatched, ${missing} missing.`;
|
|
1367
|
+
return {
|
|
1368
|
+
action: "verify_deployment",
|
|
1369
|
+
verifiedCount: results.length,
|
|
1370
|
+
matched,
|
|
1371
|
+
mismatched,
|
|
1372
|
+
missing,
|
|
1373
|
+
unreachable: 0,
|
|
1374
|
+
results,
|
|
1375
|
+
summary,
|
|
1376
|
+
};
|
|
1377
|
+
}
|