@remnic/core 9.3.654 → 9.3.656

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 (281) hide show
  1. package/dist/access-cli.js +29 -29
  2. package/dist/access-http.d.ts +4 -4
  3. package/dist/access-http.js +17 -17
  4. package/dist/access-mcp.d.ts +4 -4
  5. package/dist/access-mcp.js +16 -16
  6. package/dist/access-schema.d.ts +10 -10
  7. package/dist/{access-service-C8A5hoXJ.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 +15 -15
  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-JMQSYGXS.js → chunk-2BD7DG37.js} +2 -2
  26. package/dist/{chunk-FVRBLJP6.js → chunk-2MXEVL75.js} +2 -2
  27. package/dist/{chunk-LJCEWTG3.js → chunk-4UL7VPTD.js} +277 -58
  28. package/dist/chunk-4UL7VPTD.js.map +1 -0
  29. package/dist/{chunk-JYN7QNTA.js → chunk-54XF2FY7.js} +17 -17
  30. package/dist/{chunk-7WEB3FLJ.js → chunk-5PLUC5OB.js} +2 -2
  31. package/dist/{chunk-JX2RINDR.js → chunk-6G5JEN55.js} +2 -2
  32. package/dist/{chunk-ZCORQM74.js → chunk-AGJKWOKV.js} +2 -2
  33. package/dist/{chunk-NE2JBMLN.js → chunk-AZBV4RRY.js} +1 -1
  34. package/dist/chunk-AZBV4RRY.js.map +1 -0
  35. package/dist/{chunk-YLZLPVKK.js → chunk-CTAV55JM.js} +344 -1
  36. package/dist/chunk-CTAV55JM.js.map +1 -0
  37. package/dist/{chunk-2DSTAWNZ.js → chunk-DIBWFCLA.js} +3 -3
  38. package/dist/{chunk-NAZWHTYV.js → chunk-DR67OK4E.js} +5 -5
  39. package/dist/{chunk-XBIACVCO.js → chunk-EC2AYKRX.js} +2 -2
  40. package/dist/{chunk-JVRPJ7D4.js → chunk-EKQMQQ3U.js} +48 -12
  41. package/dist/chunk-EKQMQQ3U.js.map +1 -0
  42. package/dist/{chunk-RGPUQ66K.js → chunk-GCYFUTUC.js} +2 -2
  43. package/dist/{chunk-JBHXMCYN.js → chunk-GRYAECRV.js} +2 -2
  44. package/dist/{chunk-BJA6DQOC.js → chunk-GSHW5VVD.js} +5 -5
  45. package/dist/chunk-GYSYLGNE.js +650 -0
  46. package/dist/chunk-GYSYLGNE.js.map +1 -0
  47. package/dist/{chunk-NCGWXCSW.js → chunk-IOZ5WBWD.js} +2 -2
  48. package/dist/{chunk-QKK64Z6M.js → chunk-JSVFEHLL.js} +7 -5
  49. package/dist/chunk-JSVFEHLL.js.map +1 -0
  50. package/dist/{chunk-7LWRCOP7.js → chunk-LZTFCAKE.js} +2 -2
  51. package/dist/{chunk-2DGQLOOM.js → chunk-M3VYPE2H.js} +1 -1
  52. package/dist/{chunk-2DGQLOOM.js.map → chunk-M3VYPE2H.js.map} +1 -1
  53. package/dist/{chunk-6CVI6BP6.js → chunk-NXCK7DO7.js} +2 -2
  54. package/dist/{chunk-Z5MQI7K2.js → chunk-PEPHBH2W.js} +2 -2
  55. package/dist/{chunk-PYWNNF2I.js → chunk-QRSKPI62.js} +99 -66
  56. package/dist/chunk-QRSKPI62.js.map +1 -0
  57. package/dist/{chunk-XWQ6ERUG.js → chunk-QZRKNA5F.js} +2 -2
  58. package/dist/{chunk-PS3SYNHP.js → chunk-R5DB26G6.js} +2 -2
  59. package/dist/{chunk-OL2364SB.js → chunk-RDW5G6DO.js} +1502 -335
  60. package/dist/chunk-RDW5G6DO.js.map +1 -0
  61. package/dist/{chunk-YM3LR4LS.js → chunk-SSSXWIBP.js} +5 -5
  62. package/dist/{chunk-T2C6QJG2.js → chunk-SWDHVH2P.js} +2 -2
  63. package/dist/{chunk-DBM2BD22.js → chunk-SXYCVRLK.js} +3 -3
  64. package/dist/{chunk-K6X553JB.js → chunk-TFFZUFEP.js} +7 -5
  65. package/dist/chunk-TFFZUFEP.js.map +1 -0
  66. package/dist/{chunk-ENV6RDTD.js → chunk-TIJYQXDI.js} +2 -2
  67. package/dist/{chunk-BP2EV6W5.js → chunk-VAEAGTEQ.js} +4 -4
  68. package/dist/{chunk-3RACUBII.js → chunk-WIKMCJUR.js} +2 -2
  69. package/dist/{chunk-QW6JZO5P.js → chunk-WWMHAMAY.js} +2 -2
  70. package/dist/{chunk-GPW2E4LN.js → chunk-YEZHZCUO.js} +4 -4
  71. package/dist/{chunk-5FOCXX5E.js → chunk-YVVQUAOO.js} +3 -3
  72. package/dist/{chunk-5FOCXX5E.js.map → chunk-YVVQUAOO.js.map} +1 -1
  73. package/dist/{chunk-3XGWCZ63.js → chunk-YXLT4EMM.js} +2 -2
  74. package/dist/{chunk-Y2RIIF6H.js → chunk-Z6UDTNY6.js} +2 -2
  75. package/dist/{cli-uQgvDFNE.d.ts → cli-aYxSuPvP.d.ts} +3 -3
  76. package/dist/cli.d.ts +5 -5
  77. package/dist/cli.js +29 -29
  78. package/dist/compounding/engine.d.ts +1 -1
  79. package/dist/compounding/engine.js +3 -3
  80. package/dist/compounding/preference-consolidator.d.ts +1 -1
  81. package/dist/compression-optimizer.d.ts +1 -1
  82. package/dist/config.d.ts +1 -1
  83. package/dist/config.js +1 -1
  84. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  85. package/dist/connectors/codex-materialize-runner.js +3 -3
  86. package/dist/connectors/codex-materialize.d.ts +1 -1
  87. package/dist/connectors/index.d.ts +1 -1
  88. package/dist/connectors/index.js +3 -3
  89. package/dist/consolidation-provenance-check.d.ts +1 -1
  90. package/dist/consolidation-undo.d.ts +1 -1
  91. package/dist/contradiction/index.d.ts +1 -1
  92. package/dist/conversation-index/backend.d.ts +1 -1
  93. package/dist/conversation-index/chunker.d.ts +1 -1
  94. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  95. package/dist/conversation-index/indexer.d.ts +1 -1
  96. package/dist/conversation-index/search.d.ts +1 -1
  97. package/dist/day-summary.d.ts +1 -1
  98. package/dist/delinearize.d.ts +1 -1
  99. package/dist/direct-answer-wiring.d.ts +1 -1
  100. package/dist/direct-answer.d.ts +1 -1
  101. package/dist/embedding-fallback.d.ts +1 -1
  102. package/dist/enrichment/index.d.ts +1 -1
  103. package/dist/entity-retrieval.d.ts +1 -1
  104. package/dist/entity-retrieval.js +3 -3
  105. package/dist/entity-schema.d.ts +1 -1
  106. package/dist/explicit-capture.d.ts +3 -3
  107. package/dist/explicit-cue-recall.js +2 -2
  108. package/dist/extraction-judge-telemetry.d.ts +1 -1
  109. package/dist/extraction-judge-training.d.ts +1 -1
  110. package/dist/extraction-judge.d.ts +1 -1
  111. package/dist/extraction.d.ts +1 -1
  112. package/dist/fallback-llm.d.ts +1 -1
  113. package/dist/focused-list-recall.js +2 -2
  114. package/dist/identity-continuity.d.ts +1 -1
  115. package/dist/importance.d.ts +1 -1
  116. package/dist/index.d.ts +121 -121
  117. package/dist/index.js +39 -39
  118. package/dist/intent.d.ts +1 -1
  119. package/dist/lcm/engine.d.ts +1 -1
  120. package/dist/lcm/index.d.ts +1 -1
  121. package/dist/lcm/tools.d.ts +1 -1
  122. package/dist/lcm-fallback-read.js +1 -1
  123. package/dist/lifecycle.d.ts +1 -1
  124. package/dist/live-connectors-runner.d.ts +1 -1
  125. package/dist/local-llm.d.ts +1 -1
  126. package/dist/maintenance/memory-governance.d.ts +1 -1
  127. package/dist/maintenance/memory-governance.js +3 -3
  128. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  129. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  130. package/dist/mcp-memory-inspector-app.d.ts +4 -4
  131. package/dist/memory-action-policy.d.ts +1 -1
  132. package/dist/memory-cache.d.ts +1 -1
  133. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  134. package/dist/memory-projection-store.d.ts +1 -1
  135. package/dist/memory-provenance.d.ts +1 -1
  136. package/dist/memory-worth-outcomes.d.ts +1 -1
  137. package/dist/models-json.d.ts +1 -1
  138. package/dist/namespaces/migrate.d.ts +1 -1
  139. package/dist/namespaces/migrate.js +11 -11
  140. package/dist/namespaces/principal.d.ts +1 -1
  141. package/dist/namespaces/search.d.ts +15 -4
  142. package/dist/namespaces/search.js +7 -7
  143. package/dist/namespaces/storage.d.ts +1 -1
  144. package/dist/namespaces/storage.js +3 -3
  145. package/dist/native-knowledge.d.ts +1 -1
  146. package/dist/operator-toolkit.d.ts +1 -1
  147. package/dist/operator-toolkit.js +14 -14
  148. package/dist/{orchestrator-B4Y4sWQH.d.ts → orchestrator-D1wcmPNj.d.ts} +17 -14
  149. package/dist/orchestrator.d.ts +3 -3
  150. package/dist/orchestrator.js +25 -25
  151. package/dist/patterns-cli.d.ts +1 -1
  152. package/dist/policy-runtime.d.ts +1 -1
  153. package/dist/qmd-recall-cache.d.ts +1 -1
  154. package/dist/qmd.d.ts +5 -1
  155. package/dist/qmd.js +2 -2
  156. package/dist/recall-disclosure-escalation.d.ts +1 -1
  157. package/dist/recall-explain-renderer.d.ts +1 -1
  158. package/dist/recall-explain-renderer.js +3 -3
  159. package/dist/recall-planner-llm.d.ts +1 -1
  160. package/dist/recall-state.d.ts +1 -1
  161. package/dist/recall-tag-filter.d.ts +1 -1
  162. package/dist/recall-xray-cli.d.ts +1 -1
  163. package/dist/recall-xray-cli.js +4 -4
  164. package/dist/recall-xray-renderer.d.ts +1 -1
  165. package/dist/recall-xray-renderer.js +3 -3
  166. package/dist/recall-xray.d.ts +1 -1
  167. package/dist/recall-xray.js +2 -2
  168. package/dist/resolve-auth-token.d.ts +1 -1
  169. package/dist/response-guidance-recall.js +2 -2
  170. package/dist/resume-bundles.js +2 -2
  171. package/dist/retrieval-agents.d.ts +1 -1
  172. package/dist/retrieval-tiers.d.ts +1 -1
  173. package/dist/routing/engine.d.ts +1 -1
  174. package/dist/routing/store.d.ts +1 -1
  175. package/dist/schemas.d.ts +22 -22
  176. package/dist/search/embed-helper.d.ts +1 -1
  177. package/dist/search/factory.d.ts +1 -1
  178. package/dist/search/factory.js +6 -6
  179. package/dist/search/index.d.ts +1 -1
  180. package/dist/search/index.js +6 -6
  181. package/dist/search/lancedb-backend.d.ts +1 -1
  182. package/dist/search/lancedb-backend.js +2 -2
  183. package/dist/search/meilisearch-backend.d.ts +1 -1
  184. package/dist/search/meilisearch-backend.js +2 -2
  185. package/dist/search/noop-backend.d.ts +1 -1
  186. package/dist/search/orama-backend.d.ts +1 -1
  187. package/dist/search/orama-backend.js +2 -2
  188. package/dist/search/port.d.ts +17 -1
  189. package/dist/search/port.js +1 -1
  190. package/dist/search/remote-backend.d.ts +1 -1
  191. package/dist/{semantic-consolidation-BKd0Pype.d.ts → semantic-consolidation-MWOdNtSE.d.ts} +1 -1
  192. package/dist/semantic-consolidation.d.ts +2 -2
  193. package/dist/semantic-consolidation.js +4 -4
  194. package/dist/semantic-rule-promotion.js +3 -3
  195. package/dist/semantic-rule-verifier.d.ts +3 -2
  196. package/dist/semantic-rule-verifier.js +5 -3
  197. package/dist/session-observer-bands.d.ts +1 -1
  198. package/dist/session-observer-state.d.ts +1 -1
  199. package/dist/shared-context/manager.d.ts +1 -1
  200. package/dist/signal.d.ts +1 -1
  201. package/dist/storage.d.ts +1 -1
  202. package/dist/storage.js +2 -2
  203. package/dist/summarizer.d.ts +1 -1
  204. package/dist/summary-snapshot.d.ts +1 -1
  205. package/dist/targeted-fact-recall.js +2 -2
  206. package/dist/temporal-supersession.d.ts +1 -1
  207. package/dist/temporal-validity.d.ts +1 -1
  208. package/dist/threading.d.ts +1 -1
  209. package/dist/tier-migration.d.ts +1 -1
  210. package/dist/tier-routing.d.ts +1 -1
  211. package/dist/topics.d.ts +1 -1
  212. package/dist/transcript.d.ts +1 -1
  213. package/dist/transfer/types.d.ts +12 -12
  214. package/dist/{types-BgChEr0M.d.ts → types-CgcCpUrf.d.ts} +51 -1
  215. package/dist/types.d.ts +1 -1
  216. package/dist/types.js +1 -1
  217. package/dist/utility-runtime.d.ts +1 -1
  218. package/dist/verified-recall.d.ts +2 -1
  219. package/dist/verified-recall.js +5 -3
  220. package/package.json +1 -1
  221. package/src/access-service-observe-lcm-parity.test.ts +86 -1
  222. package/src/access-service-observe-scope.test.ts +283 -1
  223. package/src/access-service-raw-excerpt-read-gate.test.ts +53 -0
  224. package/src/access-service.ts +391 -93
  225. package/src/coding/coding-namespace.ts +0 -3
  226. package/src/config.test.ts +69 -0
  227. package/src/config.ts +417 -0
  228. package/src/lcm-fallback-read.ts +2 -6
  229. package/src/maintenance/namespace-planner.test.ts +1120 -0
  230. package/src/maintenance/namespace-planner.ts +893 -0
  231. package/src/namespaces/scope-profiles.test.ts +1074 -0
  232. package/src/namespaces/scope-profiles.ts +456 -0
  233. package/src/namespaces/search.test.ts +130 -2
  234. package/src/namespaces/search.ts +71 -10
  235. package/src/orchestrator-flush.test.ts +606 -44
  236. package/src/orchestrator-source-attribution.test.ts +73 -0
  237. package/src/orchestrator.ts +932 -229
  238. package/src/qmd-client.test.ts +59 -0
  239. package/src/qmd.ts +124 -84
  240. package/src/search/port.ts +16 -0
  241. package/src/semantic-rule-verifier.ts +13 -6
  242. package/src/types.ts +64 -0
  243. package/src/verified-recall.ts +10 -6
  244. package/dist/chunk-JVRPJ7D4.js.map +0 -1
  245. package/dist/chunk-K6X553JB.js.map +0 -1
  246. package/dist/chunk-LJCEWTG3.js.map +0 -1
  247. package/dist/chunk-MMJANTJX.js +0 -339
  248. package/dist/chunk-MMJANTJX.js.map +0 -1
  249. package/dist/chunk-NE2JBMLN.js.map +0 -1
  250. package/dist/chunk-OL2364SB.js.map +0 -1
  251. package/dist/chunk-PYWNNF2I.js.map +0 -1
  252. package/dist/chunk-QKK64Z6M.js.map +0 -1
  253. package/dist/chunk-YLZLPVKK.js.map +0 -1
  254. /package/dist/{chunk-JMQSYGXS.js.map → chunk-2BD7DG37.js.map} +0 -0
  255. /package/dist/{chunk-FVRBLJP6.js.map → chunk-2MXEVL75.js.map} +0 -0
  256. /package/dist/{chunk-JYN7QNTA.js.map → chunk-54XF2FY7.js.map} +0 -0
  257. /package/dist/{chunk-7WEB3FLJ.js.map → chunk-5PLUC5OB.js.map} +0 -0
  258. /package/dist/{chunk-JX2RINDR.js.map → chunk-6G5JEN55.js.map} +0 -0
  259. /package/dist/{chunk-ZCORQM74.js.map → chunk-AGJKWOKV.js.map} +0 -0
  260. /package/dist/{chunk-2DSTAWNZ.js.map → chunk-DIBWFCLA.js.map} +0 -0
  261. /package/dist/{chunk-NAZWHTYV.js.map → chunk-DR67OK4E.js.map} +0 -0
  262. /package/dist/{chunk-XBIACVCO.js.map → chunk-EC2AYKRX.js.map} +0 -0
  263. /package/dist/{chunk-RGPUQ66K.js.map → chunk-GCYFUTUC.js.map} +0 -0
  264. /package/dist/{chunk-JBHXMCYN.js.map → chunk-GRYAECRV.js.map} +0 -0
  265. /package/dist/{chunk-BJA6DQOC.js.map → chunk-GSHW5VVD.js.map} +0 -0
  266. /package/dist/{chunk-NCGWXCSW.js.map → chunk-IOZ5WBWD.js.map} +0 -0
  267. /package/dist/{chunk-7LWRCOP7.js.map → chunk-LZTFCAKE.js.map} +0 -0
  268. /package/dist/{chunk-6CVI6BP6.js.map → chunk-NXCK7DO7.js.map} +0 -0
  269. /package/dist/{chunk-Z5MQI7K2.js.map → chunk-PEPHBH2W.js.map} +0 -0
  270. /package/dist/{chunk-XWQ6ERUG.js.map → chunk-QZRKNA5F.js.map} +0 -0
  271. /package/dist/{chunk-PS3SYNHP.js.map → chunk-R5DB26G6.js.map} +0 -0
  272. /package/dist/{chunk-YM3LR4LS.js.map → chunk-SSSXWIBP.js.map} +0 -0
  273. /package/dist/{chunk-T2C6QJG2.js.map → chunk-SWDHVH2P.js.map} +0 -0
  274. /package/dist/{chunk-DBM2BD22.js.map → chunk-SXYCVRLK.js.map} +0 -0
  275. /package/dist/{chunk-ENV6RDTD.js.map → chunk-TIJYQXDI.js.map} +0 -0
  276. /package/dist/{chunk-BP2EV6W5.js.map → chunk-VAEAGTEQ.js.map} +0 -0
  277. /package/dist/{chunk-3RACUBII.js.map → chunk-WIKMCJUR.js.map} +0 -0
  278. /package/dist/{chunk-QW6JZO5P.js.map → chunk-WWMHAMAY.js.map} +0 -0
  279. /package/dist/{chunk-GPW2E4LN.js.map → chunk-YEZHZCUO.js.map} +0 -0
  280. /package/dist/{chunk-3XGWCZ63.js.map → chunk-YXLT4EMM.js.map} +0 -0
  281. /package/dist/{chunk-Y2RIIF6H.js.map → chunk-Z6UDTNY6.js.map} +0 -0
