@remnic/core 9.4.2 → 9.6.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 (379) hide show
  1. package/dist/access-admin-ops-surface.d.ts +6 -6
  2. package/dist/access-admin-ops-surface.js +28 -28
  3. package/dist/access-boundary.d.ts +27 -8
  4. package/dist/access-boundary.js +33 -29
  5. package/dist/access-cli.js +83 -81
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-http.d.ts +52 -6
  8. package/dist/access-http.js +40 -38
  9. package/dist/access-identity-continuity-surface.d.ts +5 -5
  10. package/dist/access-identity-continuity-surface.js +28 -28
  11. package/dist/access-lcm-surface.d.ts +6 -6
  12. package/dist/access-lcm-surface.js +28 -28
  13. package/dist/access-mcp.d.ts +16 -6
  14. package/dist/access-mcp.js +37 -35
  15. package/dist/access-observe-write-surface.d.ts +6 -6
  16. package/dist/access-observe-write-surface.js +28 -28
  17. package/dist/access-operations-batch.js +33 -32
  18. package/dist/access-operations.d.ts +9 -9
  19. package/dist/access-operations.js +35 -34
  20. package/dist/access-recall-surface.d.ts +6 -6
  21. package/dist/access-recall-surface.js +28 -28
  22. package/dist/access-schema.d.ts +4 -4
  23. package/dist/access-schema.js +4 -4
  24. package/dist/{access-service-m-2VO_Ig.d.ts → access-service-CXJaql3A.d.ts} +4 -4
  25. package/dist/access-service.d.ts +6 -6
  26. package/dist/access-service.js +28 -28
  27. package/dist/access-surface-catalog.d.ts +6 -6
  28. package/dist/access-surface-catalog.js +1 -0
  29. package/dist/access-surface-catalog.js.map +1 -1
  30. package/dist/access-token-capabilities.d.ts +174 -0
  31. package/dist/access-token-capabilities.js +33 -0
  32. package/dist/action-confidence.d.ts +1 -1
  33. package/dist/active-memory-bridge.d.ts +1 -1
  34. package/dist/active-recall.d.ts +1 -1
  35. package/dist/active-recall.js +2 -2
  36. package/dist/adapters/index.js +4 -4
  37. package/dist/adapters/registry.js +2 -2
  38. package/dist/{auto-sync-HMHQHUAG.js → auto-sync-4A5YBWYO.js} +4 -4
  39. package/dist/behavior-learner.d.ts +1 -1
  40. package/dist/behavior-signals.d.ts +1 -1
  41. package/dist/bootstrap.d.ts +5 -5
  42. package/dist/briefing.d.ts +2 -2
  43. package/dist/briefing.js +9 -9
  44. package/dist/buffer-surprise-report.d.ts +1 -1
  45. package/dist/buffer.d.ts +2 -2
  46. package/dist/calibration.d.ts +1 -1
  47. package/dist/capabilities.d.ts +1 -1
  48. package/dist/{capsule-crypto-CZJSLEFG.js → capsule-crypto-RAARDY3M.js} +2 -2
  49. package/dist/capsule-crypto-RAARDY3M.js.map +1 -0
  50. package/dist/{catalog-BxWokdC2.d.ts → catalog-C9cu67Ig.d.ts} +1 -1
  51. package/dist/causal-behavior.d.ts +1 -1
  52. package/dist/causal-behavior.js +5 -5
  53. package/dist/causal-chain.js +3 -3
  54. package/dist/causal-consolidation.d.ts +1 -1
  55. package/dist/causal-consolidation.js +13 -13
  56. package/dist/causal-retrieval.js +3 -3
  57. package/dist/causal-trajectory-graph.d.ts +1 -1
  58. package/dist/causal-trajectory.js +1 -1
  59. package/dist/{chunk-6ZIETXZ2.js → chunk-23JFRB73.js} +2 -2
  60. package/dist/{chunk-IC6QX5TC.js → chunk-3XIY7MDQ.js} +6 -6
  61. package/dist/{chunk-FZGGZAAU.js → chunk-4GNEDXDI.js} +146 -2
  62. package/dist/chunk-4GNEDXDI.js.map +1 -0
  63. package/dist/{chunk-LOR6ZM5R.js → chunk-4LV23CHK.js} +2 -2
  64. package/dist/{chunk-E2SPGGUI.js → chunk-4OKLXWDZ.js} +13 -6
  65. package/dist/chunk-4OKLXWDZ.js.map +1 -0
  66. package/dist/{chunk-AER6MT24.js → chunk-6SXVCD7W.js} +2 -2
  67. package/dist/{chunk-OHDJ34CC.js → chunk-72WG5QKN.js} +2 -2
  68. package/dist/{chunk-CZAZX7JP.js → chunk-7MW3CVLD.js} +4 -4
  69. package/dist/{chunk-CXLNZXG5.js → chunk-7XGSIUP5.js} +1 -1
  70. package/dist/{chunk-CZAA5N5S.js → chunk-AYZJID4S.js} +2 -2
  71. package/dist/{chunk-M7XQSUBB.js → chunk-B3ABVQ36.js} +116 -9
  72. package/dist/chunk-B3ABVQ36.js.map +1 -0
  73. package/dist/{chunk-6BISDEN6.js → chunk-BEM4D2QG.js} +14 -14
  74. package/dist/{chunk-ZPOROUTX.js → chunk-CYWA2CXR.js} +5 -5
  75. package/dist/{chunk-M5QKGHCR.js → chunk-CYWCKFCL.js} +5 -5
  76. package/dist/chunk-D7FOKIY5.js +173 -0
  77. package/dist/chunk-D7FOKIY5.js.map +1 -0
  78. package/dist/{chunk-4BFRBP45.js → chunk-DEG5ULFJ.js} +2 -2
  79. package/dist/{chunk-JLR3JSTL.js → chunk-DS7MM2ES.js} +3 -3
  80. package/dist/{chunk-2BFLDH5F.js → chunk-EEIROWEG.js} +2 -2
  81. package/dist/{chunk-5QZDMSSN.js → chunk-FKCRGLRD.js} +237 -82
  82. package/dist/{chunk-5QZDMSSN.js.map → chunk-FKCRGLRD.js.map} +1 -1
  83. package/dist/{chunk-X4A62ET7.js → chunk-H2DXDQCQ.js} +17 -17
  84. package/dist/{chunk-27FLVMDC.js → chunk-HLN7RROI.js} +7 -7
  85. package/dist/chunk-IC2TCZJ2.js +54 -0
  86. package/dist/chunk-IC2TCZJ2.js.map +1 -0
  87. package/dist/{chunk-2U2A5HF4.js → chunk-J3XHPFEU.js} +6 -6
  88. package/dist/{chunk-AKUCU7CI.js → chunk-JI5AVXYO.js} +5 -5
  89. package/dist/{chunk-KCJ5H6XQ.js → chunk-JSNVWNGX.js} +2 -2
  90. package/dist/{chunk-7Z2DZNWN.js → chunk-KL4UAXUY.js} +43 -2
  91. package/dist/{chunk-7Z2DZNWN.js.map → chunk-KL4UAXUY.js.map} +1 -1
  92. package/dist/{chunk-FLVL4L3L.js → chunk-KTULX4YO.js} +4 -4
  93. package/dist/{chunk-ILFJ7AVS.js → chunk-LADFWYHY.js} +35 -21
  94. package/dist/chunk-LADFWYHY.js.map +1 -0
  95. package/dist/{chunk-437S3G37.js → chunk-LNQUAJNC.js} +1 -1
  96. package/dist/{chunk-EPGWWCZF.js → chunk-LR5DZ56F.js} +6 -6
  97. package/dist/{chunk-YDSWRD33.js → chunk-N3JDIRBW.js} +4 -4
  98. package/dist/{chunk-OUMTBFSQ.js → chunk-NKCPUHCI.js} +5 -5
  99. package/dist/{chunk-7QWMTOIF.js → chunk-P454KXD6.js} +112 -34
  100. package/dist/chunk-P454KXD6.js.map +1 -0
  101. package/dist/{chunk-DR6PEJU3.js → chunk-PCLIWBGD.js} +44 -59
  102. package/dist/chunk-PCLIWBGD.js.map +1 -0
  103. package/dist/{chunk-BGTAL46Y.js → chunk-PNLFN2PZ.js} +2 -2
  104. package/dist/{chunk-P6SYWE4G.js → chunk-PVUJ6WVV.js} +10 -10
  105. package/dist/{chunk-YPEMNNNY.js → chunk-RGK27ELN.js} +6 -6
  106. package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
  107. package/dist/{chunk-66FLU5BT.js → chunk-SIRPLF54.js} +6 -6
  108. package/dist/{chunk-M7GGMGMZ.js → chunk-SVOHGUMI.js} +1202 -126
  109. package/dist/chunk-SVOHGUMI.js.map +1 -0
  110. package/dist/{chunk-HRCACVHL.js → chunk-TLUDFPZL.js} +2 -2
  111. package/dist/{chunk-2FOD2YY3.js → chunk-UFUKHGZD.js} +236 -103
  112. package/dist/chunk-UFUKHGZD.js.map +1 -0
  113. package/dist/{chunk-2MR3MFQB.js → chunk-UPSAT4PY.js} +2 -2
  114. package/dist/{chunk-4P4626RY.js → chunk-UTRJFDP2.js} +4 -4
  115. package/dist/{chunk-CGS7UKWM.js → chunk-VCW74PNH.js} +2 -2
  116. package/dist/{chunk-5GPPACXK.js → chunk-VSMRDBFT.js} +2 -1
  117. package/dist/{chunk-NG7YBCJO.js → chunk-XCL57M6Q.js} +4 -4
  118. package/dist/{chunk-CLX5VDNY.js → chunk-Y4RKAQZ7.js} +4 -4
  119. package/dist/{chunk-DGYCWCQ7.js → chunk-Y7UNNAZD.js} +2 -2
  120. package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
  121. package/dist/{chunk-XN3TUUX2.js → chunk-YQB32INI.js} +2 -2
  122. package/dist/{cli-EiBBOt3r.d.ts → cli-CqdPA6z9.d.ts} +3 -3
  123. package/dist/cli.d.ts +7 -7
  124. package/dist/cli.js +61 -59
  125. package/dist/compounding/engine.d.ts +2 -2
  126. package/dist/compounding/engine.js +9 -9
  127. package/dist/compounding/preference-consolidator.d.ts +1 -1
  128. package/dist/compression-optimizer.d.ts +1 -1
  129. package/dist/config.d.ts +1 -1
  130. package/dist/config.js +2 -2
  131. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  132. package/dist/connectors/codex-materialize-runner.js +9 -9
  133. package/dist/connectors/codex-materialize.d.ts +1 -1
  134. package/dist/connectors/index.d.ts +1 -1
  135. package/dist/connectors/index.js +13 -11
  136. package/dist/consolidation-provenance-check.d.ts +2 -2
  137. package/dist/consolidation-undo.d.ts +2 -2
  138. package/dist/contradiction/index.d.ts +2 -2
  139. package/dist/contradiction/index.js +4 -4
  140. package/dist/conversation-index/backend.d.ts +1 -1
  141. package/dist/conversation-index/backend.js +2 -2
  142. package/dist/conversation-index/chunker.d.ts +1 -1
  143. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  144. package/dist/conversation-index/indexer.d.ts +1 -1
  145. package/dist/conversation-index/search.d.ts +1 -1
  146. package/dist/dashboard-runtime.js +2 -2
  147. package/dist/day-summary.d.ts +1 -1
  148. package/dist/delinearize.d.ts +1 -1
  149. package/dist/direct-answer-wiring.d.ts +1 -1
  150. package/dist/direct-answer.d.ts +1 -1
  151. package/dist/embedding-fallback.d.ts +1 -1
  152. package/dist/enrichment/index.d.ts +1 -1
  153. package/dist/entity-retrieval.d.ts +2 -2
  154. package/dist/entity-retrieval.js +9 -9
  155. package/dist/entity-schema.d.ts +1 -1
  156. package/dist/explicit-capture.d.ts +5 -5
  157. package/dist/extraction-faithfulness.d.ts +1 -1
  158. package/dist/extraction-judge-telemetry.d.ts +1 -1
  159. package/dist/extraction-judge-training.d.ts +1 -1
  160. package/dist/extraction-judge.d.ts +1 -1
  161. package/dist/extraction.d.ts +1 -1
  162. package/dist/fallback-llm.d.ts +1 -1
  163. package/dist/{first-start-migration-DY6YCRC2.js → first-start-migration-VBJQL6OI.js} +5 -5
  164. package/dist/graph-dashboard-diff.d.ts +1 -1
  165. package/dist/graph-dashboard-key.d.ts +1 -1
  166. package/dist/graph-dashboard-parser.d.ts +1 -1
  167. package/dist/graph-edge-reinforcement.d.ts +1 -1
  168. package/dist/graph-snapshot.d.ts +1 -1
  169. package/dist/graph.d.ts +1 -1
  170. package/dist/identity-continuity.d.ts +1 -1
  171. package/dist/importance.d.ts +1 -1
  172. package/dist/index.d.ts +271 -17
  173. package/dist/index.js +163 -99
  174. package/dist/intent.d.ts +1 -1
  175. package/dist/lcm/engine.d.ts +1 -1
  176. package/dist/lcm/engine.js +2 -2
  177. package/dist/lcm/index.d.ts +1 -1
  178. package/dist/lcm/index.js +2 -2
  179. package/dist/lcm/tools.d.ts +1 -1
  180. package/dist/lifecycle.d.ts +1 -1
  181. package/dist/live-connectors-runner.d.ts +1 -1
  182. package/dist/local-llm.d.ts +1 -1
  183. package/dist/local-model-endpoint.d.ts +1 -1
  184. package/dist/maintenance/memory-governance.d.ts +1 -1
  185. package/dist/maintenance/memory-governance.js +10 -10
  186. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +9 -9
  187. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  188. package/dist/mcp-memory-inspector-app.d.ts +6 -6
  189. package/dist/mcp-read-only-tools.d.ts +16 -0
  190. package/dist/mcp-read-only-tools.js +8 -0
  191. package/dist/mcp-read-only-tools.js.map +1 -0
  192. package/dist/memory-action-policy.d.ts +1 -1
  193. package/dist/memory-cache.d.ts +1 -1
  194. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  195. package/dist/memory-projection-store.d.ts +1 -1
  196. package/dist/memory-provenance.d.ts +1 -1
  197. package/dist/memory-worth-outcomes.d.ts +2 -2
  198. package/dist/models-json.d.ts +1 -1
  199. package/dist/namespaces/migrate.d.ts +3 -3
  200. package/dist/namespaces/migrate.js +19 -19
  201. package/dist/namespaces/principal.d.ts +1 -1
  202. package/dist/namespaces/search.d.ts +1 -1
  203. package/dist/namespaces/search.js +10 -10
  204. package/dist/namespaces/storage.d.ts +3 -3
  205. package/dist/namespaces/storage.js +9 -9
  206. package/dist/native-knowledge.d.ts +1 -1
  207. package/dist/operator-toolkit.d.ts +2 -2
  208. package/dist/operator-toolkit.js +25 -25
  209. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  210. package/dist/orchestration/maintenance.d.ts +2 -2
  211. package/dist/orchestration/maintenance.js +11 -11
  212. package/dist/{orchestrator-B2Y28Z9t.d.ts → orchestrator-CWFrT8AF.d.ts} +91 -60
  213. package/dist/orchestrator.d.ts +5 -5
  214. package/dist/orchestrator.js +85 -83
  215. package/dist/patterns-cli.d.ts +1 -1
  216. package/dist/policy-runtime.d.ts +1 -1
  217. package/dist/provenance.d.ts +1 -1
  218. package/dist/{qmd-Db9mOkzB.d.ts → qmd--fnPMK60.d.ts} +1 -1
  219. package/dist/qmd-preflight.d.ts +2 -2
  220. package/dist/qmd-recall-cache.d.ts +1 -1
  221. package/dist/qmd.d.ts +2 -2
  222. package/dist/recall-disclosure-escalation.d.ts +1 -1
  223. package/dist/recall-explain-renderer.d.ts +1 -1
  224. package/dist/recall-planner-llm.d.ts +1 -1
  225. package/dist/recall-state.d.ts +1 -1
  226. package/dist/recall-tag-filter.d.ts +1 -1
  227. package/dist/recall-timings.d.ts +1 -1
  228. package/dist/recall-xray-cli.d.ts +1 -1
  229. package/dist/recall-xray-renderer.d.ts +1 -1
  230. package/dist/recall-xray.d.ts +1 -1
  231. package/dist/resolve-auth-token.d.ts +1 -1
  232. package/dist/resume-bundles.js +4 -4
  233. package/dist/retrieval-agents.d.ts +2 -2
  234. package/dist/retrieval-tiers.d.ts +1 -1
  235. package/dist/routing/engine.d.ts +1 -1
  236. package/dist/routing/store.d.ts +1 -1
  237. package/dist/schemas.d.ts +22 -22
  238. package/dist/search/document-scanner.js +2 -2
  239. package/dist/search/embed-helper.d.ts +1 -1
  240. package/dist/search/factory.d.ts +1 -1
  241. package/dist/search/factory.js +9 -9
  242. package/dist/search/index.d.ts +1 -1
  243. package/dist/search/index.js +13 -13
  244. package/dist/search/lancedb-backend.d.ts +1 -1
  245. package/dist/search/lancedb-backend.js +3 -3
  246. package/dist/search/meilisearch-backend.d.ts +1 -1
  247. package/dist/search/meilisearch-backend.js +3 -3
  248. package/dist/search/noop-backend.d.ts +1 -1
  249. package/dist/search/orama-backend.d.ts +1 -1
  250. package/dist/search/orama-backend.js +3 -3
  251. package/dist/search/port.d.ts +1 -1
  252. package/dist/search/remote-backend.d.ts +1 -1
  253. package/dist/{semantic-consolidation-BDVIbLRw.d.ts → semantic-consolidation-O5lMZ4LG.d.ts} +1 -1
  254. package/dist/semantic-consolidation.d.ts +2 -2
  255. package/dist/semantic-consolidation.js +11 -11
  256. package/dist/semantic-rule-promotion.js +9 -9
  257. package/dist/semantic-rule-verifier.d.ts +1 -1
  258. package/dist/semantic-rule-verifier.js +10 -10
  259. package/dist/session-observer-bands.d.ts +1 -1
  260. package/dist/session-observer-state.d.ts +1 -1
  261. package/dist/shared-context/manager.d.ts +1 -1
  262. package/dist/signal.d.ts +1 -1
  263. package/dist/storage-C1maCNbc.d.ts +1591 -0
  264. package/dist/storage.d.ts +5 -1239
  265. package/dist/storage.js +8 -8
  266. package/dist/summarizer.d.ts +1 -1
  267. package/dist/summary-snapshot.d.ts +1 -1
  268. package/dist/temporal-supersession.d.ts +2 -2
  269. package/dist/temporal-validity.d.ts +1 -1
  270. package/dist/threading.d.ts +1 -1
  271. package/dist/tier-migration.d.ts +2 -2
  272. package/dist/tier-routing.d.ts +1 -1
  273. package/dist/{tier-stats-TG6GUZ6N.js → tier-stats-5XF4UBRQ.js} +4 -4
  274. package/dist/tokens.d.ts +17 -2
  275. package/dist/tokens.js +3 -1
  276. package/dist/topics.d.ts +1 -1
  277. package/dist/transcript.d.ts +1 -1
  278. package/dist/transfer/backup.js +2 -2
  279. package/dist/transfer/capsule-export.js +3 -3
  280. package/dist/transfer/capsule-import.js +3 -3
  281. package/dist/transfer/types.d.ts +12 -12
  282. package/dist/trust-score-stage.d.ts +1 -1
  283. package/dist/trust-score.d.ts +1 -1
  284. package/dist/{types-ljkd82rn.d.ts → types-CGgcNWCG.d.ts} +31 -1
  285. package/dist/types.d.ts +1 -1
  286. package/dist/utility-runtime.d.ts +1 -1
  287. package/dist/verified-recall.js +10 -10
  288. package/package.json +2 -2
  289. package/src/access-boundary.test.ts +62 -0
  290. package/src/access-boundary.ts +165 -126
  291. package/src/access-http.test.ts +781 -0
  292. package/src/access-http.ts +334 -108
  293. package/src/access-mcp.test.ts +264 -0
  294. package/src/access-mcp.ts +45 -70
  295. package/src/access-operations-batch.ts +35 -12
  296. package/src/access-surface-catalog.test.ts +22 -14
  297. package/src/access-surface-catalog.ts +6 -3
  298. package/src/access-token-capabilities.test.ts +844 -0
  299. package/src/access-token-capabilities.ts +391 -0
  300. package/src/chat/chat-mcp.test.ts +232 -0
  301. package/src/chat/chat-session.ts +34 -0
  302. package/src/cli-wearables-fuse.test.ts +197 -0
  303. package/src/cli.ts +25 -0
  304. package/src/index.ts +14 -0
  305. package/src/mcp-read-only-tools.ts +61 -0
  306. package/src/storage.ts +16 -0
  307. package/src/tokens.ts +32 -3
  308. package/src/wearables/cli.test.ts +102 -0
  309. package/src/wearables/cli.ts +65 -0
  310. package/src/wearables/config.test.ts +32 -0
  311. package/src/wearables/config.ts +52 -0
  312. package/src/wearables/day-store.test.ts +246 -0
  313. package/src/wearables/day-store.ts +272 -3
  314. package/src/wearables/fusion/cluster.ts +230 -0
  315. package/src/wearables/fusion/fuse.ts +218 -0
  316. package/src/wearables/fusion/fusion.test.ts +2575 -0
  317. package/src/wearables/fusion/index.ts +50 -0
  318. package/src/wearables/fusion/reconcile.ts +831 -0
  319. package/src/wearables/fusion/reconstruct.ts +251 -0
  320. package/src/wearables/fusion/store.ts +480 -0
  321. package/src/wearables/fusion/types.ts +228 -0
  322. package/src/wearables/index.ts +41 -0
  323. package/src/wearables/service.test.ts +1030 -2
  324. package/src/wearables/service.ts +258 -16
  325. package/src/wearables/speakers.test.ts +128 -0
  326. package/src/wearables/speakers.ts +58 -4
  327. package/src/wearables/storage-io.test.ts +146 -0
  328. package/src/wearables/types.ts +31 -0
  329. package/dist/chunk-2FOD2YY3.js.map +0 -1
  330. package/dist/chunk-7QWMTOIF.js.map +0 -1
  331. package/dist/chunk-DR6PEJU3.js.map +0 -1
  332. package/dist/chunk-E2SPGGUI.js.map +0 -1
  333. package/dist/chunk-FZGGZAAU.js.map +0 -1
  334. package/dist/chunk-ILFJ7AVS.js.map +0 -1
  335. package/dist/chunk-M7GGMGMZ.js.map +0 -1
  336. package/dist/chunk-M7XQSUBB.js.map +0 -1
  337. /package/dist/{capsule-crypto-CZJSLEFG.js.map → access-token-capabilities.js.map} +0 -0
  338. /package/dist/{auto-sync-HMHQHUAG.js.map → auto-sync-4A5YBWYO.js.map} +0 -0
  339. /package/dist/{chunk-6ZIETXZ2.js.map → chunk-23JFRB73.js.map} +0 -0
  340. /package/dist/{chunk-IC6QX5TC.js.map → chunk-3XIY7MDQ.js.map} +0 -0
  341. /package/dist/{chunk-LOR6ZM5R.js.map → chunk-4LV23CHK.js.map} +0 -0
  342. /package/dist/{chunk-AER6MT24.js.map → chunk-6SXVCD7W.js.map} +0 -0
  343. /package/dist/{chunk-OHDJ34CC.js.map → chunk-72WG5QKN.js.map} +0 -0
  344. /package/dist/{chunk-CZAZX7JP.js.map → chunk-7MW3CVLD.js.map} +0 -0
  345. /package/dist/{chunk-CXLNZXG5.js.map → chunk-7XGSIUP5.js.map} +0 -0
  346. /package/dist/{chunk-CZAA5N5S.js.map → chunk-AYZJID4S.js.map} +0 -0
  347. /package/dist/{chunk-6BISDEN6.js.map → chunk-BEM4D2QG.js.map} +0 -0
  348. /package/dist/{chunk-ZPOROUTX.js.map → chunk-CYWA2CXR.js.map} +0 -0
  349. /package/dist/{chunk-M5QKGHCR.js.map → chunk-CYWCKFCL.js.map} +0 -0
  350. /package/dist/{chunk-4BFRBP45.js.map → chunk-DEG5ULFJ.js.map} +0 -0
  351. /package/dist/{chunk-JLR3JSTL.js.map → chunk-DS7MM2ES.js.map} +0 -0
  352. /package/dist/{chunk-2BFLDH5F.js.map → chunk-EEIROWEG.js.map} +0 -0
  353. /package/dist/{chunk-X4A62ET7.js.map → chunk-H2DXDQCQ.js.map} +0 -0
  354. /package/dist/{chunk-27FLVMDC.js.map → chunk-HLN7RROI.js.map} +0 -0
  355. /package/dist/{chunk-2U2A5HF4.js.map → chunk-J3XHPFEU.js.map} +0 -0
  356. /package/dist/{chunk-AKUCU7CI.js.map → chunk-JI5AVXYO.js.map} +0 -0
  357. /package/dist/{chunk-KCJ5H6XQ.js.map → chunk-JSNVWNGX.js.map} +0 -0
  358. /package/dist/{chunk-FLVL4L3L.js.map → chunk-KTULX4YO.js.map} +0 -0
  359. /package/dist/{chunk-437S3G37.js.map → chunk-LNQUAJNC.js.map} +0 -0
  360. /package/dist/{chunk-EPGWWCZF.js.map → chunk-LR5DZ56F.js.map} +0 -0
  361. /package/dist/{chunk-YDSWRD33.js.map → chunk-N3JDIRBW.js.map} +0 -0
  362. /package/dist/{chunk-OUMTBFSQ.js.map → chunk-NKCPUHCI.js.map} +0 -0
  363. /package/dist/{chunk-BGTAL46Y.js.map → chunk-PNLFN2PZ.js.map} +0 -0
  364. /package/dist/{chunk-P6SYWE4G.js.map → chunk-PVUJ6WVV.js.map} +0 -0
  365. /package/dist/{chunk-YPEMNNNY.js.map → chunk-RGK27ELN.js.map} +0 -0
  366. /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
  367. /package/dist/{chunk-66FLU5BT.js.map → chunk-SIRPLF54.js.map} +0 -0
  368. /package/dist/{chunk-HRCACVHL.js.map → chunk-TLUDFPZL.js.map} +0 -0
  369. /package/dist/{chunk-2MR3MFQB.js.map → chunk-UPSAT4PY.js.map} +0 -0
  370. /package/dist/{chunk-4P4626RY.js.map → chunk-UTRJFDP2.js.map} +0 -0
  371. /package/dist/{chunk-CGS7UKWM.js.map → chunk-VCW74PNH.js.map} +0 -0
  372. /package/dist/{chunk-5GPPACXK.js.map → chunk-VSMRDBFT.js.map} +0 -0
  373. /package/dist/{chunk-NG7YBCJO.js.map → chunk-XCL57M6Q.js.map} +0 -0
  374. /package/dist/{chunk-CLX5VDNY.js.map → chunk-Y4RKAQZ7.js.map} +0 -0
  375. /package/dist/{chunk-DGYCWCQ7.js.map → chunk-Y7UNNAZD.js.map} +0 -0
  376. /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
  377. /package/dist/{chunk-XN3TUUX2.js.map → chunk-YQB32INI.js.map} +0 -0
  378. /package/dist/{first-start-migration-DY6YCRC2.js.map → first-start-migration-VBJQL6OI.js.map} +0 -0
  379. /package/dist/{tier-stats-TG6GUZ6N.js.map → tier-stats-5XF4UBRQ.js.map} +0 -0
