@remnic/core 9.3.655 → 9.3.657

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 (249) hide show
  1. package/dist/access-cli.js +22 -22
  2. package/dist/access-http.d.ts +4 -4
  3. package/dist/access-http.js +10 -10
  4. package/dist/access-mcp.d.ts +4 -4
  5. package/dist/access-mcp.js +9 -9
  6. package/dist/access-schema.d.ts +10 -10
  7. package/dist/{access-service-BEJvriUt.d.ts → access-service-D_nbpexW.d.ts} +33 -2
  8. package/dist/access-service.d.ts +4 -4
  9. package/dist/access-service.js +8 -8
  10. package/dist/action-confidence.d.ts +1 -1
  11. package/dist/active-memory-bridge.d.ts +1 -1
  12. package/dist/active-recall.d.ts +1 -1
  13. package/dist/active-recall.js +1 -1
  14. package/dist/behavior-learner.d.ts +1 -1
  15. package/dist/behavior-signals.d.ts +1 -1
  16. package/dist/bootstrap.d.ts +3 -3
  17. package/dist/briefing.d.ts +1 -1
  18. package/dist/briefing.js +3 -3
  19. package/dist/buffer-surprise-report.d.ts +1 -1
  20. package/dist/buffer.d.ts +1 -1
  21. package/dist/calibration.d.ts +1 -1
  22. package/dist/causal-behavior.d.ts +1 -1
  23. package/dist/causal-consolidation.d.ts +1 -1
  24. package/dist/causal-consolidation.js +4 -4
  25. package/dist/{chunk-PVE7KSQP.js → chunk-2BD7DG37.js} +2 -2
  26. package/dist/{chunk-54LOUIBE.js → chunk-2MXEVL75.js} +2 -2
  27. package/dist/{chunk-55ZMNKMQ.js → chunk-4UL7VPTD.js} +276 -57
  28. package/dist/chunk-4UL7VPTD.js.map +1 -0
  29. package/dist/{chunk-COVZLGMR.js → chunk-54XF2FY7.js} +17 -17
  30. package/dist/{chunk-UYNFWZWG.js → chunk-AGJKWOKV.js} +2 -2
  31. package/dist/{chunk-TDZSSJV4.js → chunk-AZBV4RRY.js} +1 -1
  32. package/dist/chunk-AZBV4RRY.js.map +1 -0
  33. package/dist/{chunk-KOI765XP.js → chunk-CTAV55JM.js} +241 -1
  34. package/dist/chunk-CTAV55JM.js.map +1 -0
  35. package/dist/{chunk-A3Y37UWI.js → chunk-DIBWFCLA.js} +3 -3
  36. package/dist/{chunk-QDVQ4AN2.js → chunk-DR67OK4E.js} +5 -5
  37. package/dist/{chunk-XBIACVCO.js → chunk-EC2AYKRX.js} +2 -2
  38. package/dist/{chunk-IQ53ZSXV.js → chunk-GCYFUTUC.js} +2 -2
  39. package/dist/{chunk-YYN3LIYA.js → chunk-GSHW5VVD.js} +5 -5
  40. package/dist/chunk-GYSYLGNE.js +650 -0
  41. package/dist/chunk-GYSYLGNE.js.map +1 -0
  42. package/dist/{chunk-NRBGRZW4.js → chunk-IOZ5WBWD.js} +2 -2
  43. package/dist/{chunk-NCSJKK23.js → chunk-JSVFEHLL.js} +7 -5
  44. package/dist/chunk-JSVFEHLL.js.map +1 -0
  45. package/dist/{chunk-7LWRCOP7.js → chunk-LZTFCAKE.js} +2 -2
  46. package/dist/{chunk-TEO46GMM.js → chunk-NXCK7DO7.js} +2 -2
  47. package/dist/{chunk-XOFXKASO.js → chunk-PEPHBH2W.js} +2 -2
  48. package/dist/{chunk-WDTUYOLS.js → chunk-QZRKNA5F.js} +2 -2
  49. package/dist/{chunk-PS3SYNHP.js → chunk-R5DB26G6.js} +2 -2
  50. package/dist/{chunk-5QD3QD76.js → chunk-RDW5G6DO.js} +659 -123
  51. package/dist/chunk-RDW5G6DO.js.map +1 -0
  52. package/dist/{chunk-BGKXTVNG.js → chunk-SWDHVH2P.js} +2 -2
  53. package/dist/{chunk-67G4T7KI.js → chunk-SXYCVRLK.js} +3 -3
  54. package/dist/{chunk-UCEABZZN.js → chunk-TFFZUFEP.js} +7 -5
  55. package/dist/chunk-TFFZUFEP.js.map +1 -0
  56. package/dist/{chunk-UCEDY5M7.js → chunk-TIJYQXDI.js} +2 -2
  57. package/dist/{chunk-2RCGZ67B.js → chunk-VAEAGTEQ.js} +3 -3
  58. package/dist/{chunk-XRKQOQLY.js → chunk-WIKMCJUR.js} +2 -2
  59. package/dist/{chunk-KZZ4YAEC.js → chunk-WWMHAMAY.js} +2 -2
  60. package/dist/{chunk-OKW6F5S5.js → chunk-YEZHZCUO.js} +4 -4
  61. package/dist/{chunk-5FOCXX5E.js → chunk-YVVQUAOO.js} +3 -3
  62. package/dist/{chunk-5FOCXX5E.js.map → chunk-YVVQUAOO.js.map} +1 -1
  63. package/dist/{chunk-3XGWCZ63.js → chunk-YXLT4EMM.js} +2 -2
  64. package/dist/{chunk-PTMJ2FH2.js → chunk-Z6UDTNY6.js} +2 -2
  65. package/dist/{cli-BGahB_d3.d.ts → cli-aYxSuPvP.d.ts} +3 -3
  66. package/dist/cli.d.ts +5 -5
  67. package/dist/cli.js +22 -22
  68. package/dist/compounding/engine.d.ts +1 -1
  69. package/dist/compounding/engine.js +3 -3
  70. package/dist/compounding/preference-consolidator.d.ts +1 -1
  71. package/dist/compression-optimizer.d.ts +1 -1
  72. package/dist/config.d.ts +1 -1
  73. package/dist/config.js +1 -1
  74. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  75. package/dist/connectors/codex-materialize-runner.js +3 -3
  76. package/dist/connectors/codex-materialize.d.ts +1 -1
  77. package/dist/connectors/index.d.ts +1 -1
  78. package/dist/connectors/index.js +3 -3
  79. package/dist/consolidation-provenance-check.d.ts +1 -1
  80. package/dist/consolidation-undo.d.ts +1 -1
  81. package/dist/contradiction/index.d.ts +1 -1
  82. package/dist/conversation-index/backend.d.ts +1 -1
  83. package/dist/conversation-index/chunker.d.ts +1 -1
  84. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  85. package/dist/conversation-index/indexer.d.ts +1 -1
  86. package/dist/conversation-index/search.d.ts +1 -1
  87. package/dist/day-summary.d.ts +1 -1
  88. package/dist/delinearize.d.ts +1 -1
  89. package/dist/direct-answer-wiring.d.ts +1 -1
  90. package/dist/direct-answer.d.ts +1 -1
  91. package/dist/embedding-fallback.d.ts +1 -1
  92. package/dist/enrichment/index.d.ts +1 -1
  93. package/dist/entity-retrieval.d.ts +1 -1
  94. package/dist/entity-retrieval.js +3 -3
  95. package/dist/entity-schema.d.ts +1 -1
  96. package/dist/explicit-capture.d.ts +3 -3
  97. package/dist/explicit-cue-recall.js +2 -2
  98. package/dist/extraction-judge-telemetry.d.ts +1 -1
  99. package/dist/extraction-judge-training.d.ts +1 -1
  100. package/dist/extraction-judge.d.ts +1 -1
  101. package/dist/extraction.d.ts +1 -1
  102. package/dist/fallback-llm.d.ts +1 -1
  103. package/dist/focused-list-recall.js +2 -2
  104. package/dist/identity-continuity.d.ts +1 -1
  105. package/dist/importance.d.ts +1 -1
  106. package/dist/index.d.ts +121 -121
  107. package/dist/index.js +32 -32
  108. package/dist/intent.d.ts +1 -1
  109. package/dist/lcm/engine.d.ts +1 -1
  110. package/dist/lcm/index.d.ts +1 -1
  111. package/dist/lcm/tools.d.ts +1 -1
  112. package/dist/lcm-fallback-read.js +1 -1
  113. package/dist/lifecycle.d.ts +1 -1
  114. package/dist/live-connectors-runner.d.ts +1 -1
  115. package/dist/local-llm.d.ts +1 -1
  116. package/dist/maintenance/memory-governance.d.ts +1 -1
  117. package/dist/maintenance/memory-governance.js +3 -3
  118. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  119. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  120. package/dist/mcp-memory-inspector-app.d.ts +4 -4
  121. package/dist/memory-action-policy.d.ts +1 -1
  122. package/dist/memory-cache.d.ts +1 -1
  123. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  124. package/dist/memory-projection-store.d.ts +1 -1
  125. package/dist/memory-provenance.d.ts +1 -1
  126. package/dist/memory-worth-outcomes.d.ts +1 -1
  127. package/dist/models-json.d.ts +1 -1
  128. package/dist/namespaces/migrate.d.ts +1 -1
  129. package/dist/namespaces/migrate.js +4 -4
  130. package/dist/namespaces/principal.d.ts +1 -1
  131. package/dist/namespaces/search.d.ts +1 -1
  132. package/dist/namespaces/storage.d.ts +1 -1
  133. package/dist/namespaces/storage.js +3 -3
  134. package/dist/native-knowledge.d.ts +1 -1
  135. package/dist/operator-toolkit.d.ts +1 -1
  136. package/dist/operator-toolkit.js +7 -7
  137. package/dist/{orchestrator-BgzZlWxH.d.ts → orchestrator-D1wcmPNj.d.ts} +8 -2
  138. package/dist/orchestrator.d.ts +3 -3
  139. package/dist/orchestrator.js +18 -18
  140. package/dist/patterns-cli.d.ts +1 -1
  141. package/dist/policy-runtime.d.ts +1 -1
  142. package/dist/qmd-recall-cache.d.ts +1 -1
  143. package/dist/qmd.d.ts +1 -1
  144. package/dist/recall-disclosure-escalation.d.ts +1 -1
  145. package/dist/recall-explain-renderer.d.ts +1 -1
  146. package/dist/recall-explain-renderer.js +3 -3
  147. package/dist/recall-planner-llm.d.ts +1 -1
  148. package/dist/recall-state.d.ts +1 -1
  149. package/dist/recall-tag-filter.d.ts +1 -1
  150. package/dist/recall-xray-cli.d.ts +1 -1
  151. package/dist/recall-xray-cli.js +4 -4
  152. package/dist/recall-xray-renderer.d.ts +1 -1
  153. package/dist/recall-xray-renderer.js +3 -3
  154. package/dist/recall-xray.d.ts +1 -1
  155. package/dist/recall-xray.js +2 -2
  156. package/dist/resolve-auth-token.d.ts +1 -1
  157. package/dist/response-guidance-recall.js +2 -2
  158. package/dist/resume-bundles.js +2 -2
  159. package/dist/retrieval-agents.d.ts +1 -1
  160. package/dist/retrieval-tiers.d.ts +1 -1
  161. package/dist/routing/engine.d.ts +1 -1
  162. package/dist/routing/store.d.ts +1 -1
  163. package/dist/schemas.d.ts +22 -22
  164. package/dist/search/embed-helper.d.ts +1 -1
  165. package/dist/search/factory.d.ts +1 -1
  166. package/dist/search/index.d.ts +1 -1
  167. package/dist/search/lancedb-backend.d.ts +1 -1
  168. package/dist/search/meilisearch-backend.d.ts +1 -1
  169. package/dist/search/noop-backend.d.ts +1 -1
  170. package/dist/search/orama-backend.d.ts +1 -1
  171. package/dist/search/port.d.ts +1 -1
  172. package/dist/search/remote-backend.d.ts +1 -1
  173. package/dist/{semantic-consolidation-Z8d_uMq8.d.ts → semantic-consolidation-MWOdNtSE.d.ts} +1 -1
  174. package/dist/semantic-consolidation.d.ts +2 -2
  175. package/dist/semantic-consolidation.js +4 -4
  176. package/dist/semantic-rule-promotion.js +3 -3
  177. package/dist/semantic-rule-verifier.d.ts +3 -2
  178. package/dist/semantic-rule-verifier.js +5 -3
  179. package/dist/session-observer-bands.d.ts +1 -1
  180. package/dist/session-observer-state.d.ts +1 -1
  181. package/dist/shared-context/manager.d.ts +1 -1
  182. package/dist/signal.d.ts +1 -1
  183. package/dist/storage.d.ts +1 -1
  184. package/dist/storage.js +2 -2
  185. package/dist/summarizer.d.ts +1 -1
  186. package/dist/summary-snapshot.d.ts +1 -1
  187. package/dist/targeted-fact-recall.js +2 -2
  188. package/dist/temporal-supersession.d.ts +1 -1
  189. package/dist/temporal-validity.d.ts +1 -1
  190. package/dist/threading.d.ts +1 -1
  191. package/dist/tier-migration.d.ts +1 -1
  192. package/dist/tier-routing.d.ts +1 -1
  193. package/dist/topics.d.ts +1 -1
  194. package/dist/transcript.d.ts +1 -1
  195. package/dist/transfer/types.d.ts +12 -12
  196. package/dist/{types-2OPlQWJG.d.ts → types-CgcCpUrf.d.ts} +39 -1
  197. package/dist/types.d.ts +1 -1
  198. package/dist/types.js +1 -1
  199. package/dist/utility-runtime.d.ts +1 -1
  200. package/dist/verified-recall.d.ts +2 -1
  201. package/dist/verified-recall.js +5 -3
  202. package/package.json +1 -1
  203. package/src/access-service-observe-lcm-parity.test.ts +86 -1
  204. package/src/access-service-observe-scope.test.ts +283 -1
  205. package/src/access-service-raw-excerpt-read-gate.test.ts +53 -0
  206. package/src/access-service.ts +391 -93
  207. package/src/coding/coding-namespace.ts +0 -3
  208. package/src/config.ts +282 -0
  209. package/src/lcm-fallback-read.ts +2 -6
  210. package/src/namespaces/scope-profiles.test.ts +1074 -0
  211. package/src/namespaces/scope-profiles.ts +456 -0
  212. package/src/orchestrator-flush.test.ts +142 -0
  213. package/src/orchestrator-source-attribution.test.ts +73 -0
  214. package/src/orchestrator.ts +835 -163
  215. package/src/semantic-rule-verifier.ts +13 -6
  216. package/src/types.ts +52 -0
  217. package/src/verified-recall.ts +10 -6
  218. package/dist/chunk-55ZMNKMQ.js.map +0 -1
  219. package/dist/chunk-5QD3QD76.js.map +0 -1
  220. package/dist/chunk-KOI765XP.js.map +0 -1
  221. package/dist/chunk-MMJANTJX.js +0 -339
  222. package/dist/chunk-MMJANTJX.js.map +0 -1
  223. package/dist/chunk-NCSJKK23.js.map +0 -1
  224. package/dist/chunk-TDZSSJV4.js.map +0 -1
  225. package/dist/chunk-UCEABZZN.js.map +0 -1
  226. /package/dist/{chunk-PVE7KSQP.js.map → chunk-2BD7DG37.js.map} +0 -0
  227. /package/dist/{chunk-54LOUIBE.js.map → chunk-2MXEVL75.js.map} +0 -0
  228. /package/dist/{chunk-COVZLGMR.js.map → chunk-54XF2FY7.js.map} +0 -0
  229. /package/dist/{chunk-UYNFWZWG.js.map → chunk-AGJKWOKV.js.map} +0 -0
  230. /package/dist/{chunk-A3Y37UWI.js.map → chunk-DIBWFCLA.js.map} +0 -0
  231. /package/dist/{chunk-QDVQ4AN2.js.map → chunk-DR67OK4E.js.map} +0 -0
  232. /package/dist/{chunk-XBIACVCO.js.map → chunk-EC2AYKRX.js.map} +0 -0
  233. /package/dist/{chunk-IQ53ZSXV.js.map → chunk-GCYFUTUC.js.map} +0 -0
  234. /package/dist/{chunk-YYN3LIYA.js.map → chunk-GSHW5VVD.js.map} +0 -0
  235. /package/dist/{chunk-NRBGRZW4.js.map → chunk-IOZ5WBWD.js.map} +0 -0
  236. /package/dist/{chunk-7LWRCOP7.js.map → chunk-LZTFCAKE.js.map} +0 -0
  237. /package/dist/{chunk-TEO46GMM.js.map → chunk-NXCK7DO7.js.map} +0 -0
  238. /package/dist/{chunk-XOFXKASO.js.map → chunk-PEPHBH2W.js.map} +0 -0
  239. /package/dist/{chunk-WDTUYOLS.js.map → chunk-QZRKNA5F.js.map} +0 -0
  240. /package/dist/{chunk-PS3SYNHP.js.map → chunk-R5DB26G6.js.map} +0 -0
  241. /package/dist/{chunk-BGKXTVNG.js.map → chunk-SWDHVH2P.js.map} +0 -0
  242. /package/dist/{chunk-67G4T7KI.js.map → chunk-SXYCVRLK.js.map} +0 -0
  243. /package/dist/{chunk-UCEDY5M7.js.map → chunk-TIJYQXDI.js.map} +0 -0
  244. /package/dist/{chunk-2RCGZ67B.js.map → chunk-VAEAGTEQ.js.map} +0 -0
  245. /package/dist/{chunk-XRKQOQLY.js.map → chunk-WIKMCJUR.js.map} +0 -0
  246. /package/dist/{chunk-KZZ4YAEC.js.map → chunk-WWMHAMAY.js.map} +0 -0
  247. /package/dist/{chunk-OKW6F5S5.js.map → chunk-YEZHZCUO.js.map} +0 -0
  248. /package/dist/{chunk-3XGWCZ63.js.map → chunk-YXLT4EMM.js.map} +0 -0
  249. /package/dist/{chunk-PTMJ2FH2.js.map → chunk-Z6UDTNY6.js.map} +0 -0
