@remnic/core 9.3.767 → 9.3.769

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.
@@ -22,8 +22,8 @@ import {
22
22
  upsertHermesConfig,
23
23
  validateMarketplaceManifest,
24
24
  writeMarketplaceManifest
25
- } from "../chunk-4DLFJJOQ.js";
26
- import "../chunk-UG274TNV.js";
25
+ } from "../chunk-QMN3CIFS.js";
26
+ import "../chunk-E2SPGGUI.js";
27
27
  import {
28
28
  runCodexMaterialize
29
29
  } from "../chunk-2RLECZ6J.js";
package/dist/index.d.ts CHANGED
@@ -71,7 +71,7 @@ export { c as CONNECTOR_ID_PATTERN, C as ConnectorConfig, b as ConnectorCursor,
71
71
  export { b as CONNECTORS_OUTPUT_FORMATS, c as ConnectorRow, C as ConnectorRunResult, d as ConnectorState, e as ConnectorSyncStatus, a as ConnectorsOutputFormat, P as ParsedConnectorsListOptions, f as ParsedConnectorsStatusOptions, R as RunConnectorPollOnceArgs, l as listConnectorStates, p as parseConnectorsFormat, g as parseConnectorsListOptions, h as parseConnectorsRunName, i as parseConnectorsStatusOptions, r as readConnectorState, j as renderConnectorsList, k as renderConnectorsRunResult, m as runConnectorPollOnce, w as writeConnectorState } from './connectors-cli-2iaQ5tX2.js';
72
72
  export { LiveConnectorDefinition, LiveConnectorRunItem, LiveConnectorSkipReason, LiveConnectorsRunSummary, builtInLiveConnectorDefinitions, hasEnabledLiveConnector, runLiveConnectorsOnce } from './live-connectors-runner.js';
73
73
  import { TokenEntry } from './tokens.js';
74
- export { TokenStore, generateToken, getAllValidTokens, getAllValidTokensCached, listTokens, loadTokenStore, resolveConnectorFromToken, revokeToken, saveTokenStore } from './tokens.js';
74
+ export { TokenStore, buildTokenEntry, commitTokenEntry, generateToken, getAllValidTokenEntriesCached, getAllValidTokens, getAllValidTokensCached, listTokens, loadTokenStore, resolveConnectorFromToken, revokeToken, saveTokenStore } from './tokens.js';
75
75
  export { PostConsolidationMaterializeOptions, RunMaterializeOptions, runCodexMaterialize, runPostConsolidationMaterialize } from './connectors/codex-materialize-runner.js';
76
76
  export { MATERIALIZE_VERSION, MaterializeOptions, MaterializeResult, RolloutSummaryInput, SENTINEL_FILE, describeMemoriesDir, ensureSentinel, materializeForNamespace } from './connectors/codex-materialize.js';
77
77
  import { T as Taxonomy, R as ResolverDecision } from './types-DJhqDJUV.js';
package/dist/index.js CHANGED
@@ -186,7 +186,7 @@ import {
186
186
  saveRegistry,
187
187
  validateMarketplaceManifest,
188
188
  writeMarketplaceManifest
189
- } from "./chunk-4DLFJJOQ.js";
189
+ } from "./chunk-QMN3CIFS.js";
190
190
  import "./chunk-V5OCT34X.js";
191
191
  import "./chunk-LXOM6IQU.js";
192
192
  import "./chunk-VOUOLGIP.js";
@@ -196,7 +196,10 @@ import {
196
196
  } from "./chunk-S75M5ZRK.js";
197
197
  import "./chunk-ECZU5BJH.js";
198
198
  import {
199
+ buildTokenEntry,
200
+ commitTokenEntry,
199
201
  generateToken,
202
+ getAllValidTokenEntriesCached,
200
203
  getAllValidTokens,
201
204
  getAllValidTokensCached,
202
205
  listTokens,
@@ -204,7 +207,7 @@ import {
204
207
  resolveConnectorFromToken,
205
208
  revokeToken,
206
209
  saveTokenStore
207
- } from "./chunk-UG274TNV.js";
210
+ } from "./chunk-E2SPGGUI.js";
208
211
  import "./chunk-UHGBNIOS.js";
