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