@remnic/core 9.3.753 → 9.3.754

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 (160) hide show
  1. package/dist/access-admin-ops-surface.d.ts +1 -1
  2. package/dist/access-admin-ops-surface.js +26 -26
  3. package/dist/access-boundary.d.ts +1 -1
  4. package/dist/access-boundary.js +27 -27
  5. package/dist/access-cli.js +75 -75
  6. package/dist/access-http.d.ts +1 -1
  7. package/dist/access-http.js +34 -34
  8. package/dist/access-lcm-surface.d.ts +188 -0
  9. package/dist/access-lcm-surface.js +128 -0
  10. package/dist/access-lcm-surface.js.map +1 -0
  11. package/dist/access-mcp.d.ts +1 -1
  12. package/dist/access-mcp.js +31 -31
  13. package/dist/access-operations-batch.js +28 -28
  14. package/dist/access-operations.d.ts +5 -5
  15. package/dist/access-operations.js +30 -30
  16. package/dist/access-recall-surface.d.ts +1 -1
  17. package/dist/access-recall-surface.js +26 -26
  18. package/dist/access-schema.d.ts +74 -74
  19. package/dist/access-schema.js +6 -6
  20. package/dist/{access-service-MitduesD.d.ts → access-service-DKRV1zxh.d.ts} +4 -64
  21. package/dist/access-service.d.ts +1 -1
  22. package/dist/access-service.js +26 -26
  23. package/dist/access-surface-catalog.d.ts +1 -1
  24. package/dist/adapters/index.js +4 -4
  25. package/dist/adapters/registry.js +2 -2
  26. package/dist/{auto-sync-IEBKPGAM.js → auto-sync-G4YKBPOU.js} +5 -5
  27. package/dist/briefing.js +7 -7
  28. package/dist/calibration.js +2 -2
  29. package/dist/causal-consolidation.js +8 -8
  30. package/dist/{chunk-HAZVVOYT.js → chunk-24463PIV.js} +2 -2
  31. package/dist/{chunk-C3IHRWIC.js → chunk-2MHCWXX6.js} +2 -2
  32. package/dist/{chunk-GWGWBK5Y.js → chunk-2MR3MFQB.js} +7 -7
  33. package/dist/{chunk-YTU7WKYN.js → chunk-2OZ2JEUP.js} +2 -2
  34. package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
  35. package/dist/{chunk-EHYB325U.js → chunk-3DOD6ICJ.js} +2 -2
  36. package/dist/{chunk-KY4WOZ3P.js → chunk-4DVYRPLL.js} +42 -42
  37. package/dist/{chunk-VUA4S7P6.js → chunk-5J4OL7TJ.js} +4 -4
  38. package/dist/{chunk-EEO4YWQ7.js → chunk-7AL5WNPW.js} +2 -2
  39. package/dist/{chunk-BWOEYBRB.js → chunk-7CAJLUNW.js} +2 -2
  40. package/dist/{chunk-JES2O5Q6.js → chunk-BERWPOXU.js} +4 -4
  41. package/dist/{chunk-3HGZX26P.js → chunk-BQ6J6P3K.js} +8 -8
  42. package/dist/{chunk-XOPLMSVV.js → chunk-CJ276MDI.js} +2 -2
  43. package/dist/{chunk-TVISHYL5.js → chunk-COZEVVXZ.js} +2 -2
  44. package/dist/{chunk-ZIFPFKB2.js → chunk-CXCZ4LAI.js} +2 -2
  45. package/dist/{chunk-OUXPVWTZ.js → chunk-FLVL4L3L.js} +5 -5
  46. package/dist/{chunk-LZ73Y56P.js → chunk-G7LCG56O.js} +4 -4
  47. package/dist/{chunk-E3MRYDUQ.js → chunk-GAH5755F.js} +2 -2
  48. package/dist/{chunk-JGPURIEH.js → chunk-GPFXPC2V.js} +2 -2
  49. package/dist/{chunk-3ZIKDH6C.js → chunk-JBO6UBIE.js} +4 -4
  50. package/dist/{chunk-YNBEHUZH.js → chunk-KURUULFF.js} +2 -2
  51. package/dist/{chunk-ZECLLIMX.js → chunk-L4AS7TIG.js} +4998 -4950
  52. package/dist/chunk-L4AS7TIG.js.map +1 -0
  53. package/dist/{chunk-LQ6JI4VH.js → chunk-LD53WPMU.js} +4 -4
  54. package/dist/{chunk-QIRZI3C3.js → chunk-OH66EBCS.js} +2 -2
  55. package/dist/{chunk-OZMBT7N3.js → chunk-PJQMISPD.js} +2 -2
  56. package/dist/{chunk-PN5AMCK2.js → chunk-PYVZKEZZ.js} +53 -53
  57. package/dist/{chunk-DCWIQFNA.js → chunk-T422SYM6.js} +5 -5
  58. package/dist/{chunk-5CEJH5ZN.js → chunk-TAPSNIMA.js} +4 -4
  59. package/dist/{chunk-3NN73CM7.js → chunk-TH7WMHVK.js} +2 -2
  60. package/dist/{chunk-34THARMH.js → chunk-THZ4WR5L.js} +10 -10
  61. package/dist/{chunk-E6LENGYV.js → chunk-TIFF32QK.js} +2 -2
  62. package/dist/{chunk-RNWWAS5V.js → chunk-TJATYB3F.js} +11 -11
  63. package/dist/{chunk-KYHGLDFU.js → chunk-TXALJRYO.js} +6 -6
  64. package/dist/{chunk-DKIZ4BUQ.js → chunk-U2SMR57B.js} +7 -7
  65. package/dist/{chunk-VVSV2QVY.js → chunk-V4C4SFZB.js} +3 -3
  66. package/dist/{chunk-AXBRVBT6.js → chunk-VSU2NZGC.js} +2 -2
  67. package/dist/{chunk-N3VE7OCY.js → chunk-WS6GLJ7P.js} +2 -2
  68. package/dist/{chunk-ITLJATQI.js → chunk-WUVUTOUV.js} +2 -2
  69. package/dist/{chunk-WRFKZEO6.js → chunk-YQGHW5ML.js} +4 -4
  70. package/dist/{chunk-243P7YYV.js → chunk-YRBXBHU7.js} +2 -2
  71. package/dist/{chunk-ZMHDSIEX.js → chunk-ZJMSTFOR.js} +4 -4
  72. package/dist/{cli-DqonYBZ0.d.ts → cli-D-xw8Sp1.d.ts} +1 -1
  73. package/dist/cli.d.ts +2 -2
  74. package/dist/cli.js +55 -55
  75. package/dist/compounding/engine.js +7 -7
  76. package/dist/connectors/codex-materialize-runner.js +7 -7
  77. package/dist/connectors/index.js +7 -7
  78. package/dist/conversation-index/backend.js +2 -2
  79. package/dist/entity-retrieval.js +7 -7
  80. package/dist/extraction-faithfulness.js +3 -3
  81. package/dist/extraction-judge.js +3 -3
  82. package/dist/extraction.js +3 -3
  83. package/dist/fallback-llm.js +2 -2
  84. package/dist/index.d.ts +2 -2
  85. package/dist/index.js +149 -149
  86. package/dist/lcm/engine.js +2 -2
  87. package/dist/lcm/index.js +4 -4
  88. package/dist/maintenance/memory-governance.js +7 -7
  89. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
  90. package/dist/maintenance/rebuild-memory-projection.js +8 -8
  91. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  92. package/dist/namespaces/migrate.js +17 -17
  93. package/dist/namespaces/search.js +9 -9
  94. package/dist/namespaces/storage.js +7 -7
  95. package/dist/operator-toolkit.js +21 -21
  96. package/dist/orchestration/maintenance.js +9 -9
  97. package/dist/orchestrator.js +75 -75
  98. package/dist/recall-planner-llm.js +2 -2
  99. package/dist/schemas.d.ts +90 -90
  100. package/dist/search/factory.js +8 -8
  101. package/dist/search/index.js +13 -13
  102. package/dist/search/lancedb-backend.js +2 -2
  103. package/dist/search/meilisearch-backend.js +2 -2
  104. package/dist/search/orama-backend.js +2 -2
  105. package/dist/semantic-consolidation.js +8 -8
  106. package/dist/semantic-rule-promotion.js +7 -7
  107. package/dist/semantic-rule-verifier.js +7 -7
  108. package/dist/shared-context/manager.d.ts +8 -8
  109. package/dist/storage.js +6 -6
  110. package/dist/summarizer.js +3 -3
  111. package/dist/transfer/autodetect.js +1 -1
  112. package/dist/transfer/backup.js +1 -1
  113. package/dist/transfer/capsule-export.js +2 -2
  114. package/dist/transfer/types.d.ts +66 -66
  115. package/dist/verified-recall.js +7 -7
  116. package/package.json +2 -2
  117. package/src/access-lcm-surface.ts +517 -0
  118. package/src/access-service.ts +21 -417
  119. package/dist/chunk-ZECLLIMX.js.map +0 -1
  120. /package/dist/{auto-sync-IEBKPGAM.js.map → auto-sync-G4YKBPOU.js.map} +0 -0
  121. /package/dist/{chunk-HAZVVOYT.js.map → chunk-24463PIV.js.map} +0 -0
  122. /package/dist/{chunk-C3IHRWIC.js.map → chunk-2MHCWXX6.js.map} +0 -0
  123. /package/dist/{chunk-GWGWBK5Y.js.map → chunk-2MR3MFQB.js.map} +0 -0
  124. /package/dist/{chunk-YTU7WKYN.js.map → chunk-2OZ2JEUP.js.map} +0 -0
  125. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  126. /package/dist/{chunk-EHYB325U.js.map → chunk-3DOD6ICJ.js.map} +0 -0
  127. /package/dist/{chunk-KY4WOZ3P.js.map → chunk-4DVYRPLL.js.map} +0 -0
  128. /package/dist/{chunk-VUA4S7P6.js.map → chunk-5J4OL7TJ.js.map} +0 -0
  129. /package/dist/{chunk-EEO4YWQ7.js.map → chunk-7AL5WNPW.js.map} +0 -0
  130. /package/dist/{chunk-BWOEYBRB.js.map → chunk-7CAJLUNW.js.map} +0 -0
  131. /package/dist/{chunk-JES2O5Q6.js.map → chunk-BERWPOXU.js.map} +0 -0
  132. /package/dist/{chunk-3HGZX26P.js.map → chunk-BQ6J6P3K.js.map} +0 -0
  133. /package/dist/{chunk-XOPLMSVV.js.map → chunk-CJ276MDI.js.map} +0 -0
  134. /package/dist/{chunk-TVISHYL5.js.map → chunk-COZEVVXZ.js.map} +0 -0
  135. /package/dist/{chunk-ZIFPFKB2.js.map → chunk-CXCZ4LAI.js.map} +0 -0
  136. /package/dist/{chunk-OUXPVWTZ.js.map → chunk-FLVL4L3L.js.map} +0 -0
  137. /package/dist/{chunk-LZ73Y56P.js.map → chunk-G7LCG56O.js.map} +0 -0
  138. /package/dist/{chunk-E3MRYDUQ.js.map → chunk-GAH5755F.js.map} +0 -0
  139. /package/dist/{chunk-JGPURIEH.js.map → chunk-GPFXPC2V.js.map} +0 -0
  140. /package/dist/{chunk-3ZIKDH6C.js.map → chunk-JBO6UBIE.js.map} +0 -0
  141. /package/dist/{chunk-YNBEHUZH.js.map → chunk-KURUULFF.js.map} +0 -0
  142. /package/dist/{chunk-LQ6JI4VH.js.map → chunk-LD53WPMU.js.map} +0 -0
  143. /package/dist/{chunk-QIRZI3C3.js.map → chunk-OH66EBCS.js.map} +0 -0
  144. /package/dist/{chunk-OZMBT7N3.js.map → chunk-PJQMISPD.js.map} +0 -0
  145. /package/dist/{chunk-PN5AMCK2.js.map → chunk-PYVZKEZZ.js.map} +0 -0
  146. /package/dist/{chunk-DCWIQFNA.js.map → chunk-T422SYM6.js.map} +0 -0
  147. /package/dist/{chunk-5CEJH5ZN.js.map → chunk-TAPSNIMA.js.map} +0 -0
  148. /package/dist/{chunk-3NN73CM7.js.map → chunk-TH7WMHVK.js.map} +0 -0
  149. /package/dist/{chunk-34THARMH.js.map → chunk-THZ4WR5L.js.map} +0 -0
  150. /package/dist/{chunk-E6LENGYV.js.map → chunk-TIFF32QK.js.map} +0 -0
  151. /package/dist/{chunk-RNWWAS5V.js.map → chunk-TJATYB3F.js.map} +0 -0
  152. /package/dist/{chunk-KYHGLDFU.js.map → chunk-TXALJRYO.js.map} +0 -0
  153. /package/dist/{chunk-DKIZ4BUQ.js.map → chunk-U2SMR57B.js.map} +0 -0
  154. /package/dist/{chunk-VVSV2QVY.js.map → chunk-V4C4SFZB.js.map} +0 -0
  155. /package/dist/{chunk-AXBRVBT6.js.map → chunk-VSU2NZGC.js.map} +0 -0
  156. /package/dist/{chunk-N3VE7OCY.js.map → chunk-WS6GLJ7P.js.map} +0 -0
  157. /package/dist/{chunk-ITLJATQI.js.map → chunk-WUVUTOUV.js.map} +0 -0
  158. /package/dist/{chunk-WRFKZEO6.js.map → chunk-YQGHW5ML.js.map} +0 -0
  159. /package/dist/{chunk-243P7YYV.js.map → chunk-YRBXBHU7.js.map} +0 -0
  160. /package/dist/{chunk-ZMHDSIEX.js.map → chunk-ZJMSTFOR.js.map} +0 -0
