@rotifer/playground 0.31.0 → 0.33.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/CHANGELOG.md +40 -0
- package/README.md +2 -2
- package/README.zh.md +2 -2
- package/dist/cloud/client.d.ts.map +1 -1
- package/dist/cloud/client.js +9 -0
- package/dist/cloud/client.js.map +1 -1
- package/dist/cloud/types.d.ts +7 -0
- package/dist/cloud/types.d.ts.map +1 -1
- package/dist/cloud/types.js.map +1 -1
- package/dist/commands/agent-create.d.ts +11 -0
- package/dist/commands/agent-create.d.ts.map +1 -1
- package/dist/commands/agent-create.js +50 -1
- package/dist/commands/agent-create.js.map +1 -1
- package/dist/commands/agent-memory.d.ts +64 -0
- package/dist/commands/agent-memory.d.ts.map +1 -0
- package/dist/commands/agent-memory.js +235 -0
- package/dist/commands/agent-memory.js.map +1 -0
- package/dist/commands/agent-run.d.ts.map +1 -1
- package/dist/commands/agent-run.js +84 -26
- package/dist/commands/agent-run.js.map +1 -1
- package/dist/commands/arena-submit.d.ts.map +1 -1
- package/dist/commands/arena-submit.js +34 -36
- package/dist/commands/arena-submit.js.map +1 -1
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +32 -6
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/genome-format.d.ts +5 -1
- package/dist/commands/genome-format.d.ts.map +1 -1
- package/dist/commands/genome-format.js +5 -1
- package/dist/commands/genome-format.js.map +1 -1
- package/dist/commands/hello.d.ts.map +1 -1
- package/dist/commands/hello.js +15 -2
- package/dist/commands/hello.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +59 -4
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +78 -23
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/wrap.d.ts.map +1 -1
- package/dist/commands/wrap.js +28 -70
- package/dist/commands/wrap.js.map +1 -1
- package/dist/index.js +47 -1
- package/dist/index.js.map +1 -1
- package/dist/memory/access.d.ts +53 -0
- package/dist/memory/access.d.ts.map +1 -0
- package/dist/memory/access.js +111 -0
- package/dist/memory/access.js.map +1 -0
- package/dist/memory/audit.d.ts +26 -0
- package/dist/memory/audit.d.ts.map +1 -0
- package/dist/memory/audit.js +30 -0
- package/dist/memory/audit.js.map +1 -0
- package/dist/memory/backend.d.ts +141 -0
- package/dist/memory/backend.d.ts.map +1 -0
- package/dist/memory/backend.js +536 -0
- package/dist/memory/backend.js.map +1 -0
- package/dist/memory/codec.d.ts +34 -0
- package/dist/memory/codec.d.ts.map +1 -0
- package/dist/memory/codec.js +184 -0
- package/dist/memory/codec.js.map +1 -0
- package/dist/memory/data-policy.d.ts +39 -0
- package/dist/memory/data-policy.d.ts.map +1 -0
- package/dist/memory/data-policy.js +42 -0
- package/dist/memory/data-policy.js.map +1 -0
- package/dist/memory/host.d.ts +119 -0
- package/dist/memory/host.d.ts.map +1 -0
- package/dist/memory/host.js +226 -0
- package/dist/memory/host.js.map +1 -0
- package/dist/memory/index.d.ts +37 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +64 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/telemetry.d.ts +35 -0
- package/dist/memory/telemetry.d.ts.map +1 -0
- package/dist/memory/telemetry.js +51 -0
- package/dist/memory/telemetry.js.map +1 -0
- package/dist/memory/types.d.ts +110 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +74 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/publish/auto-publish.d.ts +1 -1
- package/dist/publish/auto-publish.js +1 -1
- package/dist/runtime/network-gateway.d.ts +24 -4
- package/dist/runtime/network-gateway.d.ts.map +1 -1
- package/dist/runtime/network-gateway.js +86 -12
- package/dist/runtime/network-gateway.js.map +1 -1
- package/dist/testsuite/gate.d.ts +8 -0
- package/dist/testsuite/gate.d.ts.map +1 -1
- package/dist/testsuite/gate.js +13 -3
- package/dist/testsuite/gate.js.map +1 -1
- package/dist/testsuite/run.d.ts +6 -11
- package/dist/testsuite/run.d.ts.map +1 -1
- package/dist/testsuite/run.js +29 -4
- package/dist/testsuite/run.js.map +1 -1
- package/dist/utils/arena-success.d.ts +10 -0
- package/dist/utils/arena-success.d.ts.map +1 -1
- package/dist/utils/arena-success.js +35 -8
- package/dist/utils/arena-success.js.map +1 -1
- package/dist/utils/binding.d.ts +6 -2
- package/dist/utils/binding.d.ts.map +1 -1
- package/dist/utils/binding.js.map +1 -1
- package/dist/utils/clawhub-client.d.ts +73 -0
- package/dist/utils/clawhub-client.d.ts.map +1 -0
- package/dist/utils/clawhub-client.js +176 -0
- package/dist/utils/clawhub-client.js.map +1 -0
- package/dist/utils/config.d.ts +7 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +15 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/fitness-formula.d.ts +20 -4
- package/dist/utils/fitness-formula.d.ts.map +1 -1
- package/dist/utils/fitness-formula.js +24 -6
- package/dist/utils/fitness-formula.js.map +1 -1
- package/dist/utils/gene-id-display.d.ts +14 -0
- package/dist/utils/gene-id-display.d.ts.map +1 -0
- package/dist/utils/gene-id-display.js +64 -0
- package/dist/utils/gene-id-display.js.map +1 -0
- package/dist/utils/l0-gate.d.ts.map +1 -1
- package/dist/utils/l0-gate.js +4 -1
- package/dist/utils/l0-gate.js.map +1 -1
- package/dist/utils/rust-phenotype.d.ts +17 -5
- package/dist/utils/rust-phenotype.d.ts.map +1 -1
- package/dist/utils/rust-phenotype.js +25 -4
- package/dist/utils/rust-phenotype.js.map +1 -1
- package/package.json +10 -5
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentMemory SDK (spec §38, v0.9.4) — what a gene author uses: the types,
|
|
3
|
+
* the GeneMemoryAccess interface a gene gets as ctx.memory, MemoryError and
|
|
4
|
+
* its codes, and the SDK layer over a store. Published as
|
|
5
|
+
* `@rotifer/playground/memory`; nothing here needs Node's types.
|
|
6
|
+
*
|
|
7
|
+
* The CLI's side — opening the agent's store in the project, the WASM
|
|
8
|
+
* path's binding, the throwaway memory of `rotifer test` — is in ./host.ts,
|
|
9
|
+
* which is not part of that surface.
|
|
10
|
+
*/
|
|
11
|
+
import type { GeneMemoryAccess } from "./access.js";
|
|
12
|
+
import { type MemoryTelemetrySink } from "./telemetry.js";
|
|
13
|
+
export * from "./types.js";
|
|
14
|
+
export * from "./codec.js";
|
|
15
|
+
export * from "./access.js";
|
|
16
|
+
export * from "./data-policy.js";
|
|
17
|
+
export * from "./telemetry.js";
|
|
18
|
+
export * from "./audit.js";
|
|
19
|
+
export { NapiMemoryBackend, asMemoryError, hasNativeMemory, type AgentMemoryConfig, type MemoryBackend, type MemoryCaller, type MemoryWriteRequest, type NativeMemoryApi, } from "./backend.js";
|
|
20
|
+
/**
|
|
21
|
+
* The memory a gene gets when it runs with no agent around it (`rotifer run`
|
|
22
|
+
* of a single gene): every call is MEMORY_ACCESS_DENIED with a message that
|
|
23
|
+
* starts "no agent context". `ctx.memory` is never undefined,
|
|
24
|
+
* so a gene meets a clear error, not a TypeError on `undefined`.
|
|
25
|
+
*
|
|
26
|
+
* With a `telemetry` sink each refusal is also a §17 MEMORY_ACCESS_DENIED
|
|
27
|
+
* event — agentId null, no entry, `geneId` when the gene has a v2 id — and
|
|
28
|
+
* nothing else: there is no agent's memory to audit it in (as
|
|
29
|
+
* rotifer-core records a WASM gene's).
|
|
30
|
+
*/
|
|
31
|
+
export declare function noAgentMemory(options?: {
|
|
32
|
+
telemetry?: MemoryTelemetrySink;
|
|
33
|
+
geneId?: string | null;
|
|
34
|
+
}): GeneMemoryAccess;
|
|
35
|
+
/** Every call is MEMORY_STORAGE_ERROR "native binding unavailable". */
|
|
36
|
+
export declare function memoryUnavailable(): GeneMemoryAccess;
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/memory/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAA2C,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGnG,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAG3B,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAYtB;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,GAAG,gBAAgB,CAQzH;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,IAAI,gBAAgB,CAEpD"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.hasNativeMemory = exports.asMemoryError = exports.NapiMemoryBackend = void 0;
|
|
18
|
+
exports.noAgentMemory = noAgentMemory;
|
|
19
|
+
exports.memoryUnavailable = memoryUnavailable;
|
|
20
|
+
const telemetry_js_1 = require("./telemetry.js");
|
|
21
|
+
const types_js_1 = require("./types.js");
|
|
22
|
+
__exportStar(require("./types.js"), exports);
|
|
23
|
+
__exportStar(require("./codec.js"), exports);
|
|
24
|
+
__exportStar(require("./access.js"), exports);
|
|
25
|
+
__exportStar(require("./data-policy.js"), exports);
|
|
26
|
+
__exportStar(require("./telemetry.js"), exports);
|
|
27
|
+
__exportStar(require("./audit.js"), exports);
|
|
28
|
+
// The store interface and the napi adapter; the JSON test double stays in
|
|
29
|
+
// ./backend.ts for tests and is not part of this module's surface.
|
|
30
|
+
var backend_js_1 = require("./backend.js");
|
|
31
|
+
Object.defineProperty(exports, "NapiMemoryBackend", { enumerable: true, get: function () { return backend_js_1.NapiMemoryBackend; } });
|
|
32
|
+
Object.defineProperty(exports, "asMemoryError", { enumerable: true, get: function () { return backend_js_1.asMemoryError; } });
|
|
33
|
+
Object.defineProperty(exports, "hasNativeMemory", { enumerable: true, get: function () { return backend_js_1.hasNativeMemory; } });
|
|
34
|
+
/** A GeneMemoryAccess whose every call throws `code` with `message`. */
|
|
35
|
+
function refusingMemory(code, message) {
|
|
36
|
+
const refuse = () => {
|
|
37
|
+
throw new types_js_1.MemoryError(code, message);
|
|
38
|
+
};
|
|
39
|
+
return { readMemory: refuse, writeMemory: refuse, queryMemory: refuse, deleteMemory: refuse };
|
|
40
|
+
}
|
|
41
|
+
const NO_AGENT_CONTEXT = "no agent context: this gene is not running inside an agent, so it has no agent memory";
|
|
42
|
+
/**
|
|
43
|
+
* The memory a gene gets when it runs with no agent around it (`rotifer run`
|
|
44
|
+
* of a single gene): every call is MEMORY_ACCESS_DENIED with a message that
|
|
45
|
+
* starts "no agent context". `ctx.memory` is never undefined,
|
|
46
|
+
* so a gene meets a clear error, not a TypeError on `undefined`.
|
|
47
|
+
*
|
|
48
|
+
* With a `telemetry` sink each refusal is also a §17 MEMORY_ACCESS_DENIED
|
|
49
|
+
* event — agentId null, no entry, `geneId` when the gene has a v2 id — and
|
|
50
|
+
* nothing else: there is no agent's memory to audit it in (as
|
|
51
|
+
* rotifer-core records a WASM gene's).
|
|
52
|
+
*/
|
|
53
|
+
function noAgentMemory(options = {}) {
|
|
54
|
+
const refuse = (operation) => () => {
|
|
55
|
+
options.telemetry?.((0, telemetry_js_1.accessDeniedEvent)({ agentId: null, geneId: options.geneId ?? null, entryId: null, operation, message: NO_AGENT_CONTEXT }));
|
|
56
|
+
throw new types_js_1.MemoryError("MEMORY_ACCESS_DENIED", NO_AGENT_CONTEXT);
|
|
57
|
+
};
|
|
58
|
+
return { readMemory: refuse("read"), writeMemory: refuse("write"), queryMemory: refuse("query"), deleteMemory: refuse("delete") };
|
|
59
|
+
}
|
|
60
|
+
/** Every call is MEMORY_STORAGE_ERROR "native binding unavailable". */
|
|
61
|
+
function memoryUnavailable() {
|
|
62
|
+
return refusingMemory("MEMORY_STORAGE_ERROR", "native binding unavailable: agent memory needs the rotifer native addon on this platform");
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/memory/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAsDA,sCAQC;AAGD,8CAEC;AAxDD,iDAAmG;AACnG,yCAAyC;AAEzC,6CAA2B;AAC3B,6CAA2B;AAC3B,8CAA4B;AAC5B,mDAAiC;AACjC,iDAA+B;AAC/B,6CAA2B;AAC3B,0EAA0E;AAC1E,mEAAmE;AACnE,2CASsB;AARpB,+GAAA,iBAAiB,OAAA;AACjB,2GAAA,aAAa,OAAA;AACb,6GAAA,eAAe,OAAA;AAQjB,wEAAwE;AACxE,SAAS,cAAc,CAAC,IAAqD,EAAE,OAAe;IAC5F,MAAM,MAAM,GAAG,GAAU,EAAE;QACzB,MAAM,IAAI,sBAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC,CAAC;IACF,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;AAChG,CAAC;AAED,MAAM,gBAAgB,GAAG,uFAAuF,CAAC;AAEjH;;;;;;;;;;GAUG;AACH,SAAgB,aAAa,CAAC,UAAuE,EAAE;IACrG,MAAM,MAAM,GACV,CAAC,SAA0B,EAAE,EAAE,CAC/B,GAAU,EAAE;QACV,OAAO,CAAC,SAAS,EAAE,CAAC,IAAA,gCAAiB,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;QAC/I,MAAM,IAAI,sBAAW,CAAC,sBAAsB,EAAE,gBAAgB,CAAC,CAAC;IAClE,CAAC,CAAC;IACJ,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;AACpI,CAAC;AAED,uEAAuE;AACvE,SAAgB,iBAAiB;IAC/B,OAAO,cAAc,CAAC,sBAAsB,EAAE,0FAA0F,CAAC,CAAC;AAC5I,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** The operation names rotifer-core writes in metadata.operation. */
|
|
2
|
+
export type MemoryOperation = "read" | "write" | "query" | "delete";
|
|
3
|
+
export interface MemoryTelemetryEvent {
|
|
4
|
+
eventType: "MEMORY_ACCESS_DENIED";
|
|
5
|
+
bindingExtension: true;
|
|
6
|
+
traceContext: null;
|
|
7
|
+
/** null when the calling gene has no usable v2 id. */
|
|
8
|
+
geneId: string | null;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
duration: null;
|
|
11
|
+
resourceUsage: null;
|
|
12
|
+
result: {
|
|
13
|
+
FAILURE: {
|
|
14
|
+
errorCode: "MEMORY_ACCESS_DENIED";
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
/** agentId null outside any agent; entryId null when the call named no key. */
|
|
19
|
+
metadata: {
|
|
20
|
+
agentId: string | null;
|
|
21
|
+
entryId: string | null;
|
|
22
|
+
operation: MemoryOperation;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export type MemoryTelemetrySink = (event: MemoryTelemetryEvent) => void;
|
|
26
|
+
export declare function fileTelemetrySink(root: string, env?: Record<string, string | undefined>): MemoryTelemetrySink;
|
|
27
|
+
export declare function accessDeniedEvent(args: {
|
|
28
|
+
agentId: string | null;
|
|
29
|
+
geneId: string | null;
|
|
30
|
+
entryId: string | null;
|
|
31
|
+
operation: MemoryOperation;
|
|
32
|
+
message: string;
|
|
33
|
+
timestamp?: string;
|
|
34
|
+
}): MemoryTelemetryEvent;
|
|
35
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/memory/telemetry.ts"],"names":[],"mappings":"AAkBA,qEAAqE;AACrE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEpE,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,sBAAsB,CAAC;IAClC,gBAAgB,EAAE,IAAI,CAAC;IACvB,YAAY,EAAE,IAAI,CAAC;IACnB,sDAAsD;IACtD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,IAAI,CAAC;IACf,aAAa,EAAE,IAAI,CAAC;IACpB,MAAM,EAAE;QAAE,OAAO,EAAE;YAAE,SAAS,EAAE,sBAAsB,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IAC5E,+EAA+E;IAC/E,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,eAAe,CAAA;KAAE,CAAC;CAC1F;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAExE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,mBAAmB,CAa1H;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,eAAe,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,oBAAoB,CAYvB"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fileTelemetrySink = fileTelemetrySink;
|
|
4
|
+
exports.accessDeniedEvent = accessDeniedEvent;
|
|
5
|
+
/**
|
|
6
|
+
* §17 telemetry for AgentMemory access denials (spec §38.2: "权限违规记录到
|
|
7
|
+
* §17 遥测系统").
|
|
8
|
+
*
|
|
9
|
+
* One §17.3 TelemetryEvent per line in `<root>/.rotifer/telemetry.jsonl`, in
|
|
10
|
+
* the shape rotifer-core writes (memory/events.rs MemoryTelemetryEvent), so a
|
|
11
|
+
* reader sees one format whichever binding denied the call. §17.3 has no
|
|
12
|
+
* memory event type, so `MEMORY_ACCESS_DENIED` carries `bindingExtension`.
|
|
13
|
+
* Local only, and it honours the same opt-out as the rest of the CLI's
|
|
14
|
+
* telemetry: ROTIFER_TELEMETRY=0 (or DO_NOT_TRACK) writes nothing here
|
|
15
|
+
*. The audit line for the same denial is written
|
|
16
|
+
* regardless (audit.ts). No key and no value: the entryId identifies the
|
|
17
|
+
* entry.
|
|
18
|
+
*/
|
|
19
|
+
const node_fs_1 = require("node:fs");
|
|
20
|
+
const node_path_1 = require("node:path");
|
|
21
|
+
const consent_js_1 = require("../telemetry/consent.js");
|
|
22
|
+
function fileTelemetrySink(root, env = process.env) {
|
|
23
|
+
const path = (0, node_path_1.join)(root, ".rotifer", "telemetry.jsonl");
|
|
24
|
+
return (event) => {
|
|
25
|
+
if ((0, consent_js_1.telemetryOptedOutByEnv)(env))
|
|
26
|
+
return;
|
|
27
|
+
// Telemetry is best-effort: the denial it reports stands, and the audit
|
|
28
|
+
// line (which is not best-effort) already recorded it.
|
|
29
|
+
try {
|
|
30
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true, mode: 0o700 });
|
|
31
|
+
(0, node_fs_1.appendFileSync)(path, JSON.stringify(event) + "\n", { mode: 0o600 });
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
/* not a reason to fail the call */
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function accessDeniedEvent(args) {
|
|
39
|
+
return {
|
|
40
|
+
eventType: "MEMORY_ACCESS_DENIED",
|
|
41
|
+
bindingExtension: true,
|
|
42
|
+
traceContext: null,
|
|
43
|
+
geneId: args.geneId,
|
|
44
|
+
timestamp: args.timestamp ?? new Date().toISOString(),
|
|
45
|
+
duration: null,
|
|
46
|
+
resourceUsage: null,
|
|
47
|
+
result: { FAILURE: { errorCode: "MEMORY_ACCESS_DENIED", message: args.message } },
|
|
48
|
+
metadata: { agentId: args.agentId, entryId: args.entryId, operation: args.operation },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/memory/telemetry.ts"],"names":[],"mappings":";;AAqCA,8CAaC;AAED,8CAmBC;AAvED;;;;;;;;;;;;;GAaG;AACH,qCAAoD;AACpD,yCAA0C;AAC1C,wDAAiE;AAqBjE,SAAgB,iBAAiB,CAAC,IAAY,EAAE,MAA0C,OAAO,CAAC,GAAG;IACnG,MAAM,IAAI,GAAG,IAAA,gBAAI,EAAC,IAAI,EAAE,UAAU,EAAE,iBAAiB,CAAC,CAAC;IACvD,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,IAAI,IAAA,mCAAsB,EAAC,GAAG,CAAC;YAAE,OAAO;QACxC,wEAAwE;QACxE,uDAAuD;QACvD,IAAI,CAAC;YACH,IAAA,mBAAS,EAAC,IAAA,mBAAO,EAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC3D,IAAA,wBAAc,EAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;QACrC,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,SAAgB,iBAAiB,CAAC,IAOjC;IACC,OAAO;QACL,SAAS,EAAE,sBAAsB;QACjC,gBAAgB,EAAE,IAAI;QACtB,YAAY,EAAE,IAAI;QAClB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrD,QAAQ,EAAE,IAAI;QACd,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,sBAAsB,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE;QACjF,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE;KACtF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentMemory types — spec §38.1, shared by every binding.
|
|
3
|
+
*
|
|
4
|
+
* Field names are the spec's camelCase and enum values the spec's UPPERCASE
|
|
5
|
+
* strings, with no case folding anywhere: Rust, WASM host calls and the cloud
|
|
6
|
+
* table carry the same strings, and one lowercased copy is how the bindings
|
|
7
|
+
* drift apart.
|
|
8
|
+
*
|
|
9
|
+
* Two shapes of an entry:
|
|
10
|
+
* - MemoryEntry what a gene sees: `value` is bytes (Uint8Array);
|
|
11
|
+
* - MemoryEntryJson what crosses JSON (napi, host calls, cloud): `value`
|
|
12
|
+
* is standard base64, padded.
|
|
13
|
+
* toEntryJson / fromEntryJson are the only conversion between them.
|
|
14
|
+
*/
|
|
15
|
+
export declare const MEMORY_CATEGORIES: readonly ["PREFERENCE", "PATTERN", "CONTEXT", "CACHE"];
|
|
16
|
+
export type MemoryCategory = (typeof MEMORY_CATEGORIES)[number];
|
|
17
|
+
export declare const ACCESS_SCOPES: readonly ["CREATOR_GENE_ONLY", "SAME_DOMAIN", "ALL_GENES", "DEPLOYER_CONFIGURED"];
|
|
18
|
+
export type AccessScope = (typeof ACCESS_SCOPES)[number];
|
|
19
|
+
export declare const SHARE_POLICIES: readonly ["PRIVATE", "ANONYMIZED_SHARE", "KNOWLEDGE_GENE"];
|
|
20
|
+
export type SharePolicy = (typeof SHARE_POLICIES)[number];
|
|
21
|
+
export interface MemoryAccessPolicy {
|
|
22
|
+
readScope: AccessScope;
|
|
23
|
+
writeScope: AccessScope;
|
|
24
|
+
sharePolicy: SharePolicy;
|
|
25
|
+
}
|
|
26
|
+
/** What `writeMemory` uses when the caller gives no policy. */
|
|
27
|
+
export declare const DEFAULT_ACCESS_POLICY: Readonly<MemoryAccessPolicy>;
|
|
28
|
+
/** Spec §30.2 RetentionPolicy. */
|
|
29
|
+
export interface RetentionPolicy {
|
|
30
|
+
/** null = no limit. */
|
|
31
|
+
maxRetentionDays: number | null;
|
|
32
|
+
autoDeleteOnTermination: boolean;
|
|
33
|
+
anonymizationSupported: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface MemoryEntry {
|
|
36
|
+
/** Lowercase hex SHA-256 of `agentId + "\0" + key` (entryIdFor). */
|
|
37
|
+
entryId: string;
|
|
38
|
+
category: MemoryCategory;
|
|
39
|
+
key: string;
|
|
40
|
+
value: Uint8Array;
|
|
41
|
+
/** Content-derived gene id of the gene that created the entry. */
|
|
42
|
+
createdBy: string;
|
|
43
|
+
lastAccessedBy: string | null;
|
|
44
|
+
/** RFC 3339 UTC. */
|
|
45
|
+
createdAt: string;
|
|
46
|
+
/** RFC 3339 UTC. */
|
|
47
|
+
updatedAt: string;
|
|
48
|
+
accessPolicy: MemoryAccessPolicy;
|
|
49
|
+
/** null = inherit the agent's DataPolicy retention. */
|
|
50
|
+
retentionPolicy: RetentionPolicy | null;
|
|
51
|
+
}
|
|
52
|
+
export type MemoryEntryJson = Omit<MemoryEntry, "value"> & {
|
|
53
|
+
value: string;
|
|
54
|
+
};
|
|
55
|
+
export interface AgentMemory {
|
|
56
|
+
agentId: string;
|
|
57
|
+
entries: MemoryEntry[];
|
|
58
|
+
/** Bytes of key + value over all entries (the quota's unit). */
|
|
59
|
+
totalSize: number;
|
|
60
|
+
/** Bytes. */
|
|
61
|
+
quota: number;
|
|
62
|
+
createdAt: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* queryMemory's filter (final). Every field is optional
|
|
66
|
+
* and they combine with AND:
|
|
67
|
+
* - category one MemoryCategory;
|
|
68
|
+
* - keyPrefix a plain prefix of the key, not a pattern;
|
|
69
|
+
* - since RFC 3339, inclusive, compared with `updatedAt`;
|
|
70
|
+
* - until RFC 3339, exclusive, compared with `updatedAt`;
|
|
71
|
+
* - limit default 100, capped at 1000.
|
|
72
|
+
* Results come newest first (`updatedAt` descending, ties by `entryId`).
|
|
73
|
+
* Unknown fields are MEMORY_INVALID_INPUT; no alias names are accepted.
|
|
74
|
+
*/
|
|
75
|
+
export interface MemoryFilter {
|
|
76
|
+
category?: MemoryCategory;
|
|
77
|
+
keyPrefix?: string;
|
|
78
|
+
since?: string;
|
|
79
|
+
until?: string;
|
|
80
|
+
limit?: number;
|
|
81
|
+
}
|
|
82
|
+
export declare const MEMORY_FILTER_FIELDS: readonly ["category", "keyPrefix", "since", "until", "limit"];
|
|
83
|
+
export declare const MEMORY_QUERY_DEFAULT_LIMIT = 100;
|
|
84
|
+
export declare const MEMORY_QUERY_MAX_LIMIT = 1000;
|
|
85
|
+
/**
|
|
86
|
+
* Shared across bindings. MEMORY_STORAGE_ERROR is a
|
|
87
|
+
* storage / IO failure, a missing native binding, or a database error; it is
|
|
88
|
+
* never folded into MEMORY_INVALID_INPUT.
|
|
89
|
+
*/
|
|
90
|
+
export declare const MEMORY_ERROR_CODES: readonly ["MEMORY_NOT_FOUND", "MEMORY_ACCESS_DENIED", "MEMORY_QUOTA_EXCEEDED", "MEMORY_INVALID_INPUT", "MEMORY_STORAGE_ERROR"];
|
|
91
|
+
export type MemoryErrorCode = (typeof MEMORY_ERROR_CODES)[number];
|
|
92
|
+
/**
|
|
93
|
+
* A memory error. `code` is one of the five; `message` is the explanation
|
|
94
|
+
* alone, without the code in front (a no-agent-context message
|
|
95
|
+
* starts with "no agent context").
|
|
96
|
+
*/
|
|
97
|
+
export declare class MemoryError extends Error {
|
|
98
|
+
readonly code: MemoryErrorCode;
|
|
99
|
+
constructor(code: MemoryErrorCode, message: string);
|
|
100
|
+
}
|
|
101
|
+
/** A single value, after base64 decoding. */
|
|
102
|
+
export declare const MEMORY_MAX_VALUE_BYTES: number;
|
|
103
|
+
/** One queryMemory response, serialized. */
|
|
104
|
+
export declare const MEMORY_MAX_QUERY_RESPONSE_BYTES: number;
|
|
105
|
+
/** 10 MB of key + value. */
|
|
106
|
+
export declare const DEFAULT_MEMORY_QUOTA_BYTES: number;
|
|
107
|
+
export declare function isMemoryCategory(value: unknown): value is MemoryCategory;
|
|
108
|
+
export declare function isAccessScope(value: unknown): value is AccessScope;
|
|
109
|
+
export declare function isSharePolicy(value: unknown): value is SharePolicy;
|
|
110
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/memory/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AgentMemory types — spec §38.1, shared by every binding.
|
|
4
|
+
*
|
|
5
|
+
* Field names are the spec's camelCase and enum values the spec's UPPERCASE
|
|
6
|
+
* strings, with no case folding anywhere: Rust, WASM host calls and the cloud
|
|
7
|
+
* table carry the same strings, and one lowercased copy is how the bindings
|
|
8
|
+
* drift apart.
|
|
9
|
+
*
|
|
10
|
+
* Two shapes of an entry:
|
|
11
|
+
* - MemoryEntry what a gene sees: `value` is bytes (Uint8Array);
|
|
12
|
+
* - MemoryEntryJson what crosses JSON (napi, host calls, cloud): `value`
|
|
13
|
+
* is standard base64, padded.
|
|
14
|
+
* toEntryJson / fromEntryJson are the only conversion between them.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.DEFAULT_MEMORY_QUOTA_BYTES = exports.MEMORY_MAX_QUERY_RESPONSE_BYTES = exports.MEMORY_MAX_VALUE_BYTES = exports.MemoryError = exports.MEMORY_ERROR_CODES = exports.MEMORY_QUERY_MAX_LIMIT = exports.MEMORY_QUERY_DEFAULT_LIMIT = exports.MEMORY_FILTER_FIELDS = exports.DEFAULT_ACCESS_POLICY = exports.SHARE_POLICIES = exports.ACCESS_SCOPES = exports.MEMORY_CATEGORIES = void 0;
|
|
18
|
+
exports.isMemoryCategory = isMemoryCategory;
|
|
19
|
+
exports.isAccessScope = isAccessScope;
|
|
20
|
+
exports.isSharePolicy = isSharePolicy;
|
|
21
|
+
exports.MEMORY_CATEGORIES = ["PREFERENCE", "PATTERN", "CONTEXT", "CACHE"];
|
|
22
|
+
exports.ACCESS_SCOPES = ["CREATOR_GENE_ONLY", "SAME_DOMAIN", "ALL_GENES", "DEPLOYER_CONFIGURED"];
|
|
23
|
+
exports.SHARE_POLICIES = ["PRIVATE", "ANONYMIZED_SHARE", "KNOWLEDGE_GENE"];
|
|
24
|
+
/** What `writeMemory` uses when the caller gives no policy. */
|
|
25
|
+
exports.DEFAULT_ACCESS_POLICY = Object.freeze({
|
|
26
|
+
readScope: "SAME_DOMAIN",
|
|
27
|
+
writeScope: "SAME_DOMAIN",
|
|
28
|
+
sharePolicy: "PRIVATE",
|
|
29
|
+
});
|
|
30
|
+
exports.MEMORY_FILTER_FIELDS = ["category", "keyPrefix", "since", "until", "limit"];
|
|
31
|
+
exports.MEMORY_QUERY_DEFAULT_LIMIT = 100;
|
|
32
|
+
exports.MEMORY_QUERY_MAX_LIMIT = 1000;
|
|
33
|
+
/**
|
|
34
|
+
* Shared across bindings. MEMORY_STORAGE_ERROR is a
|
|
35
|
+
* storage / IO failure, a missing native binding, or a database error; it is
|
|
36
|
+
* never folded into MEMORY_INVALID_INPUT.
|
|
37
|
+
*/
|
|
38
|
+
exports.MEMORY_ERROR_CODES = [
|
|
39
|
+
"MEMORY_NOT_FOUND",
|
|
40
|
+
"MEMORY_ACCESS_DENIED",
|
|
41
|
+
"MEMORY_QUOTA_EXCEEDED",
|
|
42
|
+
"MEMORY_INVALID_INPUT",
|
|
43
|
+
"MEMORY_STORAGE_ERROR",
|
|
44
|
+
];
|
|
45
|
+
/**
|
|
46
|
+
* A memory error. `code` is one of the five; `message` is the explanation
|
|
47
|
+
* alone, without the code in front (a no-agent-context message
|
|
48
|
+
* starts with "no agent context").
|
|
49
|
+
*/
|
|
50
|
+
class MemoryError extends Error {
|
|
51
|
+
code;
|
|
52
|
+
constructor(code, message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = "MemoryError";
|
|
55
|
+
this.code = code;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.MemoryError = MemoryError;
|
|
59
|
+
/** A single value, after base64 decoding. */
|
|
60
|
+
exports.MEMORY_MAX_VALUE_BYTES = 1024 * 1024;
|
|
61
|
+
/** One queryMemory response, serialized. */
|
|
62
|
+
exports.MEMORY_MAX_QUERY_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
63
|
+
/** 10 MB of key + value. */
|
|
64
|
+
exports.DEFAULT_MEMORY_QUOTA_BYTES = 10 * 1024 * 1024;
|
|
65
|
+
function isMemoryCategory(value) {
|
|
66
|
+
return typeof value === "string" && exports.MEMORY_CATEGORIES.includes(value);
|
|
67
|
+
}
|
|
68
|
+
function isAccessScope(value) {
|
|
69
|
+
return typeof value === "string" && exports.ACCESS_SCOPES.includes(value);
|
|
70
|
+
}
|
|
71
|
+
function isSharePolicy(value) {
|
|
72
|
+
return typeof value === "string" && exports.SHARE_POLICIES.includes(value);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/memory/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAyHH,4CAEC;AAED,sCAEC;AAED,sCAEC;AAjIY,QAAA,iBAAiB,GAAG,CAAC,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,CAAU,CAAC;AAG3E,QAAA,aAAa,GAAG,CAAC,mBAAmB,EAAE,aAAa,EAAE,WAAW,EAAE,qBAAqB,CAAU,CAAC;AAGlG,QAAA,cAAc,GAAG,CAAC,SAAS,EAAE,kBAAkB,EAAE,gBAAgB,CAAU,CAAC;AASzF,+DAA+D;AAClD,QAAA,qBAAqB,GAAiC,MAAM,CAAC,MAAM,CAAC;IAC/E,SAAS,EAAE,aAAa;IACxB,UAAU,EAAE,aAAa;IACzB,WAAW,EAAE,SAAS;CACvB,CAAC,CAAC;AA2DU,QAAA,oBAAoB,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAU,CAAC;AACrF,QAAA,0BAA0B,GAAG,GAAG,CAAC;AACjC,QAAA,sBAAsB,GAAG,IAAI,CAAC;AAE3C;;;;GAIG;AACU,QAAA,kBAAkB,GAAG;IAChC,kBAAkB;IAClB,sBAAsB;IACtB,uBAAuB;IACvB,sBAAsB;IACtB,sBAAsB;CACd,CAAC;AAGX;;;;GAIG;AACH,MAAa,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAkB;IAC/B,YAAY,IAAqB,EAAE,OAAe;QAChD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAPD,kCAOC;AAED,6CAA6C;AAChC,QAAA,sBAAsB,GAAG,IAAI,GAAG,IAAI,CAAC;AAClD,4CAA4C;AAC/B,QAAA,+BAA+B,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/D,4BAA4B;AACf,QAAA,0BAA0B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3D,SAAgB,gBAAgB,CAAC,KAAc;IAC7C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,yBAAuC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC/F,CAAC;AAED,SAAgB,aAAa,CAAC,KAAc;IAC1C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,qBAAmC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC3F,CAAC;AAED,SAAgB,aAAa,CAAC,KAAc;IAC1C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,sBAAoC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC5F,CAAC"}
|
|
@@ -90,7 +90,7 @@ export declare function needsNetworkConfigBeforePublish(geneDir: string, fidelit
|
|
|
90
90
|
* independent cursor-agent pyramid test run, 2026-08-31, which is also why
|
|
91
91
|
* this is a pure function rather than a PTY-driven E2E test: reproducing it
|
|
92
92
|
* needs a real TTY, and this repo's CI matrix runs both ubuntu-latest and
|
|
93
|
-
*
|
|
93
|
+
* macOS, whose `script` binaries take incompatible arguments — a
|
|
94
94
|
* behavioural test for a one-line data-selection bug was the wrong layer.
|
|
95
95
|
*
|
|
96
96
|
* The on-disk phenotype wins whenever one was actually loaded; the CLI flag
|
|
@@ -111,7 +111,7 @@ function needsNetworkConfigBeforePublish(geneDir, fidelity) {
|
|
|
111
111
|
* independent cursor-agent pyramid test run, 2026-08-31, which is also why
|
|
112
112
|
* this is a pure function rather than a PTY-driven E2E test: reproducing it
|
|
113
113
|
* needs a real TTY, and this repo's CI matrix runs both ubuntu-latest and
|
|
114
|
-
*
|
|
114
|
+
* macOS, whose `script` binaries take incompatible arguments — a
|
|
115
115
|
* behavioural test for a one-line data-selection bug was the wrong layer.
|
|
116
116
|
*
|
|
117
117
|
* The on-disk phenotype wins whenever one was actually loaded; the CLI flag
|
|
@@ -4,7 +4,14 @@
|
|
|
4
4
|
* Enforces domain whitelist, per-request timeout, response size cap,
|
|
5
5
|
* and per-minute rate limiting. Genes call `gatewayFetch()` instead
|
|
6
6
|
* of raw `fetch()`.
|
|
7
|
+
*
|
|
8
|
+
* SSRF hardening, the same as the WASM path (rotifer-core hybrid.rs
|
|
9
|
+
* `check_url` / `perform_fetch`, Hybrid Binding API §2.1): https only, no IP
|
|
10
|
+
* literals, and redirects followed by hand, at most MAX_REDIRECT_HOPS, each
|
|
11
|
+
* hop checked against the whitelist before it is requested.
|
|
7
12
|
*/
|
|
13
|
+
/** Same as rotifer-core `constants::MAX_REDIRECT_HOPS`. */
|
|
14
|
+
export declare const MAX_REDIRECT_HOPS = 5;
|
|
8
15
|
export interface NetworkConfig {
|
|
9
16
|
allowedDomains: string[];
|
|
10
17
|
maxTimeoutMs: number;
|
|
@@ -23,22 +30,35 @@ export interface GatewayResponse {
|
|
|
23
30
|
isTruncated: boolean;
|
|
24
31
|
}
|
|
25
32
|
export declare class NetworkGatewayError extends Error {
|
|
26
|
-
readonly code: "DOMAIN_BLOCKED" | "RATE_LIMITED" | "TIMEOUT" | "RESPONSE_TOO_LARGE" | "NETWORK_ERROR";
|
|
27
|
-
constructor(code: "DOMAIN_BLOCKED" | "RATE_LIMITED" | "TIMEOUT" | "RESPONSE_TOO_LARGE" | "NETWORK_ERROR", message: string);
|
|
33
|
+
readonly code: "DOMAIN_BLOCKED" | "INVALID_REQUEST" | "RATE_LIMITED" | "TIMEOUT" | "RESPONSE_TOO_LARGE" | "NETWORK_ERROR";
|
|
34
|
+
constructor(code: "DOMAIN_BLOCKED" | "INVALID_REQUEST" | "RATE_LIMITED" | "TIMEOUT" | "RESPONSE_TOO_LARGE" | "NETWORK_ERROR", message: string);
|
|
35
|
+
}
|
|
36
|
+
/** Binding-internal options, never read from a phenotype. */
|
|
37
|
+
export interface GatewayTestOptions {
|
|
38
|
+
/** Test hook, as rotifer-core's `allow_insecure_loopback`: permit plain
|
|
39
|
+
* http to loopback hosts and loopback IP literals. Off in every production
|
|
40
|
+
* path — `createGatewayFetch`, which the CLI uses, never sets it. */
|
|
41
|
+
allowInsecureLoopbackForTests?: boolean;
|
|
28
42
|
}
|
|
29
43
|
export declare class NetworkGateway {
|
|
30
44
|
private config;
|
|
45
|
+
private allowInsecureLoopback;
|
|
31
46
|
private requestTimestamps;
|
|
32
47
|
private totalRequests;
|
|
33
48
|
private totalBytes;
|
|
34
|
-
constructor(config?: Partial<NetworkConfig
|
|
49
|
+
constructor(config?: Partial<NetworkConfig>, testOptions?: GatewayTestOptions);
|
|
35
50
|
get stats(): {
|
|
36
51
|
totalRequests: number;
|
|
37
52
|
totalBytes: number;
|
|
38
53
|
windowRequests: number;
|
|
39
54
|
};
|
|
40
55
|
fetch(url: string, options?: GatewayFetchOptions): Promise<GatewayResponse>;
|
|
41
|
-
|
|
56
|
+
/** Whitelist first, then scheme and host shape. Runs for the first request
|
|
57
|
+
* and for every redirect hop. rotifer-core's `check_url` checks shape first;
|
|
58
|
+
* here a URL off the whitelist keeps the DOMAIN_BLOCKED callers have always
|
|
59
|
+
* had, and INVALID_REQUEST is what a whitelisted host gets over http or as
|
|
60
|
+
* an IP literal. Either way nothing is sent. */
|
|
61
|
+
private validateUrl;
|
|
42
62
|
private enforceRateLimit;
|
|
43
63
|
private readBodyCapped;
|
|
44
64
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network-gateway.d.ts","sourceRoot":"","sources":["../../src/runtime/network-gateway.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"network-gateway.d.ts","sourceRoot":"","sources":["../../src/runtime/network-gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,IAAI,EAChB,gBAAgB,GAChB,iBAAiB,GACjB,cAAc,GACd,SAAS,GACT,oBAAoB,GACpB,eAAe;gBANH,IAAI,EAChB,gBAAgB,GAChB,iBAAiB,GACjB,cAAc,GACd,SAAS,GACT,oBAAoB,GACpB,eAAe,EACnB,OAAO,EAAE,MAAM;CAKlB;AAkBD,6DAA6D;AAC7D,MAAM,WAAW,kBAAkB;IACjC;;yEAEqE;IACrE,6BAA6B,CAAC,EAAE,OAAO,CAAC;CACzC;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,qBAAqB,CAAU;IACvC,OAAO,CAAC,iBAAiB,CAAgB;IACzC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,UAAU,CAAK;gBAEX,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,EAAE,WAAW,GAAE,kBAAuB;IAKjF,IAAI,KAAK;;;;MAMR;IAEK,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IA0FjF;;;;oDAIgD;IAChD,OAAO,CAAC,WAAW;IA6CnB,OAAO,CAAC,gBAAgB;YAiBV,cAAc;CAmC7B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAC7B;IACD,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACvF,OAAO,EAAE,cAAc,CAAC;CACzB,CAMA"}
|
|
@@ -5,10 +5,17 @@
|
|
|
5
5
|
* Enforces domain whitelist, per-request timeout, response size cap,
|
|
6
6
|
* and per-minute rate limiting. Genes call `gatewayFetch()` instead
|
|
7
7
|
* of raw `fetch()`.
|
|
8
|
+
*
|
|
9
|
+
* SSRF hardening, the same as the WASM path (rotifer-core hybrid.rs
|
|
10
|
+
* `check_url` / `perform_fetch`, Hybrid Binding API §2.1): https only, no IP
|
|
11
|
+
* literals, and redirects followed by hand, at most MAX_REDIRECT_HOPS, each
|
|
12
|
+
* hop checked against the whitelist before it is requested.
|
|
8
13
|
*/
|
|
9
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.NetworkGateway = exports.NetworkGatewayError = void 0;
|
|
15
|
+
exports.NetworkGateway = exports.NetworkGatewayError = exports.MAX_REDIRECT_HOPS = void 0;
|
|
11
16
|
exports.createGatewayFetch = createGatewayFetch;
|
|
17
|
+
/** Same as rotifer-core `constants::MAX_REDIRECT_HOPS`. */
|
|
18
|
+
exports.MAX_REDIRECT_HOPS = 5;
|
|
12
19
|
class NetworkGatewayError extends Error {
|
|
13
20
|
code;
|
|
14
21
|
constructor(code, message) {
|
|
@@ -24,13 +31,23 @@ const DEFAULT_CONFIG = {
|
|
|
24
31
|
maxResponseBytes: 1_048_576, // 1 MiB
|
|
25
32
|
maxRequestsPerMin: 10,
|
|
26
33
|
};
|
|
34
|
+
/** Headers that describe a request body (fetch's "request-body-header names"),
|
|
35
|
+
* dropped when a redirect turns the request into a body-less GET. */
|
|
36
|
+
const BODY_HEADERS = new Set(["content-encoding", "content-language", "content-location", "content-type", "content-length"]);
|
|
37
|
+
function withoutBodyHeaders(headers) {
|
|
38
|
+
if (!headers)
|
|
39
|
+
return headers;
|
|
40
|
+
return Object.fromEntries(Object.entries(headers).filter(([k]) => !BODY_HEADERS.has(k.toLowerCase())));
|
|
41
|
+
}
|
|
27
42
|
class NetworkGateway {
|
|
28
43
|
config;
|
|
44
|
+
allowInsecureLoopback;
|
|
29
45
|
requestTimestamps = [];
|
|
30
46
|
totalRequests = 0;
|
|
31
47
|
totalBytes = 0;
|
|
32
|
-
constructor(config) {
|
|
48
|
+
constructor(config, testOptions = {}) {
|
|
33
49
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
50
|
+
this.allowInsecureLoopback = testOptions.allowInsecureLoopbackForTests === true;
|
|
34
51
|
}
|
|
35
52
|
get stats() {
|
|
36
53
|
return {
|
|
@@ -40,17 +57,57 @@ class NetworkGateway {
|
|
|
40
57
|
};
|
|
41
58
|
}
|
|
42
59
|
async fetch(url, options) {
|
|
43
|
-
this.
|
|
60
|
+
this.validateUrl(url);
|
|
44
61
|
this.enforceRateLimit();
|
|
45
62
|
const controller = new AbortController();
|
|
46
63
|
const timeout = setTimeout(() => controller.abort(), this.config.maxTimeoutMs);
|
|
47
64
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
// Redirects are followed here, not by fetch: fetch would follow a
|
|
66
|
+
// whitelisted host's 302 to anywhere. Every hop is checked before it
|
|
67
|
+
// is requested; the timeout covers the whole chain.
|
|
68
|
+
//
|
|
69
|
+
// What a hop carries follows fetch's own redirect rules, made stricter:
|
|
70
|
+
// - 301/302/303 turn a request that is not GET/HEAD into a GET with no
|
|
71
|
+
// body (and no body headers); 307/308 keep the method and body.
|
|
72
|
+
// - Moving to another origin (scheme, host or port) drops every header
|
|
73
|
+
// the gene set, not only Authorization: a key travels just as well in
|
|
74
|
+
// X-Api-Key or apikey, and the next host is someone else even when
|
|
75
|
+
// it matches the same *.domain wildcard.
|
|
76
|
+
let current = url;
|
|
77
|
+
let method = options?.method || "POST";
|
|
78
|
+
let requestHeaders = options?.headers;
|
|
79
|
+
let body = options?.body;
|
|
80
|
+
let response;
|
|
81
|
+
for (let hop = 0; hop <= exports.MAX_REDIRECT_HOPS; hop++) {
|
|
82
|
+
if (hop > 0)
|
|
83
|
+
this.validateUrl(current);
|
|
84
|
+
response = await fetch(current, {
|
|
85
|
+
method,
|
|
86
|
+
headers: requestHeaders,
|
|
87
|
+
body,
|
|
88
|
+
signal: controller.signal,
|
|
89
|
+
redirect: "manual",
|
|
90
|
+
});
|
|
91
|
+
const location = response.headers.get("location");
|
|
92
|
+
if (response.status < 300 || response.status >= 400 || !location)
|
|
93
|
+
break;
|
|
94
|
+
if (hop === exports.MAX_REDIRECT_HOPS) {
|
|
95
|
+
await response.body?.cancel();
|
|
96
|
+
throw new NetworkGatewayError("NETWORK_ERROR", `Request to ${url} failed: more than ${exports.MAX_REDIRECT_HOPS} redirects`);
|
|
97
|
+
}
|
|
98
|
+
await response.body?.cancel();
|
|
99
|
+
const next = new URL(location, current);
|
|
100
|
+
if ([301, 302, 303].includes(response.status) && method !== "GET" && method !== "HEAD") {
|
|
101
|
+
method = "GET";
|
|
102
|
+
body = undefined;
|
|
103
|
+
requestHeaders = withoutBodyHeaders(requestHeaders);
|
|
104
|
+
}
|
|
105
|
+
if (next.origin !== new URL(current).origin)
|
|
106
|
+
requestHeaders = undefined;
|
|
107
|
+
current = next.toString();
|
|
108
|
+
}
|
|
109
|
+
if (!response)
|
|
110
|
+
throw new Error("no response");
|
|
54
111
|
const { text, isTruncated } = await this.readBodyCapped(response);
|
|
55
112
|
this.totalRequests++;
|
|
56
113
|
this.totalBytes += text.length;
|
|
@@ -66,6 +123,8 @@ class NetworkGateway {
|
|
|
66
123
|
};
|
|
67
124
|
}
|
|
68
125
|
catch (err) {
|
|
126
|
+
if (err instanceof NetworkGatewayError)
|
|
127
|
+
throw err;
|
|
69
128
|
if (err.name === "AbortError") {
|
|
70
129
|
throw new NetworkGatewayError("TIMEOUT", `Request to ${url} timed out after ${this.config.maxTimeoutMs}ms`);
|
|
71
130
|
}
|
|
@@ -75,14 +134,20 @@ class NetworkGateway {
|
|
|
75
134
|
clearTimeout(timeout);
|
|
76
135
|
}
|
|
77
136
|
}
|
|
78
|
-
|
|
79
|
-
|
|
137
|
+
/** Whitelist first, then scheme and host shape. Runs for the first request
|
|
138
|
+
* and for every redirect hop. rotifer-core's `check_url` checks shape first;
|
|
139
|
+
* here a URL off the whitelist keeps the DOMAIN_BLOCKED callers have always
|
|
140
|
+
* had, and INVALID_REQUEST is what a whitelisted host gets over http or as
|
|
141
|
+
* an IP literal. Either way nothing is sent. */
|
|
142
|
+
validateUrl(url) {
|
|
143
|
+
let parsed;
|
|
80
144
|
try {
|
|
81
|
-
|
|
145
|
+
parsed = new URL(url);
|
|
82
146
|
}
|
|
83
147
|
catch {
|
|
84
148
|
throw new NetworkGatewayError("DOMAIN_BLOCKED", `Invalid URL: ${url}`);
|
|
85
149
|
}
|
|
150
|
+
const hostname = parsed.hostname;
|
|
86
151
|
const isAllowed = this.config.allowedDomains.some((d) => {
|
|
87
152
|
if (d.startsWith("*.")) {
|
|
88
153
|
return hostname.endsWith(d.slice(1)) || hostname === d.slice(2);
|
|
@@ -92,6 +157,15 @@ class NetworkGateway {
|
|
|
92
157
|
if (!isAllowed) {
|
|
93
158
|
throw new NetworkGatewayError("DOMAIN_BLOCKED", `Domain "${hostname}" is not in the allowed list: [${this.config.allowedDomains.join(", ")}]`);
|
|
94
159
|
}
|
|
160
|
+
const isIpLiteral = /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) || hostname.startsWith("[");
|
|
161
|
+
const isLoopback = hostname === "localhost" || hostname === "[::1]" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname);
|
|
162
|
+
const isLoopbackAdmitted = this.allowInsecureLoopback && isLoopback;
|
|
163
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && isLoopbackAdmitted)) {
|
|
164
|
+
throw new NetworkGatewayError("INVALID_REQUEST", `Only https is allowed: ${parsed.protocol}//${hostname}`);
|
|
165
|
+
}
|
|
166
|
+
if (isIpLiteral && !isLoopbackAdmitted) {
|
|
167
|
+
throw new NetworkGatewayError("INVALID_REQUEST", `IP addresses are not allowed, use a host name: ${hostname}`);
|
|
168
|
+
}
|
|
95
169
|
}
|
|
96
170
|
enforceRateLimit() {
|
|
97
171
|
const now = Date.now();
|