@remnic/core 9.3.675 → 9.3.677

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 (64) hide show
  1. package/dist/access-cli.js +16 -16
  2. package/dist/access-http.js +13 -13
  3. package/dist/access-mcp.js +12 -12
  4. package/dist/access-schema.js +3 -3
  5. package/dist/access-service.js +10 -10
  6. package/dist/{chunk-OG7A6AZX.js → chunk-2DKXY243.js} +4 -4
  7. package/dist/{chunk-Q5ZU3RNY.js → chunk-57ME5VSI.js} +4 -4
  8. package/dist/{chunk-SDLJ2W7S.js → chunk-7UTCHQTB.js} +2 -2
  9. package/dist/{chunk-T2AOOHDA.js → chunk-ACYX37IM.js} +2 -2
  10. package/dist/{chunk-ZLINDOBG.js → chunk-CZMLLVU2.js} +3 -3
  11. package/dist/{chunk-DOCTITOP.js → chunk-DGEZKYVI.js} +4 -4
  12. package/dist/{chunk-Q6MIDQEL.js → chunk-EQYP3HA6.js} +2 -2
  13. package/dist/{chunk-52LZ42LI.js → chunk-ERA5RSMZ.js} +1 -1
  14. package/dist/{chunk-IPLYGWQF.js → chunk-KQAFEZQX.js} +5 -5
  15. package/dist/{chunk-SF45RQDX.js → chunk-RP64QP7G.js} +3 -3
  16. package/dist/{chunk-QLRYXOAD.js → chunk-UDJLF3BO.js} +2 -2
  17. package/dist/{chunk-R37A3BEW.js → chunk-YEQBJXVO.js} +111 -101
  18. package/dist/chunk-YEQBJXVO.js.map +1 -0
  19. package/dist/{chunk-B55KFEGS.js → chunk-YJ4J2JJ2.js} +10 -10
  20. package/dist/{chunk-XVVEKF5I.js → chunk-Z56KDLDK.js} +20 -20
  21. package/dist/{chunk-OUWAQVDJ.js → chunk-Z6SEG36L.js} +4 -4
  22. package/dist/cli.js +22 -22
  23. package/dist/{coding-graph-types-Dd2tGrnm.d.ts → coding/coding-graph-types.d.ts} +1 -1
  24. package/dist/coding/coding-graph-types.js +10 -0
  25. package/dist/coding/coding-graph-types.js.map +1 -0
  26. package/dist/coding/optional-coding-graph.d.ts +2 -2
  27. package/dist/coding/optional-coding-graph.js +1 -1
  28. package/dist/contradiction/index.js +4 -4
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.js +33 -33
  31. package/dist/lcm/index.js +3 -3
  32. package/dist/namespaces/migrate.js +8 -8
  33. package/dist/namespaces/search.js +7 -7
  34. package/dist/operator-toolkit.js +9 -9
  35. package/dist/orchestrator.js +13 -13
  36. package/dist/schemas.d.ts +22 -22
  37. package/dist/search/factory.js +6 -6
  38. package/dist/search/index.js +11 -11
  39. package/dist/search/lancedb-backend.js +2 -2
  40. package/dist/search/meilisearch-backend.js +2 -2
  41. package/dist/search/orama-backend.js +2 -2
  42. package/dist/transfer/autodetect.js +1 -1
  43. package/dist/transfer/backup.js +1 -1
  44. package/dist/transfer/capsule-export.js +2 -2
  45. package/dist/transfer/types.d.ts +12 -12
  46. package/package.json +7 -2
  47. package/src/orchestrator.ts +50 -197
  48. package/src/scopes/scope-plan.test.ts +360 -0
  49. package/src/scopes/scope-plan.ts +320 -0
  50. package/dist/chunk-R37A3BEW.js.map +0 -1
  51. /package/dist/{chunk-OG7A6AZX.js.map → chunk-2DKXY243.js.map} +0 -0
  52. /package/dist/{chunk-Q5ZU3RNY.js.map → chunk-57ME5VSI.js.map} +0 -0
  53. /package/dist/{chunk-SDLJ2W7S.js.map → chunk-7UTCHQTB.js.map} +0 -0
  54. /package/dist/{chunk-T2AOOHDA.js.map → chunk-ACYX37IM.js.map} +0 -0
  55. /package/dist/{chunk-ZLINDOBG.js.map → chunk-CZMLLVU2.js.map} +0 -0
  56. /package/dist/{chunk-DOCTITOP.js.map → chunk-DGEZKYVI.js.map} +0 -0
  57. /package/dist/{chunk-Q6MIDQEL.js.map → chunk-EQYP3HA6.js.map} +0 -0
  58. /package/dist/{chunk-52LZ42LI.js.map → chunk-ERA5RSMZ.js.map} +0 -0
  59. /package/dist/{chunk-IPLYGWQF.js.map → chunk-KQAFEZQX.js.map} +0 -0
  60. /package/dist/{chunk-SF45RQDX.js.map → chunk-RP64QP7G.js.map} +0 -0
  61. /package/dist/{chunk-QLRYXOAD.js.map → chunk-UDJLF3BO.js.map} +0 -0
  62. /package/dist/{chunk-B55KFEGS.js.map → chunk-YJ4J2JJ2.js.map} +0 -0
  63. /package/dist/{chunk-XVVEKF5I.js.map → chunk-Z56KDLDK.js.map} +0 -0
  64. /package/dist/{chunk-OUWAQVDJ.js.map → chunk-Z6SEG36L.js.map} +0 -0
