@nekzus/liop 1.2.0-alpha.9 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -3
- package/dist/bin/agent.js +222 -51
- package/dist/bridge/index.js +7 -6
- package/dist/bridge/stream.js +11 -11
- package/dist/client/index.js +46 -35
- package/dist/crypto/logic-image-id.d.ts +3 -0
- package/dist/crypto/logic-image-id.js +27 -0
- package/dist/crypto/verifier.js +7 -19
- package/dist/economy/estimator.d.ts +53 -0
- package/dist/economy/estimator.js +69 -0
- package/dist/economy/index.d.ts +5 -0
- package/dist/economy/index.js +3 -0
- package/dist/economy/otel.d.ts +38 -0
- package/dist/economy/otel.js +100 -0
- package/dist/economy/telemetry.d.ts +77 -0
- package/dist/economy/telemetry.js +224 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +19 -0
- package/dist/gateway/hybrid.d.ts +3 -1
- package/dist/gateway/hybrid.js +38 -13
- package/dist/gateway/router.d.ts +25 -9
- package/dist/gateway/router.js +484 -133
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mesh/node.d.ts +16 -0
- package/dist/mesh/node.js +394 -113
- package/dist/prompts/adapters.d.ts +16 -0
- package/dist/prompts/adapters.js +55 -0
- package/dist/rpc/proto.js +2 -1
- package/dist/rpc/server.d.ts +1 -1
- package/dist/rpc/server.js +4 -3
- package/dist/rpc/tls.js +3 -2
- package/dist/sandbox/wasi.d.ts +1 -1
- package/dist/sandbox/wasi.js +43 -3
- package/dist/security/guardian.js +3 -2
- package/dist/security/zk.d.ts +2 -3
- package/dist/security/zk.js +22 -9
- package/dist/server/index.d.ts +53 -4
- package/dist/server/index.js +362 -49
- package/dist/server/pii.d.ts +12 -0
- package/dist/server/pii.js +90 -0
- package/dist/types.d.ts +16 -0
- package/dist/utils/logger.d.ts +21 -0
- package/dist/utils/logger.js +70 -0
- package/dist/utils/mcpCompact.d.ts +11 -0
- package/dist/utils/mcpCompact.js +29 -0
- package/dist/workers/logic-execution.d.ts +1 -1
- package/dist/workers/logic-execution.js +38 -20
- package/dist/workers/zk-verifier.js +37 -33
- package/package.json +14 -2
package/dist/server/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import path from "node:path";
|
|
@@ -8,8 +9,17 @@ import { z } from "zod";
|
|
|
8
9
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
9
10
|
import { MeshNode } from "../mesh/node.js";
|
|
10
11
|
import { LiopRpcServer } from "../rpc/server.js";
|
|
11
|
-
import {
|
|
12
|
-
|
|
12
|
+
import { log } from "../utils/logger.js";
|
|
13
|
+
import { PII_PATTERNS, PII_PRESETS, PiiScanner } from "./pii.js";
|
|
14
|
+
export { PII_PATTERNS, PII_PRESETS, PiiScanner };
|
|
15
|
+
/**
|
|
16
|
+
* When enabled, `payload` tools that are not LIOP v1 envelopes are passed through to the
|
|
17
|
+
* registered handler unchanged (no worker extraction). Default off for strict protocol tests.
|
|
18
|
+
*/
|
|
19
|
+
function respectPlainToolPayload() {
|
|
20
|
+
const v = process.env.LIOP_RESPECT_PLAIN_TOOL_PAYLOAD?.toLowerCase().trim();
|
|
21
|
+
return v === "1" || v === "true" || v === "yes";
|
|
22
|
+
}
|
|
13
23
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
24
|
export class LiopServer {
|
|
15
25
|
serverInfo;
|
|
@@ -28,16 +38,179 @@ export class LiopServer {
|
|
|
28
38
|
workerPool;
|
|
29
39
|
meshNode = null;
|
|
30
40
|
rpcServer = null;
|
|
41
|
+
boundPort = null;
|
|
31
42
|
sessions = new Map();
|
|
32
|
-
|
|
43
|
+
// Compact envelope: @LIOP{target,name}\n<code>\n@END
|
|
44
|
+
static LIOP_COMPACT_REGEX = /@LIOP\{(?<target>[^,}]+)(?:,(?<name>[^}]*))?\}\n(?<logic>[\s\S]*?)\n@END/m;
|
|
33
45
|
extractLogic(payload) {
|
|
34
|
-
const
|
|
35
|
-
return
|
|
46
|
+
const compact = payload.match(LiopServer.LIOP_COMPACT_REGEX);
|
|
47
|
+
return compact?.groups?.logic ? compact.groups.logic.trim() : null;
|
|
48
|
+
}
|
|
49
|
+
parseUnknownJson(input) {
|
|
50
|
+
if (typeof input !== "string")
|
|
51
|
+
return input;
|
|
52
|
+
const trimmed = input.trim();
|
|
53
|
+
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
54
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(trimmed);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return input;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return input;
|
|
63
|
+
}
|
|
64
|
+
runPreflightPolicy(_toolName, logic, policy) {
|
|
65
|
+
if (!policy)
|
|
66
|
+
return null;
|
|
67
|
+
const compact = logic.replace(/\s+/g, " ");
|
|
68
|
+
if (policy.enforceAggregationFirst) {
|
|
69
|
+
const rowExtractionPatterns = [
|
|
70
|
+
/return\s+env\.records\b/i,
|
|
71
|
+
/return\s*\{[\s\S]*\b(accounts|patients|rows|records)\s*:\s*env\.records/i,
|
|
72
|
+
];
|
|
73
|
+
if (rowExtractionPatterns.some((p) => p.test(compact))) {
|
|
74
|
+
return "Preflight policy rejected: potential row-level export pattern detected.";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (policy.preflightDenyPatterns?.some((p) => p.test(compact))) {
|
|
78
|
+
return "Preflight policy rejected: custom deny pattern matched.";
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
validateOutputPolicy(toolName, output, policy) {
|
|
83
|
+
if (!policy)
|
|
84
|
+
return null;
|
|
85
|
+
const parsed = this.parseUnknownJson(output);
|
|
86
|
+
if (policy.outputSchema) {
|
|
87
|
+
const schemaResult = policy.outputSchema.safeParse(parsed);
|
|
88
|
+
if (!schemaResult.success) {
|
|
89
|
+
// Include a truncated preview of the rejected value so the LLM can self-correct
|
|
90
|
+
const preview = typeof parsed === "string"
|
|
91
|
+
? parsed.slice(0, 200)
|
|
92
|
+
: JSON.stringify(parsed).slice(0, 200);
|
|
93
|
+
return `[LIOP] Output schema violation for ${toolName}: ${schemaResult.error.issues
|
|
94
|
+
.map((i) => `${i.path.join(".") || "<root>"} ${i.message}`)
|
|
95
|
+
.join("; ")}. Rejected value: ${preview}. HINT: Use 'env.records' to access the dataset inside your logic.`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (policy.enforceAggregationFirst &&
|
|
99
|
+
this.violatesAggregationFirstPolicy(this.unwrapForAggregationPolicyScan(parsed), policy.enforceAggregationFirst)) {
|
|
100
|
+
return "Aggregation-First Policy Violation: row-level export blocked. HINT: Use .reduce() to produce a flat {key:value} object. Do NOT use .map() to create arrays of objects.";
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Proxied tools stringify a full MCP CallToolResult (`{ content: [...] }`).
|
|
106
|
+
* Aggregation-first heuristics must scan the inner business JSON, not the MCP envelope
|
|
107
|
+
* (otherwise `content` looks like a tabular array of objects and everything blocks).
|
|
108
|
+
*/
|
|
109
|
+
unwrapForAggregationPolicyScan(input) {
|
|
110
|
+
if (typeof input === "string") {
|
|
111
|
+
const trimmed = input.trim();
|
|
112
|
+
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
113
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
114
|
+
try {
|
|
115
|
+
return this.unwrapForAggregationPolicyScan(JSON.parse(trimmed));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return input;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return input;
|
|
122
|
+
}
|
|
123
|
+
if (!input || typeof input !== "object") {
|
|
124
|
+
return input;
|
|
125
|
+
}
|
|
126
|
+
const rec = input;
|
|
127
|
+
if (!Array.isArray(rec.content) || rec.content.length === 0) {
|
|
128
|
+
return input;
|
|
129
|
+
}
|
|
130
|
+
const texts = [];
|
|
131
|
+
for (const part of rec.content) {
|
|
132
|
+
if (part && typeof part === "object" && "text" in part) {
|
|
133
|
+
const t = part.text;
|
|
134
|
+
if (typeof t === "string") {
|
|
135
|
+
texts.push(t);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (texts.length === 0) {
|
|
140
|
+
return input;
|
|
141
|
+
}
|
|
142
|
+
const joined = texts.length === 1 ? texts[0] : texts.join("\n");
|
|
143
|
+
return this.unwrapForAggregationPolicyScan(joined);
|
|
144
|
+
}
|
|
145
|
+
violatesAggregationFirstPolicy(input, policyObj) {
|
|
146
|
+
if (typeof input === "string") {
|
|
147
|
+
const trimmed = input.trim();
|
|
148
|
+
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
149
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
150
|
+
try {
|
|
151
|
+
return this.violatesAggregationFirstPolicy(JSON.parse(trimmed), policyObj);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
if (Array.isArray(input)) {
|
|
160
|
+
const maxRows = typeof policyObj === "object" &&
|
|
161
|
+
typeof policyObj.maxOutputRows === "number"
|
|
162
|
+
? policyObj.maxOutputRows
|
|
163
|
+
: 10;
|
|
164
|
+
const allowPrimitives = typeof policyObj === "object" &&
|
|
165
|
+
typeof policyObj.allowPrimitiveArrays === "boolean"
|
|
166
|
+
? policyObj.allowPrimitiveArrays
|
|
167
|
+
: true;
|
|
168
|
+
if (input.length > 0 &&
|
|
169
|
+
input.every((item) => typeof item === "object" && item !== null)) {
|
|
170
|
+
// Treat tabular row export as non-aggregated leakage risk if above threshold.
|
|
171
|
+
if (input.length > maxRows) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
return input.some((item) => this.violatesAggregationFirstPolicy(item, policyObj));
|
|
175
|
+
}
|
|
176
|
+
if (input.length > 0 &&
|
|
177
|
+
input.every((item) => typeof item !== "object" || item === null)) {
|
|
178
|
+
if (!allowPrimitives)
|
|
179
|
+
return true;
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return input.some((item) => this.violatesAggregationFirstPolicy(item, policyObj));
|
|
183
|
+
}
|
|
184
|
+
if (input && typeof input === "object") {
|
|
185
|
+
return Object.values(input).some((value) => this.violatesAggregationFirstPolicy(value, policyObj));
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
36
188
|
}
|
|
37
189
|
constructor(serverInfo, config) {
|
|
38
190
|
this.serverInfo = serverInfo;
|
|
39
191
|
this.config = config;
|
|
40
|
-
this.piiScanner = new PiiScanner(this.config?.security?.piiPatterns
|
|
192
|
+
this.piiScanner = new PiiScanner(this.config?.security?.piiPatterns ?? PII_PRESETS.GLOBAL_STRICT, this.config?.security?.forbiddenKeys ?? [
|
|
193
|
+
"id",
|
|
194
|
+
"name",
|
|
195
|
+
"fullName",
|
|
196
|
+
"firstName",
|
|
197
|
+
"lastName",
|
|
198
|
+
"address",
|
|
199
|
+
"street",
|
|
200
|
+
"city",
|
|
201
|
+
"postalCode",
|
|
202
|
+
"zipCode",
|
|
203
|
+
"phone",
|
|
204
|
+
"email",
|
|
205
|
+
"ssn",
|
|
206
|
+
"accountHolder",
|
|
207
|
+
"accountNumber",
|
|
208
|
+
"account_number",
|
|
209
|
+
"password",
|
|
210
|
+
"token",
|
|
211
|
+
"secret",
|
|
212
|
+
"privateKey",
|
|
213
|
+
]);
|
|
41
214
|
// Initialize Zero-Blocking Worker Pool for Heavy Cryptography & Sandboxing
|
|
42
215
|
const isTS = import.meta.url.endsWith(".ts");
|
|
43
216
|
const workerExt = isTS ? ".ts" : ".js";
|
|
@@ -67,6 +240,86 @@ export class LiopServer {
|
|
|
67
240
|
taskQueue: new FixedQueue(),
|
|
68
241
|
execArgv,
|
|
69
242
|
});
|
|
243
|
+
// [Token Economy] Auto-register LIOP protocol spec as a single Resource.
|
|
244
|
+
// This centralizes the envelope documentation that was previously
|
|
245
|
+
// duplicated in every tool description, reducing token overhead.
|
|
246
|
+
this.resource("LIOP Envelope Specification", "liop://protocol/envelope-spec", "Complete Logic-on-Origin envelope format, execution rules, and security constraints", "text/plain", () => Promise.resolve(this.buildEnvelopeSpec()));
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Builds the centralized LIOP envelope specification document.
|
|
250
|
+
* Served as a single Resource (liop://protocol/envelope-spec) instead
|
|
251
|
+
* of being duplicated across every tool description.
|
|
252
|
+
*/
|
|
253
|
+
buildEnvelopeSpec() {
|
|
254
|
+
const lines = [
|
|
255
|
+
"LIOP v1 Envelope Specification",
|
|
256
|
+
"================================",
|
|
257
|
+
"",
|
|
258
|
+
"FORMAT:",
|
|
259
|
+
"",
|
|
260
|
+
"Compact Envelope:",
|
|
261
|
+
" @LIOP{wasi_v1,TaskName}",
|
|
262
|
+
" <JavaScript code>",
|
|
263
|
+
" @END",
|
|
264
|
+
"",
|
|
265
|
+
"RUNTIME ENVIRONMENT:",
|
|
266
|
+
"- env.records: Array of data objects from the origin",
|
|
267
|
+
"- Must use 'return' to output results",
|
|
268
|
+
"- Zero-Trust WASI Sandbox (Node.js Worker Pool)",
|
|
269
|
+
"- Return aggregated objects, NOT raw row-level arrays",
|
|
270
|
+
"",
|
|
271
|
+
"SECURITY CONSTRAINTS:",
|
|
272
|
+
"- PII Egress Shield blocks raw identifiers in output",
|
|
273
|
+
"- Aggregation-First policy: prefer counts, averages, summaries",
|
|
274
|
+
"- AST Guardian: static analysis before execution",
|
|
275
|
+
];
|
|
276
|
+
if (this.config?.security?.forbiddenKeys?.length) {
|
|
277
|
+
lines.push(`- Restricted fields: ${this.config.security.forbiddenKeys.join(", ")}`);
|
|
278
|
+
}
|
|
279
|
+
lines.push("", "OPTIONAL PARAMETERS:", "- __liop_bypass_ast_cache: boolean (force AST re-evaluation)");
|
|
280
|
+
return lines.join("\n");
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Extracts a compact, human-readable field summary from a JSON Schema.
|
|
284
|
+
*
|
|
285
|
+
* Walks the schema structure to find actual data property names and types,
|
|
286
|
+
* rather than returning top-level schema metadata keys (type, items, etc.).
|
|
287
|
+
*
|
|
288
|
+
* Example output for a banking schema:
|
|
289
|
+
* "Array of {id(string), accountHolder(string), balance(number), transactions(array of {date(string), amount(number)})}"
|
|
290
|
+
*/
|
|
291
|
+
extractSchemaFieldSummary(schema, depth = 0) {
|
|
292
|
+
// Prevent excessive recursion in deeply nested schemas
|
|
293
|
+
if (depth > 3)
|
|
294
|
+
return "{...}";
|
|
295
|
+
const schemaType = schema.type;
|
|
296
|
+
const properties = schema.properties;
|
|
297
|
+
const items = schema.items;
|
|
298
|
+
// Object with properties → list field names with their types
|
|
299
|
+
if (properties) {
|
|
300
|
+
const fields = Object.entries(properties).map(([key, prop]) => {
|
|
301
|
+
const propType = prop.type;
|
|
302
|
+
if (propType === "array" && prop.items) {
|
|
303
|
+
const nested = this.extractSchemaFieldSummary(prop.items, depth + 1);
|
|
304
|
+
return `${key}(array of ${nested})`;
|
|
305
|
+
}
|
|
306
|
+
if (propType === "object" && prop.properties) {
|
|
307
|
+
const nested = this.extractSchemaFieldSummary(prop, depth + 1);
|
|
308
|
+
return `${key}(${nested})`;
|
|
309
|
+
}
|
|
310
|
+
return `${key}(${propType || "unknown"})`;
|
|
311
|
+
});
|
|
312
|
+
return `{${fields.join(", ")}}`;
|
|
313
|
+
}
|
|
314
|
+
// Array type → describe the items structure
|
|
315
|
+
if (schemaType === "array" && items) {
|
|
316
|
+
const itemsSummary = this.extractSchemaFieldSummary(items, depth + 1);
|
|
317
|
+
return `Array of ${itemsSummary}`;
|
|
318
|
+
}
|
|
319
|
+
// Simple type or unknown structure → fallback to key listing
|
|
320
|
+
if (schemaType)
|
|
321
|
+
return schemaType;
|
|
322
|
+
return Object.keys(schema).join(", ");
|
|
70
323
|
}
|
|
71
324
|
/**
|
|
72
325
|
* Convenience alias for connectToMesh(), matching official documentation.
|
|
@@ -77,7 +330,7 @@ export class LiopServer {
|
|
|
77
330
|
/**
|
|
78
331
|
* Register a new Tool
|
|
79
332
|
*/
|
|
80
|
-
tool(name, description, shape, handler) {
|
|
333
|
+
tool(name, description, shape, handler, policy) {
|
|
81
334
|
if (this.tools.has(name)) {
|
|
82
335
|
throw new Error(`Tool already registered: ${name}`);
|
|
83
336
|
}
|
|
@@ -88,14 +341,21 @@ export class LiopServer {
|
|
|
88
341
|
// LIOP Zero-Shot Autonomy Middleware: Detect Logic-on-Origin tools
|
|
89
342
|
if (shape.payload && shape.payload instanceof z.ZodString) {
|
|
90
343
|
const blockedKeys = this.config?.security?.forbiddenKeys || [];
|
|
91
|
-
|
|
344
|
+
// [Token Economy] Centralized description: reference the protocol spec
|
|
345
|
+
// Resource instead of duplicating the full envelope format per tool.
|
|
346
|
+
// Same information, delivered once via liop://protocol/envelope-spec.
|
|
347
|
+
finalDescription +=
|
|
348
|
+
"\n\nPayload: LIOP v1 envelope (WASI sandbox)." +
|
|
349
|
+
" Format: @LIOP{wasi_v1,TaskName}\\n<JS code>\\n@END" +
|
|
350
|
+
" | Access data: env.records. Return aggregated object." +
|
|
351
|
+
" | Full spec: resource liop://protocol/envelope-spec";
|
|
92
352
|
if (blockedKeys.length > 0) {
|
|
93
|
-
finalDescription += `\
|
|
353
|
+
finalDescription += `\nRestricted fields: ${blockedKeys.join(", ")}.`;
|
|
94
354
|
}
|
|
95
355
|
if (this.activeSchema) {
|
|
96
|
-
|
|
356
|
+
const schemaDigest = this.extractSchemaFieldSummary(this.activeSchema);
|
|
357
|
+
finalDescription += `\nData structure: ${schemaDigest}. Full schema: resource liop://schema/global`;
|
|
97
358
|
}
|
|
98
|
-
finalDescription += `\n\nOptional: You can include an "__liop_bypass_ast_cache" boolean parameter set to true to force AST re-evaluation.`;
|
|
99
359
|
finalHandler = async (args, _extra) => {
|
|
100
360
|
const clientId = "global_connection"; // Simplify for now, treating the instance as one connection
|
|
101
361
|
const now = Date.now();
|
|
@@ -131,10 +391,20 @@ export class LiopServer {
|
|
|
131
391
|
if (logic) {
|
|
132
392
|
args.payload = logic;
|
|
133
393
|
// DELEGATE TO WORKER POOL: Parallel PQC & Sandboxing
|
|
134
|
-
|
|
394
|
+
const preflightReason = this.runPreflightPolicy(name, logic, policy);
|
|
395
|
+
if (preflightReason) {
|
|
396
|
+
return {
|
|
397
|
+
content: [{ type: "text", text: preflightReason }],
|
|
398
|
+
isError: true,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
return await this.executeInWorkerPool(args, logic, name);
|
|
135
402
|
}
|
|
136
403
|
}
|
|
137
404
|
if (!logic) {
|
|
405
|
+
if (respectPlainToolPayload()) {
|
|
406
|
+
return await handler(args, _extra);
|
|
407
|
+
}
|
|
138
408
|
stats.failures++;
|
|
139
409
|
stats.lastAttempt = now;
|
|
140
410
|
this.connectionStats.set(clientId, stats);
|
|
@@ -142,7 +412,7 @@ export class LiopServer {
|
|
|
142
412
|
content: [
|
|
143
413
|
{
|
|
144
414
|
type: "text",
|
|
145
|
-
text:
|
|
415
|
+
text: "Error: Malformed payload. Missing @LIOP boundary.\\nYou MUST wrap your logic exactly like this:\\n\\n@LIOP{wasi_v1,DynamicAudit}\\n// Your JS code here\\n@END",
|
|
146
416
|
},
|
|
147
417
|
],
|
|
148
418
|
isError: true,
|
|
@@ -155,7 +425,17 @@ export class LiopServer {
|
|
|
155
425
|
// Extract pure logic and deliver it to the developer's function
|
|
156
426
|
args.payload = logic;
|
|
157
427
|
// DELEGATE TO WORKER POOL: Parallel PQC & Sandboxing (Includes PII Shield)
|
|
158
|
-
const
|
|
428
|
+
const preflightReason = this.runPreflightPolicy(name, logic, policy);
|
|
429
|
+
if (preflightReason) {
|
|
430
|
+
stats.failures++;
|
|
431
|
+
stats.lastAttempt = now;
|
|
432
|
+
this.connectionStats.set(clientId, stats);
|
|
433
|
+
return {
|
|
434
|
+
content: [{ type: "text", text: preflightReason }],
|
|
435
|
+
isError: true,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
const result = await this.executeInWorkerPool(args, logic, name);
|
|
159
439
|
if (!result.isError) {
|
|
160
440
|
this.connectionStats.set(clientId, {
|
|
161
441
|
failures: 0,
|
|
@@ -196,11 +476,12 @@ export class LiopServer {
|
|
|
196
476
|
tool: { name, description: finalDescription, inputSchema },
|
|
197
477
|
handler: finalHandler,
|
|
198
478
|
schema,
|
|
479
|
+
policy,
|
|
199
480
|
});
|
|
200
481
|
// [LIOP-ALPHA] Auto-announce capability to the Mesh P2P DHT if node is active
|
|
201
482
|
if (this.meshNode) {
|
|
202
483
|
this.meshNode.announceCapability(name).catch((err) => {
|
|
203
|
-
|
|
484
|
+
log.info(`[LIOP-Mesh] Failed to auto-announce tool ${name}: ${err.message}`);
|
|
204
485
|
});
|
|
205
486
|
}
|
|
206
487
|
}
|
|
@@ -234,13 +515,11 @@ Your objective is to perform secure Logic-on-Origin injections. You must process
|
|
|
234
515
|
INDUSTRIAL CONSTRAINTS & PROTOCOL RULES:
|
|
235
516
|
1. DATA PRIVACY: NEVER attempt to export Personally Identifiable Information (PII). The LIOP Egress Shield will block any response containing raw IDs, names, or addresses.
|
|
236
517
|
2. AGGREGATION FIRST: Always prefer returning counts, averages, or anonymized summaries.
|
|
237
|
-
3. PAYLOAD ENCAPSULATION: Your JavaScript payloads MUST strictly adhere to the
|
|
518
|
+
3. PAYLOAD ENCAPSULATION: Your JavaScript payloads MUST strictly adhere to the Compact Envelope. DO NOT include markdown backticks or leading text inside the 'payload' argument.
|
|
238
519
|
Structure:
|
|
239
|
-
|
|
240
|
-
MANIFEST:{"target":"wasi_v1","name":"AnalysisTask","integrity_checks":true}
|
|
241
|
-
---BEGIN_LOGIC---
|
|
520
|
+
@LIOP{wasi_v1,AnalysisTask}
|
|
242
521
|
// Your JS Code Here
|
|
243
|
-
|
|
522
|
+
@END
|
|
244
523
|
4. RUNTIME SCOPE: The execution environment provides a global 'env' object. Use 'env.records' to access the target dataset.
|
|
245
524
|
5. LOCALIZATION: Format all JSON response keys in the language used by the user in their query (e.g., use Spanish keys if the query is in Spanish).
|
|
246
525
|
6. SCHEMA RIGIDITY: Only use fields defined in the 'Data Dictionary'. Usage of non-existent fields will trigger a sandbox runtime exception.${this.activeSchema
|
|
@@ -268,13 +547,15 @@ Protocol Adherence is mandatory for successful execution.`,
|
|
|
268
547
|
*/
|
|
269
548
|
dataDictionary(schema, name = "Global Medical Data Dictionary", uri = "liop://schema/global", description = "Exposes the internal database schema for Zero-Shot Autonomy planning") {
|
|
270
549
|
this.activeSchema = schema;
|
|
271
|
-
// Retroactively update tool descriptions
|
|
550
|
+
// [Token Economy] Retroactively update tool descriptions with schema field references.
|
|
551
|
+
// Extracts actual data property names from the JSON Schema structure.
|
|
552
|
+
const schemaDigest = this.extractSchemaFieldSummary(schema);
|
|
272
553
|
for (const [toolName, entry] of this.tools.entries()) {
|
|
273
554
|
if (entry.schema.shape.payload &&
|
|
274
555
|
entry.schema.shape.payload instanceof z.ZodString &&
|
|
275
556
|
entry.tool.description &&
|
|
276
|
-
!entry.tool.description.includes("
|
|
277
|
-
entry.tool.description += `\
|
|
557
|
+
!entry.tool.description.includes("Data structure:")) {
|
|
558
|
+
entry.tool.description += `\nData structure: ${schemaDigest}. Full schema: resource ${uri}`;
|
|
278
559
|
this.tools.set(toolName, entry);
|
|
279
560
|
}
|
|
280
561
|
}
|
|
@@ -285,7 +566,7 @@ Protocol Adherence is mandatory for successful execution.`,
|
|
|
285
566
|
*/
|
|
286
567
|
clearAstCache() {
|
|
287
568
|
this.logicCache.clear();
|
|
288
|
-
|
|
569
|
+
log.info("[LIOP-SDK] AST Security Cache cleared by Admin.");
|
|
289
570
|
}
|
|
290
571
|
/**
|
|
291
572
|
* Emulates calling a tool (used locally or via LIOPMcpBridge)
|
|
@@ -310,8 +591,15 @@ Protocol Adherence is mandatory for successful execution.`,
|
|
|
310
591
|
.payload;
|
|
311
592
|
const logic = this.extractLogic(payload);
|
|
312
593
|
if (logic) {
|
|
594
|
+
const preflightReason = this.runPreflightPolicy(request.name, logic, entry.policy);
|
|
595
|
+
if (preflightReason) {
|
|
596
|
+
return {
|
|
597
|
+
content: [{ type: "text", text: preflightReason }],
|
|
598
|
+
isError: true,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
313
601
|
parsedArgs.payload = logic;
|
|
314
|
-
return await this.executeInWorkerPool(parsedArgs, logic);
|
|
602
|
+
return await this.executeInWorkerPool(parsedArgs, logic, request.name);
|
|
315
603
|
}
|
|
316
604
|
}
|
|
317
605
|
const result = await entry.handler(parsedArgs, {});
|
|
@@ -401,12 +689,18 @@ Protocol Adherence is mandatory for successful execution.`,
|
|
|
401
689
|
setSandboxData(records) {
|
|
402
690
|
this.sandboxRecords = records;
|
|
403
691
|
}
|
|
692
|
+
getBoundPort() {
|
|
693
|
+
return this.boundPort;
|
|
694
|
+
}
|
|
404
695
|
/**
|
|
405
696
|
* Connects to the libp2p Kademlia DHT and announces capabilities.
|
|
406
697
|
* Boots the gRPC server for secure Logic-on-Origin.
|
|
407
698
|
*/
|
|
408
699
|
async connectToMesh(options = {}) {
|
|
409
|
-
const
|
|
700
|
+
const envPort = process.env.LIOP_GRPC_PORT
|
|
701
|
+
? Number.parseInt(process.env.LIOP_GRPC_PORT, 10)
|
|
702
|
+
: undefined;
|
|
703
|
+
const port = options.port ?? envPort ?? 50051;
|
|
410
704
|
// 1. Initialize Mesh Node (Discovery)
|
|
411
705
|
this.meshNode = new MeshNode(options.meshConfig);
|
|
412
706
|
await this.meshNode.start();
|
|
@@ -436,16 +730,16 @@ Protocol Adherence is mandatory for successful execution.`,
|
|
|
436
730
|
});
|
|
437
731
|
// 3. Announce local tools to the DHT
|
|
438
732
|
for (const tool of this.listTools()) {
|
|
439
|
-
await this.meshNode.announceCapability(tool.name).catch(
|
|
733
|
+
await this.meshNode.announceCapability(tool.name).catch(log.info);
|
|
440
734
|
}
|
|
441
735
|
// 4. Announce manifest availability
|
|
442
|
-
await this.meshNode.announceManifest().catch(
|
|
736
|
+
await this.meshNode.announceManifest().catch(log.info);
|
|
443
737
|
// 5. Initialize gRPC Server (Execution)
|
|
444
738
|
this.rpcServer = new LiopRpcServer();
|
|
445
739
|
this.rpcServer.addService({
|
|
446
740
|
negotiateIntent: (call, callback) => {
|
|
447
741
|
const request = call.request;
|
|
448
|
-
|
|
742
|
+
log.info(`[LIOP-RPC] Negotiating intent for capability: ${request.capability_hash}`);
|
|
449
743
|
// Standard dynamic import to avoid potential circularity
|
|
450
744
|
import("../rpc/crypto/kyber.js").then(async ({ Kyber768Wrapper }) => {
|
|
451
745
|
const { publicKey, secretKey } = await Kyber768Wrapper.generateKeyPair();
|
|
@@ -464,7 +758,7 @@ Protocol Adherence is mandatory for successful execution.`,
|
|
|
464
758
|
},
|
|
465
759
|
executeLogic: async (call) => {
|
|
466
760
|
const request = call.request;
|
|
467
|
-
|
|
761
|
+
log.info(`[LIOP-RPC] Executing Logic-on-Origin for session: ${request.session_token}`);
|
|
468
762
|
const session = this.sessions.get(request.session_token);
|
|
469
763
|
if (!session) {
|
|
470
764
|
call.emit("error", {
|
|
@@ -485,13 +779,16 @@ Protocol Adherence is mandatory for successful execution.`,
|
|
|
485
779
|
sessionToken: request.session_token,
|
|
486
780
|
isEncrypted: true,
|
|
487
781
|
});
|
|
488
|
-
let finalOutput
|
|
489
|
-
// [PROTOCOL TRANSFORMER] Support for Proxied Tool Calls
|
|
490
|
-
// If the execution resulted in a special proxy command, handle it
|
|
782
|
+
let finalOutput;
|
|
491
783
|
try {
|
|
784
|
+
finalOutput =
|
|
785
|
+
typeof workerResponse.output === "string"
|
|
786
|
+
? workerResponse.output
|
|
787
|
+
: JSON.stringify(workerResponse.output);
|
|
788
|
+
// [PROTOCOL TRANSFORMER] Support for Proxied Tool Calls
|
|
492
789
|
const decoded = JSON.parse(finalOutput);
|
|
493
790
|
if (decoded.__liop_proxy_tool) {
|
|
494
|
-
|
|
791
|
+
log.info(`[LIOP-RPC] Executing Proxied Tool: ${decoded.__liop_proxy_tool}`);
|
|
495
792
|
const toolResult = await this.callTool({
|
|
496
793
|
name: decoded.__liop_proxy_tool,
|
|
497
794
|
arguments: decoded.__liop_proxy_args || {},
|
|
@@ -500,24 +797,26 @@ Protocol Adherence is mandatory for successful execution.`,
|
|
|
500
797
|
}
|
|
501
798
|
}
|
|
502
799
|
catch {
|
|
503
|
-
|
|
800
|
+
finalOutput = String(workerResponse.output);
|
|
504
801
|
}
|
|
505
802
|
const response = {
|
|
506
803
|
semantic_evidence: finalOutput,
|
|
507
804
|
cryptographic_proof: Buffer.from(workerResponse.image_id || "", "hex"),
|
|
508
|
-
zk_receipt:
|
|
509
|
-
.
|
|
510
|
-
.
|
|
511
|
-
.digest(), // Deterministic Beta Seal
|
|
805
|
+
zk_receipt: workerResponse.zk_receipt
|
|
806
|
+
? Buffer.from(workerResponse.zk_receipt, "base64")
|
|
807
|
+
: Buffer.from(""),
|
|
512
808
|
is_error: false,
|
|
513
809
|
};
|
|
514
810
|
// Final PII check for gRPC egress
|
|
515
811
|
const violation = this.piiScanner.scan([
|
|
516
812
|
{ type: "text", text: finalOutput },
|
|
517
813
|
]);
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
814
|
+
const aggregationViolation = this.violatesAggregationFirstPolicy(this.unwrapForAggregationPolicyScan(finalOutput));
|
|
815
|
+
if (violation || aggregationViolation) {
|
|
816
|
+
const reason = violation ||
|
|
817
|
+
"Aggregation-First Policy Violation: row-level export blocked. HINT: Use .reduce() to produce a flat {key:value} object. Do NOT use .map() to create arrays of objects.";
|
|
818
|
+
log.info(`[LIOP-RPC] Secure egress blocked in gRPC stream: ${reason}`);
|
|
819
|
+
response.semantic_evidence = `[LIOP] Egress Security Violation. Output blocked due to policy enforcement (${reason}).`;
|
|
521
820
|
response.is_error = true;
|
|
522
821
|
}
|
|
523
822
|
call.write(response, () => {
|
|
@@ -526,7 +825,7 @@ Protocol Adherence is mandatory for successful execution.`,
|
|
|
526
825
|
}
|
|
527
826
|
catch (error) {
|
|
528
827
|
const e = error;
|
|
529
|
-
|
|
828
|
+
log.error(`[LIOP-RPC] Execution Error: ${e.message}`);
|
|
530
829
|
// Send error response before closing, avoiding "stream closed without results"
|
|
531
830
|
const errorResponse = {
|
|
532
831
|
semantic_evidence: `Execution Error: ${e.message}`,
|
|
@@ -545,13 +844,13 @@ Protocol Adherence is mandatory for successful execution.`,
|
|
|
545
844
|
}
|
|
546
845
|
},
|
|
547
846
|
});
|
|
548
|
-
await this.rpcServer.listen(port);
|
|
549
|
-
|
|
847
|
+
this.boundPort = await this.rpcServer.listen(port);
|
|
848
|
+
log.info(`[LIOP-SDK] Node successfully announced to Mesh. PeerID: ${this.meshNode.getPeerId()}`);
|
|
550
849
|
}
|
|
551
850
|
/**
|
|
552
851
|
* Internal worker execution with Egress Filtering logic.
|
|
553
852
|
*/
|
|
554
|
-
async executeInWorkerPool(_args, rawPayload) {
|
|
853
|
+
async executeInWorkerPool(_args, rawPayload, toolName) {
|
|
555
854
|
try {
|
|
556
855
|
// Transparent local execution without dynamic PQC
|
|
557
856
|
const workerResponse = await this.workerPool.run({
|
|
@@ -577,15 +876,29 @@ Protocol Adherence is mandatory for successful execution.`,
|
|
|
577
876
|
text: textOutput,
|
|
578
877
|
},
|
|
579
878
|
];
|
|
879
|
+
const toolPolicy = toolName
|
|
880
|
+
? this.tools.get(toolName)?.policy
|
|
881
|
+
: undefined;
|
|
882
|
+
const policyViolation = this.validateOutputPolicy(toolName || "unknown_tool", workerResponse.output, toolPolicy);
|
|
883
|
+
if (policyViolation) {
|
|
884
|
+
log.info(`[LIOP-SDK] Output policy blocked for ${toolName || "unknown_tool"}: ${policyViolation}`);
|
|
885
|
+
return {
|
|
886
|
+
content: [{ type: "text", text: `[LIOP] ${policyViolation}` }],
|
|
887
|
+
isError: true,
|
|
888
|
+
};
|
|
889
|
+
}
|
|
580
890
|
// Professional PII Protection Guard
|
|
581
891
|
const violation = this.piiScanner.scan(content);
|
|
582
|
-
|
|
583
|
-
|
|
892
|
+
const aggregationViolation = this.violatesAggregationFirstPolicy(workerResponse.output);
|
|
893
|
+
if (violation || aggregationViolation) {
|
|
894
|
+
const reason = violation ||
|
|
895
|
+
"Aggregation-First Policy Violation: row-level export blocked. HINT: Use .reduce() to produce a flat {key:value} object. Do NOT use .map() to create arrays of objects.";
|
|
896
|
+
log.info(`[LIOP-SDK] Secure egress blocked in local execution: ${reason}`);
|
|
584
897
|
return {
|
|
585
898
|
content: [
|
|
586
899
|
{
|
|
587
900
|
type: "text",
|
|
588
|
-
text: `[LIOP] Egress Security Violation. Output blocked due to
|
|
901
|
+
text: `[LIOP] Egress Security Violation. Output blocked due to policy enforcement (${reason}).`,
|
|
589
902
|
},
|
|
590
903
|
],
|
|
591
904
|
isError: true,
|
package/dist/server/pii.d.ts
CHANGED
|
@@ -14,6 +14,18 @@ export declare const PII_PATTERNS: {
|
|
|
14
14
|
CREDIT_CARD: PiiRuleDefinition;
|
|
15
15
|
IP_ADDRESS: PiiRuleDefinition;
|
|
16
16
|
PHONE: PiiRuleDefinition;
|
|
17
|
+
SSN: PiiRuleDefinition;
|
|
18
|
+
IBAN: PiiRuleDefinition;
|
|
19
|
+
PASSPORT_MRZ: PiiRuleDefinition;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Regional and Cultural Security Presets for Out-Of-The-Box compliance.
|
|
23
|
+
* Developers can override, merge, or omit these based on local laws.
|
|
24
|
+
*/
|
|
25
|
+
export declare const PII_PRESETS: {
|
|
26
|
+
GLOBAL_STRICT: PiiRuleDefinition[];
|
|
27
|
+
US_COMPLIANT: PiiRuleDefinition[];
|
|
28
|
+
EU_GDPR: PiiRuleDefinition[];
|
|
17
29
|
};
|
|
18
30
|
export declare class PiiScanner {
|
|
19
31
|
private patterns;
|