@@ -0,0 +1,456 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { combineNamespaces, type CodingNamespaceOverlay } from "../coding/coding-namespace.js";
4
+ import { stableHash } from "../coding/git-context.js";
5
+ import { isSafeRouteNamespace } from "../routing/engine.js";
6
+ import type {
7
+ CodingContext,
8
+ PluginConfig,
9
+ ScopeProfileConfig,
10
+ ScopeProfileLayerId,
11
+ ScopeProfilePromotionTarget,
12
+ ScopeTeamConfig,
13
+ } from "../types.js";
14
+ import { canReadNamespace, canWriteNamespace, defaultNamespaceForPrincipal } from "./principal.js";
15
+
16
+ type ScopeProfileCodingOverlay = Pick<CodingNamespaceOverlay, "namespace" | "readFallbacks">;
17
+
18
+ export interface ScopeProfileLayerResolution {
19
+ id: ScopeProfileLayerId;
20
+ kind: "user-project" | "team-project" | "user-global" | "server-shared";
21
+ namespace?: string;
22
+ readable: boolean;
23
+ writable: boolean;
24
+ promotable: boolean;
25
+ reason: string;
26
+ }
27
+
28
+ export interface ScopeProfilePromotionResolution {
29
+ target: ScopeProfilePromotionTarget;
30
+ namespace?: string;
31
+ authorized: boolean;
32
+ reason: string;
33
+ }
34
+
35
+ export interface ResolvedScopeProfilePlan {
36
+ profileId: string;
37
+ profile: ScopeProfileConfig;
38
+ baseNamespace: string;
39
+ writeLayer: ScopeProfileLayerId;
40
+ writeNamespace: string;
41
+ readNamespaces: string[];
42
+ layers: ScopeProfileLayerResolution[];
43
+ promotionTargets: ScopeProfilePromotionResolution[];
44
+ warnings: string[];
45
+ }
46
+
47
+ export interface ResolveScopeProfilePlanOptions {
48
+ config: PluginConfig;
49
+ principal?: string;
50
+ codingContext?: CodingContext | null;
51
+ codingOverlay?: ScopeProfileCodingOverlay | null;
52
+ }
53
+
54
+ function activeScopeProfile(config: PluginConfig): { profileId: string; profile: ScopeProfileConfig } | null {
55
+ const profileId = config.defaultScopeProfile;
56
+ if (!profileId) return null;
57
+ const profile = (config.scopeProfiles ?? {})[profileId];
58
+ return profile ? { profileId, profile } : null;
59
+ }
60
+
61
+ function principalListed(list: string[], principal: string | undefined): boolean {
62
+ if (!principal) return false;
63
+ return list.includes(principal) || list.includes("*");
64
+ }
65
+
66
+ function derivedScopeProfileSelfNamespace(principal: string | undefined, config: PluginConfig): string | null {
67
+ if (!principal || principal === config.defaultNamespace || principal === config.sharedNamespace) return null;
68
+ if (isSafeRouteNamespace(principal)) return principal;
69
+ return "principal-" + createHash("sha256").update(principal).digest("hex").slice(0, 54);
70
+ }
71
+
72
+ function scopeProfileSelfNamespace(principal: string | undefined, config: PluginConfig): string {
73
+ const existing = defaultNamespaceForPrincipal(principal, config);
74
+ if (existing !== config.defaultNamespace) return existing;
75
+ return derivedScopeProfileSelfNamespace(principal, config) ?? existing;
76
+ }
77
+
78
+ function hasExplicitNamespacePolicy(namespace: string, config: PluginConfig): boolean {
79
+ return (config.namespacePolicies ?? []).some((policy) => policy.name === namespace);
80
+ }
81
+
82
+ function isScopeProfileImplicitSelfNamespace(
83
+ principal: string | undefined,
84
+ namespace: string,
85
+ config: PluginConfig,
86
+ ): boolean {
87
+ const derived = derivedScopeProfileSelfNamespace(principal, config);
88
+ return Boolean(
89
+ derived &&
90
+ namespace === derived &&
91
+ namespace !== config.defaultNamespace &&
92
+ namespace !== config.sharedNamespace &&
93
+ isSafeRouteNamespace(namespace) &&
94
+ !hasExplicitNamespacePolicy(namespace, config),
95
+ );
96
+ }
97
+
98
+ function canReadScopeProfileNamespace(
99
+ principal: string | undefined,
100
+ namespace: string,
101
+ config: PluginConfig,
102
+ ): boolean {
103
+ return isScopeProfileImplicitSelfNamespace(principal, namespace, config) || canReadNamespace(principal, namespace, config);
104
+ }
105
+
106
+ function canWriteScopeProfileNamespace(
107
+ principal: string | undefined,
108
+ namespace: string,
109
+ config: PluginConfig,
110
+ ): boolean {
111
+ return isScopeProfileImplicitSelfNamespace(principal, namespace, config) || canWriteNamespace(principal, namespace, config);
112
+ }
113
+
114
+ function resolveTeam(
115
+ config: PluginConfig,
116
+ profile: ScopeProfileConfig,
117
+ principal: string | undefined,
118
+ ): { teamId: string; team: ScopeTeamConfig } | null {
119
+ const configuredTeamId = profile.teamProject?.teamId;
120
+ if (configuredTeamId) {
121
+ const configured = (config.teams ?? {})[configuredTeamId];
122
+ return configured ? { teamId: configuredTeamId, team: configured } : null;
123
+ }
124
+ const readableTeams = Object.entries(config.teams ?? {}).filter(([, team]) =>
125
+ principalListed(team.principals, principal) || principalListed(team.read, principal),
126
+ );
127
+ const needsWritableTeam = profile.writeDefault === "teamProject" || profile.readOrder.includes("teamProject");
128
+ if (needsWritableTeam) {
129
+ const writableTeam = readableTeams.find(([, team]) => principalListed(team.write, principal));
130
+ if (writableTeam) return { teamId: writableTeam[0], team: writableTeam[1] };
131
+ }
132
+ const needsPromotableTeam =
133
+ profile.promotionTargets.includes("teamProject") || profile.autoPromote.targets.includes("teamProject");
134
+ if (needsPromotableTeam) {
135
+ const promotableTeam = readableTeams.find(
136
+ ([, team]) => principalListed(team.promote, principal) || principalListed(team.write, principal),
137
+ );
138
+ if (promotableTeam) return { teamId: promotableTeam[0], team: promotableTeam[1] };
139
+ }
140
+ const firstReadableTeam = readableTeams[0];
141
+ return firstReadableTeam
142
+ ? { teamId: firstReadableTeam[0], team: firstReadableTeam[1] }
143
+ : null;
144
+ }
145
+
146
+ function renderTeamProjectNamespace(params: {
147
+ template: string;
148
+ teamId: string;
149
+ principal: string | undefined;
150
+ codingContext: CodingContext;
151
+ codingOverlay: ScopeProfileCodingOverlay;
152
+ }): { namespace: string; unknownPlaceholders: string[] } {
153
+ const replacements: Record<string, string> = {
154
+ teamId: params.teamId,
155
+ principal: params.principal ?? "anonymous",
156
+ projectId: params.codingContext.projectId,
157
+ projectHash: stableHash(params.codingContext.projectId),
158
+ projectNamespace: params.codingOverlay.namespace,
159
+ };
160
+ const unknownPlaceholders: string[] = [];
161
+ const namespace = params.template.replace(/\{([A-Za-z][A-Za-z0-9]*)\}/g, (match, key: string) => {
162
+ const replacement = replacements[key];
163
+ if (replacement !== undefined) return replacement;
164
+ if (!unknownPlaceholders.includes(key)) unknownPlaceholders.push(key);
165
+ return match;
166
+ });
167
+ return { namespace, unknownPlaceholders };
168
+ }
169
+
170
+ function resolveLayer(params: {
171
+ id: ScopeProfileLayerId;
172
+ config: PluginConfig;
173
+ profile: ScopeProfileConfig;
174
+ principal: string | undefined;
175
+ baseNamespace: string;
176
+ codingContext: CodingContext | null | undefined;
177
+ codingOverlay: ScopeProfileCodingOverlay | null | undefined;
178
+ }): ScopeProfileLayerResolution {
179
+ const { id, config, profile, principal, baseNamespace, codingContext, codingOverlay } = params;
180
+ if (id === "userGlobal") {
181
+ return {
182
+ id,
183
+ kind: "user-global",
184
+ namespace: baseNamespace,
185
+ readable: canReadScopeProfileNamespace(principal, baseNamespace, config),
186
+ writable: canWriteScopeProfileNamespace(principal, baseNamespace, config),
187
+ promotable: canWriteScopeProfileNamespace(principal, baseNamespace, config),
188
+ reason: "principal self/global namespace",
189
+ };
190
+ }
191
+ if (id === "serverShared") {
192
+ return {
193
+ id,
194
+ kind: "server-shared",
195
+ namespace: config.sharedNamespace,
196
+ readable: canReadNamespace(principal, config.sharedNamespace, config),
197
+ writable: canWriteNamespace(principal, config.sharedNamespace, config),
198
+ promotable: canWriteNamespace(principal, config.sharedNamespace, config),
199
+ reason: "configured shared namespace",
200
+ };
201
+ }
202
+ if (id === "userProject") {
203
+ if (!codingContext || !codingOverlay) {
204
+ return {
205
+ id,
206
+ kind: "user-project",
207
+ readable: false,
208
+ writable: false,
209
+ promotable: false,
210
+ reason: "missing project context",
211
+ };
212
+ }
213
+ const namespace = combineNamespaces(baseNamespace, codingOverlay.namespace);
214
+ const explicitProjectPolicy = hasExplicitNamespacePolicy(namespace, config);
215
+ const baseReadable = canReadScopeProfileNamespace(principal, baseNamespace, config);
216
+ const baseWritable = canWriteScopeProfileNamespace(principal, baseNamespace, config);
217
+ const projectReadable = explicitProjectPolicy
218
+ ? canReadNamespace(principal, namespace, config)
219
+ : baseReadable;
220
+ const projectWritable = explicitProjectPolicy
221
+ ? canWriteNamespace(principal, namespace, config)
222
+ : baseWritable;
223
+ return {
224
+ id,
225
+ kind: "user-project",
226
+ namespace,
227
+ readable: projectReadable,
228
+ writable: projectWritable,
229
+ promotable: projectWritable,
230
+ reason: explicitProjectPolicy
231
+ ? "explicit user-project namespace policy"
232
+ : baseReadable || baseWritable
233
+ ? "principal project namespace derived from coding context"
234
+ : "principal base namespace is not authorized",
235
+ };
236
+ }
237
+ const team = resolveTeam(config, profile, principal);
238
+ if (!team) {
239
+ return {
240
+ id,
241
+ kind: "team-project",
242
+ readable: false,
243
+ writable: false,
244
+ promotable: false,
245
+ reason: "no authorized team mapping",
246
+ };
247
+ }
248
+ if (!codingContext || !codingOverlay) {
249
+ return {
250
+ id,
251
+ kind: "team-project",
252
+ readable: false,
253
+ writable: false,
254
+ promotable: false,
255
+ reason: "missing project context",
256
+ };
257
+ }
258
+ const template =
259
+ profile.teamProject?.namespaceTemplate ??
260
+ team.team.projectNamespaceTemplate ??
261
+ "team-{teamId}-project-{projectHash}";
262
+ const renderedNamespace = renderTeamProjectNamespace({
263
+ template,
264
+ teamId: team.teamId,
265
+ principal,
266
+ codingContext,
267
+ codingOverlay,
268
+ });
269
+ const namespace = renderedNamespace.namespace.trim();
270
+ if (renderedNamespace.unknownPlaceholders.length > 0) {
271
+ return {
272
+ id,
273
+ kind: "team-project",
274
+ namespace,
275
+ readable: false,
276
+ writable: false,
277
+ promotable: false,
278
+ reason: `unknown team-project namespace template placeholder(s): ${renderedNamespace.unknownPlaceholders.join(", ")}`,
279
+ };
280
+ }
281
+ if (!namespace || !isSafeRouteNamespace(namespace)) {
282
+ return {
283
+ id,
284
+ kind: "team-project",
285
+ namespace,
286
+ readable: false,
287
+ writable: false,
288
+ promotable: false,
289
+ reason: "team-project namespace template resolved to an unsafe namespace",
290
+ };
291
+ }
292
+ const teamReadable = principalListed(team.team.read, principal) || principalListed(team.team.principals, principal);
293
+ const teamWritable = principalListed(team.team.write, principal);
294
+ const teamPromotable = principalListed(team.team.promote, principal) || principalListed(team.team.write, principal);
295
+ const userProjectSuffix = `-${codingOverlay.namespace}`;
296
+ const userProjectBase = namespace.endsWith(userProjectSuffix)
297
+ ? namespace.slice(0, -userProjectSuffix.length)
298
+ : "";
299
+ const dynamicUserProjectCollision =
300
+ userProjectBase.length > 0 &&
301
+ (userProjectBase === config.defaultNamespace ||
302
+ userProjectBase === config.sharedNamespace ||
303
+ (config.namespacePolicies ?? []).some((policy) => policy.name === userProjectBase));
304
+ const protectedNamespace =
305
+ namespace === config.defaultNamespace ||
306
+ namespace === config.sharedNamespace ||
307
+ dynamicUserProjectCollision ||
308
+ (config.namespacePolicies ?? []).some((policy) => policy.name === namespace);
309
+ const policyReadable = !protectedNamespace || canReadNamespace(principal, namespace, config);
310
+ const policyWritable = !protectedNamespace || canWriteNamespace(principal, namespace, config);
311
+ const policyBlocked = protectedNamespace && (!policyReadable || !policyWritable);
312
+ return {
313
+ id,
314
+ kind: "team-project",
315
+ namespace,
316
+ readable: teamReadable && policyReadable,
317
+ writable: teamWritable && policyWritable,
318
+ promotable: teamPromotable && policyWritable,
319
+ reason: policyBlocked
320
+ ? "team-project namespace collides with a protected namespace policy"
321
+ : "trusted team-project namespace derived from team and project config",
322
+ };
323
+ }
324
+
325
+ export function resolveScopeProfilePlan(
326
+ options: ResolveScopeProfilePlanOptions,
327
+ ): ResolvedScopeProfilePlan | null {
328
+ const active = activeScopeProfile(options.config);
329
+ if (!active || !options.config.namespacesEnabled) return null;
330
+
331
+ const baseNamespace = scopeProfileSelfNamespace(options.principal, options.config);
332
+ const layerIds = Array.from(
333
+ new Set<ScopeProfileLayerId>([
334
+ ...active.profile.readOrder,
335
+ active.profile.writeDefault,
336
+ "userGlobal",
337
+ ...active.profile.promotionTargets.filter((target): target is ScopeProfileLayerId =>
338
+ ["userProject", "teamProject", "userGlobal", "serverShared"].includes(target),
339
+ ),
340
+ ]),
341
+ );
342
+ const layerMap = new Map<ScopeProfileLayerId, ScopeProfileLayerResolution>();
343
+ for (const id of layerIds) {
344
+ layerMap.set(
345
+ id,
346
+ resolveLayer({
347
+ id,
348
+ config: options.config,
349
+ profile: active.profile,
350
+ principal: options.principal,
351
+ baseNamespace,
352
+ codingContext: options.codingContext,
353
+ codingOverlay: options.codingOverlay,
354
+ }),
355
+ );
356
+ }
357
+
358
+ const readNamespaces: string[] = [];
359
+ for (const id of active.profile.readOrder) {
360
+ const layer = layerMap.get(id);
361
+ if (layer?.readable && layer.namespace && !readNamespaces.includes(layer.namespace)) {
362
+ readNamespaces.push(layer.namespace);
363
+ }
364
+ }
365
+
366
+ const preferredWriteLayer = layerMap.get(active.profile.writeDefault);
367
+ const readableWriteLayers = active.profile.readOrder
368
+ .map((id) => layerMap.get(id))
369
+ .filter(
370
+ (layer): layer is ScopeProfileLayerResolution =>
371
+ Boolean(layer?.writable && layer.namespace && readNamespaces.includes(layer.namespace)),
372
+ );
373
+ const fallbackWriteLayer =
374
+ preferredWriteLayer?.writable &&
375
+ preferredWriteLayer.namespace &&
376
+ readNamespaces.includes(preferredWriteLayer.namespace)
377
+ ? preferredWriteLayer
378
+ : readableWriteLayers[0];
379
+ const warnings: string[] = [];
380
+ if (!fallbackWriteLayer?.namespace) {
381
+ warnings.push(`scope profile ${active.profileId} has no writable layer inside the profile read stack; writes disabled`);
382
+ } else if (fallbackWriteLayer.id !== active.profile.writeDefault) {
383
+ warnings.push(
384
+ `scope profile ${active.profileId} writeDefault ${active.profile.writeDefault} unavailable: ${preferredWriteLayer?.reason ?? "not resolved"}`,
385
+ );
386
+ }
387
+
388
+ const promotionTargets = active.profile.promotionTargets.map((target) => {
389
+ const layer = layerMap.get(target as ScopeProfileLayerId);
390
+ if (!layer) {
391
+ return {
392
+ target,
393
+ authorized: false,
394
+ reason: "promotion target did not resolve to a profile layer",
395
+ };
396
+ }
397
+ return {
398
+ target,
399
+ namespace: layer.namespace,
400
+ authorized: layer.promotable && Boolean(layer.namespace),
401
+ reason: layer.reason,
402
+ };
403
+ });
404
+
405
+ return {
406
+ profileId: active.profileId,
407
+ profile: active.profile,
408
+ baseNamespace,
409
+ writeLayer: fallbackWriteLayer?.id ?? active.profile.writeDefault,
410
+ writeNamespace: fallbackWriteLayer?.namespace ?? "",
411
+ readNamespaces,
412
+ layers: [...layerMap.values()],
413
+ promotionTargets,
414
+ warnings,
415
+ };
416
+ }
417
+
418
+ export function expandScopeProfileReadNamespaces(options: {
419
+ profilePlan: ResolvedScopeProfilePlan;
420
+ principalSelfNamespace: string;
421
+ config: PluginConfig;
422
+ principal?: string;
423
+ codingOverlay?: ScopeProfileCodingOverlay | null;
424
+ legacyRecallNamespaces?: string[];
425
+ }): string[] {
426
+ if (options.profilePlan.readNamespaces.length === 0) {
427
+ return [];
428
+ }
429
+ const out = [...options.profilePlan.readNamespaces];
430
+ const add = (namespace: string | undefined): void => {
431
+ if (namespace && !out.includes(namespace)) out.push(namespace);
432
+ };
433
+ const userProjectReadable =
434
+ options.profilePlan.profile.readOrder.includes("userProject") &&
435
+ options.profilePlan.layers.some(
436
+ (layer) => layer.id === "userProject" && layer.readable && layer.namespace,
437
+ );
438
+ const userGlobalReadable =
439
+ options.profilePlan.profile.readOrder.includes("userGlobal") &&
440
+ options.profilePlan.layers.some(
441
+ (layer) => layer.id === "userGlobal" && layer.readable && layer.namespace,
442
+ );
443
+ if (userProjectReadable) {
444
+ for (const fallback of options.codingOverlay?.readFallbacks ?? []) {
445
+ if (fallback === "" && !userGlobalReadable) continue;
446
+ const fallbackNamespace = combineNamespaces(options.principalSelfNamespace, fallback);
447
+ if (
448
+ !hasExplicitNamespacePolicy(fallbackNamespace, options.config) ||
449
+ canReadScopeProfileNamespace(options.principal, fallbackNamespace, options.config)
450
+ ) {
451
+ add(fallbackNamespace);
452
+ }
453
+ }
454
+ }
455
+ return out;
456
+ }
@@ -13,6 +13,12 @@ type CollectionState = "present" | "missing" | "unknown" | "skipped";
13
13
 