@@ -38,7 +38,7 @@ import {
38
38
  projectNamespaceName,
39
39
  projectTagProjectId,
40
40
  } from "./coding/coding-namespace.js";
41
- import { resolveGitContext } from "./coding/git-context.js";
41
+ import { resolveGitContext, stableHash } from "./coding/git-context.js";
42
42
  import { defaultNamespaceForPrincipal } from "./namespaces/principal.js";
43
43
  import type { CodingContext, PluginConfig } from "./types.js";
44
44
 
@@ -652,6 +652,91 @@ test("#1505 thread 2 compaction regression: flush/record overlay-derived key mat
652
652
  );
653
653
  });
654
654
 
655
+ test("#1501 scope profile lcmSearch fans out prefix-only reads across profile namespaces", async () => {
656
+ const probe = makeParityProbe({
657
+ ...withSelfPolicyPrefix("pi-observer"),
658
+ namespacePolicies: [
659
+ { name: "pi-observer", readPrincipals: ["pi-observer"], writePrincipals: ["pi-observer"] },
660
+ { name: "shared", readPrincipals: ["pi-observer"], writePrincipals: [] },
661
+ ],
662
+ defaultScopeProfile: "profilePrefix",
663
+ scopeProfiles: {
664
+ profilePrefix: {
665
+ readOrder: ["userGlobal", "serverShared"],
666
+ writeDefault: "userGlobal",
667
+ promotionTargets: [],
668
+ autoPromote: {
669
+ enabled: false,
670
+ targets: [],
671
+ categories: ["fact", "correction", "decision", "preference"],
672
+ minConfidenceTier: "explicit",
673
+ },
674
+ },
675
+ },
676
+ } as Partial<PluginConfig>);
677
+ const service = new EngramAccessService(probe.orch);
678
+
679
+ await service.lcmSearch({
680
+ query: "database",
681
+ sessionPrefix: "pi-observer:",
682
+ authenticatedPrincipal: "pi-observer",
683
+ });
684
+
685
+ assert.deepEqual(probe.searchSessionIds, [undefined, undefined]);
686
+ assert.deepEqual(probe.searchSessionPrefixes, [
687
+ encodeNs("pi-observer", "pi-observer:"),
688
+ encodeNs("shared", "pi-observer:"),
689
+ ]);
690
+ });
691
+
692
+ test("#1501 scope profile lcmSearch reads the team-project profile key", async () => {
693
+ const probe = makeParityProbe({
694
+ ...withSelfPolicyPrefix("pi-observer"),
695
+ defaultScopeProfile: "teamCoding",
696
+ scopeProfiles: {
697
+ teamCoding: {
698
+ readOrder: ["teamProject"],
699
+ writeDefault: "teamProject",
700
+ promotionTargets: ["teamProject"],
701
+ autoPromote: {
702
+ enabled: false,
703
+ targets: [],
704
+ categories: ["fact", "correction", "decision", "preference"],
705
+ minConfidenceTier: "explicit",
706
+ },
707
+ teamProject: { namespaceTemplate: "team-{teamId}-project-{projectHash}" },
708
+ },
709
+ },
710
+ teams: {
711
+ pi: {
712
+ principals: ["pi-observer"],
713
+ read: ["pi-observer"],
714
+ write: ["pi-observer"],
715
+ promote: ["pi-observer"],
716
+ },
717
+ },
718
+ } as Partial<PluginConfig>);
719
+ const service = new EngramAccessService(probe.orch);
720
+
721
+ await service.observe(
722
+ observeRequest({ sessionKey: "pi-observer:abc123", projectTag: "Remnic" }),
723
+ );
724
+ const expectedTeamProject = `team-pi-project-${stableHash(projectTagProjectId("Remnic"))}`;
725
+ const expectedKey = encodeNs(expectedTeamProject, "pi-observer:abc123");
726
+ assert.equal(probe.lcmWriteKeys[0], expectedKey);
727
+
728
+ await service.lcmSearch({
729
+ query: "what database are we using?",
730
+ sessionKey: "pi-observer:abc123",
731
+ });
732
+
733
+ assert.equal(
734
+ probe.searchSessionIds[0],
735
+ expectedKey,
736
+ "lcmSearch must read the same team-project key the scope-profile observe wrote",
737
+ );
738
+ });
739
+
655
740
  test("#1505 round 3: access lcmSearch routes the session_id through the SCOPED (overlay) key", async () => {
656
741
  // cursor "LCM search misses overlay keys" / codex "Route access LCM search
657
742
  // through the scoped key". A project-scoped observe (no explicit namespace)
@@ -41,7 +41,8 @@ import {
41
41
  projectNamespaceName,
42
42
  projectTagProjectId,
43
43
  } from "./coding/coding-namespace.js";
44
- import { resolveGitContext } from "./coding/git-context.js";
44
+ import { resolveGitContext, stableHash } from "./coding/git-context.js";
45
+ import { namespaceCollectionName } from "./namespaces/search.js";
45
46
  import type { CodingContext, PluginConfig } from "./types.js";
46
47
 
47
48
  /**
@@ -212,6 +213,72 @@ test("#1495 projectTag: LCM, extraction, objective-state, and response all agree
212
213
  );
213
214
  });
214
215
 
216
+ test("#1501 scope profile exposes layered read/write/promotion diagnostics without changing user-project write default", async () => {
217
+ const probe = makeObserveProbe({
218
+ namespacePolicies: [
219
+ { name: "pi-geek", readPrincipals: ["pi-geek"], writePrincipals: ["pi-geek"] },
220
+ { name: "shared", readPrincipals: ["pi-geek"], writePrincipals: ["pi-geek"] },
221
+ ],
222
+ principalFromSessionKeyMode: "prefix",
223
+ principalFromSessionKeyRules: [{ match: "pi-geek:", principal: "pi-geek" }],
224
+ scopeProfiles: {
225
+ teamCoding: {
226
+ readOrder: ["userProject", "teamProject", "userGlobal", "serverShared"],
227
+ writeDefault: "userProject",
228
+ promotionTargets: ["teamProject", "serverShared"],
229
+ teamProject: { namespaceTemplate: "team-{teamId}-project-{projectHash}" },
230
+ autoPromote: {
231
+ enabled: false,
232
+ targets: [],
233
+ categories: ["fact", "correction", "decision", "preference"],
234
+ minConfidenceTier: "explicit",
235
+ },
236
+ },
237
+ },
238
+ defaultScopeProfile: "teamCoding",
239
+ teams: {
240
+ pi: {
241
+ principals: ["pi-geek", "pi-friend"],
242
+ read: ["pi-geek", "pi-friend"],
243
+ write: ["pi-geek", "pi-friend"],
244
+ promote: ["pi-geek", "pi-friend"],
245
+ },
246
+ },
247
+ } as Partial<PluginConfig>);
248
+ const service = new EngramAccessService(probe.orch);
249
+
250
+ const res = await service.observe(
251
+ observeRequest({ sessionKey: "pi-geek:abc123", projectTag: "Remnic" }),
252
+ );
253
+ const expectedUserProject = combineNamespaces(
254
+ "pi-geek",
255
+ projectNamespaceName(projectTagProjectId("Remnic")),
256
+ );
257
+ const expectedTeamProject = `team-pi-project-${stableHash(projectTagProjectId("Remnic"))}`;
258
+
259
+ assert.equal(res.effectiveNamespace, expectedUserProject);
260
+ assert.equal(res.scopeDebug?.scopeProfile, "teamCoding");
261
+ assert.equal(res.scopeDebug?.writeLayer, "userProject");
262
+ assert.deepEqual(res.scopeDebug?.readNamespaces, [
263
+ expectedUserProject,
264
+ expectedTeamProject,
265
+ "pi-geek",
266
+ "shared",
267
+ ]);
268
+ assert.deepEqual(
269
+ res.scopeDebug?.promotionTargets?.map((target) => [
270
+ target.target,
271
+ target.namespace,
272
+ target.authorized,
273
+ ]),
274
+ [
275
+ ["teamProject", expectedTeamProject, true],
276
+ ["serverShared", "shared", true],
277
+ ],
278
+ );
279
+ assert.equal(probe.extractionCalls[0]?.writeNamespaceOverride, expectedUserProject);
280
+ });
281
+
215
282
  test("#1495 cwd (git repo): every observe side effect agrees on the effective namespace", async () => {
216
283
  const repoDir = mkdtempSync(join(tmpdir(), "remnic-observe-git-"));
217
284
  // A real (synthetic) git repo so resolveGitContext can read rev-parse output.
@@ -577,6 +644,120 @@ test("#1495 the scope plan's writeNamespace matches resolveCodingScopedWriteName
577
644
  }
578
645
  });
579
646
 
647
+ test("#1501 profile write auth rejects memory_store when no profile layer is writable", async () => {
648
+ const probe = makeObserveProbe({
649
+ namespacePolicies: [
650
+ { name: "pi-observer", readPrincipals: ["pi-observer"], writePrincipals: [] },
651
+ { name: "shared", readPrincipals: ["pi-observer"], writePrincipals: [] },
652
+ ],
653
+ principalFromSessionKeyMode: "prefix",
654
+ principalFromSessionKeyRules: [{ match: "pi-observer:", principal: "pi-observer" }],
655
+ scopeProfiles: {
656
+ teamCoding: {
657
+ readOrder: ["userProject", "teamProject", "serverShared"],
658
+ writeDefault: "userProject",
659
+ promotionTargets: ["teamProject", "serverShared"],
660
+ teamProject: { namespaceTemplate: "team-{teamId}-project-{projectHash}" },
661
+ autoPromote: {
662
+ enabled: false,
663
+ targets: [],
664
+ categories: ["fact", "correction", "decision", "preference"],
665
+ minConfidenceTier: "explicit",
666
+ },
667
+ },
668
+ },
669
+ defaultScopeProfile: "teamCoding",
670
+ teams: {
671
+ pi: {
672
+ principals: ["pi-observer"],
673
+ read: ["pi-observer"],
674
+ write: [],
675
+ promote: [],
676
+ },
677
+ },
678
+ } as Partial<PluginConfig>);
679
+ probe.contexts.set("pi-observer:abc123", {
680
+ projectId: projectTagProjectId("Remnic"),
681
+ branch: null,
682
+ rootPath: projectTagProjectId("Remnic"),
683
+ defaultBranch: null,
684
+ });
685
+ const service = new EngramAccessService(probe.orch);
686
+ const internals = service as unknown as {
687
+ resolveMemoryScopePlan: (r: unknown) => Promise<{ writeNamespace: string }>;
688
+ resolveCodingScopedWriteNamespace: (r: unknown) => Promise<string>;
689
+ };
690
+ const req = {
691
+ sessionKey: "pi-observer:abc123",
692
+ authenticatedPrincipal: "pi-observer",
693
+ };
694
+
695
+ await assert.rejects(
696
+ () => internals.resolveMemoryScopePlan.call(service, req),
697
+ /scope profile teamCoding has no writable layer/,
698
+ );
699
+ await assert.rejects(
700
+ () => internals.resolveCodingScopedWriteNamespace.call(service, req),
701
+ /scope profile teamCoding has no writable layer/,
702
+ );
703
+ });
704
+
705
+ test("#1501 team-project profile observe reports the profile write namespace as legacy namespace", async () => {
706
+ const projectId = projectTagProjectId("Remnic");
707
+ const expectedTeamProject = `team-pi-project-${stableHash(projectId)}`;
708
+ const probe = makeObserveProbe({
709
+ namespacePolicies: [
710
+ {
711
+ name: expectedTeamProject,
712
+ readPrincipals: ["pi-observer"],
713
+ writePrincipals: ["pi-observer"],
714
+ },
715
+ ],
716
+ principalFromSessionKeyMode: "prefix",
717
+ principalFromSessionKeyRules: [{ match: "pi-observer:", principal: "pi-observer" }],
718
+ scopeProfiles: {
719
+ teamCoding: {
720
+ readOrder: ["teamProject", "serverShared"],
721
+ writeDefault: "teamProject",
722
+ promotionTargets: ["teamProject"],
723
+ teamProject: { namespaceTemplate: "team-{teamId}-project-{projectHash}" },
724
+ autoPromote: {
725
+ enabled: false,
726
+ targets: [],
727
+ categories: ["fact", "correction", "decision", "preference"],
728
+ minConfidenceTier: "explicit",
729
+ },
730
+ },
731
+ },
732
+ defaultScopeProfile: "teamCoding",
733
+ teams: {
734
+ pi: {
735
+ principals: ["pi-observer"],
736
+ read: ["pi-observer"],
737
+ write: ["pi-observer"],
738
+ promote: ["pi-observer"],
739
+ },
740
+ },
741
+ } as Partial<PluginConfig>);
742
+ const service = new EngramAccessService(probe.orch);
743
+
744
+ const res = await service.observe(
745
+ observeRequest({
746
+ sessionKey: "pi-observer:abc123",
747
+ authenticatedPrincipal: "pi-observer",
748
+ projectTag: "Remnic",
749
+ }),
750
+ );
751
+
752
+ assert.equal(res.scopeDebug?.baseNamespace, "pi-observer");
753
+ assert.equal(res.scopeDebug?.writeLayer, "teamProject");
754
+ assert.equal(res.scopeDebug?.codingOverlayApplied, true);
755
+ assert.equal(res.namespace, expectedTeamProject);
756
+ assert.equal(res.effectiveNamespace, expectedTeamProject);
757
+ assert.equal(probe.lcmCalls[0]?.sessionKey, encodeNs(expectedTeamProject, "pi-observer:abc123"));
758
+ assert.equal(probe.extractionCalls[0]?.writeNamespaceOverride, expectedTeamProject);
759
+ });
760
+
580
761
  test("#1495 skipExtraction does not enqueue extraction but still archives LCM under the effective namespace", async () => {
581
762
  const probe = makeObserveProbe(withSelfPolicyPrefix("pi-geek"));
582
763
  const service = new EngramAccessService(probe.orch);
@@ -597,3 +778,104 @@ test("#1495 skipExtraction does not enqueue extraction but still archives LCM un
597
778
  assert.equal(probe.extractionCalls.length, 0);
598
779
  assert.equal(probe.lcmCalls[0].sessionKey, encodeNs(expected, "pi-geek:abc123"));
599
780
  });
781
+
782
+ test("#1501 implicit memorySearch honors active scope profile readOrder", async () => {
783
+ let searchedNamespaces: string[] | null = null;
784
+ const config = {
785
+ namespacesEnabled: true,
786
+ defaultNamespace: "default",
787
+ sharedNamespace: "shared",
788
+ memoryDir: "/synthetic/remnic-memory-search",
789
+ namespacePolicies: [
790
+ { name: "pi-geek", readPrincipals: ["pi-geek"], writePrincipals: ["pi-geek"] },
791
+ { name: "shared", readPrincipals: ["pi-geek"], writePrincipals: ["pi-geek"] },
792
+ ],
793
+ defaultRecallNamespaces: ["self", "shared"],
794
+ defaultScopeProfile: "projectOnly",
795
+ scopeProfiles: {
796
+ projectOnly: {
797
+ readOrder: ["userProject"],
798
+ writeDefault: "userProject",
799
+ promotionTargets: [],
800
+ autoPromote: {
801
+ enabled: false,
802
+ targets: [],
803
+ categories: ["fact", "correction", "decision", "preference"],
804
+ minConfidenceTier: "explicit",
805
+ },
806
+ },
807
+ },
808
+ codingMode: { projectScope: true },
809
+ } as unknown as PluginConfig;
810
+ const orch = {
811
+ config,
812
+ qmd: { isAvailable: () => true },
813
+ searchAcrossNamespaces: async (options: { namespaces: string[] }) => {
814
+ searchedNamespaces = options.namespaces;
815
+ return [];
816
+ },
817
+ } as unknown as Orchestrator;
818
+ const service = new EngramAccessService(orch);
819
+
820
+ const result = await service.memorySearch({
821
+ query: "deployment",
822
+ principal: "pi-geek",
823
+ });
824
+
825
+ assert.equal(result.count, 0);
826
+ assert.equal(
827
+ searchedNamespaces,
828
+ null,
829
+ "userProject-only profiles without project context must not fall back to shared/global search",
830
+ );
831
+ });
832
+
833
+ test("#1501 memorySearch collection names stay constrained to active scope profile namespaces", async () => {
834
+ let searchedNamespaces: string[] | null = null;
835
+ const config = {
836
+ namespacesEnabled: true,
837
+ defaultNamespace: "default",
838
+ sharedNamespace: "shared",
839
+ memoryDir: "/synthetic/remnic-memory-search-collection",
840
+ qmdCollection: "memories",
841
+ namespacePolicies: [
842
+ { name: "pi-geek", readPrincipals: ["pi-geek"], writePrincipals: ["pi-geek"] },
843
+ { name: "shared", readPrincipals: ["pi-geek"], writePrincipals: ["pi-geek"] },
844
+ ],
845
+ defaultRecallNamespaces: ["self", "shared"],
846
+ defaultScopeProfile: "privateOnly",
847
+ scopeProfiles: {
848
+ privateOnly: {
849
+ readOrder: ["userGlobal"],
850
+ writeDefault: "userGlobal",
851
+ promotionTargets: [],
852
+ autoPromote: {
853
+ enabled: false,
854
+ targets: [],
855
+ categories: ["fact", "correction", "decision", "preference"],
856
+ minConfidenceTier: "explicit",
857
+ },
858
+ },
859
+ },
860
+ codingMode: { projectScope: true },
861
+ } as unknown as PluginConfig;
862
+ const orch = {
863
+ config,
864
+ qmd: { isAvailable: () => true },
865
+ searchAcrossNamespaces: async (options: { namespaces: string[] }) => {
866
+ searchedNamespaces = options.namespaces;
867
+ return [];
868
+ },
869
+ } as unknown as Orchestrator;
870
+ const service = new EngramAccessService(orch);
871
+ const sharedCollection = namespaceCollectionName(config.qmdCollection, "shared", {
872
+ defaultNamespace: config.defaultNamespace,
873
+ useLegacyDefaultCollection: false,
874
+ });
875
+
876
+ await assert.rejects(
877
+ () => service.memorySearch({ query: "deployment", principal: "pi-geek", collection: sharedCollection }),
878
+ /collection is not namespace-scoped/,
879
+ );
880
+ assert.equal(searchedNamespaces, null);
881
+ });
@@ -165,6 +165,13 @@ type ExecuteRecallInternals = {
165
165
  executeRecall: (request: unknown) => Promise<unknown>;
166
166
  };
167
167
 
168
+ type RawExcerptInternals = {
169
+ fetchRawExcerpts: (
170
+ disclosure: "raw",
171
+ context: { query: string; sessionKey: string; lcmSessionIds: string[] },
172
+ ) => Promise<Array<{ turnIndex: number; role: string; content: string; sessionId: string }> | null>;
173
+ };
174
+
168
175
  const SESSION_KEY = "pi-geek:abc123";
169
176
  const PROJECT_TAG = "Blend/Supply";
170
177
 
@@ -441,3 +448,49 @@ test("#1505 thread 2f7 (single-store regression): namespaces disabled ⇒ raw ex
441
448
  assert.equal(probe.searchSessionIds.length, 1);
442
449
  assert.equal(probe.searchSessionIds[0], SESSION_KEY);
443
450
  });
451
+
452
+ test("scope-profile raw excerpts preserve successful sibling LCM keys when one key fails", async () => {
453
+ const probe = makeRawExcerptProbe({
454
+ config: {},
455
+ snapshotNamespace: "default",
456
+ sessionKey: SESSION_KEY,
457
+ });
458
+ const orchestrator = (probe.service as unknown as { orchestrator: Orchestrator }).orchestrator as unknown as {
459
+ lcmEngine: {
460
+ enabled: boolean;
461
+ searchContextFull: (query: string, limit: number, sessionId?: string) => Promise<Array<{
462
+ session_id: string;
463
+ turn_index: number;
464
+ role: string;
465
+ content: string;
466
+ }>>;
467
+ };
468
+ };
469
+ orchestrator.lcmEngine.searchContextFull = async (_query, _limit, sessionId) => {
470
+ probe.searchSessionIds.push(sessionId);
471
+ if (sessionId === "bad") throw new Error("synthetic LCM failure");
472
+ return [
473
+ {
474
+ session_id: sessionId ?? "default",
475
+ turn_index: sessionId === "good" ? 1 : 2,
476
+ role: "user",
477
+ content: `raw row from ${sessionId}`,
478
+ },
479
+ ];
480
+ };
481
+
482
+ const excerpts = await (probe.service as unknown as RawExcerptInternals).fetchRawExcerpts("raw", {
483
+ query: "what happened?",
484
+ sessionKey: SESSION_KEY,
485
+ lcmSessionIds: ["good", "bad", "later"],
486
+ });
487
+
488
+ assert.deepEqual(probe.searchSessionIds, ["good", "bad", "later"]);
489
+ assert.deepEqual(
490
+ excerpts?.map((excerpt) => [excerpt.sessionId, excerpt.turnIndex, excerpt.content]),
491
+ [
492
+ ["good", 1, "raw row from good"],
493
+ ["later", 2, "raw row from later"],
494
+ ],
495
+ );
496
+ });