209
212
  import "./chunk-BCDKBDSY.js";
210
213
  import "./chunk-YN2UN7N3.js";
@@ -344,7 +347,7 @@ import {
344
347
  registerTrainingExportAdapter,
345
348
  runBulkImportCliCommand,
346
349
  runWearablesCliCommand
347
- } from "./chunk-HBOPSFQQ.js";
350
+ } from "./chunk-EVXI2I6G.js";
348
351
  import "./chunk-MC4FJXPA.js";
349
352
  import "./chunk-LQHDIS7L.js";
350
353
  import "./chunk-7F7Z6MOS.js";
@@ -545,7 +548,7 @@ import "./chunk-H3FZVNRN.js";
545
548
  import "./chunk-OADWQ5CR.js";
546
549
  import {
547
550
  EngramAccessHttpServer
548
- } from "./chunk-J3UJJZKI.js";
551
+ } from "./chunk-SIPZ5UMK.js";
549
552
  import {
550
553
  WearablesInputError,
551
554
  describeErrorForOperator
@@ -559,7 +562,7 @@ import "./chunk-42NQ7AVG.js";
559
562
  import "./chunk-7RXCMVFQ.js";
560
563
  import {
561
564
  EngramMcpServer
562
- } from "./chunk-TVLN5EZZ.js";
565
+ } from "./chunk-CNXMWYLA.js";
563
566
  import {
564
567
  buildCitationGuidance,
565
568
  formatOaiMemCitation,
@@ -1070,6 +1073,7 @@ export {
1070
1073
  buildResponseGuidanceRecallSection,
1071
1074
  buildRetrievedMemoryProvenance,
1072
1075
  buildTargetedFactRecallSection,
1076
+ buildTokenEntry,
1073
1077
  buildTrajectoryAnalysisRecallSection,
1074
1078
  builtInLiveConnectorDefinitions,
1075
1079
  checkMarketplaceManifest,
@@ -1090,6 +1094,7 @@ export {
1090
1094
  collectQuestionSlotCues,
1091
1095
  collectStructuredPlanCues,
1092
1096
  collectTemporalLexicalCues,
1097
+ commitTokenEntry,
1093
1098
  compileCorrectionRule,
1094
1099
  compileCorrectionRules,
1095
1100
  compileOfflineSyncExcludeGlobs,
@@ -1163,6 +1168,7 @@ export {
1163
1168
  generateToken,
1164
1169
  generateWearableMemories,
1165
1170
  getActiveSpace,
1171
+ getAllValidTokenEntriesCached,
1166
1172
  getAllValidTokens,
1167
1173
  getAllValidTokensCached,
1168
1174
  getAuditLog,
@@ -68,13 +68,13 @@ import "./chunk-ABP6QMVM.js";
68
68
  import "./chunk-HW5EF6LV.js";
69
69
  import "./chunk-QPTP6SKC.js";
70
70
  import "./chunk-JRZV3767.js";
71
- import "./chunk-4DLFJJOQ.js";
71
+ import "./chunk-QMN3CIFS.js";
72
72
  import "./chunk-V5OCT34X.js";
73
73
  import "./chunk-LXOM6IQU.js";
74
74
  import "./chunk-VOUOLGIP.js";
75
75
  import "./chunk-S75M5ZRK.js";
76
76
  import "./chunk-ECZU5BJH.js";
77
- import "./chunk-UG274TNV.js";
77
+ import "./chunk-E2SPGGUI.js";
78
78
  import "./chunk-UHGBNIOS.js";
79
79
  import "./chunk-BCDKBDSY.js";
80
80
  import "./chunk-YN2UN7N3.js";
@@ -125,7 +125,7 @@ import "./chunk-LUPVCGYK.js";
125
125
  import "./chunk-PYPOFEMK.js";
126
126
  import "./chunk-LOPZ2DWR.js";
127
127
  import "./chunk-VEWZZM3H.js";
128
- import "./chunk-HBOPSFQQ.js";
128
+ import "./chunk-EVXI2I6G.js";
129
129
  import "./chunk-MC4FJXPA.js";
130
130
  import "./chunk-LQHDIS7L.js";
131
131
  import "./chunk-7F7Z6MOS.js";
@@ -216,7 +216,7 @@ import "./chunk-KD3QD3A5.js";
216
216
  import "./chunk-VGUOEDTU.js";
217
217
  import "./chunk-H3FZVNRN.js";
218
218
  import "./chunk-OADWQ5CR.js";
219
- import "./chunk-J3UJJZKI.js";
219
+ import "./chunk-SIPZ5UMK.js";
220
220
  import "./chunk-7WV3F5DQ.js";
221
221
  import "./chunk-SEDEKFYQ.js";
222
222
  import "./chunk-JBPKEARU.js";
@@ -225,7 +225,7 @@ import "./chunk-J64TK33U.js";
225
225
  import "./chunk-RSUYKGGZ.js";
226
226
  import "./chunk-42NQ7AVG.js";
227
227
  import "./chunk-7RXCMVFQ.js";
228
- import "./chunk-TVLN5EZZ.js";
228
+ import "./chunk-CNXMWYLA.js";
229
229
  import "./chunk-D24OXEPB.js";
230
230
  import "./chunk-4JG55YV3.js";
231
231
  import "./chunk-GLERZDM7.js";
package/dist/tokens.d.ts CHANGED
@@ -38,7 +38,9 @@ declare function generateToken(connector: string, tokensPath?: string): TokenEnt
38
38
  declare function listTokens(tokensPath?: string): TokenEntry[];
39
39
  declare function revokeToken(connector: string, tokensPath?: string): boolean;
40
40
  declare function getAllValidTokens(tokensPath?: string): string[];
41
+ /** Cached token-entry snapshot ({token, connector} pairs). */
42
+ declare function getAllValidTokenEntriesCached(tokensPath?: string): readonly TokenEntry[];
41
43
  declare function getAllValidTokensCached(tokensPath?: string): string[];
42
44
  declare function resolveConnectorFromToken(token: string, tokensPath?: string): string | undefined;
43
45
 
44
- export { type TokenEntry, type TokenStore, buildTokenEntry, commitTokenEntry, generateToken, getAllValidTokens, getAllValidTokensCached, listTokens, loadTokenStore, resolveConnectorFromToken, revokeToken, saveTokenStore };
46
+ export { type TokenEntry, type TokenStore, buildTokenEntry, commitTokenEntry, generateToken, getAllValidTokenEntriesCached, getAllValidTokens, getAllValidTokensCached, listTokens, loadTokenStore, resolveConnectorFromToken, revokeToken, saveTokenStore };
package/dist/tokens.js CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  buildTokenEntry,
3
3
  commitTokenEntry,
4
4
  generateToken,
5
+ getAllValidTokenEntriesCached,
5
6
  getAllValidTokens,
6
7
  getAllValidTokensCached,
7
8
  listTokens,
@@ -9,13 +10,14 @@ import {
9
10
  resolveConnectorFromToken,
10
11
  revokeToken,
11
12
  saveTokenStore
12
- } from "./chunk-UG274TNV.js";
13
+ } from "./chunk-E2SPGGUI.js";
13
14
  import "./chunk-JUC24CTX.js";
14
15
  import "./chunk-PZ5AY32C.js";
15
16
  export {
16
17
  buildTokenEntry,
17
18
  commitTokenEntry,
18
19
  generateToken,
20
+ getAllValidTokenEntriesCached,
19
21
  getAllValidTokens,
20
22
  getAllValidTokensCached,
21
23
  listTokens,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.767",
3
+ "version": "9.3.769",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3000,7 +3000,7 @@
3000
3000
  "core"
3001
3001
  ],
3002
3002
  "peerDependencies": {
3003
- "@remnic/coding-graph": "^9.3.767"
3003
+ "@remnic/coding-graph": "^9.3.769"
3004
3004
  },
3005
3005
  "peerDependenciesMeta": {
3006
3006
  "@remnic/coding-graph": {
@@ -1454,3 +1454,353 @@ test("HTTP offline apply requires a changeset", async () => {
1454
1454
  await server.stop();
1455
1455
  }
1456
1456
  });
1457
+
1458
+ test("HTTP server rejects invalid resourceMetadataUrl at construction", () => {
1459
+ const service = {} as EngramAccessService;
1460
+ for (const bad of ["not a url", "ftp://example.com/oauth", "//relative/path", ""]) {
1461
+ assert.throws(
1462
+ () =>
1463
+ new EngramAccessHttpServer({
1464
+ service,
1465
+ port: 0,
1466
+ authToken: "test-token",
1467
+ adminConsoleEnabled: false,
1468
+ resourceMetadataUrl: bad,
1469
+ }),
1470
+ /access HTTP resourceMetadataUrl/,
1471
+ `resourceMetadataUrl=${JSON.stringify(bad)} must be rejected`,
1472
+ );
1473
+ }
1474
+ // http and https are accepted.
1475
+ for (const ok of [
1476
+ "https://example.com/.well-known/oauth-protected-resource",
1477
+ "http://127.0.0.1:8787/.well-known/oauth-protected-resource",
1478
+ ]) {
1479
+ const server = new EngramAccessHttpServer({
1480
+ service,
1481
+ port: 0,
1482
+ authToken: "test-token",
1483
+ adminConsoleEnabled: false,
1484
+ resourceMetadataUrl: ok,
1485
+ });
1486
+ assert.ok(server, `resourceMetadataUrl=${ok} should be accepted`);
1487
+ }
1488
+ });
1489
+
1490
+ test("HTTP 401 www-authenticate carries resource_metadata exactly when configured", async () => {
1491
+ const service = {} as EngramAccessService;
1492
+ const metadataUrl = "https://example.test/.well-known/oauth-protected-resource";
1493
+ const server = new EngramAccessHttpServer({
1494
+ service,
1495
+ port: 0,
1496
+ authToken: "test-token",
1497
+ adminConsoleEnabled: false,
1498
+ resourceMetadataUrl: metadataUrl,
1499
+ });
1500
+ const status = await server.start();
1501
+ try {
1502
+ const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/health`);
1503
+ assert.equal(response.status, 401);
1504
+ assert.equal(
1505
+ response.headers.get("www-authenticate"),
1506
+ `Bearer resource_metadata="${metadataUrl}"`,
1507
+ );
1508
+ } finally {
1509
+ await server.stop();
1510
+ }
1511
+ });
1512
+
1513
+ test("HTTP 401 www-authenticate is the bare Bearer challenge when resourceMetadataUrl is unset", async () => {
1514
+ const service = {} as EngramAccessService;
1515
+ const server = new EngramAccessHttpServer({
1516
+ service,
1517
+ port: 0,
1518
+ authToken: "test-token",
1519
+ adminConsoleEnabled: false,
1520
+ });
1521
+ const status = await server.start();
1522
+ try {
1523
+ const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/health`);
1524
+ assert.equal(response.status, 401);
1525
+ assert.equal(response.headers.get("www-authenticate"), "Bearer");
1526
+ } finally {
1527
+ await server.stop();
1528
+ }
1529
+ });
1530
+
1531
+ test("HTTP /mcp returns 405 with Allow: POST for GET and DELETE (authorized requests)", async () => {
1532
+ const service = {} as EngramAccessService;
1533
+ const server = new EngramAccessHttpServer({
1534
+ service,
1535
+ port: 0,
1536
+ authToken: "test-token",
1537
+ adminConsoleEnabled: false,
1538
+ });
1539
+ const status = await server.start();
1540
+ try {
1541
+ for (const method of ["GET", "DELETE"]) {
1542
+ const response = await fetch(`http://127.0.0.1:${status.port}/mcp`, {
1543
+ method,
1544
+ headers: { authorization: "Bearer test-token" },
1545
+ });
1546
+ assert.equal(response.status, 405, `${method} /mcp must be 405`);
1547
+ assert.equal(response.headers.get("allow"), "POST", `${method} /mcp must advertise Allow: POST`);
1548
+ const body = await response.json() as { code?: string };
1549
+ assert.equal(body.code, "method_not_allowed");
1550
+ }
1551
+ // Unauthenticated GET /mcp still gets 401 first (auth gate beats method-conformance).
1552
+ const unauth = await fetch(`http://127.0.0.1:${status.port}/mcp`, { method: "GET" });
1553
+ assert.equal(unauth.status, 401);
1554
+ } finally {
1555
+ await server.stop();
1556
+ }
1557
+ });
1558
+
1559
+ test("HTTP /mcp rejects unknown MCP-Protocol-Version header with 400 JSON-RPC error", async () => {
1560
+ const service = {} as EngramAccessService;
1561
+ const server = new EngramAccessHttpServer({
1562
+ service,
1563
+ port: 0,
1564
+ authToken: "test-token",
1565
+ adminConsoleEnabled: false,
1566
+ });
1567
+ const status = await server.start();
1568
+ try {
1569
+ const response = await fetch(`http://127.0.0.1:${status.port}/mcp`, {
1570
+ method: "POST",
1571
+ headers: {
1572
+ authorization: "Bearer test-token",
1573
+ "content-type": "application/json",
1574
+ "mcp-protocol-version": "1999-01-01",
1575
+ },
1576
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "ping" }),
1577
+ });
1578
+ assert.equal(response.status, 400);
1579
+ const body = await response.json() as { jsonrpc?: string; error?: { message?: string } };
1580
+ assert.equal(body.jsonrpc, "2.0");
1581
+ assert.match(body.error?.message ?? "", /unsupported MCP-Protocol-Version/);
1582
+
1583
+ // A supported header is accepted (and a valid request proceeds normally).
1584
+ for (const v of ["2025-06-18", "2025-03-26", "2024-11-05"]) {
1585
+ const ok = await fetch(`http://127.0.0.1:${status.port}/mcp`, {
1586
+ method: "POST",
1587
+ headers: {
1588
+ authorization: "Bearer test-token",
1589
+ "content-type": "application/json",
1590
+ "mcp-protocol-version": v,
1591
+ },
1592
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "ping" }),
1593
+ });
1594
+ assert.equal(ok.status, 200, `version ${v} should be accepted`);
1595
+ }
1596
+
1597
+ // Absent header is also fine.
1598
+ const absent = await fetch(`http://127.0.0.1:${status.port}/mcp`, {
1599
+ method: "POST",
1600
+ headers: { authorization: "Bearer test-token", "content-type": "application/json" },
1601
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "ping" }),
1602
+ });
1603
+ assert.equal(absent.status, 200);
1604
+ } finally {
1605
+ await server.stop();
1606
+ }
1607
+ });
1608
+
1609
+ test("HTTP externalRequestHandler runs pre-auth, can end responses, and falls through on false", async () => {
1610
+ // Minimal service stub: the fall-through leg hits /engram/v1/health, which
1611
+ // calls service.health(). The stub is signature-faithful (full
1612
+ // EngramAccessHealthResponse via `satisfies`) so interface drift fails here
1613
+ // instead of passing vacuously; everything else in this test bypasses the
1614
+ // service.
1615
+ const healthStub = {
1616
+ health: async () => ({
1617
+ ok: true as const,
1618
+ memoryDir: "/tmp/remnic-test",
1619
+ namespacesEnabled: false,
1620
+ defaultNamespace: "default",
1621
+ searchBackend: "recent",
1622
+ qmdEnabled: false,
1623
+ qmd: {
1624
+ enabled: false,
1625
+ active: false,
1626
+ degraded: false,
1627
+ mode: "disabled" as const,
1628
+ collection: "",
1629
+ collectionState: "skipped" as const,
1630
+ installedVersion: null,
1631
+ supportedVersion: null,
1632
+ supported: null,
1633
+ upgradeAvailable: null,
1634
+ doctorAvailable: null,
1635
+ debugStatus: "disabled",
1636
+ },
1637
+ nativeKnowledgeEnabled: false,
1638
+ projectionAvailable: false,
1639
+ }),
1640
+ } satisfies Pick<EngramAccessService, "health">;
1641
+ const service = healthStub as EngramAccessService;
1642
+ const seen: Array<{ path: string; method: string; authorized: boolean }> = [];
1643
+ const server = new EngramAccessHttpServer({
1644
+ service,
1645
+ port: 0,
1646
+ authToken: "test-token",
1647
+ adminConsoleEnabled: false,
1648
+ externalRequestHandler: async (_req, res, ctx) => {
1649
+ seen.push({
1650
+ path: new URL(_req.url ?? "/", "http://placeholder").pathname,
1651
+ method: _req.method ?? "",
1652
+ authorized: ctx.authorized,
1653
+ });
1654
+ if (new URL(_req.url ?? "/", "http://placeholder").pathname === "/probe/handled") {
1655
+ res.writeHead(200, { "content-type": "application/json" });
1656
+ res.end(JSON.stringify({ handled: true, authorized: ctx.authorized }));
1657
+ return true;
1658
+ }
1659
+ return false; // fall through to the normal pipeline
1660
+ },
1661
+ });
1662
+ const status = await server.start();
1663
+ try {
1664
+ // Pre-auth, the handler sees authorized=false (no token sent).
1665
+ const handled = await fetch(`http://127.0.0.1:${status.port}/probe/handled`);
1666
+ assert.equal(handled.status, 200);
1667
+ const handledBody = await handled.json() as { handled?: boolean; authorized?: boolean };
1668
+ assert.equal(handledBody.handled, true);
1669
+ assert.equal(handledBody.authorized, false, "handler must observe authorized=false pre-token");
1670
+
1671
+ // Fall-through path: same handler returns false, request continues to normal
1672
+ // routing. Hit a real endpoint so we know the request reached it.
1673
+ const passthrough = await fetch(
1674
+ `http://127.0.0.1:${status.port}/engram/v1/health`,
1675
+ { headers: { authorization: "Bearer test-token" } },
1676
+ );
1677
+ assert.equal(passthrough.status, 200, "fall-through should reach the normal health route");
1678
+ const passthroughBody = await passthrough.json() as { ok?: boolean; memoryDir?: string };
1679
+ assert.equal(passthroughBody.ok, true, "fall-through must return the stubbed health payload");
1680
+ assert.equal(passthroughBody.memoryDir, "/tmp/remnic-test");
1681
+
1682
+ // Authorized request: handler sees ctx.authorized=true.
1683
+ const authed = await fetch(
1684
+ `http://127.0.0.1:${status.port}/probe/handled`,
1685
+ { headers: { authorization: "Bearer test-token" } },
1686
+ );
1687
+ assert.equal(authed.status, 200);
1688
+ const authedBody = await authed.json() as { authorized?: boolean };
1689
+ assert.equal(authedBody.authorized, true, "handler must observe authorized=true with valid token");
1690
+
1691
+ assert.deepEqual(seen, [
1692
+ { path: "/probe/handled", method: "GET", authorized: false },
1693
+ { path: "/engram/v1/health", method: "GET", authorized: true },
1694
+ { path: "/probe/handled", method: "GET", authorized: true },
1695
+ ]);
1696
+ } finally {
1697
+ await server.stop();
1698
+ }
1699
+ });
1700
+
1701
+ test("HTTP externalRequestHandler errors flow through the existing error handler", async () => {
1702
+ const service = {} as EngramAccessService;
1703
+ const server = new EngramAccessHttpServer({
1704
+ service,
1705
+ port: 0,
1706
+ authToken: "test-token",
1707
+ adminConsoleEnabled: false,
1708
+ externalRequestHandler: async () => {
1709
+ throw new Error("external-handler-explosion");
1710
+ },
1711
+ });
1712
+ const status = await server.start();
1713
+ try {
1714
+ const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/health`);
1715
+ assert.equal(response.status, 500, "thrown errors must produce a 500 via the existing error handler");
1716
+ const body = await response.json() as { code?: string };
1717
+ assert.equal(body.code, "internal_error");
1718
+ } finally {
1719
+ await server.stop();
1720
+ }
1721
+ });
1722
+
1723
+ test("HTTP authTokenEntriesGetter is authoritative: scope policy binds connectors and never falls through", async () => {
1724
+ // Signature-faithful health stub so non-MCP authorization outcomes are
1725
+ // observable as 200-with-body (a bare `{}` service would 500 and mask
1726
+ // accidental policy application).
1727
+ const healthStub = {
1728
+ health: async () => ({
1729
+ ok: true as const,
1730
+ memoryDir: "/tmp/remnic-scope-test",
1731
+ namespacesEnabled: false,
1732
+ defaultNamespace: "default",
1733
+ searchBackend: "recent",
1734
+ qmdEnabled: false,
1735
+ qmd: {
1736
+ enabled: false,
1737
+ active: false,
1738
+ degraded: false,
1739
+ mode: "disabled" as const,
1740
+ collection: "",
1741
+ collectionState: "skipped" as const,
1742
+ installedVersion: null,
1743
+ supportedVersion: null,
1744
+ supported: null,
1745
+ upgradeAvailable: null,
1746
+ doctorAvailable: null,
1747
+ debugStatus: "disabled",
1748
+ },
1749
+ nativeKnowledgeEnabled: false,
1750
+ projectionAvailable: false,
1751
+ }),
1752
+ } satisfies Pick<EngramAccessService, "health">;
1753
+ const entries = [
1754
+ { token: "remnic_cg_scoped", connector: "chatgpt" },
1755
+ { token: "remnic_cx_free", connector: "codex" },
1756
+ { token: "remnic_xx_anon" }, // no connector — must fail closed under a policy
1757
+ ];
1758
+ const server = new EngramAccessHttpServer({
1759
+ service: healthStub as EngramAccessService,
1760
+ port: 0,
1761
+ authToken: "operator-token",
1762
+ // Dangerous shape on purpose: BOTH getters configured, and the string
1763
+ // getter is a superset (extra "string_only_token"). The entries getter
1764
+ // must decide alone; nothing may leak into the string getter.
1765
+ authTokensGetter: () => [...entries.map((entry) => entry.token), "string_only_token"],
1766
+ authTokenEntriesGetter: () => entries,
1767
+ tokenPathPolicy: (connector, pathname) => connector !== "chatgpt" || pathname === "/mcp",
1768
+ adminConsoleEnabled: false,
1769
+ });
1770
+ const status = await server.start();
1771
+ const request = (token: string, path: string, method = "GET") =>
1772
+ fetch(`http://127.0.0.1:${status.port}${path}`, {
1773
+ method,
1774
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
1775
+ ...(method === "POST" ? { body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "ping" }) } : {}),
1776
+ });
1777
+ try {
1778
+ // Scoped chatgpt token: /mcp only; health denied.
1779
+ assert.equal((await request("remnic_cg_scoped", "/mcp", "POST")).status, 200);
1780
+ assert.equal(
1781
+ (await request("remnic_cg_scoped", "/engram/v1/health")).status,
1782
+ 401,
1783
+ "chatgpt token must be denied off /mcp even with a permissive string getter present",
1784
+ );
1785
+ // Other connector tokens are unrestricted by this policy: /mcp AND health.
1786
+ assert.equal((await request("remnic_cx_free", "/mcp", "POST")).status, 200);
1787
+ const codexHealth = await request("remnic_cx_free", "/engram/v1/health");
1788
+ assert.equal(codexHealth.status, 200);
1789
+ assert.equal(((await codexHealth.json()) as { ok?: boolean }).ok, true);
1790
+ // Entry without connector fails closed when a policy is configured.
1791
+ assert.equal((await request("remnic_xx_anon", "/mcp", "POST")).status, 401);
1792
+ // A token present ONLY in the string getter is NOT honored: the entries
1793
+ // getter is authoritative and there is no fall-through.
1794
+ assert.equal((await request("string_only_token", "/mcp", "POST")).status, 401);
1795
+ assert.equal((await request("string_only_token", "/engram/v1/health")).status, 401);
1796
+ // Unknown tokens are rejected everywhere.
1797
+ assert.equal((await request("remnic_zz_unknown", "/mcp", "POST")).status, 401);
1798
+ // Static operator token bypasses the policy entirely: /mcp AND health.
1799
+ assert.equal((await request("operator-token", "/mcp", "POST")).status, 200);
1800
+ const operatorHealth = await request("operator-token", "/engram/v1/health");
1801
+ assert.equal(operatorHealth.status, 200);
1802
+ assert.equal(((await operatorHealth.json()) as { ok?: boolean }).ok, true);
1803
+ } finally {
1804
+ await server.stop();
1805
+ }
1806
+ });