14
14
  class FakeBackend implements SearchBackend {
15
15
  updates = 0;
16
+ strictUpdates = 0;
17
+ strictCollectionUpdates: string[] = [];
18
+ embeds = 0;
19
+ collectionEmbeds: string[] = [];
20
+ strictEmbeds = 0;
21
+ strictCollectionEmbeds: string[] = [];
16
22
  disposed = 0;
17
23
  available = true;
18
24
  calls: Array<{
@@ -130,15 +136,35 @@ class FakeBackend implements SearchBackend {
130
136
  this.updates += 1;
131
137
  }
132
138
 
139
+ async updateStrict(): Promise<void> {
140
+ this.strictUpdates += 1;
141
+ }
142
+
133
143
  async updateCollection(): Promise<void> {}
134
144
 
145
+ async updateCollectionStrict(collection: string): Promise<void> {
146
+ this.strictCollectionUpdates.push(collection);
147
+ }
148
+
135
149
  updatesAllCollections(): boolean {
136
150
  return this.globalUpdate;
137
151
  }
138
152
 
139
- async embed(): Promise<void> {}
153
+ async embed(): Promise<void> {
154
+ this.embeds += 1;
155
+ }
156
+
157
+ async embedStrict(): Promise<void> {
158
+ this.strictEmbeds += 1;
159
+ }
140
160
 
141
- async embedCollection(): Promise<void> {}
161
+ async embedCollection(collection: string): Promise<void> {
162
+ this.collectionEmbeds.push(collection);
163
+ }
164
+
165
+ async embedCollectionStrict(collection: string): Promise<void> {
166
+ this.strictCollectionEmbeds.push(collection);
167
+ }
142
168
 
143
169
  async ensureCollection(
144
170
  _memoryDir?: string,
@@ -253,6 +279,108 @@ test("updateNamespaces still updates every namespace for scoped backends", async
253
279
  assert.equal(created.reduce((sum, backend) => sum + backend.updates, 0), 3);
254
280
  });
255
281
 
282
+ test("updateNamespaces uses strict global update when requested", async () => {
283
+ const created: FakeBackend[] = [];
284
+ const router = new NamespaceSearchRouter(
285
+ config(),
286
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
287
+ () => {
288
+ const backend = new FakeBackend(true);
289
+ created.push(backend);
290
+ return backend;
291
+ },
292
+ );
293
+
294
+ const updated = await router.updateNamespaces(
295
+ ["main", "shared", "main", "project"],
296
+ undefined,
297
+ { strict: true },
298
+ );
299
+
300
+ assert.equal(updated, 1);
301
+ assert.equal(created.reduce((sum, backend) => sum + backend.strictUpdates, 0), 1);
302
+ assert.equal(created.reduce((sum, backend) => sum + backend.updates, 0), 0);
303
+ });
304
+
305
+ test("updateNamespaces uses strict collection updates for scoped backends when requested", async () => {
306
+ const created: FakeBackend[] = [];
307
+ const router = new NamespaceSearchRouter(
308
+ config(),
309
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
310
+ () => {
311
+ const backend = new FakeBackend(false);
312
+ created.push(backend);
313
+ return backend;
314
+ },
315
+ );
316
+
317
+ const updated = await router.updateNamespaces(
318
+ ["main", "shared", "main", "project"],
319
+ undefined,
320
+ { strict: true },
321
+ );
322
+
323
+ assert.equal(updated, 3);
324
+ assert.equal(created.reduce((sum, backend) => sum + backend.strictCollectionUpdates.length, 0), 3);
325
+ assert.equal(created.reduce((sum, backend) => sum + backend.updates, 0), 0);
326
+ });
327
+
328
+ test("embedNamespaces uses strict collection embeds when requested", async () => {
329
+ const created: FakeBackend[] = [];
330
+ const router = new NamespaceSearchRouter(
331
+ config(),
332
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
333
+ () => {
334
+ const backend = new FakeBackend(false);
335
+ created.push(backend);
336
+ return backend;
337
+ },
338
+ );
339
+
340
+ await router.embedNamespaces(["main", "shared", "main", "project"], { strict: true });
341
+
342
+ assert.equal(created.reduce((sum, backend) => sum + backend.strictCollectionEmbeds.length, 0), 3);
343
+ assert.equal(created.reduce((sum, backend) => sum + backend.collectionEmbeds.length, 0), 0);
344
+ assert.equal(created.reduce((sum, backend) => sum + backend.embeds, 0), 0);
345
+ });
346
+
347
+ test("embedNamespaces propagates strict embed failures", async () => {
348
+ const router = new NamespaceSearchRouter(
349
+ config(),
350
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
351
+ () => {
352
+ const backend = new FakeBackend(false);
353
+ backend.embedCollectionStrict = async () => {
354
+ throw new Error("embed failed");
355
+ };
356
+ return backend;
357
+ },
358
+ );
359
+
360
+ await assert.rejects(
361
+ () => router.embedNamespaces(["main"], { strict: true }),
362
+ /embed failed/,
363
+ );
364
+ });
365
+
366
+ test("updateNamespacesDetailed reports only eligible namespaces", async () => {
367
+ const router = new NamespaceSearchRouter(
368
+ config(),
369
+ { storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
370
+ (scopedConfig) => {
371
+ const backend = new FakeBackend(false, [], {
372
+ ensure: scopedConfig.memoryDir.endsWith("/missing") ? "missing" : "present",
373
+ });
374
+ return backend;
375
+ },
376
+ );
377
+
378
+ const result = await router.updateNamespacesDetailed(["main", "missing", "shared"]);
379
+
380
+ assert.equal(result.backendCount, 2);
381
+ assert.deepEqual(result.eligibleNamespaces.sort(), ["main", "shared"]);
382
+ });
383
+
256
384
  test("searchAcrossNamespaces preserves same path results from distinct namespaces", async () => {
257
385
  const router = new NamespaceSearchRouter(
258
386
  config(),