@@ -251,6 +251,7 @@ import {
251
251
  import { formatProfileTraceAscii } from "./profiling.js";
252
252
  import { resolveAccessSetupCapabilities, resolveGraphConstructionCapabilities, resolveIndexingCapabilities } from "./capabilities.js";
253
253
  import { resolveRecallEnhancementCapabilities } from "./capabilities.js";
254
+ import { AccessLcmSurface } from "./access-lcm-surface.js";
254
255
  import { AccessAdminOpsSurface } from "./access-admin-ops-surface.js";
255
256
  import { AccessRecallSurface } from "./access-recall-surface.js";
256
257
  import { selfDeps } from "./orchestration/self-deps.js";
@@ -1308,6 +1309,18 @@ export class EngramAccessService {
1308
1309
  private readonly budget: CrossNamespaceBudget;
1309
1310
  private readonly auditAdapter: AccessAuditAdapter | null;
1310
1311
 
1312
+ /** AccessLcmSurface (access-service decomposition). Lazy; selfDeps live wiring. */
1313
+ private _accessLcmSurface: AccessLcmSurface | undefined;
1314
+
1315
+ private get accessLcmSurface(): AccessLcmSurface {
1316
+ if (!this._accessLcmSurface) {
1317
+ this._accessLcmSurface = new AccessLcmSurface(
1318
+ selfDeps<ConstructorParameters<typeof AccessLcmSurface>[0]>(this),
1319
+ );
1320
+ }
1321
+ return this._accessLcmSurface;
1322
+ }
1323
+
1311
1324
  /** AccessAdminOpsSurface (access-service decomposition). Lazy; selfDeps live wiring. */
1312
1325
  private _accessAdminOpsSurface: AccessAdminOpsSurface | undefined;
1313
1326
 
@@ -4603,212 +4616,9 @@ export class EngramAccessService {
4603
4616
  }
4604
4617
 
4605
4618
  async lcmSearch(request: EngramAccessLcmSearchRequest): Promise<EngramAccessLcmSearchResponse> {
4606
- if (!request.query || typeof request.query !== "string" || request.query.trim().length === 0) {
4607
- throw new EngramAccessInputError("query is required and must be a non-empty string");
4608
- }
4609
-
4610
- const principal = this.resolveRequestPrincipal(request.sessionKey, request.authenticatedPrincipal);
4611
- const hasExplicitNamespace =
4612
- typeof request.namespace === "string" &&
4613
- request.namespace.trim().length > 0;
4614
- // Resolve the readable base namespace WITHOUT pre-authorizing `default`
4615
- // (#1505 thread NBHWz). An EXPLICIT namespace is still authorized strictly
4616
- // via `resolveReadableNamespace` (explicit reads must pass the ACL — throws
4617
- // on an unreadable explicit namespace). For an IMPLICIT read, derive the
4618
- // fallback from the ALREADY read-authorized recall namespace set instead of
4619
- // read-authorizing `config.defaultNamespace`: under a restrictive `default`
4620
- // READ policy where the principal's self namespace is readable, normal recall
4621
- // still succeeds via `recallNamespacesForPrincipal`, so `lcmSearch` must too
4622
- // (the same defect class the raw-excerpt path fixes). `undefined` ⇒ no
4623
- // readable LCM namespace exists, so return NO rows rather than throwing.
4624
- const profileLcmReadNamespaces = hasExplicitNamespace
4625
- ? null
4626
- : this.resolveScopeProfileLcmReadNamespaces(
4627
- request.sessionKey,
4628
- request.authenticatedPrincipal,
4629
- );
4630
- const namespace = hasExplicitNamespace
4631
- ? this.resolveReadableNamespace(request.namespace, principal)
4632
- : profileLcmReadNamespaces !== null
4633
- ? profileLcmReadNamespaces[0]
4634
- : this.resolveImplicitLcmReadFallbackNamespace(principal);
4635
-
4636
- if (!this.orchestrator.lcmEngine || !this.orchestrator.lcmEngine.enabled) {
4637
- return {
4638
- query: request.query,
4639
- namespace: namespace ?? this.orchestrator.config.defaultNamespace,
4640
- results: [],
4641
- count: 0,
4642
- lcmEnabled: false,
4643
- };
4644
- }
4645
-
4646
- // An active scope profile with no readable layers is authoritative: search
4647
- // no legacy/default LCM keys instead of falling back around the profile.
4648
- if (profileLcmReadNamespaces !== null && profileLcmReadNamespaces.length === 0) {
4649
- return {
4650
- query: request.query,
4651
- namespace: this.orchestrator.config.defaultNamespace,
4652
- results: [],
4653
- count: 0,
4654
- lcmEnabled: true,
4655
- };
4656
- }
4657
-
4658
- // No readable LCM namespace for an IMPLICIT read (restrictive `default` READ
4659
- // policy, no readable overlay/self) ⇒ return NO rows instead of pre-
4660
- // authorizing the denied default (#1505 thread NBHWz). Normal recall still
4661
- // succeeds through the readable self namespace; LCM search degrades to empty.
4662
- if (namespace === undefined) {
4663
- return {
4664
- query: request.query,
4665
- namespace: this.orchestrator.config.defaultNamespace,
4666
- results: [],
4667
- count: 0,
4668
- lcmEnabled: true,
4669
- };
4670
- }
4671
-
4672
- const limit = Math.max(1, Math.min(request.limit ?? 10, 100));
4673
- // Route the LCM read session_id AND prefix through the SAME overlay-aware
4674
- // namespace `observe`'s write key and compaction use (#1505 round 3). A
4675
- // project-scoped `observe` with no explicit namespace archived under the
4676
- // coding-overlay namespace; deriving the prefix only from the readable
4677
- // `namespace` (as before) would search the raw key and miss those turns.
4678
- // The effective namespace is resolved ONCE from the real session
4679
- // (`request.sessionKey`) — the prefix is a search fragment with no bound
4680
- // coding context, so it inherits the same namespace. Collapses to the raw key
4681
- // for single-store / no-overlay / explicit-default flows (existing behavior).
4682
- const lcmReadNamespace = profileLcmReadNamespaces !== null
4683
- ? profileLcmReadNamespaces[0] ?? this.orchestrator.config.defaultNamespace
4684
- : this.resolveLcmReadNamespace(
4685
- request.namespace,
4686
- namespace,
4687
- request.sessionKey,
4688
- request.authenticatedPrincipal,
4689
- );
4690
- // Ordered, read-authorized LCM read key SET for a concrete `sessionKey`
4691
- // (#1505 fallback unification + #1501 scope profiles). A branch-scoped
4692
- // session whose rows were archived at project/root scope is found by querying
4693
- // the primary overlay key first, then each fallback. When a scope profile is
4694
- // active, the profile's expanded read namespace set is authoritative,
4695
- // including the empty set.
4696
- const lcmSessionKeyIds = request.sessionKey
4697
- ? profileLcmReadNamespaces !== null
4698
- ? this.lcmSessionIdsForNamespaces(
4699
- profileLcmReadNamespaces,
4700
- request.sessionKey,
4701
- )
4702
- : this.resolveLcmReadSessionIds(
4703
- request.namespace,
4704
- namespace,
4705
- request.sessionKey,
4706
- request.authenticatedPrincipal,
4707
- )
4708
- : [undefined];
4709
- const lcmSessionPrefixes = request.sessionPrefix
4710
- ? profileLcmReadNamespaces !== null && !request.sessionKey
4711
- ? this.lcmSessionIdsForNamespaces(
4712
- profileLcmReadNamespaces,
4713
- request.sessionPrefix,
4714
- )
4715
- : [
4716
- lcmSessionKeyForNamespace(
4717
- lcmReadNamespace,
4718
- request.sessionPrefix,
4719
- this.orchestrator.config.defaultNamespace,
4720
- ) ?? request.sessionPrefix,
4721
- ]
4722
- : [undefined];
4723
- // SECURITY (#1495 P1 + codex P1 r2 "Require a scoped LCM filter before
4724
- // archive searches"): a sessionless, prefixless `lcmSearch` issues
4725
- // `searchContextFull(query, limit, undefined, undefined)`, an archive-wide
4726
- // FTS scan over EVERY `session_id`, including the sentinel-framed
4727
- // `<ns>`-scoped overlay/tenant rows. The LCM archive is keyed by the
4728
- // `session_id` STRING and is NOT partitioned by namespace, so an unscoped
4729
- // scan CANNOT be constrained to the caller's authorized namespace — neither
4730
- // an explicit `namespace` nor a readable `default` confines its results to
4731
- // rows the caller may read. The scan is therefore safe ONLY in single-store
4732
- // mode (namespaces disabled, one shared archive owned by the caller). When
4733
- // namespaces are ENABLED, an unscoped `lcmSearch` (no `sessionKey` AND no
4734
- // `sessionPrefix`) must be SUPPRESSED — return EMPTY — regardless of an
4735
- // explicit namespace or default-readability, so a caller authorized for
4736
- // `default` (or for one explicit namespace) cannot read other namespaces'
4737
- // transcript rows via the archive-wide scan (cross-tenant read leak). A
4738
- // SCOPED call (sessionKey or sessionPrefix present) is unaffected: it carries
4739
- // a namespace-framed `session_id` / prefix filter that already constrains the
4740
- // search to the caller's authorized, read-gated namespace.
4741
- const hasScopedSession =
4742
- (typeof request.sessionKey === "string" &&
4743
- request.sessionKey.length > 0) ||
4744
- lcmSessionPrefixes.some((prefix) => typeof prefix === "string" && prefix.length > 0);
4745
- if (!hasScopedSession && resolveNamespaceCapabilities(this.orchestrator.config).namespaces === true) {
4746
- return {
4747
- query: request.query,
4748
- namespace,
4749
- results: [],
4750
- count: 0,
4751
- lcmEnabled: true,
4752
- };
4753
- }
4754
- // Query each LCM read key in order, merging + deduping rows (by
4755
- // sessionId+turnIndex) and preserving first-seen order, capped at `limit`.
4756
- // Use allSettled so one corrupt/failed namespace key cannot discard sibling
4757
- // results from other authorized profile namespaces.
4758
- const seenRows = new Set<string>();
4759
- const results: Array<{ sessionId: string; content: string; turnIndex: number }> = [];
4760
- const lcmSearches: Array<{
4761
- key: string | undefined;
4762
- prefix: string | undefined;
4763
- promise: Promise<Array<{ session_id: string; content: string; turn_index: number }>>;
4764
- }> = [];
4765
- for (const lcmSessionKey of lcmSessionKeyIds) {
4766
- for (const lcmSessionPrefix of lcmSessionPrefixes) {
4767
- lcmSearches.push({
4768
- key: lcmSessionKey,
4769
- prefix: lcmSessionPrefix,
4770
- promise: this.orchestrator.lcmEngine.searchContextFull(
4771
- request.query,
4772
- limit,
4773
- lcmSessionKey,
4774
- lcmSessionPrefix,
4775
- ) as Promise<Array<{ session_id: string; content: string; turn_index: number }>>,
4776
- });
4777
- }
4778
- }
4779
- const settledSearches = await Promise.allSettled(
4780
- lcmSearches.map((search) => search.promise),
4619
+ return this.accessLcmSurface.lcmSearch(
4620
+ request,
4781
4621
  );
4782
- for (let i = 0; i < settledSearches.length; i += 1) {
4783
- if (results.length >= limit) break;
4784
- const settled = settledSearches[i];
4785
- if (!settled || settled.status === "rejected") {
4786
- const failed = lcmSearches[i];
4787
- log.warn(
4788
- `lcmSearch: failed for key=${failed?.key ?? "<none>"} prefix=${failed?.prefix ?? "<none>"}: ${settled?.status === "rejected" ? settled.reason : "missing result"}`,
4789
- );
4790
- continue;
4791
- }
4792
- for (const r of settled.value) {
4793
- const dedupeKey = `${r.session_id}\0${r.turn_index}`;
4794
- if (seenRows.has(dedupeKey)) continue;
4795
- seenRows.add(dedupeKey);
4796
- results.push({
4797
- sessionId: r.session_id,
4798
- content: r.content,
4799
- turnIndex: r.turn_index,
4800
- });
4801
- if (results.length >= limit) break;
4802
- }
4803
- }
4804
-
4805
- return {
4806
- query: request.query,
4807
- namespace,
4808
- results,
4809
- count: results.length,
4810
- lcmEnabled: true,
4811
- };
4812
4622
  }
4813
4623
 
4814
4624
  /**
@@ -4833,38 +4643,6 @@ export class EngramAccessService {
4833
4643
  * so the read key is byte-for-byte what the LCM write and the recall readers
4834
4644
  * use.
4835
4645
  */
4836
- /**
4837
- * Resolve the effective LCM NAMESPACE a same-session operation must prefix
4838
- * with (the namespace half of {@link resolveLcmReadSessionKey}). Split out so
4839
- * `lcmSearch` can apply ONE namespace to BOTH its `sessionKey` and its
4840
- * `sessionPrefix` — the prefix is a search fragment, not a real session, so its
4841
- * own coding context can't be looked up; it must inherit the namespace resolved
4842
- * from the real session (`sessionKeyForOverlay`).
4843
- *
4844
- * `purpose` selects the AUTHORIZATION gate applied before honouring the
4845
- * coding overlay (#1505 round 3 + round 4, codex P2):
4846
- *
4847
- * - `"read"` (`lcmSearch` / raw-excerpt recall): the overlay rows are only
4848
- * visible when the principal SELF base is in the READABLE RECALL SET — the
4849
- * same gate the orchestrator's `lcmReadNamespaceForSession` and the recall
4850
- * namespace set use (`recallNamespacesForPrincipal`, gated by both
4851
- * `defaultRecallNamespaces.includes("self")` AND `canReadNamespace`). A
4852
- * caller that passed the default read check must NOT receive
4853
- * `<principal>-project-*` rows the policy never granted (cross-tenant read
4854
- * leak). When the self base is not readable, keep the just-authorized
4855
- * namespace (collapses to the raw key on the default store).
4856
- *
4857
- * - `"write"` (`lcmCompactionFlush` / `lcmCompactionRecord`): these are
4858
- * write/maintenance operations on the SAME queue `observe` just wrote, so
4859
- * the gate must mirror observe's WRITE authorization (`canWriteNamespace`
4860
- * on the self base), NOT readability. A principal that can WRITE but not
4861
- * READ its self namespace (or whose `defaultRecallNamespaces` omits `self`)
4862
- * archived under the overlay key via `observe`; gating compaction by
4863
- * readability would fall back to the default/raw key and leave that queue
4864
- * never flushed/recorded (round-4 codex P2). Write-authorized ⇒ overlay
4865
- * key, matching the observe write key (rule 42 read/write parity; rule 39
4866
- * identical gates across paths).
4867
- */
4868
4646
  private resolveLcmReadNamespace(
4869
4647
  explicitNamespace: string | undefined,
4870
4648
  resolvedNamespace: string,
@@ -4872,134 +4650,24 @@ export class EngramAccessService {
4872
4650
  authenticatedPrincipal: string | undefined,
4873
4651
  purpose: "read" | "write" = "read",
4874
4652
  ): string {
4875
- const hasExplicitNamespace =
4876
- typeof explicitNamespace === "string" && explicitNamespace.trim().length > 0;
4877
- if (hasExplicitNamespace) return resolvedNamespace;
4878
- // Mirror observe's write resolution: use the coding-overlay namespace when
4879
- // one applies, else the default store. NOT the principal self base — an
4880
- // unqualified observe archives under the default store, so a self-base prefix
4881
- // here would target a queue observe never wrote to (#1495).
4882
- const principal = this.resolveRequestPrincipal(
4653
+ return this.accessLcmSurface.resolveLcmReadNamespace(
4654
+ explicitNamespace,
4655
+ resolvedNamespace,
4883
4656
  sessionKeyForOverlay,
4884
4657
  authenticatedPrincipal,
4658
+ purpose,
4885
4659
  );
4886
- const base = defaultNamespaceForPrincipal(principal, this.orchestrator.config);
4887
- const overlaid = this.orchestrator.applyCodingNamespaceOverlay(
4888
- sessionKeyForOverlay,
4889
- base,
4890
- );
4891
- // No overlay → the default store (raw sessionKey), as before.
4892
- if (overlaid === base) return this.orchestrator.config.defaultNamespace;
4893
- // Overlay applied. Authorize access to the principal's `<principal>-project-*`
4894
- // overlay base before switching the LCM key to it. The gate differs by
4895
- // operation purpose (see the doc comment): reads use the readable-recall-set
4896
- // gate (no cross-tenant read leak), writes use observe's write authorization
4897
- // (so compaction targets the same overlay queue observe wrote to).
4898
- const authorized =
4899
- purpose === "write"
4900
- ? canWriteNamespace(principal, base, this.orchestrator.config)
4901
- : recallNamespacesForPrincipal(
4902
- principal,
4903
- this.orchestrator.config,
4904
- ).includes(base);
4905
- if (authorized) return overlaid;
4906
- // Unauthorized overlay base. For READS, collapse to the DEFAULT STORE (the
4907
- // raw sessionKey) EXACTLY like the orchestrator's `lcmReadNamespaceForSession`
4908
- // (rule 39 / 42) — NOT the caller's `resolvedNamespace`, which for an implicit
4909
- // read can be a readable recall namespace (e.g. `shared`). Returning that
4910
- // would prefix LCM reads with `shared:sessionKey` while in-prompt recall uses
4911
- // the raw `sessionKey`, diverging `lcmSearch`/raw disclosure from orchestrator
4912
- // LCM reads (cursor "LCM read gate wrong fallback"). For an explicit read the
4913
- // method already returned at the top, so this only affects implicit reads.
4914
- // The (currently unused) write purpose preserves its prior `resolvedNamespace`
4915
- // fallback for backward compatibility.
4916
- if (purpose === "read") return this.orchestrator.config.defaultNamespace;
4917
- return resolvedNamespace;
4918
4660
  }
4919
4661
 
4920
- /**
4921
- * Resolve the namespace the raw-disclosure excerpt lookup
4922
- * ({@link fetchRawExcerpts}) must prefix its LCM `session_id` with (#1505
4923
- * thread 2f7). Raw disclosure reads the SAME LCM archive `lcmSearch` and the
4924
- * in-prompt LCM sections read, so it MUST pass through the identical
4925
- * read-authorization gate — NOT `snapshot.namespace`, which records the
4926
- * effective WRITE/overlay namespace (`<principal>-project-*`) even when the
4927
- * principal can WRITE but not READ its self base (or `defaultRecallNamespaces`
4928
- * omits `self`). Routing through `resolveLcmReadNamespace(..., "read")` makes
4929
- * raw disclosure fall back to the default store exactly like normal recall +
4930
- * `lcmSearch`, so it never attaches overlay transcript rows the read gate
4931
- * excludes (cross-tenant read leak). Collapses to the default store / raw
4932
- * sessionKey for single-store / no-overlay / explicit-default flows, so
4933
- * single-user recall is byte-for-byte unchanged.
4934
- *
4935
- * Returns `undefined` when NO readable LCM namespace exists for an IMPLICIT
4936
- * (no explicit `namespace`) raw recall — i.e. a restrictive `default` READ
4937
- * policy denies the principal `default` AND no overlay/self namespace is
4938
- * readable. In that case the caller emits NO excerpts rather than throwing
4939
- * `namespace is not readable: default` (#1505 thread NBHWz): normal recall
4940
- * still succeeds via `recallNamespacesForPrincipal`, so `disclosure: "raw"`
4941
- * must degrade gracefully (empty excerpts), never pre-authorize `default`.
4942
- *
4943
- * IMPLICIT-namespace fallback selection derives from the ALREADY
4944
- * read-authorized recall namespace set (`recallNamespacesForPrincipal` +
4945
- * `canReadNamespace`) — the principal's self base when it is in the readable
4946
- * recall set, else `config.defaultNamespace` ONLY when the principal may read
4947
- * it. It NEVER pre-authorizes `default`. An EXPLICIT `namespace` is still
4948
- * authorized strictly via `resolveReadableNamespace` (explicit reads must pass
4949
- * the ACL — no behavior change).
4950
- */
4951
4662
  private resolveRawExcerptReadNamespace(
4952
4663
  explicitNamespace: string | undefined,
4953
4664
  sessionKey: string | undefined,
4954
4665
  authenticatedPrincipal: string | undefined,
4955
4666
  ): string | undefined {
4956
- const principal = this.resolveRequestPrincipal(
4957
- sessionKey,
4958
- authenticatedPrincipal,
4959
- );
4960
- const hasExplicitNamespace =
4961
- typeof explicitNamespace === "string" &&
4962
- explicitNamespace.trim().length > 0;
4963
- if (hasExplicitNamespace) {
4964
- // Explicit reads must pass the ACL — authorize strictly, exactly as
4965
- // `lcmSearch` does (throws on an unreadable explicit namespace).
4966
- const resolvedNamespace = this.resolveReadableNamespace(
4967
- explicitNamespace,
4968
- principal,
4969
- );
4970
- return this.resolveLcmReadNamespace(
4971
- explicitNamespace,
4972
- resolvedNamespace,
4973
- sessionKey,
4974
- authenticatedPrincipal,
4975
- "read",
4976
- );
4977
- }
4978
- // IMPLICIT raw recall: an active scope profile owns the same LCM read
4979
- // namespace set used by recall and lcmSearch. Return the first profile
4980
- // namespace as the legacy raw-excerpt namespace hint; the concrete ordered
4981
- // key set is still produced by resolveLcmReadSessionIds(), which also treats
4982
- // an empty profile read set as authoritative.
4983
- const profileReadNamespaces = this.resolveScopeProfileLcmReadNamespaces(
4984
- sessionKey,
4985
- authenticatedPrincipal,
4986
- );
4987
- if (profileReadNamespaces !== null) return profileReadNamespaces[0];
4988
-
4989
- // Otherwise derive the read fallback from the ALREADY read-authorized recall
4990
- // namespace set — NEVER pre-authorize `default` (#1505 thread NBHWz). When
4991
- // namespaces are disabled the default store is the only namespace and is
4992
- // always readable (byte-for-byte single-user path).
4993
- const fallbackNamespace =
4994
- this.resolveImplicitLcmReadFallbackNamespace(principal);
4995
- // No readable LCM namespace at all ⇒ no excerpts (caller short-circuits).
4996
- if (fallbackNamespace === undefined) return undefined;
4997
- return this.resolveLcmReadNamespace(
4667
+ return this.accessLcmSurface.resolveRawExcerptReadNamespace(
4998
4668
  explicitNamespace,
4999
- fallbackNamespace,
5000
4669
  sessionKey,
5001
4670
  authenticatedPrincipal,
5002
- "read",
5003
4671
  );
5004
4672
  }
5005
4673
 
@@ -5175,76 +4843,12 @@ export class EngramAccessService {
5175
4843
  sessionKey: string,
5176
4844
  authenticatedPrincipal: string | undefined,
5177
4845
  ): string[] {
5178
- const primary = this.resolveLcmReadSessionKey(
4846
+ return this.accessLcmSurface.resolveLcmReadSessionIds(
5179
4847
  explicitNamespace,
5180
4848
  resolvedNamespace,
5181
4849
  sessionKey,
5182
4850
  authenticatedPrincipal,
5183
- "read",
5184
- );
5185
- const hasExplicitNamespace =
5186
- typeof explicitNamespace === "string" &&
5187
- explicitNamespace.trim().length > 0;
5188
- // Explicit namespace → no overlay fallbacks (the overlay never applies to an
5189
- // explicit read). Single key, unchanged.
5190
- if (hasExplicitNamespace) return [primary];
5191
-
5192
- const profileReadNamespaces = this.resolveScopeProfileLcmReadNamespaces(
5193
- sessionKey,
5194
- authenticatedPrincipal,
5195
- );
5196
- if (profileReadNamespaces !== null) {
5197
- return this.lcmSessionIdsForNamespaces(profileReadNamespaces, sessionKey);
5198
- }
5199
-
5200
- const principal = this.resolveRequestPrincipal(
5201
- sessionKey,
5202
- authenticatedPrincipal,
5203
- );
5204
- const base = defaultNamespaceForPrincipal(
5205
- principal,
5206
- this.orchestrator.config,
5207
- );
5208
- const overlaid = this.orchestrator.applyCodingNamespaceOverlay(
5209
- sessionKey,
5210
- base,
5211
- );
5212
- // No overlay → single default-store key, unchanged.
5213
- if (overlaid === base) return [primary];
5214
- // Overlay present but self base unreadable → the "read" gate already
5215
- // collapsed `primary` to the default store; do NOT add overlay fallbacks
5216
- // (they would be unauthorized `<principal>-project-*` keys). Single key.
5217
- const selfReadableInRecall = recallNamespacesForPrincipal(
5218
- principal,
5219
- this.orchestrator.config,
5220
- ).includes(base);
5221
- if (!selfReadableInRecall) return [primary];
5222
- // Self base readable → overlay rows authorized. Append one LCM key per coding
5223
- // read fallback (project → root), combined with the principal base for
5224
- // isolation — the SAME ordered set the orchestrator recall path searches.
5225
- const overlay = resolveCodingNamespaceOverlay(
5226
- this.orchestrator.getCodingContextForSession(sessionKey),
5227
- this.orchestrator.config.codingMode,
5228
- this.orchestrator.config.defaultNamespace,
5229
4851
  );
5230
- const fallbackNamespaces = (overlay?.readFallbacks ?? []).map((fallback) =>
5231
- combineNamespaces(base, fallback),
5232
- );
5233
- const out = [primary];
5234
- const seen = new Set<string>([primary]);
5235
- for (const ns of fallbackNamespaces) {
5236
- const key =
5237
- lcmSessionKeyForNamespace(
5238
- ns,
5239
- sessionKey,
5240
- this.orchestrator.config.defaultNamespace,
5241
- ) ?? sessionKey;
5242
- if (!seen.has(key)) {
5243
- seen.add(key);
5244
- out.push(key);
5245
- }
5246
- }
5247
- return out;
5248
4852
  }
5249
4853
 
5250
4854
  async lcmCompactionFlush(