@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.
Files changed (125) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +2 -2
  3. package/README.zh.md +2 -2
  4. package/dist/cloud/client.d.ts.map +1 -1
  5. package/dist/cloud/client.js +9 -0
  6. package/dist/cloud/client.js.map +1 -1
  7. package/dist/cloud/types.d.ts +7 -0
  8. package/dist/cloud/types.d.ts.map +1 -1
  9. package/dist/cloud/types.js.map +1 -1
  10. package/dist/commands/agent-create.d.ts +11 -0
  11. package/dist/commands/agent-create.d.ts.map +1 -1
  12. package/dist/commands/agent-create.js +50 -1
  13. package/dist/commands/agent-create.js.map +1 -1
  14. package/dist/commands/agent-memory.d.ts +64 -0
  15. package/dist/commands/agent-memory.d.ts.map +1 -0
  16. package/dist/commands/agent-memory.js +235 -0
  17. package/dist/commands/agent-memory.js.map +1 -0
  18. package/dist/commands/agent-run.d.ts.map +1 -1
  19. package/dist/commands/agent-run.js +84 -26
  20. package/dist/commands/agent-run.js.map +1 -1
  21. package/dist/commands/arena-submit.d.ts.map +1 -1
  22. package/dist/commands/arena-submit.js +34 -36
  23. package/dist/commands/arena-submit.js.map +1 -1
  24. package/dist/commands/compile.d.ts.map +1 -1
  25. package/dist/commands/compile.js +32 -6
  26. package/dist/commands/compile.js.map +1 -1
  27. package/dist/commands/genome-format.d.ts +5 -1
  28. package/dist/commands/genome-format.d.ts.map +1 -1
  29. package/dist/commands/genome-format.js +5 -1
  30. package/dist/commands/genome-format.js.map +1 -1
  31. package/dist/commands/hello.d.ts.map +1 -1
  32. package/dist/commands/hello.js +15 -2
  33. package/dist/commands/hello.js.map +1 -1
  34. package/dist/commands/run.d.ts.map +1 -1
  35. package/dist/commands/run.js +59 -4
  36. package/dist/commands/run.js.map +1 -1
  37. package/dist/commands/test.d.ts.map +1 -1
  38. package/dist/commands/test.js +78 -23
  39. package/dist/commands/test.js.map +1 -1
  40. package/dist/commands/wrap.d.ts.map +1 -1
  41. package/dist/commands/wrap.js +28 -70
  42. package/dist/commands/wrap.js.map +1 -1
  43. package/dist/index.js +47 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/memory/access.d.ts +53 -0
  46. package/dist/memory/access.d.ts.map +1 -0
  47. package/dist/memory/access.js +111 -0
  48. package/dist/memory/access.js.map +1 -0
  49. package/dist/memory/audit.d.ts +26 -0
  50. package/dist/memory/audit.d.ts.map +1 -0
  51. package/dist/memory/audit.js +30 -0
  52. package/dist/memory/audit.js.map +1 -0
  53. package/dist/memory/backend.d.ts +141 -0
  54. package/dist/memory/backend.d.ts.map +1 -0
  55. package/dist/memory/backend.js +536 -0
  56. package/dist/memory/backend.js.map +1 -0
  57. package/dist/memory/codec.d.ts +34 -0
  58. package/dist/memory/codec.d.ts.map +1 -0
  59. package/dist/memory/codec.js +184 -0
  60. package/dist/memory/codec.js.map +1 -0
  61. package/dist/memory/data-policy.d.ts +39 -0
  62. package/dist/memory/data-policy.d.ts.map +1 -0
  63. package/dist/memory/data-policy.js +42 -0
  64. package/dist/memory/data-policy.js.map +1 -0
  65. package/dist/memory/host.d.ts +119 -0
  66. package/dist/memory/host.d.ts.map +1 -0
  67. package/dist/memory/host.js +226 -0
  68. package/dist/memory/host.js.map +1 -0
  69. package/dist/memory/index.d.ts +37 -0
  70. package/dist/memory/index.d.ts.map +1 -0
  71. package/dist/memory/index.js +64 -0
  72. package/dist/memory/index.js.map +1 -0
  73. package/dist/memory/telemetry.d.ts +35 -0
  74. package/dist/memory/telemetry.d.ts.map +1 -0
  75. package/dist/memory/telemetry.js +51 -0
  76. package/dist/memory/telemetry.js.map +1 -0
  77. package/dist/memory/types.d.ts +110 -0
  78. package/dist/memory/types.d.ts.map +1 -0
  79. package/dist/memory/types.js +74 -0
  80. package/dist/memory/types.js.map +1 -0
  81. package/dist/publish/auto-publish.d.ts +1 -1
  82. package/dist/publish/auto-publish.js +1 -1
  83. package/dist/runtime/network-gateway.d.ts +24 -4
  84. package/dist/runtime/network-gateway.d.ts.map +1 -1
  85. package/dist/runtime/network-gateway.js +86 -12
  86. package/dist/runtime/network-gateway.js.map +1 -1
  87. package/dist/testsuite/gate.d.ts +8 -0
  88. package/dist/testsuite/gate.d.ts.map +1 -1
  89. package/dist/testsuite/gate.js +13 -3
  90. package/dist/testsuite/gate.js.map +1 -1
  91. package/dist/testsuite/run.d.ts +6 -11
  92. package/dist/testsuite/run.d.ts.map +1 -1
  93. package/dist/testsuite/run.js +29 -4
  94. package/dist/testsuite/run.js.map +1 -1
  95. package/dist/utils/arena-success.d.ts +10 -0
  96. package/dist/utils/arena-success.d.ts.map +1 -1
  97. package/dist/utils/arena-success.js +35 -8
  98. package/dist/utils/arena-success.js.map +1 -1
  99. package/dist/utils/binding.d.ts +6 -2
  100. package/dist/utils/binding.d.ts.map +1 -1
  101. package/dist/utils/binding.js.map +1 -1
  102. package/dist/utils/clawhub-client.d.ts +73 -0
  103. package/dist/utils/clawhub-client.d.ts.map +1 -0
  104. package/dist/utils/clawhub-client.js +176 -0
  105. package/dist/utils/clawhub-client.js.map +1 -0
  106. package/dist/utils/config.d.ts +7 -0
  107. package/dist/utils/config.d.ts.map +1 -1
  108. package/dist/utils/config.js +15 -1
  109. package/dist/utils/config.js.map +1 -1
  110. package/dist/utils/fitness-formula.d.ts +20 -4
  111. package/dist/utils/fitness-formula.d.ts.map +1 -1
  112. package/dist/utils/fitness-formula.js +24 -6
  113. package/dist/utils/fitness-formula.js.map +1 -1
  114. package/dist/utils/gene-id-display.d.ts +14 -0
  115. package/dist/utils/gene-id-display.d.ts.map +1 -0
  116. package/dist/utils/gene-id-display.js +64 -0
  117. package/dist/utils/gene-id-display.js.map +1 -0
  118. package/dist/utils/l0-gate.d.ts.map +1 -1
  119. package/dist/utils/l0-gate.js +4 -1
  120. package/dist/utils/l0-gate.js.map +1 -1
  121. package/dist/utils/rust-phenotype.d.ts +17 -5
  122. package/dist/utils/rust-phenotype.d.ts.map +1 -1
  123. package/dist/utils/rust-phenotype.js +25 -4
  124. package/dist/utils/rust-phenotype.js.map +1 -1
  125. 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
- * macos-14, whose `script` binaries take incompatible arguments — a
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
- * macos-14, whose `script` binaries take incompatible arguments — a
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
- private validateDomain;
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;;;;;;GAMG;AAEH,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,cAAc,GACd,SAAS,GACT,oBAAoB,GACpB,eAAe;gBALH,IAAI,EAChB,gBAAgB,GAChB,cAAc,GACd,SAAS,GACT,oBAAoB,GACpB,eAAe,EACnB,OAAO,EAAE,MAAM;CAKlB;AASD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,iBAAiB,CAAgB;IACzC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,UAAU,CAAK;gBAEX,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC;IAI3C,IAAI,KAAK;;;;MAMR;IAEK,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAkDjF,OAAO,CAAC,cAAc;IA0BtB,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"}
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.validateDomain(url);
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
- const response = await fetch(url, {
49
- method: options?.method || "POST",
50
- headers: options?.headers,
51
- body: options?.body,
52
- signal: controller.signal,
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
- validateDomain(url) {
79
- let hostname;
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
- hostname = new URL(url).hostname;
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();