@@ -0,0 +1,320 @@
1
+ /**
2
+ * ScopePlan resolver (issue #1521).
3
+ *
4
+ * A single pure function that resolves every namespace-bearing read/write path
5
+ * to one {@link ScopePlan} value object. Consumers (recall tiers, QMD router,
6
+ * LCM reads, maintenance) never call the ad-hoc resolution helpers directly —
7
+ * they receive a resolved plan and read its fields.
8
+ *
9
+ * The resolver DELEGATES to the existing helpers (`resolvePrincipal`,
10
+ * `recallNamespacesForPrincipal`, `resolveCodingNamespaceOverlay`,
11
+ * `resolveScopeProfilePlan`, `expandScopeProfileReadNamespaces`,
12
+ * `combineNamespaces`, `lcmReadSessionIdsForNamespaces`) — no logic rewrite.
13
+ * The ad-hoc inline resolution that previously lived in the orchestrator's
14
+ * `recallInternal` and `enqueueDirectAnswerObservation` paths is replaced by a
15
+ * single call to {@link resolveScopePlan}, eliminating the duplicated
16
+ * namespace-set construction that produced the largest share of #1519's review
17
+ * threads (scope-profile recall paths missing `readFallbacks` appends).
18
+ *
19
+ * Migration tranches (issue #1521 step 4):
20
+ * - recall tiers (this PR): the two orchestrator recall entry points consume
21
+ * the plan instead of building `recallNamespaces`/`observationNamespaces`
22
+ * inline;
23
+ * - QMD router calls, LCM reads, maintenance: follow-up PRs.
24
+ *
25
+ * CLAUDE.md rules honoured (pitfalls from the issue):
26
+ * - rule 42: read/write resolve through the same layer; the coding overlay is
27
+ * COMBINED with the principal base via `combineNamespaces`;
28
+ * - rule 39: feature gates identical across every path the plan feeds;
29
+ * - rule 22/48: LCM keys derived through `lcmSessionKeyForNamespace` (via
30
+ * `lcmReadSessionIdsForNamespaces`), never hardcoded `:`-joins; unscoped LCM
31
+ * search stays suppressed when `namespacesEnabled`.
32
+ */
33
+
34
+ import {
35
+ canReadNamespace,
36
+ defaultNamespaceForPrincipal,
37
+ recallNamespacesForPrincipal,
38
+ resolvePrincipal,
39
+ } from "../namespaces/principal.js";
40
+ import type { ResolvedScopeProfilePlan } from "../namespaces/scope-profiles.js";
41
+ import {
42
+ expandScopeProfileReadNamespaces,
43
+ resolveScopeProfilePlan,
44
+ } from "../namespaces/scope-profiles.js";
45
+ import {
46
+ combineNamespaces,
47
+ lcmReadSessionIdsForNamespaces,
48
+ resolveCodingNamespaceOverlay,
49
+ type CodingNamespaceOverlay,
50
+ } from "../coding/coding-namespace.js";
51
+ import type { CodingContext, PluginConfig } from "../types.js";
52
+
53
+ /**
54
+ * A resolved scope plan: every namespace-bearing field a read or write path
55
+ * needs, produced by ONE call to {@link resolveScopePlan}.
56
+ *
57
+ * Consumers read these fields directly — they never re-resolve namespaces.
58
+ */
59
+ export interface ScopePlan {
60
+ /**
61
+ * Resolved principal under which the operation runs. `undefined` only when
62
+ * `namespacesEnabled` is false (collapses to `"default"`) or no session key
63
+ * was supplied in single-user mode.
64
+ */
65
+ readonly principal: string | undefined;
66
+
67
+ /**
68
+ * Explicit namespace override, trimmed, when the caller supplied one AND it
69
+ * is readable by the principal. `undefined` when no override was given or the
70
+ * override is not readable (the plan falls through to the coding/scope-profile
71
+ * resolution in that case, mirroring the pre-existing observe-path behavior).
72
+ */
73
+ readonly namespaceOverride: string | undefined;
74
+
75
+ /**
76
+ * Resolved base (self) namespace — the effective namespace absent an explicit
77
+ * override. This is the principal-self namespace, optionally substituted by a
78
+ * scope-profile write layer or a coding overlay.
79
+ *
80
+ * Callers that persisted this as the `selfNamespace` / response namespace
81
+ * read it here.
82
+ */
83
+ readonly baseNamespace: string;
84
+
85
+ /**
86
+ * Ordered, deduped read-namespace set. Includes the coding overlay fallbacks
87
+ * (branch → project → root) combined with the principal base exactly once, so
88
+ * the #1519 miss (scope-profile path omitted `readFallbacks` appends) cannot
89
+ * recur — the fallback appends live in ONE place.
90
+ */
91
+ readonly readNamespaces: string[];
92
+
93
+ /**
94
+ * Coding overlay fallback namespaces combined with the principal base (rule
95
+ * 42). Empty when no coding overlay applies. These are the SAME entries
96
+ * appended into {@link readNamespaces} for the coding-overlay branch,
97
+ * surfaced separately so consumers that need just the fallback set (e.g.
98
+ * LCM read-key derivation) can read them without re-deriving.
99
+ */
100
+ readonly readFallbacks: string[];
101
+
102
+ /**
103
+ * LCM read namespace set. May differ from {@link readNamespaces} for
104
+ * read-authorization reasons: when the principal self base is NOT in the
105
+ * readable recall set, the overlay LCM keys collapse to the default store
106
+ * (rule 42 read/write parity; rule 48 least-privilege) even though
107
+ * {@link readNamespaces} still searches the overlay for QMD/file recall.
108
+ */
109
+ readonly lcmReadNamespaces: string[];
110
+
111
+ /**
112
+ * LCM read `session_id` set, encoded via
113
+ * `lcmReadSessionIdsForNamespaces` (which delegates to
114
+ * `lcmSessionKeyForNamespace`). Ordered and deduped; the primary overlay key
115
+ * is first. Single-user / no-overlay recall collapses to `[sessionKey]` —
116
+ * byte-for-byte the pre-#1495 behavior.
117
+ *
118
+ * Empty (`[]`) when a scope-profile plan is active and no `sessionKey` was
119
+ * supplied (mirrors the pre-existing guard).
120
+ */
121
+ readonly lcmReadSessionIds: ReadonlyArray<string | undefined>;
122
+
123
+ /**
124
+ * Resolved coding overlay, or `null` when none applies (no coding context,
125
+ * `codingMode.projectScope` false, `namespacesEnabled` false, or an explicit
126
+ * readable namespace override is set).
127
+ */
128
+ readonly codingOverlay: { readonly namespace: string; readonly readFallbacks: readonly string[] } | null;
129
+
130
+ /**
131
+ * Resolved scope-profile plan, or `null` when no scope profile is active.
132
+ * Consumers that branched on "profile vs. non-profile" read this directly.
133
+ */
134
+ readonly scopeProfilePlan: ResolvedScopeProfilePlan | null;
135
+ }
136
+
137
+ /**
138
+ * Options for {@link resolveScopePlan}.
139
+ */
140
+ export interface ResolveScopePlanOptions {
141
+ /** Plugin config (namespace policies, coding mode, default namespace, …). */
142
+ readonly config: PluginConfig;
143
+ /** Session key (may derive the principal and/or coding context). */
144
+ readonly sessionKey?: string;
145
+ /**
146
+ * Explicit namespace override (raw — the resolver trims it). When supplied
147
+ * AND readable by the resolved principal, it wins over every other layer.
148
+ */
149
+ readonly namespace?: string;
150
+ /**
151
+ * Authenticated principal override. Access surfaces that already resolved
152
+ * identity at the transport layer pass it here so namespace ACL decisions
153
+ * use the same identity the surface authorized.
154
+ */
155
+ readonly principalOverride?: string;
156
+ /**
157
+ * Coding context for the session. Callers that track this on the orchestrator
158
+ * pass `getCodingContextForSession(sessionKey)` here; the resolver never
159
+ * reaches back into orchestrator state.
160
+ */
161
+ readonly codingContext?: CodingContext | null;
162
+ /**
163
+ * Whether namespace routing is enabled. Callers that have already read the
164
+ * namespaces-enabled flag pass it here so the resolver does NOT re-read it
165
+ * (keeps the scattered-read ratchet from growing, #1523).
166
+ */
167
+ readonly namespacesEnabled: boolean;
168
+ }
169
+
170
+ /**
171
+ * Resolve a {@link ScopePlan} from the inputs by delegating to the existing
172
+ * namespace-resolution helpers. Pure — no side effects, no orchestrator state.
173
+ *
174
+ * The caller is responsible for authorization checks that should THROW (e.g.
175
+ * `namespacesEnabled && !principal`, or an unreadable explicit override in the
176
+ * recall path). The resolver computes the plan; enforcement stays at the call
177
+ * site so error semantics are unchanged.
178
+ */
179
+ export function resolveScopePlan(options: ResolveScopePlanOptions): ScopePlan {
180
+ const { config, sessionKey } = options;
181
+
182
+ const namespaceOverride = options.namespace?.trim() || undefined;
183
+
184
+ const principal =
185
+ typeof options.principalOverride === "string" && options.principalOverride.length > 0
186
+ ? options.principalOverride
187
+ : resolvePrincipal(sessionKey, config);
188
+
189
+ // A namespace override gates the overlay/scope-profile layers. When it is
190
+ // readable it wins; when it is NOT readable the plan falls through to the
191
+ // coding/scope-profile/legacy branches (mirrors the observe-path behavior
192
+ // where an unreadable override does not throw but simply does not suppress
193
+ // the overlay). The recall path validates readability and throws BEFORE
194
+ // calling the resolver, so a reachable override is always readable there.
195
+ const namespaceOverrideReadable =
196
+ namespaceOverride !== undefined && canReadNamespace(principal, namespaceOverride, config);
197
+
198
+ const readableRecallNamespaces = recallNamespacesForPrincipal(principal, config);
199
+
200
+ // The orchestrator's `applyCodingRecallOverlay` gates on `namespacesEnabled`
201
+ // (returning null when disabled), so the resolver must too — otherwise
202
+ // single-store mode would produce apparent route separation with no actual
203
+ // storage isolation (false-isolation trap, rule 39). The caller passes the
204
+ // pre-read flag so the resolver does not add a scattered `config.*Enabled`
205
+ // read (ratchet, #1523).
206
+ const namespacesEnabled = options.namespacesEnabled;
207
+ const codingOverlay: CodingNamespaceOverlay | null =
208
+ namespaceOverrideReadable || !namespacesEnabled
209
+ ? null
210
+ : resolveCodingNamespaceOverlay(
211
+ options.codingContext ?? null,
212
+ config.codingMode,
213
+ config.defaultNamespace,
214
+ );
215
+
216
+ const principalSelfNamespace = defaultNamespaceForPrincipal(principal, config);
217
+ const codingSelfNamespace = codingOverlay
218
+ ? combineNamespaces(principalSelfNamespace, codingOverlay.namespace)
219
+ : null;
220
+
221
+ const scopeProfilePlan = namespaceOverrideReadable
222
+ ? null
223
+ : resolveScopeProfilePlan({
224
+ config,
225
+ principal,
226
+ codingContext: options.codingContext ?? null,
227
+ codingOverlay,
228
+ });
229
+
230
+ const profileEffectiveNamespace =
231
+ scopeProfilePlan?.writeNamespace || scopeProfilePlan?.readNamespaces[0];
232
+
233
+ const baseNamespace = namespaceOverrideReadable
234
+ ? namespaceOverride!
235
+ : profileEffectiveNamespace ?? codingSelfNamespace ?? principalSelfNamespace;
236
+
237
+ // ── Read namespace set ────────────────────────────────────────────────────
238
+ let readNamespaces: string[];
239
+ if (namespaceOverrideReadable) {
240
+ readNamespaces = [namespaceOverride!];
241
+ } else if (scopeProfilePlan) {
242
+ readNamespaces = expandScopeProfileReadNamespaces({
243
+ profilePlan: scopeProfilePlan,
244
+ principalSelfNamespace: scopeProfilePlan.baseNamespace,
245
+ config,
246
+ principal,
247
+ codingOverlay,
248
+ legacyRecallNamespaces: readableRecallNamespaces,
249
+ });
250
+ } else if (codingOverlay && codingSelfNamespace) {
251
+ // Substitute the principal's self namespace with the coding-scoped one, and
252
+ // append any read fallbacks (branch → project, rule 42) combined with the
253
+ // principal base so principal isolation is preserved on fallback entries.
254
+ const mapped = readableRecallNamespaces.map((ns) =>
255
+ ns === principalSelfNamespace ? codingSelfNamespace : ns,
256
+ );
257
+ const fallbackNs = codingOverlay.readFallbacks.map((fallback) =>
258
+ combineNamespaces(principalSelfNamespace, fallback),
259
+ );
260
+ readNamespaces = Array.from(new Set<string>([...mapped, ...fallbackNs]));
261
+ } else {
262
+ readNamespaces = readableRecallNamespaces;
263
+ }
264
+
265
+ const readFallbacks = codingOverlay
266
+ ? codingOverlay.readFallbacks.map((fb) => combineNamespaces(principalSelfNamespace, fb))
267
+ : [];
268
+
269
+ // ── LCM read namespace set ────────────────────────────────────────────────
270
+ // The LCM overlay keys are `<principal>-project-*` sub-namespaces authorized
271
+ // transitively by the principal SELF base. Include them ONLY when that base
272
+ // is in the readable recall set (rule 42 / 48). When it is NOT readable, the
273
+ // overlay rows are unauthorized for this reader, so the LCM read collapses to
274
+ // the default store — exactly what QMD/file recall surfaces for such a
275
+ // principal.
276
+ const codingOverlaySelfReadable =
277
+ codingOverlay !== null &&
278
+ (scopeProfilePlan
279
+ ? scopeProfilePlan.layers.some((layer) => layer.id === "userProject" && layer.readable)
280
+ : readableRecallNamespaces.includes(principalSelfNamespace));
281
+
282
+ let lcmReadNamespaces: string[];
283
+ if (namespaceOverrideReadable) {
284
+ lcmReadNamespaces = [namespaceOverride!];
285
+ } else if (scopeProfilePlan) {
286
+ // Scope profiles define a layered read stack; LCM-backed evidence uses the
287
+ // same namespace set as QMD/file recall so team/global/shared observations
288
+ // are not silently skipped.
289
+ lcmReadNamespaces = readNamespaces;
290
+ } else if (codingOverlay && codingSelfNamespace && codingOverlaySelfReadable) {
291
+ const fallbackNs = codingOverlay.readFallbacks.map((fallback) =>
292
+ combineNamespaces(principalSelfNamespace, fallback),
293
+ );
294
+ lcmReadNamespaces = [codingSelfNamespace, ...fallbackNs];
295
+ } else {
296
+ // No overlay, OR overlay present but self base unreadable → collapse to the
297
+ // default store (raw sessionKey). No `<principal>-project-*` overlay key is
298
+ // searched.
299
+ lcmReadNamespaces = [config.defaultNamespace];
300
+ }
301
+
302
+ const lcmReadSessionIds =
303
+ scopeProfilePlan && !sessionKey
304
+ ? []
305
+ : lcmReadSessionIdsForNamespaces(lcmReadNamespaces, sessionKey, config.defaultNamespace);
306
+
307
+ return {
308
+ principal,
309
+ namespaceOverride: namespaceOverrideReadable ? namespaceOverride : undefined,
310
+ baseNamespace,
311
+ readNamespaces,
312
+ readFallbacks,
313
+ lcmReadNamespaces,
314
+ lcmReadSessionIds,
315
+ codingOverlay: codingOverlay
316
+ ? { namespace: codingOverlay.namespace, readFallbacks: codingOverlay.readFallbacks }
317
+ : null,
318
+ scopeProfilePlan,
319
+ };
320
+ }