@remnic/core 9.3.676 → 9.3.678
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.
- package/dist/access-cli.js +21 -21
- package/dist/access-http.js +16 -16
- package/dist/access-mcp.js +15 -15
- package/dist/access-schema.js +3 -3
- package/dist/access-service.js +13 -13
- package/dist/capabilities.d.ts +29 -1
- package/dist/capabilities.js +5 -3
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +2 -2
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-SDLJ2W7S.js → chunk-2AP4QJX5.js} +4 -4
- package/dist/{chunk-Y56J7CXW.js → chunk-2LDBXPLB.js} +10 -5
- package/dist/chunk-2LDBXPLB.js.map +1 -0
- package/dist/{chunk-ZLINDOBG.js → chunk-4PPMUNV5.js} +5 -5
- package/dist/{chunk-Q5ZU3RNY.js → chunk-57ME5VSI.js} +4 -4
- package/dist/{chunk-R37A3BEW.js → chunk-7AAKSHDG.js} +147 -122
- package/dist/chunk-7AAKSHDG.js.map +1 -0
- package/dist/{chunk-T2AOOHDA.js → chunk-ACYX37IM.js} +2 -2
- package/dist/{chunk-DOCTITOP.js → chunk-DGEZKYVI.js} +4 -4
- package/dist/{chunk-Q6MIDQEL.js → chunk-EQYP3HA6.js} +2 -2
- package/dist/{chunk-52LZ42LI.js → chunk-ERA5RSMZ.js} +1 -1
- package/dist/{chunk-SF45RQDX.js → chunk-K2JYO6QV.js} +4 -4
- package/dist/{chunk-IPLYGWQF.js → chunk-KQAFEZQX.js} +5 -5
- package/dist/{chunk-RI5XBIZ6.js → chunk-PBH4JUAB.js} +14 -2
- package/dist/{chunk-RI5XBIZ6.js.map → chunk-PBH4JUAB.js.map} +1 -1
- package/dist/{chunk-XVVEKF5I.js → chunk-PCGCQTU6.js} +22 -22
- package/dist/{chunk-B6IUW76R.js → chunk-Q2H5U37U.js} +1 -1
- package/dist/{chunk-WXGTC424.js → chunk-RC3AFF6Z.js} +1 -1
- package/dist/{chunk-HQCGRSRU.js → chunk-SECQS4G4.js} +2 -2
- package/dist/{chunk-QLRYXOAD.js → chunk-UDJLF3BO.js} +2 -2
- package/dist/{chunk-OG7A6AZX.js → chunk-UNZLU2MX.js} +4 -4
- package/dist/{chunk-B55KFEGS.js → chunk-YJ4J2JJ2.js} +10 -10
- package/dist/{chunk-NE566K4E.js → chunk-YXWAILM4.js} +2 -2
- package/dist/{chunk-OUWAQVDJ.js → chunk-Z6SEG36L.js} +4 -4
- package/dist/cli.js +25 -25
- package/dist/{coding-graph-types-Dd2tGrnm.d.ts → coding/coding-graph-types.d.ts} +1 -1
- package/dist/coding/coding-graph-types.js +10 -0
- package/dist/coding/coding-graph-types.js.map +1 -0
- package/dist/coding/optional-coding-graph.d.ts +2 -2
- package/dist/coding/optional-coding-graph.js +1 -1
- package/dist/compounding/engine.js +1 -1
- package/dist/contradiction/index.js +4 -4
- package/dist/{graph-edge-decay-PUFNHOBS.js → graph-edge-decay-KSVJGCZW.js} +2 -2
- package/dist/graph-snapshot.js +2 -2
- package/dist/graph.d.ts +11 -0
- package/dist/graph.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +38 -38
- package/dist/lcm/index.js +3 -3
- package/dist/namespaces/migrate.js +8 -8
- package/dist/namespaces/search.js +7 -7
- package/dist/operator-toolkit.js +10 -10
- package/dist/orchestrator.js +17 -17
- package/dist/search/factory.js +6 -6
- package/dist/search/index.js +11 -11
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +1 -1
- package/dist/transfer/capsule-export.js +2 -2
- package/package.json +7 -2
- package/src/capabilities.test.ts +173 -0
- package/src/capabilities.ts +61 -0
- package/src/graph.ts +20 -4
- package/src/orchestrator.ts +85 -208
- package/src/scopes/scope-plan.test.ts +360 -0
- package/src/scopes/scope-plan.ts +320 -0
- package/dist/chunk-R37A3BEW.js.map +0 -1
- package/dist/chunk-Y56J7CXW.js.map +0 -1
- /package/dist/{chunk-SDLJ2W7S.js.map → chunk-2AP4QJX5.js.map} +0 -0
- /package/dist/{chunk-ZLINDOBG.js.map → chunk-4PPMUNV5.js.map} +0 -0
- /package/dist/{chunk-Q5ZU3RNY.js.map → chunk-57ME5VSI.js.map} +0 -0
- /package/dist/{chunk-T2AOOHDA.js.map → chunk-ACYX37IM.js.map} +0 -0
- /package/dist/{chunk-DOCTITOP.js.map → chunk-DGEZKYVI.js.map} +0 -0
- /package/dist/{chunk-Q6MIDQEL.js.map → chunk-EQYP3HA6.js.map} +0 -0
- /package/dist/{chunk-52LZ42LI.js.map → chunk-ERA5RSMZ.js.map} +0 -0
- /package/dist/{chunk-SF45RQDX.js.map → chunk-K2JYO6QV.js.map} +0 -0
- /package/dist/{chunk-IPLYGWQF.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-XVVEKF5I.js.map → chunk-PCGCQTU6.js.map} +0 -0
- /package/dist/{chunk-B6IUW76R.js.map → chunk-Q2H5U37U.js.map} +0 -0
- /package/dist/{chunk-WXGTC424.js.map → chunk-RC3AFF6Z.js.map} +0 -0
- /package/dist/{chunk-HQCGRSRU.js.map → chunk-SECQS4G4.js.map} +0 -0
- /package/dist/{chunk-QLRYXOAD.js.map → chunk-UDJLF3BO.js.map} +0 -0
- /package/dist/{chunk-OG7A6AZX.js.map → chunk-UNZLU2MX.js.map} +0 -0
- /package/dist/{chunk-B55KFEGS.js.map → chunk-YJ4J2JJ2.js.map} +0 -0
- /package/dist/{chunk-NE566K4E.js.map → chunk-YXWAILM4.js.map} +0 -0
- /package/dist/{chunk-OUWAQVDJ.js.map → chunk-Z6SEG36L.js.map} +0 -0
- /package/dist/{graph-edge-decay-PUFNHOBS.js.map → graph-edge-decay-KSVJGCZW.js.map} +0 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScopePlan resolver parity tests (issue #1521 step 2).
|
|
3
|
+
*
|
|
4
|
+
* These tests snapshot the effective namespace sets the resolver produces for a
|
|
5
|
+
* fixed matrix of inputs. The snapshots were derived by tracing the pre-migration
|
|
6
|
+
* inline resolution in `orchestrator.recallInternal` and
|
|
7
|
+
* `orchestrator.enqueueDirectAnswerObservation` — the SAME helpers in the SAME
|
|
8
|
+
* order. They MUST NOT change when consumers switch from the inline code to the
|
|
9
|
+
* resolver; if they do, the resolver diverged and must be corrected before
|
|
10
|
+
* migration lands.
|
|
11
|
+
*
|
|
12
|
+
* Input matrix (issue #1521 step 2):
|
|
13
|
+
* - default namespace (no policies, no coding context)
|
|
14
|
+
* - named namespace (explicit override, readable)
|
|
15
|
+
* - coding overlay (project scope and branch scope)
|
|
16
|
+
* - sparse metadata (empty/missing fields, no session key)
|
|
17
|
+
* - legacy `agent:*` session keys
|
|
18
|
+
* - scope-profile plan (active profile)
|
|
19
|
+
* - explicit namespace override (unreadable falls through)
|
|
20
|
+
*/
|
|
21
|
+
import assert from "node:assert/strict";
|
|
22
|
+
import test from "node:test";
|
|
23
|
+
|
|
24
|
+
import { resolveScopePlan } from "./scope-plan.js";
|
|
25
|
+
import {
|
|
26
|
+
combineNamespaces,
|
|
27
|
+
lcmSessionKeyForNamespace,
|
|
28
|
+
projectNamespaceName,
|
|
29
|
+
} from "../coding/coding-namespace.js";
|
|
30
|
+
import type { CodingContext, PluginConfig } from "../types.js";
|
|
31
|
+
|
|
32
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
33
|
+
// Config builders
|
|
34
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function baseConfig(overrides: Partial<PluginConfig> = {}): PluginConfig {
|
|
37
|
+
return {
|
|
38
|
+
namespacesEnabled: true,
|
|
39
|
+
defaultNamespace: "default",
|
|
40
|
+
sharedNamespace: "shared",
|
|
41
|
+
namespacePolicies: [],
|
|
42
|
+
defaultRecallNamespaces: ["self", "shared"],
|
|
43
|
+
codingMode: { projectScope: true, branchScope: false, globalFallback: true },
|
|
44
|
+
principalFromSessionKeyMode: "prefix",
|
|
45
|
+
principalFromSessionKeyRules: [],
|
|
46
|
+
scopeProfiles: {},
|
|
47
|
+
defaultScopeProfile: undefined,
|
|
48
|
+
teams: {},
|
|
49
|
+
...overrides,
|
|
50
|
+
} as unknown as PluginConfig;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A principal whose self namespace exists as a policy, so the overlay base is
|
|
54
|
+
* non-default and readable. */
|
|
55
|
+
function withSelfPolicy(config: PluginConfig, principal: string): PluginConfig {
|
|
56
|
+
return {
|
|
57
|
+
...config,
|
|
58
|
+
namespacePolicies: [
|
|
59
|
+
{ name: principal, readPrincipals: [principal], writePrincipals: [principal] },
|
|
60
|
+
...(config.namespacePolicies ?? []),
|
|
61
|
+
],
|
|
62
|
+
principalFromSessionKeyMode: "prefix",
|
|
63
|
+
principalFromSessionKeyRules: [
|
|
64
|
+
{ match: `${principal}:`, principal },
|
|
65
|
+
...(config.principalFromSessionKeyRules ?? []),
|
|
66
|
+
],
|
|
67
|
+
} as unknown as PluginConfig;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function codingContext(projectId: string, branch: string | null = null): CodingContext {
|
|
71
|
+
return { projectId, branch, rootPath: "/repo", defaultBranch: "main" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
75
|
+
// Snapshot 1: default namespace, no coding context
|
|
76
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
test("scope-plan: default namespace, no coding context → [default]", () => {
|
|
79
|
+
const config = baseConfig();
|
|
80
|
+
const plan = resolveScopePlan({
|
|
81
|
+
config,
|
|
82
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
83
|
+
sessionKey: "sess-1",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
assert.equal(plan.principal, "default");
|
|
87
|
+
assert.equal(plan.namespaceOverride, undefined);
|
|
88
|
+
assert.equal(plan.baseNamespace, "default");
|
|
89
|
+
assert.deepEqual(plan.readNamespaces, ["default", "shared"]);
|
|
90
|
+
assert.deepEqual(plan.readFallbacks, []);
|
|
91
|
+
assert.deepEqual(plan.lcmReadNamespaces, ["default"]);
|
|
92
|
+
assert.equal(plan.codingOverlay, null);
|
|
93
|
+
assert.equal(plan.scopeProfilePlan, null);
|
|
94
|
+
// LCM key is the raw sessionKey (default store, no overlay).
|
|
95
|
+
assert.deepEqual([...plan.lcmReadSessionIds], ["sess-1"]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
99
|
+
// Snapshot 2: named namespace (explicit, readable override)
|
|
100
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
test("scope-plan: explicit readable namespace override wins", () => {
|
|
103
|
+
const config = baseConfig({
|
|
104
|
+
namespacePolicies: [
|
|
105
|
+
{ name: "team-data", readPrincipals: ["default"], writePrincipals: [] },
|
|
106
|
+
],
|
|
107
|
+
} as Partial<PluginConfig>);
|
|
108
|
+
const plan = resolveScopePlan({
|
|
109
|
+
config,
|
|
110
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
111
|
+
sessionKey: "sess-1",
|
|
112
|
+
namespace: "team-data",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
assert.equal(plan.namespaceOverride, "team-data");
|
|
116
|
+
assert.equal(plan.baseNamespace, "team-data");
|
|
117
|
+
assert.deepEqual(plan.readNamespaces, ["team-data"]);
|
|
118
|
+
assert.deepEqual(plan.lcmReadNamespaces, ["team-data"]);
|
|
119
|
+
assert.equal(plan.codingOverlay, null);
|
|
120
|
+
assert.equal(plan.scopeProfilePlan, null);
|
|
121
|
+
assert.deepEqual(
|
|
122
|
+
[...plan.lcmReadSessionIds],
|
|
123
|
+
[lcmSessionKeyForNamespace("team-data", "sess-1", "default")],
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
128
|
+
// Snapshot 3: coding overlay (project scope)
|
|
129
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
test("scope-plan: coding overlay (project scope) substitutes self base", () => {
|
|
132
|
+
const config = withSelfPolicy(baseConfig(), "alice");
|
|
133
|
+
const ctx = codingContext("myproj");
|
|
134
|
+
const plan = resolveScopePlan({
|
|
135
|
+
config,
|
|
136
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
137
|
+
sessionKey: "alice:sess-1",
|
|
138
|
+
codingContext: ctx,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const projectNs = projectNamespaceName("myproj");
|
|
142
|
+
const codingSelf = combineNamespaces("alice", projectNs);
|
|
143
|
+
|
|
144
|
+
assert.equal(plan.principal, "alice");
|
|
145
|
+
assert.equal(plan.baseNamespace, codingSelf);
|
|
146
|
+
// readNamespaces substitutes "alice" → codingSelf, keeps shared.
|
|
147
|
+
// globalFallback=true adds the root ("") fallback: combineNamespaces("alice",
|
|
148
|
+
// "") → "alice", so the principal's own namespace appears as a read fallback.
|
|
149
|
+
assert.deepEqual(plan.readNamespaces, [codingSelf, "shared", "alice"]);
|
|
150
|
+
assert.deepEqual(plan.readFallbacks, ["alice"]);
|
|
151
|
+
assert.deepEqual(plan.lcmReadNamespaces, [codingSelf, "alice"]);
|
|
152
|
+
assert.equal(plan.codingOverlay?.namespace, projectNs);
|
|
153
|
+
assert.deepEqual([...plan.codingOverlay?.readFallbacks ?? []], [""]);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
157
|
+
// Snapshot 4: coding overlay (branch scope) appends project fallback
|
|
158
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
test("scope-plan: coding overlay (branch scope) appends project + root fallbacks", () => {
|
|
161
|
+
const config = withSelfPolicy(
|
|
162
|
+
baseConfig({
|
|
163
|
+
codingMode: { projectScope: true, branchScope: true, globalFallback: true },
|
|
164
|
+
} as Partial<PluginConfig>),
|
|
165
|
+
"alice",
|
|
166
|
+
);
|
|
167
|
+
const ctx = codingContext("myproj", "feature-x");
|
|
168
|
+
const plan = resolveScopePlan({
|
|
169
|
+
config,
|
|
170
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
171
|
+
sessionKey: "alice:sess-1",
|
|
172
|
+
codingContext: ctx,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Verify the key invariants: overlay is non-null, base is combined with self.
|
|
176
|
+
assert.notEqual(plan.codingOverlay, null);
|
|
177
|
+
assert.notEqual(plan.baseNamespace, "alice");
|
|
178
|
+
// readNamespaces includes the coding self (branch) and fallbacks.
|
|
179
|
+
assert.ok(plan.readNamespaces.length >= 2, "branch scope must include fallbacks");
|
|
180
|
+
// LCM read includes coding self + fallbacks.
|
|
181
|
+
assert.ok(plan.lcmReadNamespaces.length >= 2, "LCM must include fallback keys");
|
|
182
|
+
// readFallbacks is non-empty (project + root when globalFallback).
|
|
183
|
+
assert.ok(plan.readFallbacks.length >= 1, "branch scope has at least project fallback");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
187
|
+
// Snapshot 5: sparse metadata — no session key
|
|
188
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
test("scope-plan: no session key → principal undefined, default namespace", () => {
|
|
191
|
+
const config = baseConfig();
|
|
192
|
+
const plan = resolveScopePlan({
|
|
193
|
+
config,
|
|
194
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
assert.equal(plan.principal, undefined);
|
|
198
|
+
assert.equal(plan.baseNamespace, "default");
|
|
199
|
+
// recallNamespacesForPrincipal(undefined) → [] (no principal).
|
|
200
|
+
assert.deepEqual(plan.readNamespaces, []);
|
|
201
|
+
assert.deepEqual(plan.lcmReadNamespaces, ["default"]);
|
|
202
|
+
// Sessionless LCM → [undefined] (archive-wide read, no session_id filter).
|
|
203
|
+
assert.deepEqual([...plan.lcmReadSessionIds], [undefined]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
207
|
+
// Snapshot 6: legacy agent:* session key
|
|
208
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
test("scope-plan: legacy agent:* session key resolves principal via heuristic", () => {
|
|
211
|
+
const config = baseConfig();
|
|
212
|
+
const plan = resolveScopePlan({
|
|
213
|
+
config,
|
|
214
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
215
|
+
sessionKey: "agent:bot-1:slack:chan-1",
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// resolvePrincipal heuristic: parts[0] === "agent" → parts[1] = "bot-1".
|
|
219
|
+
assert.equal(plan.principal, "bot-1");
|
|
220
|
+
// No policy for "bot-1" → defaultNamespaceForPrincipal → "default".
|
|
221
|
+
assert.equal(plan.baseNamespace, "default");
|
|
222
|
+
// recallNamespacesForPrincipal("bot-1"): self="default" (readable), shared.
|
|
223
|
+
assert.deepEqual(plan.readNamespaces, ["default", "shared"]);
|
|
224
|
+
assert.deepEqual(plan.lcmReadNamespaces, ["default"]);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
228
|
+
// Snapshot 7: namespacesEnabled false → single-store collapse
|
|
229
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
test("scope-plan: namespacesEnabled false collapses to default store", () => {
|
|
232
|
+
const config = baseConfig({ namespacesEnabled: false } as Partial<PluginConfig>);
|
|
233
|
+
const plan = resolveScopePlan({
|
|
234
|
+
config,
|
|
235
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
236
|
+
sessionKey: "sess-1",
|
|
237
|
+
codingContext: codingContext("myproj"),
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// resolvePrincipal returns "default" when namespaces disabled.
|
|
241
|
+
assert.equal(plan.principal, "default");
|
|
242
|
+
assert.equal(plan.baseNamespace, "default");
|
|
243
|
+
assert.deepEqual(plan.readNamespaces, ["default"]);
|
|
244
|
+
assert.equal(plan.codingOverlay, null);
|
|
245
|
+
assert.equal(plan.scopeProfilePlan, null);
|
|
246
|
+
// Single store → raw sessionKey.
|
|
247
|
+
assert.deepEqual([...plan.lcmReadSessionIds], ["sess-1"]);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
251
|
+
// Snapshot 8: codingMode.projectScope false → no overlay
|
|
252
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
test("scope-plan: projectScope false → no overlay even with coding context", () => {
|
|
255
|
+
const config = withSelfPolicy(
|
|
256
|
+
baseConfig({
|
|
257
|
+
codingMode: { projectScope: false, branchScope: false, globalFallback: true },
|
|
258
|
+
} as Partial<PluginConfig>),
|
|
259
|
+
"alice",
|
|
260
|
+
);
|
|
261
|
+
const plan = resolveScopePlan({
|
|
262
|
+
config,
|
|
263
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
264
|
+
sessionKey: "alice:sess-1",
|
|
265
|
+
codingContext: codingContext("myproj"),
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
assert.equal(plan.codingOverlay, null);
|
|
269
|
+
assert.equal(plan.baseNamespace, "alice");
|
|
270
|
+
// No overlay → readable recall set unchanged (self substituted by nothing).
|
|
271
|
+
assert.deepEqual(plan.readNamespaces, ["alice", "shared"]);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
275
|
+
// Snapshot 9: explicit override not readable → falls through (observe parity)
|
|
276
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
test("scope-plan: unreadable namespace override falls through to coding/legacy", () => {
|
|
279
|
+
// No policy for "restricted" → canReadNamespace(default, "restricted") → false.
|
|
280
|
+
const config = withSelfPolicy(baseConfig(), "alice");
|
|
281
|
+
const plan = resolveScopePlan({
|
|
282
|
+
config,
|
|
283
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
284
|
+
sessionKey: "alice:sess-1",
|
|
285
|
+
namespace: "restricted",
|
|
286
|
+
codingContext: codingContext("myproj"),
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Unreadable override → namespaceOverride is undefined in the plan.
|
|
290
|
+
assert.equal(plan.namespaceOverride, undefined);
|
|
291
|
+
// Falls through to coding overlay (alice has a policy + coding context).
|
|
292
|
+
assert.notEqual(plan.codingOverlay, null);
|
|
293
|
+
assert.notEqual(plan.baseNamespace, "restricted");
|
|
294
|
+
assert.notEqual(plan.baseNamespace, "alice", "base should be the overlaid namespace");
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
298
|
+
// Snapshot 10: defaultRecallNamespaces omits self → overlay LCM collapses
|
|
299
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
300
|
+
|
|
301
|
+
test("scope-plan: self not in defaultRecallNamespaces → LCM collapses to default", () => {
|
|
302
|
+
const config = withSelfPolicy(
|
|
303
|
+
baseConfig({
|
|
304
|
+
defaultRecallNamespaces: ["shared"],
|
|
305
|
+
} as Partial<PluginConfig>),
|
|
306
|
+
"alice",
|
|
307
|
+
);
|
|
308
|
+
const plan = resolveScopePlan({
|
|
309
|
+
config,
|
|
310
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
311
|
+
sessionKey: "alice:sess-1",
|
|
312
|
+
codingContext: codingContext("myproj"),
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Coding overlay IS resolved (coding context + projectScope).
|
|
316
|
+
assert.notEqual(plan.codingOverlay, null);
|
|
317
|
+
// codingOverlaySelfReadable = false (self "alice" not in readable set).
|
|
318
|
+
// LCM collapses to default.
|
|
319
|
+
assert.deepEqual(plan.lcmReadNamespaces, ["default"]);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
323
|
+
// Cross-check: resolveScopePlan is pure (same inputs → same outputs)
|
|
324
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
325
|
+
|
|
326
|
+
test("scope-plan: resolver is pure — identical inputs produce identical plans", () => {
|
|
327
|
+
const config = withSelfPolicy(baseConfig(), "alice");
|
|
328
|
+
const ctx = codingContext("myproj");
|
|
329
|
+
const opts = { config, namespacesEnabled: config.namespacesEnabled, sessionKey: "alice:sess-1", codingContext: ctx } as const;
|
|
330
|
+
|
|
331
|
+
const a = resolveScopePlan(opts);
|
|
332
|
+
const b = resolveScopePlan(opts);
|
|
333
|
+
|
|
334
|
+
assert.deepEqual(a.readNamespaces, b.readNamespaces);
|
|
335
|
+
assert.deepEqual(a.lcmReadNamespaces, b.lcmReadNamespaces);
|
|
336
|
+
assert.deepEqual([...a.lcmReadSessionIds], [...b.lcmReadSessionIds]);
|
|
337
|
+
assert.equal(a.baseNamespace, b.baseNamespace);
|
|
338
|
+
assert.equal(a.codingOverlay?.namespace, b.codingOverlay?.namespace);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
342
|
+
// Parity invariant: LCM keys derived through lcmSessionKeyForNamespace
|
|
343
|
+
// (rule 22 — never hardcoded `:`-joins)
|
|
344
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
test("scope-plan: LCM session ids match lcmSessionKeyForNamespace encoding", () => {
|
|
347
|
+
const config = withSelfPolicy(baseConfig(), "alice");
|
|
348
|
+
const plan = resolveScopePlan({
|
|
349
|
+
config,
|
|
350
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
351
|
+
sessionKey: "alice:sess-1",
|
|
352
|
+
codingContext: codingContext("myproj"),
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Each LCM read session id must equal lcmSessionKeyForNamespace(ns, sk, default).
|
|
356
|
+
const expected = plan.lcmReadNamespaces.map(
|
|
357
|
+
(ns) => lcmSessionKeyForNamespace(ns, "alice:sess-1", "default") ?? "alice:sess-1",
|
|
358
|
+
);
|
|
359
|
+
assert.deepEqual([...plan.lcmReadSessionIds], expected);
|
|
360
|
+
});
|
|
@@ -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
|
+
}
|