@remnic/core 9.3.649 → 9.3.651
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 +36 -35
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +16 -16
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-mcp.js +15 -15
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-DFXIlGvZ.d.ts → access-service-DIZRHQ7Q.d.ts} +255 -2
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +13 -13
- package/dist/{auto-sync-54QQHOG5.js → auto-sync-5CJBJMPZ.js} +5 -5
- package/dist/bootstrap.d.ts +1 -1
- package/dist/briefing.js +3 -3
- package/dist/calibration.js +2 -2
- package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/causal-consolidation.js +6 -6
- package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-OAZ5MFUB.js → chunk-3XGWCZ63.js} +45 -28
- package/dist/chunk-3XGWCZ63.js.map +1 -0
- package/dist/{chunk-QKE4LHNR.js → chunk-4HYSMH7D.js} +2 -2
- package/dist/{chunk-NMIOW7XG.js → chunk-4PTKFBST.js} +2 -2
- package/dist/{chunk-DDRNDPX4.js → chunk-4SKKVWLQ.js} +2 -2
- package/dist/chunk-5FOCXX5E.js +34 -0
- package/dist/chunk-5FOCXX5E.js.map +1 -0
- package/dist/{chunk-XUGVP7ZU.js → chunk-5WSDHTBO.js} +166 -47
- package/dist/chunk-5WSDHTBO.js.map +1 -0
- package/dist/{chunk-WPCCNSWO.js → chunk-6UKL6IXM.js} +4 -4
- package/dist/{chunk-DB5A3NHS.js → chunk-7LWRCOP7.js} +9 -2
- package/dist/chunk-7LWRCOP7.js.map +1 -0
- package/dist/{chunk-APJQ6UEA.js → chunk-AGNBY3VG.js} +4 -4
- package/dist/{chunk-4BISW7RX.js → chunk-AJE7FJVE.js} +2 -2
- package/dist/{chunk-ZXWAQFDE.js → chunk-CFOCZPIQ.js} +2 -2
- package/dist/{chunk-NT5TINK5.js → chunk-DHGSZ3UD.js} +2 -2
- package/dist/{chunk-OTC2KOZ2.js → chunk-EHQLDFSH.js} +2 -2
- package/dist/{chunk-AMACWKM4.js → chunk-IJHLC5CH.js} +2 -2
- package/dist/{chunk-OR7R6M5Z.js → chunk-IVYSVAC6.js} +2 -2
- package/dist/{chunk-UMKPSD35.js → chunk-JF7SFXTG.js} +2 -2
- package/dist/{chunk-MCYT2RNT.js → chunk-KJDKZVF3.js} +3 -3
- package/dist/{chunk-BUKK5SWA.js → chunk-KQAFEZQX.js} +2 -2
- package/dist/{chunk-PQFUUXWK.js → chunk-KWM33SPU.js} +2 -2
- package/dist/{chunk-A3BS64GV.js → chunk-LCC5EZTT.js} +4 -4
- package/dist/{chunk-ZT6R3WR3.js → chunk-LFTLXOFX.js} +4 -4
- package/dist/{chunk-CNRZ6WJU.js → chunk-MF32AL7N.js} +5 -5
- package/dist/{chunk-6GIKAUTN.js → chunk-MMJANTJX.js} +33 -2
- package/dist/{chunk-6GIKAUTN.js.map → chunk-MMJANTJX.js.map} +1 -1
- package/dist/{chunk-D6WVJIS3.js → chunk-ORGWWNJG.js} +2 -2
- package/dist/{chunk-Z3PZRDLW.js → chunk-PRQXUSQV.js} +2 -2
- package/dist/{chunk-VWT3F4IV.js → chunk-PS3SYNHP.js} +12 -4
- package/dist/chunk-PS3SYNHP.js.map +1 -0
- package/dist/{chunk-IMWFHBG2.js → chunk-QWRC7GIO.js} +2 -2
- package/dist/{chunk-FQYFMIKG.js → chunk-RKN5J4RO.js} +26 -26
- package/dist/{chunk-FUXV6HSO.js → chunk-RSS2KWN6.js} +5 -5
- package/dist/{chunk-U3GQ33JC.js → chunk-SLTKP5WJ.js} +2 -2
- package/dist/{chunk-5ETA6OAS.js → chunk-SLYD3AH4.js} +617 -89
- package/dist/chunk-SLYD3AH4.js.map +1 -0
- package/dist/{chunk-6NKAQ74D.js → chunk-UU6MVCJ6.js} +1 -1
- package/dist/chunk-UU6MVCJ6.js.map +1 -0
- package/dist/{chunk-WEPMT6SC.js → chunk-V25ZAOSB.js} +5 -5
- package/dist/{chunk-UMTG2BN2.js → chunk-V4UDXYGG.js} +2 -2
- package/dist/{chunk-RRRCNIPK.js → chunk-WJK75OCH.js} +4 -4
- package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-OZKZ2TRP.js → chunk-XBIACVCO.js} +9 -2
- package/dist/chunk-XBIACVCO.js.map +1 -0
- package/dist/{chunk-ALUZN7BE.js → chunk-XMN6MMTU.js} +2 -2
- package/dist/{chunk-A4BTPHIN.js → chunk-Y7NWBBHV.js} +6 -6
- package/dist/{chunk-M75TBFKQ.js → chunk-Z2OXSMZK.js} +2 -2
- package/dist/{cli-DrL2Nv4j.d.ts → cli-BG4ybtJr.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +31 -31
- package/dist/compounding/engine.js +3 -3
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/index.js +3 -3
- package/dist/entity-retrieval.js +3 -3
- package/dist/event-order-recall.js +1 -1
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/explicit-cue-recall.d.ts +7 -0
- package/dist/explicit-cue-recall.js +2 -1
- package/dist/extraction-judge.js +3 -3
- package/dist/extraction.js +3 -3
- package/dist/fallback-llm.js +2 -2
- package/dist/focused-list-recall.d.ts +6 -0
- package/dist/focused-list-recall.js +2 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +84 -83
- package/dist/index.js.map +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +5 -5
- package/dist/lcm-fallback-read.d.ts +71 -0
- package/dist/lcm-fallback-read.js +10 -0
- package/dist/lcm-fallback-read.js.map +1 -0
- 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 +2 -2
- package/dist/namespaces/migrate.js +7 -7
- package/dist/namespaces/search.js +3 -3
- package/dist/namespaces/storage.js +3 -3
- package/dist/operator-toolkit.js +9 -9
- package/dist/{orchestrator-DEQW9j0Z.d.ts → orchestrator-CX-oqwJq.d.ts} +58 -0
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +30 -29
- package/dist/recall-planner-llm.js +2 -2
- package/dist/response-guidance-recall.d.ts +6 -0
- package/dist/response-guidance-recall.js +2 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/search/factory.js +2 -2
- package/dist/search/index.js +4 -4
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/storage.js +2 -2
- package/dist/summarizer.js +3 -3
- package/dist/targeted-fact-recall.d.ts +6 -0
- package/dist/targeted-fact-recall.js +2 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/import-sqlite.js +2 -2
- package/dist/transfer/types.d.ts +12 -12
- package/dist/verified-recall.js +3 -3
- package/package.json +1 -1
- package/src/access-service-lcm-forgery.test.ts +410 -0
- package/src/access-service-observe-lcm-parity.test.ts +1397 -0
- package/src/access-service-observe-scope.test.ts +599 -0
- package/src/access-service-raw-excerpt-read-gate.test.ts +443 -0
- package/src/access-service.ts +1270 -113
- package/src/coding/coding-namespace.test.ts +44 -0
- package/src/coding/coding-namespace.ts +163 -0
- package/src/event-order-recall.ts +8 -0
- package/src/explicit-cue-recall.ts +70 -29
- package/src/focused-list-recall.ts +23 -1
- package/src/lcm-fallback-read.ts +113 -0
- package/src/orchestrator.ts +331 -26
- package/src/response-guidance-recall.ts +21 -1
- package/src/targeted-fact-recall.ts +24 -3
- package/dist/chunk-5ETA6OAS.js.map +0 -1
- package/dist/chunk-6NKAQ74D.js.map +0 -1
- package/dist/chunk-DB5A3NHS.js.map +0 -1
- package/dist/chunk-OAZ5MFUB.js.map +0 -1
- package/dist/chunk-OZKZ2TRP.js.map +0 -1
- package/dist/chunk-VWT3F4IV.js.map +0 -1
- package/dist/chunk-XUGVP7ZU.js.map +0 -1
- /package/dist/{auto-sync-54QQHOG5.js.map → auto-sync-5CJBJMPZ.js.map} +0 -0
- /package/dist/{capsule-crypto-GWVG7LGC.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
- /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-QKE4LHNR.js.map → chunk-4HYSMH7D.js.map} +0 -0
- /package/dist/{chunk-NMIOW7XG.js.map → chunk-4PTKFBST.js.map} +0 -0
- /package/dist/{chunk-DDRNDPX4.js.map → chunk-4SKKVWLQ.js.map} +0 -0
- /package/dist/{chunk-WPCCNSWO.js.map → chunk-6UKL6IXM.js.map} +0 -0
- /package/dist/{chunk-APJQ6UEA.js.map → chunk-AGNBY3VG.js.map} +0 -0
- /package/dist/{chunk-4BISW7RX.js.map → chunk-AJE7FJVE.js.map} +0 -0
- /package/dist/{chunk-ZXWAQFDE.js.map → chunk-CFOCZPIQ.js.map} +0 -0
- /package/dist/{chunk-NT5TINK5.js.map → chunk-DHGSZ3UD.js.map} +0 -0
- /package/dist/{chunk-OTC2KOZ2.js.map → chunk-EHQLDFSH.js.map} +0 -0
- /package/dist/{chunk-AMACWKM4.js.map → chunk-IJHLC5CH.js.map} +0 -0
- /package/dist/{chunk-OR7R6M5Z.js.map → chunk-IVYSVAC6.js.map} +0 -0
- /package/dist/{chunk-UMKPSD35.js.map → chunk-JF7SFXTG.js.map} +0 -0
- /package/dist/{chunk-MCYT2RNT.js.map → chunk-KJDKZVF3.js.map} +0 -0
- /package/dist/{chunk-BUKK5SWA.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-PQFUUXWK.js.map → chunk-KWM33SPU.js.map} +0 -0
- /package/dist/{chunk-A3BS64GV.js.map → chunk-LCC5EZTT.js.map} +0 -0
- /package/dist/{chunk-ZT6R3WR3.js.map → chunk-LFTLXOFX.js.map} +0 -0
- /package/dist/{chunk-CNRZ6WJU.js.map → chunk-MF32AL7N.js.map} +0 -0
- /package/dist/{chunk-D6WVJIS3.js.map → chunk-ORGWWNJG.js.map} +0 -0
- /package/dist/{chunk-Z3PZRDLW.js.map → chunk-PRQXUSQV.js.map} +0 -0
- /package/dist/{chunk-IMWFHBG2.js.map → chunk-QWRC7GIO.js.map} +0 -0
- /package/dist/{chunk-FQYFMIKG.js.map → chunk-RKN5J4RO.js.map} +0 -0
- /package/dist/{chunk-FUXV6HSO.js.map → chunk-RSS2KWN6.js.map} +0 -0
- /package/dist/{chunk-U3GQ33JC.js.map → chunk-SLTKP5WJ.js.map} +0 -0
- /package/dist/{chunk-WEPMT6SC.js.map → chunk-V25ZAOSB.js.map} +0 -0
- /package/dist/{chunk-UMTG2BN2.js.map → chunk-V4UDXYGG.js.map} +0 -0
- /package/dist/{chunk-RRRCNIPK.js.map → chunk-WJK75OCH.js.map} +0 -0
- /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-ALUZN7BE.js.map → chunk-XMN6MMTU.js.map} +0 -0
- /package/dist/{chunk-A4BTPHIN.js.map → chunk-Y7NWBBHV.js.map} +0 -0
- /package/dist/{chunk-M75TBFKQ.js.map → chunk-Z2OXSMZK.js.map} +0 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1505 thread 2f7: the `disclosure: "raw"` excerpt path MUST pass through the
|
|
3
|
+
* SAME read-authorization gate as normal recall + `lcmSearch` + the in-prompt
|
|
4
|
+
* LCM sections — NOT `snapshot.namespace` (the effective WRITE/overlay
|
|
5
|
+
* namespace).
|
|
6
|
+
*
|
|
7
|
+
* The defect class: a project-scoped session whose principal can WRITE but not
|
|
8
|
+
* READ its self base (or whose `defaultRecallNamespaces` omits `self`) archives
|
|
9
|
+
* LCM rows under the `<principal>-project-*` overlay key (the write key). When
|
|
10
|
+
* `recall({ disclosure: "raw" })` derived the raw-excerpt LCM `session_id` from
|
|
11
|
+
* `snapshot.namespace`, it prefixed the lookup with that overlay namespace and
|
|
12
|
+
* surfaced `<principal>-project-*` transcript rows that normal recall and
|
|
13
|
+
* `lcmSearch` intentionally EXCLUDE for that reader — a cross-tenant read leak.
|
|
14
|
+
*
|
|
15
|
+
* After the fix the raw-excerpt lookup routes through
|
|
16
|
+
* `resolveRawExcerptReadNamespace` → `resolveLcmReadNamespace(..., "read")`,
|
|
17
|
+
* which honours the overlay only when the principal SELF base is in the readable
|
|
18
|
+
* recall set. When it is not, the lookup falls back to the default store (raw
|
|
19
|
+
* sessionKey) exactly like normal recall + `lcmSearch`.
|
|
20
|
+
*
|
|
21
|
+
* These tests exercise the private `executeRecall` directly (the budget /
|
|
22
|
+
* idempotency wrapper is orthogonal to the namespace gate) and assert the
|
|
23
|
+
* `session_id` that reaches the LCM engine's `searchContextFull`.
|
|
24
|
+
*
|
|
25
|
+
* All fixtures are synthetic — no real user data.
|
|
26
|
+
*/
|
|
27
|
+
import assert from "node:assert/strict";
|
|
28
|
+
import test from "node:test";
|
|
29
|
+
|
|
30
|
+
import { EngramAccessService } from "./access-service.js";
|
|
31
|
+
import { CrossNamespaceBudget } from "./cross-namespace-budget.js";
|
|
32
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
33
|
+
import type { LastRecallSnapshot } from "./recall-state.js";
|
|
34
|
+
import {
|
|
35
|
+
combineNamespaces,
|
|
36
|
+
lcmSessionKeyForNamespace,
|
|
37
|
+
projectNamespaceName,
|
|
38
|
+
projectTagProjectId,
|
|
39
|
+
} from "./coding/coding-namespace.js";
|
|
40
|
+
import type { StorageManager } from "./storage.js";
|
|
41
|
+
import type { CodingContext, PluginConfig } from "./types.js";
|
|
42
|
+
|
|
43
|
+
interface RawExcerptProbe {
|
|
44
|
+
service: EngramAccessService;
|
|
45
|
+
/** session_id values that reached the LCM engine's `searchContextFull`. */
|
|
46
|
+
searchSessionIds: Array<string | undefined>;
|
|
47
|
+
contexts: Map<string, CodingContext>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build a service whose orchestrator stub:
|
|
52
|
+
* - delegates principal / overlay resolution to the REAL Orchestrator
|
|
53
|
+
* prototype so the read gate is exercised exactly as production does it,
|
|
54
|
+
* - records the `session_id` the raw-excerpt LCM lookup prefixes,
|
|
55
|
+
* - returns a fixed `lastRecall` snapshot whose `namespace` is the WRITE/overlay
|
|
56
|
+
* namespace (simulating what `observe` wrote), with NO result paths so the
|
|
57
|
+
* test isolates the raw-excerpt session_id.
|
|
58
|
+
*/
|
|
59
|
+
function makeRawExcerptProbe(options: {
|
|
60
|
+
config: Partial<PluginConfig>;
|
|
61
|
+
snapshotNamespace: string;
|
|
62
|
+
sessionContext?: CodingContext;
|
|
63
|
+
sessionKey: string;
|
|
64
|
+
}): RawExcerptProbe {
|
|
65
|
+
const searchSessionIds: Array<string | undefined> = [];
|
|
66
|
+
const contexts = new Map<string, CodingContext>();
|
|
67
|
+
if (options.sessionContext) {
|
|
68
|
+
contexts.set(options.sessionKey, options.sessionContext);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const config = {
|
|
72
|
+
namespacesEnabled: true,
|
|
73
|
+
defaultNamespace: "default",
|
|
74
|
+
sharedNamespace: "shared",
|
|
75
|
+
namespacePolicies: [],
|
|
76
|
+
defaultRecallNamespaces: ["self", "shared"],
|
|
77
|
+
codingMode: { projectScope: true },
|
|
78
|
+
memoryDir: "/synthetic/remnic-raw-excerpt-read-gate",
|
|
79
|
+
objectiveStateMemoryEnabled: false,
|
|
80
|
+
objectiveStateSnapshotWritesEnabled: false,
|
|
81
|
+
principalFromSessionKeyMode: "prefix",
|
|
82
|
+
principalFromSessionKeyRules: [],
|
|
83
|
+
recallCrossNamespaceBudgetEnabled: false,
|
|
84
|
+
...options.config,
|
|
85
|
+
} as unknown as PluginConfig;
|
|
86
|
+
|
|
87
|
+
const snapshot: LastRecallSnapshot = {
|
|
88
|
+
sessionKey: options.sessionKey,
|
|
89
|
+
recordedAt: new Date().toISOString(),
|
|
90
|
+
queryHash: "hash",
|
|
91
|
+
queryLen: 5,
|
|
92
|
+
memoryIds: [],
|
|
93
|
+
namespace: options.snapshotNamespace,
|
|
94
|
+
recallNamespaces: [options.snapshotNamespace],
|
|
95
|
+
resultPaths: [],
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const storage = {
|
|
99
|
+
dir: "/synthetic/remnic-raw-excerpt-read-gate/store",
|
|
100
|
+
async readMemoryByPath() {
|
|
101
|
+
return null;
|
|
102
|
+
},
|
|
103
|
+
async getMemoryById() {
|
|
104
|
+
return null;
|
|
105
|
+
},
|
|
106
|
+
} as unknown as StorageManager;
|
|
107
|
+
|
|
108
|
+
const service = Object.create(
|
|
109
|
+
EngramAccessService.prototype,
|
|
110
|
+
) as EngramAccessService;
|
|
111
|
+
|
|
112
|
+
const orch = {
|
|
113
|
+
config,
|
|
114
|
+
getCodingContextForSession: (sk: string | undefined) =>
|
|
115
|
+
(sk ? contexts.get(sk) : null) ?? null,
|
|
116
|
+
setCodingContextForSession: (sk: string, ctx: CodingContext | null) => {
|
|
117
|
+
if (ctx === null) contexts.delete(sk);
|
|
118
|
+
else contexts.set(sk, ctx);
|
|
119
|
+
},
|
|
120
|
+
applyCodingNamespaceOverlay: (sk: string | undefined, base: string) =>
|
|
121
|
+
Orchestrator.prototype.applyCodingNamespaceOverlay.call(orch, sk, base),
|
|
122
|
+
resolvePrincipal: (sk?: string) =>
|
|
123
|
+
Orchestrator.prototype.resolvePrincipal.call(orch, sk),
|
|
124
|
+
resolveSelfNamespace: (sk?: string) =>
|
|
125
|
+
Orchestrator.prototype.resolveSelfNamespace.call(orch, sk),
|
|
126
|
+
async getStorage() {
|
|
127
|
+
return storage;
|
|
128
|
+
},
|
|
129
|
+
lastRecall: new Map<string, LastRecallSnapshot>([
|
|
130
|
+
[options.sessionKey, snapshot],
|
|
131
|
+
]),
|
|
132
|
+
async recall() {
|
|
133
|
+
return "";
|
|
134
|
+
},
|
|
135
|
+
lcmEngine: {
|
|
136
|
+
enabled: true,
|
|
137
|
+
searchContextFull: async (
|
|
138
|
+
_query: string,
|
|
139
|
+
_limit: number,
|
|
140
|
+
sessionId?: string,
|
|
141
|
+
) => {
|
|
142
|
+
searchSessionIds.push(sessionId);
|
|
143
|
+
return [];
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
} as unknown as Orchestrator;
|
|
147
|
+
|
|
148
|
+
(service as unknown as { orchestrator: Orchestrator }).orchestrator = orch;
|
|
149
|
+
|
|
150
|
+
// `executeRecall` consults the cross-namespace budget; `Object.create` skips
|
|
151
|
+
// the constructor that builds the real limiter, so install one. Budget is
|
|
152
|
+
// disabled in config, so it never denies — orthogonal to the namespace gate.
|
|
153
|
+
(service as unknown as { budget: CrossNamespaceBudget }).budget =
|
|
154
|
+
new CrossNamespaceBudget({
|
|
155
|
+
enabled: false,
|
|
156
|
+
windowMs: 60_000,
|
|
157
|
+
softLimit: 10,
|
|
158
|
+
hardLimit: 30,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return { service, searchSessionIds, contexts };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
type ExecuteRecallInternals = {
|
|
165
|
+
executeRecall: (request: unknown) => Promise<unknown>;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const SESSION_KEY = "pi-geek:abc123";
|
|
169
|
+
const PROJECT_TAG = "Blend/Supply";
|
|
170
|
+
|
|
171
|
+
function overlayNamespace(): string {
|
|
172
|
+
return combineNamespaces(
|
|
173
|
+
"pi-geek",
|
|
174
|
+
projectNamespaceName(projectTagProjectId(PROJECT_TAG)),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function sessionContext(): CodingContext {
|
|
179
|
+
return {
|
|
180
|
+
projectId: projectTagProjectId(PROJECT_TAG),
|
|
181
|
+
branch: null,
|
|
182
|
+
rootPath: projectTagProjectId(PROJECT_TAG),
|
|
183
|
+
defaultBranch: null,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
test("#1505 thread 2f7: WRITE-only / self-unreadable principal ⇒ raw excerpts fall back to the default store (no overlay prefix)", async () => {
|
|
188
|
+
// Self namespace EXISTS and is WRITABLE by pi-geek, but NOT readable by it
|
|
189
|
+
// (only `other` may read). An unqualified project-scoped observe archived LCM
|
|
190
|
+
// under the `<pi-geek>-project-*` overlay key. The read gate
|
|
191
|
+
// (`recallNamespacesForPrincipal`) excludes that overlay for pi-geek, so raw
|
|
192
|
+
// disclosure MUST fall back to the default store — the raw sessionKey — never
|
|
193
|
+
// the overlay key.
|
|
194
|
+
const probe = makeRawExcerptProbe({
|
|
195
|
+
config: {
|
|
196
|
+
namespacePolicies: [
|
|
197
|
+
{ name: "pi-geek", readPrincipals: ["other"], writePrincipals: ["pi-geek"] },
|
|
198
|
+
],
|
|
199
|
+
principalFromSessionKeyMode: "prefix",
|
|
200
|
+
principalFromSessionKeyRules: [{ match: "pi-geek:", principal: "pi-geek" }],
|
|
201
|
+
},
|
|
202
|
+
snapshotNamespace: overlayNamespace(),
|
|
203
|
+
sessionContext: sessionContext(),
|
|
204
|
+
sessionKey: SESSION_KEY,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
await (probe.service as unknown as ExecuteRecallInternals).executeRecall({
|
|
208
|
+
query: "what database are we using?",
|
|
209
|
+
sessionKey: SESSION_KEY,
|
|
210
|
+
authenticatedPrincipal: "pi-geek",
|
|
211
|
+
disclosure: "raw",
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
assert.equal(probe.searchSessionIds.length, 1, "raw excerpt lookup must run once");
|
|
215
|
+
// FAIL-BEFORE: previously prefixed with `snapshot.namespace` (the overlay),
|
|
216
|
+
// i.e. `${overlayNamespace()}:${SESSION_KEY}`. PASS-AFTER: gated read namespace
|
|
217
|
+
// collapses to the default store ⇒ the raw sessionKey.
|
|
218
|
+
assert.equal(
|
|
219
|
+
probe.searchSessionIds[0],
|
|
220
|
+
SESSION_KEY,
|
|
221
|
+
"raw excerpts must NOT prefix with the unreadable overlay namespace",
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("#1505 thread 2f7: defaultRecallNamespaces omits 'self' ⇒ raw excerpts fall back to the default store", async () => {
|
|
226
|
+
// pi-geek may both read AND write its self base, but the operator's
|
|
227
|
+
// `defaultRecallNamespaces` omits `self`, so normal recall + lcmSearch never
|
|
228
|
+
// surface overlay rows for this reader. Raw disclosure must match.
|
|
229
|
+
const probe = makeRawExcerptProbe({
|
|
230
|
+
config: {
|
|
231
|
+
defaultRecallNamespaces: ["shared"],
|
|
232
|
+
namespacePolicies: [
|
|
233
|
+
{ name: "pi-geek", readPrincipals: ["pi-geek"], writePrincipals: ["pi-geek"] },
|
|
234
|
+
],
|
|
235
|
+
principalFromSessionKeyMode: "prefix",
|
|
236
|
+
principalFromSessionKeyRules: [{ match: "pi-geek:", principal: "pi-geek" }],
|
|
237
|
+
},
|
|
238
|
+
snapshotNamespace: overlayNamespace(),
|
|
239
|
+
sessionContext: sessionContext(),
|
|
240
|
+
sessionKey: SESSION_KEY,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await (probe.service as unknown as ExecuteRecallInternals).executeRecall({
|
|
244
|
+
query: "what database are we using?",
|
|
245
|
+
sessionKey: SESSION_KEY,
|
|
246
|
+
authenticatedPrincipal: "pi-geek",
|
|
247
|
+
disclosure: "raw",
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
assert.equal(probe.searchSessionIds.length, 1);
|
|
251
|
+
assert.equal(
|
|
252
|
+
probe.searchSessionIds[0],
|
|
253
|
+
SESSION_KEY,
|
|
254
|
+
"self-omitted recall set ⇒ raw excerpts fall back to the default store",
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("#1505 thread 2f7 (positive): overlay IS readable ⇒ raw disclosure includes the overlay rows", async () => {
|
|
259
|
+
// pi-geek may read its self base AND `defaultRecallNamespaces` includes
|
|
260
|
+
// `self`, so the overlay is in the readable recall set. Raw disclosure MUST
|
|
261
|
+
// continue to prefix with the overlay key so the session finds its own
|
|
262
|
+
// project-scoped transcript rows (no regression for the normal case).
|
|
263
|
+
const probe = makeRawExcerptProbe({
|
|
264
|
+
config: {
|
|
265
|
+
defaultRecallNamespaces: ["self", "shared"],
|
|
266
|
+
namespacePolicies: [
|
|
267
|
+
{ name: "pi-geek", readPrincipals: ["pi-geek"], writePrincipals: ["pi-geek"] },
|
|
268
|
+
],
|
|
269
|
+
principalFromSessionKeyMode: "prefix",
|
|
270
|
+
principalFromSessionKeyRules: [{ match: "pi-geek:", principal: "pi-geek" }],
|
|
271
|
+
},
|
|
272
|
+
snapshotNamespace: overlayNamespace(),
|
|
273
|
+
sessionContext: sessionContext(),
|
|
274
|
+
sessionKey: SESSION_KEY,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await (probe.service as unknown as ExecuteRecallInternals).executeRecall({
|
|
278
|
+
query: "what database are we using?",
|
|
279
|
+
sessionKey: SESSION_KEY,
|
|
280
|
+
authenticatedPrincipal: "pi-geek",
|
|
281
|
+
disclosure: "raw",
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
assert.equal(probe.searchSessionIds.length, 1);
|
|
285
|
+
assert.equal(
|
|
286
|
+
probe.searchSessionIds[0],
|
|
287
|
+
lcmSessionKeyForNamespace(overlayNamespace(), SESSION_KEY, "default"),
|
|
288
|
+
"readable overlay ⇒ raw disclosure keeps the overlay prefix",
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("#1505 thread NBHWz (codex P2): restrictive `default` READ policy + readable self ⇒ raw excerpts read the self/recall-authorized namespace (no `not readable: default` throw)", async () => {
|
|
293
|
+
// The root defect: the raw-excerpt path PRE-authorized
|
|
294
|
+
// `undefined ⇒ config.defaultNamespace` via `resolveReadableNamespace` BEFORE
|
|
295
|
+
// computing the LCM excerpt key. Under a deployment whose `default` namespace
|
|
296
|
+
// has a RESTRICTIVE read policy (pi-geek may NOT read `default`) but where
|
|
297
|
+
// pi-geek's self namespace IS readable, normal recall still succeeds via
|
|
298
|
+
// `recallNamespacesForPrincipal`, yet `disclosure: "raw"` threw `namespace is
|
|
299
|
+
// not readable: default` before serialization.
|
|
300
|
+
//
|
|
301
|
+
// FAIL-BEFORE: `executeRecall({ disclosure: "raw" })` throws `namespace is not
|
|
302
|
+
// readable: default`. PASS-AFTER: the fallback comes from the already
|
|
303
|
+
// read-authorized recall namespace set, so the raw lookup runs and prefixes
|
|
304
|
+
// its LCM session_id with the readable self namespace (no pre-auth of default).
|
|
305
|
+
const probe = makeRawExcerptProbe({
|
|
306
|
+
config: {
|
|
307
|
+
// RESTRICTIVE default: pi-geek may NOT read `default`.
|
|
308
|
+
namespacePolicies: [
|
|
309
|
+
{ name: "default", readPrincipals: [], writePrincipals: [] },
|
|
310
|
+
{ name: "pi-geek", readPrincipals: ["pi-geek"], writePrincipals: ["pi-geek"] },
|
|
311
|
+
],
|
|
312
|
+
// self IS in the recall set and IS readable, so the overlay resolves and
|
|
313
|
+
// the read gate keeps it (no pre-auth of the denied default).
|
|
314
|
+
defaultRecallNamespaces: ["self", "shared"],
|
|
315
|
+
codingMode: { projectScope: true, branchScope: false, globalFallback: true },
|
|
316
|
+
principalFromSessionKeyMode: "prefix",
|
|
317
|
+
principalFromSessionKeyRules: [{ match: "pi-geek:", principal: "pi-geek" }],
|
|
318
|
+
},
|
|
319
|
+
// snapshot.namespace records the overlay (the write target observe used);
|
|
320
|
+
// the read gate independently derives the readable overlay.
|
|
321
|
+
snapshotNamespace: overlayNamespace(),
|
|
322
|
+
sessionContext: sessionContext(),
|
|
323
|
+
sessionKey: SESSION_KEY,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
await (probe.service as unknown as ExecuteRecallInternals).executeRecall({
|
|
327
|
+
query: "what database are we using?",
|
|
328
|
+
sessionKey: SESSION_KEY,
|
|
329
|
+
authenticatedPrincipal: "pi-geek",
|
|
330
|
+
disclosure: "raw",
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
assert.ok(
|
|
334
|
+
probe.searchSessionIds.length >= 1,
|
|
335
|
+
"raw excerpt lookup must run (NOT throw `not readable: default`)",
|
|
336
|
+
);
|
|
337
|
+
// The readable self overlay is honoured ⇒ the PRIMARY LCM session_id is
|
|
338
|
+
// prefixed with it, matching what normal recall + `lcmSearch` search for this
|
|
339
|
+
// principal. The premature `default` read-auth (which would have thrown) is
|
|
340
|
+
// gone. (The fallback-unification may append project/root read-fallback keys
|
|
341
|
+
// after the primary; the primary overlay key is what matters here.)
|
|
342
|
+
assert.equal(
|
|
343
|
+
probe.searchSessionIds[0],
|
|
344
|
+
lcmSessionKeyForNamespace(overlayNamespace(), SESSION_KEY, "default"),
|
|
345
|
+
"raw excerpts must read the recall-authorized self overlay, not pre-authorize the denied default",
|
|
346
|
+
);
|
|
347
|
+
// No queried key may be the bare default-store key (the unprefixed raw
|
|
348
|
+
// sessionKey) — that would mean the read gate fell back to the DENIED default
|
|
349
|
+
// store. Every searched key must be namespace-prefixed with an AUTHORIZED
|
|
350
|
+
// namespace the principal may read (the `pi-geek` self base or its
|
|
351
|
+
// `pi-geek-project-*` overlay), matching what normal recall + `lcmSearch`
|
|
352
|
+
// search.
|
|
353
|
+
// #1495 P1: the namespaced key is sentinel-framed (`\x1f<ns>\x1f<sessionKey>`),
|
|
354
|
+
// so parse the namespace out of the frame and assert it is an authorized
|
|
355
|
+
// pi-geek namespace (the self base or its `pi-geek-project-*` overlay).
|
|
356
|
+
const SENTINEL = "\u001f";
|
|
357
|
+
for (const id of probe.searchSessionIds) {
|
|
358
|
+
assert.notEqual(
|
|
359
|
+
id,
|
|
360
|
+
SESSION_KEY,
|
|
361
|
+
"raw-excerpt LCM keys must NOT fall back to the bare default store (the denied default)",
|
|
362
|
+
);
|
|
363
|
+
assert.ok(
|
|
364
|
+
typeof id === "string" && id.startsWith(SENTINEL),
|
|
365
|
+
`every raw-excerpt LCM key must be the sentinel-framed namespaced key, got ${String(id)}`,
|
|
366
|
+
);
|
|
367
|
+
const framedNs = (id as string).slice(SENTINEL.length).split(SENTINEL)[0]!;
|
|
368
|
+
assert.ok(
|
|
369
|
+
framedNs.startsWith("pi-geek"),
|
|
370
|
+
`every raw-excerpt LCM key must be framed with an authorized pi-geek namespace, got ${String(id)}`,
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("#1505 thread NBHWz (codex P2): no readable LCM namespace ⇒ raw excerpts are EMPTY (no throw, no fallback to unreadable default)", async () => {
|
|
376
|
+
// alice authenticates but her policy denies reading `default`, `shared` is not
|
|
377
|
+
// in the recall set, and she has NO readable self namespace
|
|
378
|
+
// (`defaultRecallNamespaces` omits `self` AND her self base is unreadable). No
|
|
379
|
+
// readable LCM namespace exists for an implicit raw recall.
|
|
380
|
+
//
|
|
381
|
+
// FAIL-BEFORE: throws `namespace is not readable: default`. PASS-AFTER: the
|
|
382
|
+
// raw-excerpt lookup is suppressed (returns EMPTY) — `searchContextFull` is
|
|
383
|
+
// never called — so raw recall degrades gracefully instead of throwing.
|
|
384
|
+
const probe = makeRawExcerptProbe({
|
|
385
|
+
config: {
|
|
386
|
+
namespacePolicies: [
|
|
387
|
+
{ name: "default", readPrincipals: [], writePrincipals: [] },
|
|
388
|
+
// alice can WRITE but NOT read her self namespace, and self is omitted
|
|
389
|
+
// from the recall set ⇒ nothing readable to fall back to.
|
|
390
|
+
{ name: "alice", readPrincipals: [], writePrincipals: ["alice"] },
|
|
391
|
+
],
|
|
392
|
+
// `shared` deliberately not granted either (default policy denies, no
|
|
393
|
+
// shared policy ⇒ canReadNamespace(alice, "shared") is true by the
|
|
394
|
+
// default-or-shared fallback). Omit shared from the recall set so it is not
|
|
395
|
+
// a fallback.
|
|
396
|
+
defaultRecallNamespaces: [],
|
|
397
|
+
codingMode: { projectScope: false, branchScope: false, globalFallback: true },
|
|
398
|
+
principalFromSessionKeyMode: "prefix",
|
|
399
|
+
principalFromSessionKeyRules: [],
|
|
400
|
+
},
|
|
401
|
+
snapshotNamespace: "default",
|
|
402
|
+
sessionKey: SESSION_KEY,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Must NOT throw.
|
|
406
|
+
await (probe.service as unknown as ExecuteRecallInternals).executeRecall({
|
|
407
|
+
query: "what database are we using?",
|
|
408
|
+
sessionKey: SESSION_KEY,
|
|
409
|
+
authenticatedPrincipal: "alice",
|
|
410
|
+
disclosure: "raw",
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
assert.equal(
|
|
414
|
+
probe.searchSessionIds.length,
|
|
415
|
+
0,
|
|
416
|
+
"no readable LCM namespace ⇒ raw excerpts must be EMPTY (searchContextFull never called), not throw",
|
|
417
|
+
);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("#1505 thread 2f7 (single-store regression): namespaces disabled ⇒ raw excerpts use the raw sessionKey", async () => {
|
|
421
|
+
// Byte-for-byte single-user behavior: no namespaces, no overlay, raw key.
|
|
422
|
+
const probe = makeRawExcerptProbe({
|
|
423
|
+
config: {
|
|
424
|
+
namespacesEnabled: false,
|
|
425
|
+
codingMode: {
|
|
426
|
+
projectScope: false,
|
|
427
|
+
branchScope: false,
|
|
428
|
+
globalFallback: true,
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
snapshotNamespace: "default",
|
|
432
|
+
sessionKey: SESSION_KEY,
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
await (probe.service as unknown as ExecuteRecallInternals).executeRecall({
|
|
436
|
+
query: "what database are we using?",
|
|
437
|
+
sessionKey: SESSION_KEY,
|
|
438
|
+
disclosure: "raw",
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
assert.equal(probe.searchSessionIds.length, 1);
|
|
442
|
+
assert.equal(probe.searchSessionIds[0], SESSION_KEY);
|
|
443
|
+
});
|