@@ -0,0 +1,844 @@
1
+ /**
2
+ * Comprehensive tests for per-token capabilities (issue #1837).
3
+ *
4
+ * Locks in EVERY semantic of the capability system:
5
+ * - legacy token (absent record) ⇒ full access
6
+ * - present ops allow-list ⇒ default-deny, listed-only
7
+ * - explicitly empty ops ⇒ deny all (fail-closed)
8
+ * - namespaces allow-list ⇒ in-scope only
9
+ * - monitor-scoped token ⇒ monitoring ops allowed, content + admin denied
10
+ * - malformed input at mint ⇒ rejected
11
+ * - new token without flags ⇒ explicit versioned unrestricted record
12
+ * - capabilities survive persist + reload round-trip
13
+ * - boundary run() enforces for every registered op (no bypass)
14
+ */
15
+
16
+ import assert from "node:assert/strict";
17
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
18
+ import os from "node:os";
19
+ import path from "node:path";
20
+ import test from "node:test";
21
+ import { z } from "zod";
22
+
23
+ import {
24
+ OPERATION_NAMES,
25
+ type OperationContext,
26
+ __resetRegistryForTest,
27
+ defineOperation,
28
+ getOperation,
29
+ listRegisteredOperations,
30
+ } from "./access-boundary.js";
31
+ import { EngramAccessForbiddenError } from "./access-errors.js";
32
+ import { EngramAccessInputError, type EngramAccessService } from "./access-service.js";
33
+ import {
34
+ TOKEN_CAPABILITIES_VERSION,
35
+ type TokenCapabilities,
36
+ assertFleetWideOperationAllowed,
37
+ assertNamespaceAllowed,
38
+ assertOperationAllowed,
39
+ capabilityAllowsNamespace,
40
+ capabilityAllowsOp,
41
+ enforceNamespaceAllowList,
42
+ resolveEffectiveNamespace,
43
+ isCapabilityRestricted,
44
+ isValidNamespaceValue,
45
+ normalizeCapabilities,
46
+ tokenCapabilityStore,
47
+ validateCapabilitiesForMint,
48
+ } from "./access-token-capabilities.js";
49
+ import { buildTokenEntry, generateToken, loadTokenStore } from "./tokens.js";
50
+
51
+ // Importing access-operations registers the boundary operations as a side
52
+ // effect — we enumerate them in the critical-coverage test below.
53
+ import "./access-operations.js";
54
+ import { readPair, writePair } from "./contradiction/contradiction-review.js";
55
+ import { parseConfig } from "./config.js";
56
+ import type { StorageManager } from "./storage.js";
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Test fixtures
60
+ // ---------------------------------------------------------------------------
61
+
62
+ function mockService(): EngramAccessService {
63
+ return {} as unknown as EngramAccessService;
64
+ }
65
+
66
+ function mockCtx(): OperationContext {
67
+ return { service: mockService(), authenticatedPrincipal: undefined };
68
+ }
69
+
70
+ async function makeTempTokenPath(): Promise<{ dir: string; tokensPath: string }> {
71
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-capabilities-"));
72
+ return { dir, tokensPath: path.join(dir, "tokens.json") };
73
+ }
74
+
75
+ // ===========================================================================
76
+ // 1. Legacy token (absent capabilities record) ⇒ full access
77
+ // ===========================================================================
78
+
79
+ test("legacy token: absent record is unrestricted on every axis", () => {
80
+ const caps: TokenCapabilities | undefined = undefined;
81
+ assert.equal(capabilityAllowsOp(caps, "memory_store"), true);
82
+ assert.equal(capabilityAllowsOp(caps, "recall"), true);
83
+ assert.equal(capabilityAllowsOp(caps, "anything_at_all"), true);
84
+ assert.equal(capabilityAllowsNamespace(caps, "default"), true);
85
+ assert.equal(capabilityAllowsNamespace(caps, "other"), true);
86
+ assert.equal(isCapabilityRestricted(caps), false);
87
+ });
88
+
89
+ test("legacy token: assertOperationAllowed / assertNamespaceAllowed are no-ops", () => {
90
+ assert.doesNotThrow(() => assertOperationAllowed(undefined, "memory_store"));
91
+ assert.doesNotThrow(() => assertNamespaceAllowed(undefined, "default"));
92
+ assert.doesNotThrow(() => assertNamespaceAllowed(undefined, undefined));
93
+ });
94
+
95
+ test("legacy token: null record is also treated as unrestricted", () => {
96
+ assert.equal(capabilityAllowsOp(null, "recall"), true);
97
+ assert.equal(isCapabilityRestricted(null), false);
98
+ });
99
+
100
+ // ===========================================================================
101
+ // 2. Present ops allow-list ⇒ listed op permitted, unlisted rejected
102
+ // ===========================================================================
103
+
104
+ test("ops allow-list: validateCapabilitiesForMint accepts valid ops", () => {
105
+ const caps = validateCapabilitiesForMint({ ops: ["recall", "memory_get"] }, OPERATION_NAMES);
106
+ assert.equal(caps.version, TOKEN_CAPABILITIES_VERSION);
107
+ assert.deepEqual(caps.ops, ["memory_get", "recall"]); // deduped + sorted
108
+ });
109
+
110
+ test("ops allow-list: listed op permitted, unlisted rejected", () => {
111
+ const caps = validateCapabilitiesForMint({ ops: ["recall"] }, OPERATION_NAMES);
112
+ assert.equal(capabilityAllowsOp(caps, "recall"), true);
113
+ assert.equal(capabilityAllowsOp(caps, "memory_store"), false);
114
+ });
115
+
116
+ test("ops allow-list: assertOperationAllowed throws EngramAccessForbiddenError for unlisted op", () => {
117
+ const caps = validateCapabilitiesForMint({ ops: ["recall"] }, OPERATION_NAMES);
118
+ assert.doesNotThrow(() => assertOperationAllowed(caps, "recall"));
119
+ assert.throws(
120
+ () => assertOperationAllowed(caps, "memory_store"),
121
+ (err: unknown) => err instanceof EngramAccessForbiddenError && /memory_store/.test(err.message)
122
+ );
123
+ });
124
+
125
+ test("ops allow-list: isCapabilityRestricted is true", () => {
126
+ const caps = validateCapabilitiesForMint({ ops: ["recall"] }, OPERATION_NAMES);
127
+ assert.equal(isCapabilityRestricted(caps), true);
128
+ });
129
+
130
+ // ===========================================================================
131
+ // 3. Explicitly EMPTY ops array ⇒ deny ALL ops (fail-closed)
132
+ // ===========================================================================
133
+
134
+ test("empty ops: validateCapabilitiesForMint preserves empty array", () => {
135
+ const caps = validateCapabilitiesForMint({ ops: [] }, OPERATION_NAMES);
136
+ assert.deepEqual(caps.ops, []);
137
+ assert.equal(isCapabilityRestricted(caps), true);
138
+ });
139
+
140
+ test("empty ops: every op is denied (fail-closed, NOT unrestricted)", () => {
141
+ const caps = validateCapabilitiesForMint({ ops: [] }, OPERATION_NAMES);
142
+ // Sample across the catalog — every one must be denied.
143
+ for (const opName of ["recall", "memory_store", "memory_get", "wearables_status"]) {
144
+ assert.equal(capabilityAllowsOp(caps, opName), false, `${opName} must be denied`);
145
+ }
146
+ assert.throws(() => assertOperationAllowed(caps, "recall"), EngramAccessForbiddenError);
147
+ });
148
+
149
+ // ===========================================================================
150
+ // 4. Namespaces allow-list ⇒ in-scope permitted, out-of-scope rejected
151
+ // ===========================================================================
152
+
153
+ test("namespaces allow-list: validateCapabilitiesForMint accepts valid namespaces", () => {
154
+ const caps = validateCapabilitiesForMint({ namespaces: ["default", "project-x"] }, OPERATION_NAMES);
155
+ assert.deepEqual(caps.namespaces, ["default", "project-x"]); // sorted
156
+ assert.equal(caps.ops, undefined); // ops axis absent ⇒ unrestricted
157
+ });
158
+
159
+ test("namespaces allow-list: in-scope permitted, out-of-scope rejected", () => {
160
+ const caps = validateCapabilitiesForMint({ namespaces: ["default"] }, OPERATION_NAMES);
161
+ assert.equal(capabilityAllowsNamespace(caps, "default"), true);
162
+ assert.equal(capabilityAllowsNamespace(caps, "other"), false);
163
+ });
164
+
165
+ test("namespaces allow-list: scoped token rejects undefined namespace", () => {
166
+ const caps = validateCapabilitiesForMint({ namespaces: ["default"] }, OPERATION_NAMES);
167
+ assert.equal(capabilityAllowsNamespace(caps, undefined), false);
168
+ assert.throws(() => assertNamespaceAllowed(caps, undefined), EngramAccessForbiddenError);
169
+ });
170
+
171
+ test("namespaces allow-list: out-of-scope throws EngramAccessForbiddenError", () => {
172
+ const caps = validateCapabilitiesForMint({ namespaces: ["default"] }, OPERATION_NAMES);
173
+ assert.doesNotThrow(() => assertNamespaceAllowed(caps, "default"));
174
+ assert.throws(
175
+ () => assertNamespaceAllowed(caps, "secret"),
176
+ (err: unknown) => err instanceof EngramAccessForbiddenError && /secret/.test(err.message)
177
+ );
178
+ });
179
+
180
+ test("empty namespaces: deny all (fail-closed)", () => {
181
+ const caps = validateCapabilitiesForMint({ namespaces: [] }, OPERATION_NAMES);
182
+ assert.deepEqual(caps.namespaces, []);
183
+ assert.equal(capabilityAllowsNamespace(caps, "default"), false);
184
+ assert.equal(capabilityAllowsNamespace(caps, undefined), false);
185
+ });
186
+
187
+ // -------------------------------------------------------------------------
188
+ // 4b. Effective-namespace chokepoint (issue #1850) — the SINGLE function the
189
+ // HTTP query/body routes, MCP dispatch, and id-loaded contradiction routes
190
+ // all route through. Models undefined→default mapping, fail-closed, and
191
+ // no-op-for-unrestricted.
192
+ // -------------------------------------------------------------------------
193
+
194
+ test("resolveEffectiveNamespace: present value wins; empty/undefined → default", () => {
195
+ assert.equal(resolveEffectiveNamespace("team", "default"), "team");
196
+ assert.equal(resolveEffectiveNamespace("", "default"), "default", "empty string is treated as absent");
197
+ assert.equal(resolveEffectiveNamespace(undefined, "default"), "default");
198
+ assert.equal(resolveEffectiveNamespace(undefined, undefined), undefined, "no value and no default → undefined");
199
+ });
200
+
201
+ test("enforceNamespaceAllowList: no-op for unrestricted tokens (absent + explicit-unrestricted)", () => {
202
+ const scoped = validateCapabilitiesForMint({ namespaces: ["default"] }, OPERATION_NAMES);
203
+ // Legacy token (absent capabilities record) — unrestricted.
204
+ assert.doesNotThrow(() => enforceNamespaceAllowList(undefined, "anything", "default"));
205
+ assert.doesNotThrow(() => enforceNamespaceAllowList(null, undefined, "default"));
206
+ // Explicit-unrestricted record ({version:1}, no namespaces axis).
207
+ const explicitUnrestricted = validateCapabilitiesForMint(undefined, OPERATION_NAMES);
208
+ assert.doesNotThrow(() => enforceNamespaceAllowList(explicitUnrestricted, "anything", "default"));
209
+ // Sanity: the scoped token IS restricted (control for the tests below).
210
+ assert.equal(isCapabilityRestricted(scoped), true);
211
+ });
212
+
213
+ test("enforceNamespaceAllowList: scoped token — explicit allowed namespace passes", () => {
214
+ const caps = validateCapabilitiesForMint({ namespaces: ["ns_a", "default"] }, OPERATION_NAMES);
215
+ assert.doesNotThrow(() => enforceNamespaceAllowList(caps, "ns_a", "default"));
216
+ });
217
+
218
+ test("enforceNamespaceAllowList: scoped token — explicit unlisted namespace rejected (fail closed)", () => {
219
+ const caps = validateCapabilitiesForMint({ namespaces: ["ns_a"] }, OPERATION_NAMES);
220
+ assert.throws(
221
+ () => enforceNamespaceAllowList(caps, "ns_b", "default"),
222
+ (err: unknown) => err instanceof EngramAccessForbiddenError && /ns_b/.test(err.message),
223
+ );
224
+ });
225
+
226
+ test("enforceNamespaceAllowList: undefined maps to default — allow-list INCLUDING default passes (legacy pair fix)", () => {
227
+ // The round-4 bug: a legacy pair carries namespace:undefined, which
228
+ // downstream storage maps to the server default. A scoped token whose
229
+ // allow-list INCLUDES the default must be permitted; the prior
230
+ // assertNamespaceAllowed(caps, undefined) wrongly denied it.
231
+ const caps = validateCapabilitiesForMint({ namespaces: ["default"] }, OPERATION_NAMES);
232
+ assert.doesNotThrow(() => enforceNamespaceAllowList(caps, undefined, "default"));
233
+ });
234
+
235
+ test("enforceNamespaceAllowList: undefined maps to default — allow-list NOT including default rejected", () => {
236
+ const caps = validateCapabilitiesForMint({ namespaces: ["ns_a"] }, OPERATION_NAMES);
237
+ assert.throws(
238
+ () => enforceNamespaceAllowList(caps, undefined, "default"),
239
+ (err: unknown) => err instanceof EngramAccessForbiddenError && /server default namespace is not permitted/.test(err.message),
240
+ );
241
+ });
242
+
243
+ test("enforceNamespaceAllowList: absent server default + scoped token → fail closed", () => {
244
+ // No request namespace AND no configured default ⇒ an undefined effective
245
+ // namespace cannot satisfy any allow-list; fail closed rather than let an
246
+ // unconfigured server silently admit the default tenant.
247
+ const caps = validateCapabilitiesForMint({ namespaces: ["default"] }, OPERATION_NAMES);
248
+ assert.throws(
249
+ () => enforceNamespaceAllowList(caps, undefined, undefined),
250
+ EngramAccessForbiddenError,
251
+ );
252
+ });
253
+
254
+ // ===========================================================================
255
+ // 5. Monitor-scoped token — monitoring ops allowed, content + admin denied
256
+ // ===========================================================================
257
+
258
+ const MONITOR_OPS = ["maintenance_status", "quality_status", "procedural_stats", "recall_timings"];
259
+
260
+ test("monitor-scoped token: only monitoring ops are permitted", () => {
261
+ const caps = validateCapabilitiesForMint({ ops: MONITOR_OPS }, OPERATION_NAMES);
262
+ for (const op of MONITOR_OPS) {
263
+ assert.equal(capabilityAllowsOp(caps, op), true, `${op} must be allowed`);
264
+ }
265
+ // Memory-content ops must be denied.
266
+ for (const denied of ["memory_store", "memory_get", "memory_search", "recall", "observe"]) {
267
+ assert.equal(capabilityAllowsOp(caps, denied), false, `${denied} must be denied`);
268
+ }
269
+ });
270
+
271
+ test("monitor-scoped token: isCapabilityRestricted → true (operator gate would reject)", () => {
272
+ const caps = validateCapabilitiesForMint({ ops: MONITOR_OPS }, OPERATION_NAMES);
273
+ // requireOperatorToken() checks isCapabilityRestricted — a scoped token
274
+ // must be flagged so operator/admin routes deny it.
275
+ assert.equal(isCapabilityRestricted(caps), true);
276
+ });
277
+
278
+ test("monitor-scoped token: content op throws EngramAccessForbiddenError", () => {
279
+ const caps = validateCapabilitiesForMint({ ops: MONITOR_OPS }, OPERATION_NAMES);
280
+ assert.doesNotThrow(() => assertOperationAllowed(caps, "maintenance_status"));
281
+ assert.throws(() => assertOperationAllowed(caps, "memory_store"), EngramAccessForbiddenError);
282
+ });
283
+
284
+ // ===========================================================================
285
+ // 6. Malformed input at mint ⇒ rejected (validateCapabilitiesForMint throws)
286
+ // ===========================================================================
287
+
288
+ test("malformed mint: unknown op name is rejected", () => {
289
+ assert.throws(() => validateCapabilitiesForMint({ ops: ["bogus_op"] }, OPERATION_NAMES), /unknown operation name/i);
290
+ });
291
+
292
+ test("malformed mint: non-array ops is rejected", () => {
293
+ assert.throws(() => validateCapabilitiesForMint({ ops: "not-an-array" }, OPERATION_NAMES), /must be an array/i);
294
+ });
295
+
296
+ test("malformed mint: non-string element in ops is rejected", () => {
297
+ assert.throws(
298
+ () => validateCapabilitiesForMint({ ops: [123] }, OPERATION_NAMES),
299
+ /must be an array of non-empty strings/i
300
+ );
301
+ });
302
+
303
+ test("malformed mint: traversal namespace (../x) is rejected", () => {
304
+ assert.throws(() => validateCapabilitiesForMint({ namespaces: ["../x"] }, OPERATION_NAMES), /malformed namespace/i);
305
+ });
306
+
307
+ test("malformed mint: path-separator namespace (a/b) is rejected", () => {
308
+ assert.throws(() => validateCapabilitiesForMint({ namespaces: ["a/b"] }, OPERATION_NAMES), /malformed namespace/i);
309
+ });
310
+
311
+ test("malformed mint: backslash namespace is rejected", () => {
312
+ assert.throws(() => validateCapabilitiesForMint({ namespaces: ["a\\b"] }, OPERATION_NAMES), /malformed namespace/i);
313
+ });
314
+
315
+ test("malformed mint: non-object capabilities is rejected", () => {
316
+ assert.throws(() => validateCapabilitiesForMint("oops", OPERATION_NAMES), /must be an object/i);
317
+ assert.throws(() => validateCapabilitiesForMint([1, 2], OPERATION_NAMES), /must be an object/i);
318
+ });
319
+
320
+ test("malformed mint: wrong version is rejected", () => {
321
+ assert.throws(
322
+ () => validateCapabilitiesForMint({ version: 99, ops: ["recall"] }, OPERATION_NAMES),
323
+ /version must be/i
324
+ );
325
+ });
326
+
327
+ test("isValidNamespaceValue: accepts simple identifiers, rejects traversal/separators", () => {
328
+ assert.equal(isValidNamespaceValue("default"), true);
329
+ assert.equal(isValidNamespaceValue("project-x"), true);
330
+ assert.equal(isValidNamespaceValue("a.b.c"), true); // dots allowed
331
+ assert.equal(isValidNamespaceValue("../x"), false);
332
+ assert.equal(isValidNamespaceValue("a/b"), false);
333
+ assert.equal(isValidNamespaceValue("a\\b"), false);
334
+ assert.equal(isValidNamespaceValue("has space"), false);
335
+ assert.equal(isValidNamespaceValue(""), false);
336
+ assert.equal(isValidNamespaceValue(42), false);
337
+ });
338
+
339
+ // ===========================================================================
340
+ // 7. New token without flags ⇒ explicit versioned unrestricted record
341
+ // ===========================================================================
342
+
343
+ test("validateCapabilitiesForMint(undefined) yields explicit {version:1} (not omitted)", () => {
344
+ const caps = validateCapabilitiesForMint(undefined, OPERATION_NAMES);
345
+ assert.equal(caps.version, TOKEN_CAPABILITIES_VERSION);
346
+ assert.equal(caps.ops, undefined); // unrestricted on ops axis
347
+ assert.equal(caps.namespaces, undefined); // unrestricted on namespaces axis
348
+ assert.equal(isCapabilityRestricted(caps), false);
349
+ });
350
+
351
+ test("validateCapabilitiesForMint(null) yields explicit {version:1}", () => {
352
+ const caps = validateCapabilitiesForMint(null, OPERATION_NAMES);
353
+ assert.equal(caps.version, TOKEN_CAPABILITIES_VERSION);
354
+ assert.equal(isCapabilityRestricted(caps), false);
355
+ });
356
+
357
+ test("buildTokenEntry without capabilities records explicit {version:1}", () => {
358
+ const entry = buildTokenEntry("test-connector");
359
+ assert.ok(entry.capabilities, "new token must carry an explicit capabilities record");
360
+ assert.equal(entry.capabilities?.version, TOKEN_CAPABILITIES_VERSION);
361
+ assert.equal(entry.capabilities?.ops, undefined);
362
+ assert.equal(entry.capabilities?.namespaces, undefined);
363
+ assert.equal(isCapabilityRestricted(entry.capabilities), false);
364
+ // Distinguishable from legacy: legacy has capabilities === undefined,
365
+ // new-unrestricted has capabilities === {version:1}.
366
+ assert.notEqual(entry.capabilities, undefined);
367
+ });
368
+
369
+ test("explicit-unrestricted token still has full access", () => {
370
+ const caps = validateCapabilitiesForMint(undefined, OPERATION_NAMES);
371
+ assert.equal(capabilityAllowsOp(caps, "memory_store"), true);
372
+ assert.equal(capabilityAllowsOp(caps, "recall"), true);
373
+ assert.equal(capabilityAllowsNamespace(caps, "anything"), true);
374
+ });
375
+
376
+ // ===========================================================================
377
+ // 8. Capabilities survive token-store PERSIST + RELOAD round-trip
378
+ // ===========================================================================
379
+
380
+ test("persist + reload: scoped capabilities survive round-trip", async () => {
381
+ const { dir, tokensPath } = await makeTempTokenPath();
382
+ try {
383
+ generateToken(
384
+ "scoped-rt",
385
+ tokensPath,
386
+ validateCapabilitiesForMint({ ops: ["recall", "memory_get"], namespaces: ["default"] }, OPERATION_NAMES)
387
+ );
388
+
389
+ const store = loadTokenStore(tokensPath);
390
+ const entry = store.tokens.find((t) => t.connector === "scoped-rt");
391
+ assert.ok(entry, "scoped token not found after reload");
392
+ assert.ok(entry?.capabilities, "capabilities record must survive reload");
393
+ assert.equal(entry?.capabilities?.version, TOKEN_CAPABILITIES_VERSION);
394
+ assert.deepEqual(entry?.capabilities?.ops, ["memory_get", "recall"]);
395
+ assert.deepEqual(entry?.capabilities?.namespaces, ["default"]);
396
+ } finally {
397
+ await rm(dir, { recursive: true, force: true });
398
+ }
399
+ });
400
+
401
+ test("persist + reload: explicit-unrestricted marker survives round-trip", async () => {
402
+ const { dir, tokensPath } = await makeTempTokenPath();
403
+ try {
404
+ generateToken("plain-rt", tokensPath);
405
+
406
+ const store = loadTokenStore(tokensPath);
407
+ const entry = store.tokens.find((t) => t.connector === "plain-rt");
408
+ assert.ok(entry);
409
+ assert.ok(entry?.capabilities, "explicit {version:1} must survive reload");
410
+ assert.equal(entry?.capabilities?.version, TOKEN_CAPABILITIES_VERSION);
411
+ assert.equal(entry?.capabilities?.ops, undefined);
412
+ } finally {
413
+ await rm(dir, { recursive: true, force: true });
414
+ }
415
+ });
416
+
417
+ test("persist + reload: legacy token (no capabilities field) stays undefined", async () => {
418
+ const { dir, tokensPath } = await makeTempTokenPath();
419
+ try {
420
+ await writeFile(
421
+ tokensPath,
422
+ JSON.stringify({
423
+ tokens: [{ token: "remnic_oc_legacy_xx", connector: "legacy", createdAt: "2024-01-01T00:00:00.000Z" }],
424
+ }),
425
+ "utf8"
426
+ );
427
+
428
+ const store = loadTokenStore(tokensPath);
429
+ assert.equal(store.tokens.length, 1);
430
+ assert.equal(store.tokens[0].capabilities, undefined, "legacy entry must have NO capabilities record");
431
+ } finally {
432
+ await rm(dir, { recursive: true, force: true });
433
+ }
434
+ });
435
+
436
+ test("persist + reload: deny-all (empty ops) survives round-trip", async () => {
437
+ const { dir, tokensPath } = await makeTempTokenPath();
438
+ try {
439
+ generateToken("denyall-rt", tokensPath, validateCapabilitiesForMint({ ops: [] }, OPERATION_NAMES));
440
+
441
+ const store = loadTokenStore(tokensPath);
442
+ const entry = store.tokens.find((t) => t.connector === "denyall-rt");
443
+ assert.ok(entry?.capabilities);
444
+ assert.deepEqual(entry?.capabilities?.ops, []); // still empty, NOT coerced to undefined
445
+ assert.equal(capabilityAllowsOp(entry?.capabilities, "recall"), false);
446
+ } finally {
447
+ await rm(dir, { recursive: true, force: true });
448
+ }
449
+ });
450
+
451
+ // ===========================================================================
452
+ // 9. normalizeCapabilities (load-time normalization)
453
+ // ===========================================================================
454
+
455
+ test("normalizeCapabilities: absent ⇒ undefined (legacy)", () => {
456
+ assert.equal(normalizeCapabilities(undefined), undefined);
457
+ assert.equal(normalizeCapabilities(null), undefined);
458
+ });
459
+
460
+ test("normalizeCapabilities: {version:1} ⇒ explicit-unrestricted", () => {
461
+ const caps = normalizeCapabilities({ version: 1 });
462
+ assert.deepEqual(caps, { version: 1 });
463
+ assert.equal(isCapabilityRestricted(caps), false);
464
+ });
465
+
466
+ test("normalizeCapabilities: preserves ops and namespaces", () => {
467
+ const caps = normalizeCapabilities({ version: 1, ops: ["recall"], namespaces: ["default"] });
468
+ assert.deepEqual(caps, { version: 1, ops: ["recall"], namespaces: ["default"] });
469
+ });
470
+
471
+ test("normalizeCapabilities: rejects bad version", () => {
472
+ assert.throws(() => normalizeCapabilities({ version: 2 }), /version must be/i);
473
+ });
474
+
475
+ test("normalizeCapabilities: rejects non-object", () => {
476
+ assert.throws(() => normalizeCapabilities("bad"), /must be an object/i);
477
+ assert.throws(() => normalizeCapabilities([1]), /must be an object/i);
478
+ });
479
+ // ===========================================================================
480
+ // 9b. Mint/load version symmetry (issue #1837 r11) — validateCapabilitiesForMint
481
+ // and normalizeCapabilities MUST treat the version marker identically:
482
+ // an absent/undefined version coerces to the current default (1) in both,
483
+ // and an explicitly-wrong version is rejected by both. The prior asymmetry
484
+ // let {version: undefined} pass mint (coerced to {version:1}) yet THROW on
485
+ // load — a record that mint-validated had to also load-normalize.
486
+ // ===========================================================================
487
+
488
+ test("version symmetry: undefined version coerces to 1 in BOTH mint and load", () => {
489
+ const minted = validateCapabilitiesForMint({ version: undefined, ops: ["recall"] }, OPERATION_NAMES);
490
+ assert.deepEqual(minted, { version: TOKEN_CAPABILITIES_VERSION, ops: ["recall"] });
491
+ // The minted record must load-normalize without throwing (the original bug).
492
+ const loaded = normalizeCapabilities(minted);
493
+ assert.deepEqual(loaded, { version: TOKEN_CAPABILITIES_VERSION, ops: ["recall"] });
494
+ });
495
+
496
+ test("version symmetry: absent version key coerces to 1 in BOTH mint and load", () => {
497
+ const minted = validateCapabilitiesForMint({ ops: ["recall"] }, OPERATION_NAMES);
498
+ assert.deepEqual(minted, { version: TOKEN_CAPABILITIES_VERSION, ops: ["recall"] });
499
+ // A present record with NO version key loads as version 1 (no throw).
500
+ const loaded = normalizeCapabilities({ ops: ["recall"] });
501
+ assert.deepEqual(loaded, { version: TOKEN_CAPABILITIES_VERSION, ops: ["recall"] });
502
+ });
503
+
504
+ test("version symmetry: undefined-version record round-trips mint → load", () => {
505
+ // The mint output is what is persisted; feeding it back through the load
506
+ // normalizer must reproduce it exactly (mint ⇄ load equivalence).
507
+ const minted = validateCapabilitiesForMint(
508
+ { version: undefined, namespaces: ["default"] },
509
+ OPERATION_NAMES,
510
+ );
511
+ const reloaded = normalizeCapabilities(minted);
512
+ assert.deepEqual(reloaded, minted);
513
+ assert.deepEqual(reloaded, { version: TOKEN_CAPABILITIES_VERSION, namespaces: ["default"] });
514
+ });
515
+
516
+ test("version symmetry: explicitly-wrong versions (2, 'x', 0) rejected identically by mint AND load", () => {
517
+ for (const badVersion of [2, "x", 0]) {
518
+ assert.throws(
519
+ () => validateCapabilitiesForMint({ version: badVersion }, OPERATION_NAMES),
520
+ /version must be/i,
521
+ `mint must reject version ${JSON.stringify(badVersion)}`,
522
+ );
523
+ assert.throws(
524
+ () => normalizeCapabilities({ version: badVersion }),
525
+ /version must be/i,
526
+ `load must reject version ${JSON.stringify(badVersion)}`,
527
+ );
528
+ }
529
+ });
530
+
531
+ test("version symmetry: version 1 is accepted identically by mint AND load", () => {
532
+ const minted = validateCapabilitiesForMint({ version: 1, ops: ["recall"] }, OPERATION_NAMES);
533
+ const loaded = normalizeCapabilities({ version: 1, ops: ["recall"] });
534
+ assert.deepEqual(minted, loaded);
535
+ assert.deepEqual(loaded, { version: TOKEN_CAPABILITIES_VERSION, ops: ["recall"] });
536
+ });
537
+
538
+ // ===========================================================================
539
+ // 10. CRITICAL COVERAGE — boundary run() enforces for EVERY registered op
540
+ // These tests run FIRST in section 10, before any test that resets the
541
+ // registry, so the ops registered by the module-level import are present.
542
+ // ===========================================================================
543
+
544
+ test("CRITICAL: every registered op blocks handler under deny-all ALS (no bypass)", async () => {
545
+ // Enumerate every operation registered through the boundary. Under a
546
+ // deny-all capability ({version:1, ops:[]}), the handler must NEVER execute
547
+ // for ANY op — proving no op bypasses the capability chokepoint.
548
+ //
549
+ // Ops with permissive schemas (accept {}) pass zod and hit the capability
550
+ // gate → EngramAccessForbiddenError. Ops with strict schemas reject {} at
551
+ // zod → EngramAccessInputError. In BOTH cases the handler body is not
552
+ // reached. If ANY op's handler ran, that would be a security hole.
553
+ const denyAll: TokenCapabilities = { version: 1, ops: [] };
554
+ const registered = listRegisteredOperations();
555
+ assert.ok(registered.length > 10, `expected many registered ops; got ${registered.length}`);
556
+
557
+ const ctx = mockCtx();
558
+ const reachedGate: string[] = [];
559
+ const rejectedAtInput: string[] = [];
560
+
561
+ for (const opName of registered) {
562
+ const op = getOperation(opName);
563
+ assert.ok(op, `operation ${opName} not found in registry`);
564
+
565
+ await tokenCapabilityStore.run(denyAll, async () => {
566
+ try {
567
+ await op?.run({}, ctx);
568
+ // If we reach here, the handler executed under deny-all — SECURITY HOLE.
569
+ assert.fail(`SECURITY HOLE: op "${opName}" handler ran under deny-all capabilities`);
570
+ } catch (err) {
571
+ if (err instanceof EngramAccessForbiddenError) {
572
+ reachedGate.push(opName);
573
+ } else if (err instanceof EngramAccessInputError) {
574
+ // Schema rejected {} before the gate — still safe, handler didn't run.
575
+ rejectedAtInput.push(opName);
576
+ } else {
577
+ assert.fail(
578
+ `op "${opName}": unexpected error type: ${(err as Error).constructor.name}: ${(err as Error).message}`
579
+ );
580
+ }
581
+ }
582
+ });
583
+ }
584
+
585
+ // At least one op must have a permissive-enough schema to reach the gate,
586
+ // proving the capability check actually fires (not just that all ops fail
587
+ // at input validation).
588
+ assert.ok(
589
+ reachedGate.length > 0,
590
+ `expected at least one op to reach the capability gate under deny-all; reachedGate=[${reachedGate.join(", ")}] rejectedAtInput=[${rejectedAtInput.join(", ")}]`
591
+ );
592
+ });
593
+
594
+ test("CRITICAL: ops that reach the gate are individually denied under scoped ALS", async () => {
595
+ // For ops with permissive schemas (they accept {}), verify that under a
596
+ // scoped-to-different-ops ALS, they throw EngramAccessForbiddenError —
597
+ // proving each op is individually gated by its own name.
598
+ const scopedAway: TokenCapabilities = {
599
+ version: 1,
600
+ ops: ["nonexistent_placeholder_op"],
601
+ };
602
+ const ctx = mockCtx();
603
+
604
+ const registered = listRegisteredOperations();
605
+ assert.ok(registered.length > 0, "expected registered ops before structural tests clear the registry");
606
+
607
+ for (const opName of registered) {
608
+ const op = getOperation(opName);
609
+ assert.ok(op);
610
+
611
+ await tokenCapabilityStore.run(scopedAway, async () => {
612
+ try {
613
+ await op?.run({}, ctx);
614
+ assert.fail(`SECURITY HOLE: op "${opName}" ran under scoped-away ALS`);
615
+ } catch (err) {
616
+ assert.ok(
617
+ err instanceof EngramAccessForbiddenError || err instanceof EngramAccessInputError,
618
+ `op "${opName}": unexpected error ${(err as Error).constructor.name}`
619
+ );
620
+ }
621
+ });
622
+ }
623
+ });
624
+
625
+ test("CRITICAL: review_resolve enforces token namespace allow-list on the loaded pair (issue #1850 round 9)", async () => {
626
+ // Direct boundary-handler test (the "batch-ops" path). review_resolve selects
627
+ // its target BY pairId, so the pair's namespace comes from the record — NOT
628
+ // a request param. A namespace-scoped bearer must NOT mutate a pair in a
629
+ // namespace outside its allow-list. The handler must load the pair, assert
630
+ // its intrinsic namespace via enforceNamespaceAllowList, and fail closed
631
+ // BEFORE executeResolution. Runs before the registry reset below so the
632
+ // module-registered op is present.
633
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-batch-review-resolve-scoped-"));
634
+ const allowedPair = writePair(dir, {
635
+ namespace: "ns_a",
636
+ memoryIds: ["a-1", "a-2"],
637
+ verdict: "contradicts",
638
+ rationale: "pair in the allowed namespace",
639
+ confidence: 0.9,
640
+ detectedAt: new Date().toISOString(),
641
+ });
642
+ const deniedPair = writePair(dir, {
643
+ namespace: "ns_b",
644
+ memoryIds: ["b-1", "b-2"],
645
+ verdict: "contradicts",
646
+ rationale: "pair in a denied namespace",
647
+ confidence: 0.9,
648
+ detectedAt: new Date().toISOString(),
649
+ });
650
+ const storage = { dir } as unknown as StorageManager;
651
+ const ctx: OperationContext = {
652
+ service: {
653
+ configRef: parseConfig({ memoryDir: dir, namespacesEnabled: true, defaultNamespace: "default" }),
654
+ memoryDir: dir,
655
+ storageRef: storage,
656
+ getWritableStorageForNamespace: async (namespace: string | undefined) => ({
657
+ namespace: namespace ?? "default",
658
+ storage,
659
+ }),
660
+ } as unknown as EngramAccessService,
661
+ authenticatedPrincipal: "writer",
662
+ };
663
+ const op = getOperation("review_resolve");
664
+ if (!op) { assert.fail("review_resolve must be registered"); }
665
+ try {
666
+ // ── scoped to ns_a (ops axis absent ⇒ op-gate no-op): denied-namespace pair
667
+ // rejects EngramAccessForbiddenError BEFORE mutation ──
668
+ await assert.rejects(
669
+ () => tokenCapabilityStore.run({ version: 1, namespaces: ["ns_a"] }, () =>
670
+ op.run({ pairId: deniedPair.pairId, verb: "both-valid" }, ctx)),
671
+ (err: unknown) => err instanceof EngramAccessForbiddenError && /ns_b/.test(err.message),
672
+ "scoped resolve: a pair in an unlisted namespace must be denied before mutation",
673
+ );
674
+ assert.notEqual(
675
+ readPair(dir, deniedPair.pairId)?.resolution,
676
+ "both-valid",
677
+ "the denied pair must remain unresolved (no mutation leak)",
678
+ );
679
+
680
+ // ── scoped to ns_a: allowed-namespace pair resolves and is mutated ──
681
+ await tokenCapabilityStore.run({ version: 1, namespaces: ["ns_a"] }, () =>
682
+ op.run({ pairId: allowedPair.pairId, verb: "both-valid" }, ctx));
683
+ assert.equal(
684
+ readPair(dir, allowedPair.pairId)?.resolution,
685
+ "both-valid",
686
+ "the allowed pair is marked resolved",
687
+ );
688
+
689
+ // ── unrestricted (namespaces axis absent): the denied-namespace pair is reachable ──
690
+ await tokenCapabilityStore.run({ version: 1 }, () =>
691
+ op.run({ pairId: deniedPair.pairId, verb: "both-valid" }, ctx));
692
+ assert.equal(
693
+ readPair(dir, deniedPair.pairId)?.resolution,
694
+ "both-valid",
695
+ "unrestricted token resolves the previously-denied pair",
696
+ );
697
+ } finally {
698
+ await rm(dir, { recursive: true, force: true });
699
+ }
700
+ });
701
+
702
+ // -------------------------------------------------------------------------
703
+ // Structural enforcement tests — these reset the registry and register
704
+ // custom test ops, so they run AFTER the critical-coverage enumeration.
705
+ // -------------------------------------------------------------------------
706
+
707
+ test("defineOperation run(): under scoped ALS, listed op passes, unlisted rejected", async () => {
708
+ __resetRegistryForTest();
709
+ let handlerCalls = 0;
710
+ const op = defineOperation({
711
+ name: "memory_get",
712
+ description: "test op",
713
+ schema: z.object({}),
714
+ handler: async () => {
715
+ handlerCalls++;
716
+ return { ok: true };
717
+ },
718
+ });
719
+
720
+ const ctx = mockCtx();
721
+
722
+ // Under a scoped ALS that DOES list the op → handler runs.
723
+ const allowsOp: TokenCapabilities = { version: 1, ops: ["memory_get"] };
724
+ const resultAllowed = await tokenCapabilityStore.run(allowsOp, () => op.run({}, ctx));
725
+ assert.deepEqual(resultAllowed, { ok: true });
726
+ assert.equal(handlerCalls, 1);
727
+
728
+ // Under a scoped ALS that does NOT list the op → EngramAccessForbiddenError.
729
+ const deniesOp: TokenCapabilities = { version: 1, ops: ["recall"] };
730
+ await assert.rejects(() => tokenCapabilityStore.run(deniesOp, () => op.run({}, ctx)), EngramAccessForbiddenError);
731
+ assert.equal(handlerCalls, 1, "handler must not run for denied op");
732
+
733
+ // Under empty-ops (deny-all) ALS → EngramAccessForbiddenError.
734
+ const denyAll: TokenCapabilities = { version: 1, ops: [] };
735
+ await assert.rejects(() => tokenCapabilityStore.run(denyAll, () => op.run({}, ctx)), EngramAccessForbiddenError);
736
+ assert.equal(handlerCalls, 1, "handler must not run under deny-all");
737
+ });
738
+
739
+ test("defineOperation run(): legacy/unrestricted ALS → handler runs", async () => {
740
+ __resetRegistryForTest();
741
+ const op = defineOperation({
742
+ name: "memory_get",
743
+ description: "test op",
744
+ schema: z.object({}),
745
+ handler: async () => ({ ok: true }),
746
+ });
747
+ const ctx = mockCtx();
748
+
749
+ // No ALS set (undefined store) → unrestricted → handler runs.
750
+ const result = await op.run({}, ctx);
751
+ assert.deepEqual(result, { ok: true });
752
+
753
+ // Explicit-unrestricted capabilities ({version:1}) → handler runs.
754
+ const unrestricted: TokenCapabilities = { version: 1 };
755
+ const result2 = await tokenCapabilityStore.run(unrestricted, () => op.run({}, ctx));
756
+ assert.deepEqual(result2, { ok: true });
757
+ });
758
+
759
+ test("defineOperation run(): namespaces enforcement via assertNamespaceAllowed pattern", async () => {
760
+ // The boundary run() checks ops via the ALS. Namespace enforcement is
761
+ // additionally handled in the HTTP layer's resolveNamespace(). Here we
762
+ // verify the capability primitives the handler layer relies on.
763
+ const scopedNs = validateCapabilitiesForMint({ namespaces: ["default"] }, OPERATION_NAMES);
764
+ assert.doesNotThrow(() => assertNamespaceAllowed(scopedNs, "default"));
765
+ assert.throws(() => assertNamespaceAllowed(scopedNs, "other"), EngramAccessForbiddenError);
766
+ });
767
+
768
+ test("CRITICAL: boundary enforcement reads from tokenCapabilityStore ALS", async () => {
769
+ // Prove the enforcement check reads the ALS value, not a static/closure
770
+ // variable. The same op must be denied under one ALS and allowed under
771
+ // another within the same process.
772
+ __resetRegistryForTest();
773
+ const op = defineOperation({
774
+ name: "memory_get",
775
+ description: "test",
776
+ schema: z.object({}),
777
+ handler: async () => ({ ran: true }),
778
+ });
779
+ const ctx = mockCtx();
780
+
781
+ // Denied under scoped ALS.
782
+ await assert.rejects(
783
+ () => tokenCapabilityStore.run({ version: 1, ops: ["recall"] }, () => op.run({}, ctx)),
784
+ EngramAccessForbiddenError
785
+ );
786
+
787
+ // Allowed under different scoped ALS that includes this op.
788
+ const result = await tokenCapabilityStore.run({ version: 1, ops: ["memory_get"] }, () => op.run({}, ctx));
789
+ assert.deepEqual(result, { ran: true });
790
+
791
+ // Allowed under no ALS (undefined).
792
+ assert.deepEqual(await op.run({}, ctx), { ran: true });
793
+ });
794
+
795
+ // ===========================================================================
796
+ // assertFleetWideOperationAllowed — fleet-wide maintenance guard (issue #1850 round 10).
797
+ // Distinct class from the id-addressed routes (round 9) and param-namespace
798
+ // ops (round 4): maintenance ops that run ACROSS ALL namespaces carry no
799
+ // `namespace` arg, so the tools/call effective-namespace gate never fires. A
800
+ // namespace-scoped token must not trigger cross-namespace maintenance.
801
+ // ===========================================================================
802
+
803
+ test("assertFleetWideOperationAllowed: no-op for unrestricted / legacy tokens", () => {
804
+ // Legacy (absent record) — unrestricted.
805
+ assert.doesNotThrow(() => assertFleetWideOperationAllowed(undefined));
806
+ assert.doesNotThrow(() => assertFleetWideOperationAllowed(null));
807
+ // Explicit-unrestricted (version present, no namespaces axis).
808
+ const explicitUnrestricted = validateCapabilitiesForMint(undefined, OPERATION_NAMES);
809
+ assert.doesNotThrow(() => assertFleetWideOperationAllowed(explicitUnrestricted));
810
+ // Op-scoped but namespace-UNrestricted ⇒ still allowed: the fleet-wide
811
+ // concern is the namespaces axis; the ops axis is enforced separately by
812
+ // assertOperationAllowed. A token with only an ops allow-list may run the
813
+ // op (it cannot exceed its op scope, and has no namespace restriction).
814
+ const opScoped = validateCapabilitiesForMint({ ops: ["graph_edge_decay_run"] }, OPERATION_NAMES);
815
+ assert.doesNotThrow(() => assertFleetWideOperationAllowed(opScoped));
816
+ });
817
+
818
+ test("assertFleetWideOperationAllowed: namespace-SCOPED token is denied (fail closed)", () => {
819
+ const scoped = validateCapabilitiesForMint({ namespaces: ["ns_a"] }, OPERATION_NAMES);
820
+ assert.throws(
821
+ () => assertFleetWideOperationAllowed(scoped),
822
+ (err: unknown) => err instanceof EngramAccessForbiddenError && /across all namespaces/.test(err.message),
823
+ );
824
+ });
825
+
826
+ test("assertFleetWideOperationAllowed: scoped token whose allow-list INCLUDES the default is STILL denied (fleet-wide ≠ default)", () => {
827
+ // A fleet-wide op affects EVERY namespace — it is not equivalent to an op
828
+ // scoped to the default namespace. enforceNamespaceAllowList maps undefined
829
+ // → default and would wrongly PASS such a token; the fleet-wide guard must
830
+ // NOT (that is the whole point of a dedicated guard for this class).
831
+ const scopedWithDefault = validateCapabilitiesForMint({ namespaces: ["default"] }, OPERATION_NAMES);
832
+ assert.throws(
833
+ () => assertFleetWideOperationAllowed(scopedWithDefault),
834
+ EngramAccessForbiddenError,
835
+ );
836
+ });
837
+
838
+ test("assertFleetWideOperationAllowed: deny-all namespaces ([]) is denied", () => {
839
+ const denyAll = validateCapabilitiesForMint({ namespaces: [] }, OPERATION_NAMES);
840
+ assert.throws(
841
+ () => assertFleetWideOperationAllowed(denyAll),
842
+ EngramAccessForbiddenError,
843
+ );
844
+ });