@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
package/src/orchestrator.ts
CHANGED
|
@@ -307,6 +307,7 @@ import {
|
|
|
307
307
|
} from "./namespaces/principal.js";
|
|
308
308
|
import {
|
|
309
309
|
combineNamespaces,
|
|
310
|
+
lcmReadSessionIdsForNamespaces,
|
|
310
311
|
resolveCodingNamespaceOverlay,
|
|
311
312
|
} from "./coding/coding-namespace.js";
|
|
312
313
|
import type { CodingContext } from "./types.js";
|
|
@@ -2011,6 +2012,66 @@ export class Orchestrator {
|
|
|
2011
2012
|
return this.applyCodingNamespaceOverlay(sessionKey, base);
|
|
2012
2013
|
}
|
|
2013
2014
|
|
|
2015
|
+
/**
|
|
2016
|
+
* Effective namespace a same-session LCM/structured-history READER must use
|
|
2017
|
+
* to find what the access `observe` surface WROTE (#1495 thread 2).
|
|
2018
|
+
*
|
|
2019
|
+
* This MUST mirror the `observe` scope plan's write-namespace resolution, NOT
|
|
2020
|
+
* `resolveSelfNamespace`: when no coding overlay applies, `observe` archives
|
|
2021
|
+
* under `config.defaultNamespace` (an unqualified observed turn is NOT moved
|
|
2022
|
+
* to the principal self namespace — identical to
|
|
2023
|
+
* `resolveCodingScopedWriteNamespace`/`memory_store`, rule 39). Only when a
|
|
2024
|
+
* coding overlay actually changes the namespace does the writer (and so the
|
|
2025
|
+
* reader) use the overlaid `project-*` namespace. Returning the self base for
|
|
2026
|
+
* the no-overlay case would prefix the read key with a namespace the writer
|
|
2027
|
+
* never used, so the reader would miss its own evidence.
|
|
2028
|
+
*
|
|
2029
|
+
* Honours the access-surface `principalOverride` (#1505 thread 2, codex): when
|
|
2030
|
+
* a recall supplies an authenticated principal NOT encoded in the raw
|
|
2031
|
+
* `sessionKey`, `observe` archived LCM under THAT principal's base namespace.
|
|
2032
|
+
* Deriving the base from `resolvePrincipal(sessionKey)` alone could fall back
|
|
2033
|
+
* to `default`, so principal `alice` observing `sess-1` would write under
|
|
2034
|
+
* `alice` but READ under `default`. Threading the override here keeps the read
|
|
2035
|
+
* base identical to the write base.
|
|
2036
|
+
*
|
|
2037
|
+
* READ-AUTHORIZATION gate (#1505 round 3, codex P2 "Gate LCM recall keys by
|
|
2038
|
+
* readable namespaces"): the overlay LCM read key is a `<principal>-project-*`
|
|
2039
|
+
* sub-namespace of the principal SELF base. The normal recall namespace set
|
|
2040
|
+
* below only substitutes the coding overlay when the principal SELF base is
|
|
2041
|
+
* actually in the readable recall set (`recallNamespacesForPrincipal` — gated
|
|
2042
|
+
* by `defaultRecallNamespaces.includes("self")` AND `canReadNamespace`). If a
|
|
2043
|
+
* principal can WRITE but not READ its self namespace (or `defaultRecall-
|
|
2044
|
+
* Namespaces` omits `self`), QMD/file recall never touches those overlay rows,
|
|
2045
|
+
* so neither may the LCM read key. When the self base is NOT readable, fall
|
|
2046
|
+
* back to the default store — exactly what an unqualified, unauthorized recall
|
|
2047
|
+
* resolves to — rather than injecting overlay rows the rest of recall excludes
|
|
2048
|
+
* (rule 42 read/write parity; rule 48 least-privilege).
|
|
2049
|
+
*/
|
|
2050
|
+
private lcmReadNamespaceForSession(
|
|
2051
|
+
sessionKey?: string,
|
|
2052
|
+
principalOverride?: string,
|
|
2053
|
+
): string {
|
|
2054
|
+
const principal =
|
|
2055
|
+
typeof principalOverride === "string" && principalOverride.length > 0
|
|
2056
|
+
? principalOverride
|
|
2057
|
+
: this.resolvePrincipal(sessionKey);
|
|
2058
|
+
const base = defaultNamespaceForPrincipal(principal, this.config);
|
|
2059
|
+
const overlaid = this.applyCodingNamespaceOverlay(sessionKey, base);
|
|
2060
|
+
// No overlay → collapse to the default store so the LCM key is the raw
|
|
2061
|
+
// sessionKey, exactly what an unqualified observe archived under.
|
|
2062
|
+
if (overlaid === base) return this.config.defaultNamespace;
|
|
2063
|
+
// Overlay applied. Only honour it when the principal SELF base is in the
|
|
2064
|
+
// readable recall set (same gate the recall namespace set uses to
|
|
2065
|
+
// substitute the overlay). Otherwise the overlay rows are unauthorized for
|
|
2066
|
+
// this reader — fall back to the default store so the LCM read matches
|
|
2067
|
+
// what QMD/file recall would surface.
|
|
2068
|
+
const selfReadableInRecall = recallNamespacesForPrincipal(
|
|
2069
|
+
principal,
|
|
2070
|
+
this.config,
|
|
2071
|
+
).includes(base);
|
|
2072
|
+
return selfReadableInRecall ? overlaid : this.config.defaultNamespace;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2014
2075
|
/**
|
|
2015
2076
|
* Attach a coding-agent context to a session (issue #569). Called by the
|
|
2016
2077
|
* Claude Code / Codex / Cursor connectors at session start after
|
|
@@ -6890,6 +6951,10 @@ export class Orchestrator {
|
|
|
6890
6951
|
.digest("hex")
|
|
6891
6952
|
.slice(0, 16);
|
|
6892
6953
|
const sectionBuckets = new Map<string, string[]>();
|
|
6954
|
+
// The effective LCM read session_id SET is computed below from
|
|
6955
|
+
// `recallNamespaces` (the SAME read-authorized namespace set normal QMD/file
|
|
6956
|
+
// recall searches, incl. coding `readFallbacks`). See the
|
|
6957
|
+
// `lcmReadSessionIds` derivation after `recallNamespaces` is built.
|
|
6893
6958
|
const queryPolicy = buildRecallQueryPolicy(prompt, sessionKey, {
|
|
6894
6959
|
cronRecallPolicyEnabled: this.config.cronRecallPolicyEnabled,
|
|
6895
6960
|
cronRecallNormalizedQueryMaxChars:
|
|
@@ -7119,6 +7184,94 @@ export class Orchestrator {
|
|
|
7119
7184
|
} else {
|
|
7120
7185
|
recallNamespaces = readableRecallNamespaces;
|
|
7121
7186
|
}
|
|
7187
|
+
// Effective LCM read NAMESPACE SET (#1505 thread "Include coding fallback
|
|
7188
|
+
// namespaces in LCM reads"). `observe` archives LCM / structured history
|
|
7189
|
+
// under `${effectiveNamespace}:${sessionKey}` for whichever namespace was
|
|
7190
|
+
// effective at write time. A branch-scoped session whose evidence was
|
|
7191
|
+
// archived at project / root scope must still surface it, exactly as normal
|
|
7192
|
+
// QMD/file recall does — QMD/file recall searches the primary overlay key AND
|
|
7193
|
+
// `codingOverlay.readFallbacks` (project / root), NOT just the primary
|
|
7194
|
+
// overlay key. The prior single `lcmReadSessionId` only targeted the primary
|
|
7195
|
+
// overlay, so branch-scoped sessions missed fallback LCM evidence.
|
|
7196
|
+
//
|
|
7197
|
+
// READ-AUTHORIZATION (preserved from the prior round's
|
|
7198
|
+
// `lcmReadNamespaceForSession` gate; rule 39 / 42 / 48): the coding-overlay
|
|
7199
|
+
// namespace AND its fallbacks are `<principal>-project-*` sub-namespaces of
|
|
7200
|
+
// the principal SELF base, authorized transitively by that base. They are
|
|
7201
|
+
// included ONLY when the principal self base is in the readable recall set
|
|
7202
|
+
// (`readableRecallNamespaces` — gated by `defaultRecallNamespaces.includes
|
|
7203
|
+
// ("self")` AND `canReadNamespace`). When the self base is NOT readable (e.g.
|
|
7204
|
+
// a write-only / self-omitted principal), the overlay rows are unauthorized
|
|
7205
|
+
// for this reader, so the LCM read collapses to the default store — exactly
|
|
7206
|
+
// what an unqualified, unauthorized recall resolves to — and NEVER searches a
|
|
7207
|
+
// `<principal>-project-*` key (no cross-tenant read leak). This mirrors what
|
|
7208
|
+
// the rest of recall surfaces for such a principal (its readable
|
|
7209
|
+
// shared/policy namespaces have no per-session LCM key, so they contribute
|
|
7210
|
+
// nothing here). `recallNamespaces` itself appends fallbacks unconditionally
|
|
7211
|
+
// for QMD/file recall; the LCM read keys apply the stricter, self-base gate
|
|
7212
|
+
// so the prior round's authorization invariant is preserved.
|
|
7213
|
+
const codingOverlaySelfReadable =
|
|
7214
|
+
codingOverlay !== null &&
|
|
7215
|
+
readableRecallNamespaces.includes(principalSelfNamespace);
|
|
7216
|
+
let lcmReadNamespaces: string[];
|
|
7217
|
+
if (namespaceOverride) {
|
|
7218
|
+
// Explicit namespace already read-authorized above (canReadNamespace gate).
|
|
7219
|
+
lcmReadNamespaces = [namespaceOverride];
|
|
7220
|
+
} else if (codingOverlay && codingSelfNamespace && codingOverlaySelfReadable) {
|
|
7221
|
+
// Self base readable → overlay rows authorized. Read the primary overlay
|
|
7222
|
+
// key first, then each coding read fallback (project → root), combined with
|
|
7223
|
+
// the principal base for isolation — the SAME ordered set QMD/file recall
|
|
7224
|
+
// searches for this authorized coding session.
|
|
7225
|
+
const fallbackNs = codingOverlay.readFallbacks.map((fallback) =>
|
|
7226
|
+
combineNamespaces(principalSelfNamespace, fallback),
|
|
7227
|
+
);
|
|
7228
|
+
lcmReadNamespaces = [codingSelfNamespace, ...fallbackNs];
|
|
7229
|
+
} else {
|
|
7230
|
+
// No overlay, OR overlay present but self base unreadable → collapse to the
|
|
7231
|
+
// default store (raw sessionKey), exactly as the prior round did. No
|
|
7232
|
+
// `<principal>-project-*` overlay key is searched.
|
|
7233
|
+
lcmReadNamespaces = [this.config.defaultNamespace];
|
|
7234
|
+
}
|
|
7235
|
+
// Map the ordered, read-authorized namespace set → ordered, deduped LCM read
|
|
7236
|
+
// session_id set. Single-user / no-overlay recall passes a single-namespace
|
|
7237
|
+
// set that collapses to the raw `sessionKey`, so this is `[sessionKey]` —
|
|
7238
|
+
// byte-for-byte the pre-#1495 single-key behavior.
|
|
7239
|
+
const lcmReadSessionIds = lcmReadSessionIdsForNamespaces(
|
|
7240
|
+
lcmReadNamespaces,
|
|
7241
|
+
sessionKey,
|
|
7242
|
+
this.config.defaultNamespace,
|
|
7243
|
+
);
|
|
7244
|
+
// Query an LCM-backed read across the ordered read key set and return the
|
|
7245
|
+
// FIRST non-empty result (#1505 fallback-namespace unification). The primary
|
|
7246
|
+
// overlay key is tried first; if a branch-scoped session has no rows under its
|
|
7247
|
+
// branch key, the project / root fallback keys are tried in order.
|
|
7248
|
+
//
|
|
7249
|
+
// #1505 codex P2 ("Merge LCM fallback reads instead of short-circuiting"): the
|
|
7250
|
+
// query-SCORED sections (explicit-cue, targeted-facts, focused-list,
|
|
7251
|
+
// response-guidance, event-order, structured message-parts) no longer use this
|
|
7252
|
+
// helper — they MERGE candidates across EVERY authorized key under their single
|
|
7253
|
+
// budget (a weak primary-key hit must not mask stronger fallback evidence; the
|
|
7254
|
+
// section builders take `sessionIds`, structured-parts merges inline below).
|
|
7255
|
+
// This first-non-empty helper now serves ONLY the compressed-history section,
|
|
7256
|
+
// which is a per-session HOLISTIC DAG narrative with no per-item id to merge or
|
|
7257
|
+
// dedupe on — see its call site for the rationale.
|
|
7258
|
+
//
|
|
7259
|
+
// When the set is a single key (single-user / no-overlay / explicit-namespace),
|
|
7260
|
+
// this is exactly one call — unchanged. `lcmSessionId` is `string | undefined`:
|
|
7261
|
+
// a SESSIONLESS recall yields the single `undefined` key so the read runs ONE
|
|
7262
|
+
// archive-wide read with no `session_id` filter (pre-#1505 behavior). NEVER the
|
|
7263
|
+
// literal "default" session id (codex P2).
|
|
7264
|
+
const firstNonEmptyLcmRead = async <T>(
|
|
7265
|
+
read: (lcmSessionId: string | undefined) => Promise<T>,
|
|
7266
|
+
isEmpty: (value: T) => boolean,
|
|
7267
|
+
empty: T,
|
|
7268
|
+
): Promise<T> => {
|
|
7269
|
+
for (const lcmSessionId of lcmReadSessionIds) {
|
|
7270
|
+
const value = await read(lcmSessionId);
|
|
7271
|
+
if (!isEmpty(value)) return value;
|
|
7272
|
+
}
|
|
7273
|
+
return empty;
|
|
7274
|
+
};
|
|
7122
7275
|
const qmdAvailable = this.qmd.isAvailable();
|
|
7123
7276
|
let graphDecisionStatus: IntentDebugSnapshot["graphDecision"]["status"] =
|
|
7124
7277
|
recallDecision.plannedMode === "graph_mode" ? "skipped" : "not_requested";
|
|
@@ -9370,7 +9523,13 @@ export class Orchestrator {
|
|
|
9370
9523
|
try {
|
|
9371
9524
|
const explicitCueSection = await buildExplicitCueRecallSection({
|
|
9372
9525
|
engine: this.lcmEngine,
|
|
9373
|
-
|
|
9526
|
+
// #1495 thread 3 + #1505 fallback unification: read across the ordered
|
|
9527
|
+
// LCM read key set (primary overlay → coding fallbacks) so a
|
|
9528
|
+
// branch-scoped session finds its own explicit-cue evidence even when
|
|
9529
|
+
// archived at project/root scope (rule 39). #1505 codex P2: the builder
|
|
9530
|
+
// MERGES candidates across every key under its single budget instead of
|
|
9531
|
+
// short-circuiting on the first non-empty key.
|
|
9532
|
+
sessionIds: lcmReadSessionIds,
|
|
9374
9533
|
query: retrievalQuery,
|
|
9375
9534
|
maxChars: explicitCueMaxChars,
|
|
9376
9535
|
maxReferences:
|
|
@@ -9408,7 +9567,11 @@ export class Orchestrator {
|
|
|
9408
9567
|
try {
|
|
9409
9568
|
const targetedFactSection = await buildTargetedFactRecallSection({
|
|
9410
9569
|
engine: this.lcmEngine,
|
|
9411
|
-
|
|
9570
|
+
// #1495 + #1505 fallback unification: read across the ordered LCM read
|
|
9571
|
+
// key set so a branch-scoped session finds its own targeted-fact
|
|
9572
|
+
// evidence even when archived at project/root scope. #1505 codex P2: the
|
|
9573
|
+
// builder MERGES candidates across every key under its single budget.
|
|
9574
|
+
sessionIds: lcmReadSessionIds,
|
|
9412
9575
|
query: retrievalQuery,
|
|
9413
9576
|
maxChars: targetedFactMaxChars,
|
|
9414
9577
|
maxSearchResults:
|
|
@@ -9453,7 +9616,12 @@ export class Orchestrator {
|
|
|
9453
9616
|
try {
|
|
9454
9617
|
const focusedListSection = await buildFocusedListRecallSection({
|
|
9455
9618
|
engine: this.lcmEngine,
|
|
9456
|
-
|
|
9619
|
+
// #1495 thread 3 + #1505 fallback unification: read across the ordered
|
|
9620
|
+
// LCM read key set so a branch-scoped session reads its own
|
|
9621
|
+
// focused-list/count evidence even at project/root scope (rule 39).
|
|
9622
|
+
// #1505 codex P2: the builder MERGES candidates across every key under
|
|
9623
|
+
// its single budget.
|
|
9624
|
+
sessionIds: lcmReadSessionIds,
|
|
9457
9625
|
query: retrievalQuery,
|
|
9458
9626
|
maxChars: focusedListMaxChars,
|
|
9459
9627
|
maxSearchResults:
|
|
@@ -9502,7 +9670,12 @@ export class Orchestrator {
|
|
|
9502
9670
|
try {
|
|
9503
9671
|
const responseGuidanceSection = await buildResponseGuidanceRecallSection({
|
|
9504
9672
|
engine: this.lcmEngine,
|
|
9505
|
-
|
|
9673
|
+
// #1495 thread 3 + #1505 fallback unification: read across the ordered
|
|
9674
|
+
// LCM read key set so a branch-scoped session reads its own
|
|
9675
|
+
// response-guidance evidence even at project/root scope (rule 39).
|
|
9676
|
+
// #1505 codex P2: the builder MERGES candidates across every key under
|
|
9677
|
+
// its single budget.
|
|
9678
|
+
sessionIds: lcmReadSessionIds,
|
|
9506
9679
|
query: retrievalQuery,
|
|
9507
9680
|
maxChars: responseGuidanceMaxChars,
|
|
9508
9681
|
maxSearchResults:
|
|
@@ -9544,21 +9717,38 @@ export class Orchestrator {
|
|
|
9544
9717
|
shouldRecallEventOrderEvidence(retrievalQuery)
|
|
9545
9718
|
) {
|
|
9546
9719
|
try {
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9720
|
+
// #1495 thread 3 + #1505 fallback unification: read across the ordered LCM
|
|
9721
|
+
// read key set so a branch-scoped session reads its own chronological
|
|
9722
|
+
// event-order evidence even at project/root scope. UNLIKE the relevance-
|
|
9723
|
+
// ranked sections, event-order must NOT merge across keys: `turn_index` is
|
|
9724
|
+
// LOCAL to each LCM `session_id` (`observe` numbers turns per session via
|
|
9725
|
+
// `getMaxTurnIndex`), so interleaving two keys and sorting by `turn_index`
|
|
9726
|
+
// would place an older project-scope turn after a newer branch-scope turn
|
|
9727
|
+
// and misstate the chronology (#1505 codex P2). Like compressed-history,
|
|
9728
|
+
// event-order is an inherently per-session ORDERED artifact, so it takes
|
|
9729
|
+
// the highest-priority authorized key (primary overlay → project/root)
|
|
9730
|
+
// that actually has chronological evidence — each key's timeline is
|
|
9731
|
+
// internally consistent.
|
|
9732
|
+
const eventOrderSection = await firstNonEmptyLcmRead(
|
|
9733
|
+
(lcmSessionId) =>
|
|
9734
|
+
buildEventOrderRecallSection({
|
|
9735
|
+
engine: this.lcmEngine,
|
|
9736
|
+
sessionId: lcmSessionId,
|
|
9737
|
+
query: retrievalQuery,
|
|
9738
|
+
maxChars: eventOrderMaxChars,
|
|
9739
|
+
maxItems:
|
|
9740
|
+
this.getRecallSectionNumber("event-order", "maxResults") ??
|
|
9741
|
+
this.config.eventOrderRecallMaxResults,
|
|
9742
|
+
maxScanWindowTurns:
|
|
9743
|
+
this.getRecallSectionNumber("event-order", "maxTurns") ??
|
|
9744
|
+
this.config.eventOrderRecallScanWindowTurns,
|
|
9745
|
+
maxScanWindowTokens:
|
|
9746
|
+
this.getRecallSectionNumber("event-order", "maxTokens") ??
|
|
9747
|
+
this.config.eventOrderRecallScanWindowTokens,
|
|
9748
|
+
}),
|
|
9749
|
+
(s) => !s,
|
|
9750
|
+
"",
|
|
9751
|
+
);
|
|
9562
9752
|
if (eventOrderSection) {
|
|
9563
9753
|
this.appendRecallSection(
|
|
9564
9754
|
sectionBuckets,
|
|
@@ -9732,10 +9922,59 @@ export class Orchestrator {
|
|
|
9732
9922
|
(recallMode as RecallPlanMode) !== "no_recall"
|
|
9733
9923
|
) {
|
|
9734
9924
|
try {
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9925
|
+
// #1495 + #1505 fallback unification: read across the ordered LCM read
|
|
9926
|
+
// key set so a branch-scoped session reads its own structured
|
|
9927
|
+
// message-part evidence even when archived at project/root scope.
|
|
9928
|
+
// #1505 codex P2: structured matches are query-SCORED evidence, so MERGE
|
|
9929
|
+
// across EVERY key (primary overlay → project/root fallbacks) instead of
|
|
9930
|
+
// short-circuiting on the first non-empty key — a weak branch-key hit must
|
|
9931
|
+
// not mask stronger project-fallback parts. Keys are queried in priority
|
|
9932
|
+
// order; dedupe by session_id+turn_index+part_id keeps the primary key's
|
|
9933
|
+
// row on collision. `formatStructuredRecall` applies the single budget
|
|
9934
|
+
// below. A sessionless key (`undefined`) normalizes to "" → no matches
|
|
9935
|
+
// (structured parts are inherently per-session; pre-#1505 behavior, codex
|
|
9936
|
+
// P2).
|
|
9937
|
+
// FAULT ISOLATION (allSettled, not all): the pre-#1505 first-non-empty read
|
|
9938
|
+
// short-circuited, so a fallback key was often never queried and its latent
|
|
9939
|
+
// search failure never surfaced. Querying every key eagerly must NOT let one
|
|
9940
|
+
// key's failure (e.g. a SqliteError from a corrupt/locked fallback index)
|
|
9941
|
+
// reject the batch and discard the OTHER keys' parts — or, since this and
|
|
9942
|
+
// the compressed-history read below share one try block, silently drop the
|
|
9943
|
+
// compressed-history section a healthy primary key would still produce. So
|
|
9944
|
+
// read each key independently and keep the fulfilled batches.
|
|
9945
|
+
const structuredSettled = await Promise.allSettled(
|
|
9946
|
+
lcmReadSessionIds.map((lcmSessionId) =>
|
|
9947
|
+
this.lcmEngine!.searchStructuredParts(lcmSessionId ?? "", retrievalQuery),
|
|
9948
|
+
),
|
|
9738
9949
|
);
|
|
9950
|
+
for (const settled of structuredSettled) {
|
|
9951
|
+
if (settled.status === "rejected") {
|
|
9952
|
+
log.debug(
|
|
9953
|
+
`LCM structured-parts read failed for one key: ${settled.reason}`,
|
|
9954
|
+
);
|
|
9955
|
+
}
|
|
9956
|
+
}
|
|
9957
|
+
const seenStructuredParts = new Set<string>();
|
|
9958
|
+
const structuredMatches = structuredSettled
|
|
9959
|
+
.flatMap((settled) => (settled.status === "fulfilled" ? settled.value : []))
|
|
9960
|
+
.filter((match) => {
|
|
9961
|
+
const key = `${match.session_id} ${match.turn_index} ${match.part_id}`;
|
|
9962
|
+
if (seenStructuredParts.has(key)) return false;
|
|
9963
|
+
seenStructuredParts.add(key);
|
|
9964
|
+
return true;
|
|
9965
|
+
})
|
|
9966
|
+
// Restore the archive's per-key ordering (score DESC, then turn DESC)
|
|
9967
|
+
// across the MERGED set so the strongest parts win the shared budget in
|
|
9968
|
+
// `formatStructuredRecall` — otherwise weak primary-key parts could crowd
|
|
9969
|
+
// out stronger fallback parts. Stable sort: a single key is already in
|
|
9970
|
+
// this order, so it stays byte-for-byte the pre-#1505 behavior.
|
|
9971
|
+
// `?? 0` is defensive: `LcmStructuredRecallMatch.score` is always a
|
|
9972
|
+
// number here, but a bare `b.score - a.score` would yield NaN (falsy)
|
|
9973
|
+
// for any future unscored match and silently fall through to turn order.
|
|
9974
|
+
.sort(
|
|
9975
|
+
(a, b) =>
|
|
9976
|
+
(b.score ?? 0) - (a.score ?? 0) || b.turn_index - a.turn_index,
|
|
9977
|
+
);
|
|
9739
9978
|
const structuredSection = this.lcmEngine.formatStructuredRecall(
|
|
9740
9979
|
structuredMatches,
|
|
9741
9980
|
Math.ceil(this.config.recallBudgetChars * 0.08),
|
|
@@ -9758,9 +9997,25 @@ export class Orchestrator {
|
|
|
9758
9997
|
}
|
|
9759
9998
|
}
|
|
9760
9999
|
}
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
10000
|
+
// #1495 + #1505 fallback unification: read across the ordered LCM read key
|
|
10001
|
+
// set so a branch-scoped session reads its own compressed-history evidence
|
|
10002
|
+
// even at project/root scope. UNLIKE the query-scored sections above, the
|
|
10003
|
+
// compressed history is a per-session HOLISTIC DAG narrative, not a set of
|
|
10004
|
+
// independently-rankable evidence items — concatenating two sessions'
|
|
10005
|
+
// summaries would double-count the conversation and blow the budget, and
|
|
10006
|
+
// there is no per-item id to dedupe on. So this section deliberately keeps
|
|
10007
|
+
// first-non-empty semantics (#1505 codex P2 scope: "merge the query-matched
|
|
10008
|
+
// sections"): the highest-priority authorized key (primary overlay →
|
|
10009
|
+
// project/root) that actually has a compressed history wins. A sessionless
|
|
10010
|
+
// key (`undefined`) normalizes to empty → no section (pre-#1505 behavior).
|
|
10011
|
+
const lcmSection = await firstNonEmptyLcmRead(
|
|
10012
|
+
(lcmSessionId) =>
|
|
10013
|
+
this.lcmEngine!.assembleRecall(
|
|
10014
|
+
lcmSessionId ?? "",
|
|
10015
|
+
this.config.recallBudgetChars,
|
|
10016
|
+
),
|
|
10017
|
+
(s) => !s,
|
|
10018
|
+
"",
|
|
9764
10019
|
);
|
|
9765
10020
|
if (lcmSection) {
|
|
9766
10021
|
this.appendRecallSection(
|
|
@@ -11235,6 +11490,28 @@ export class Orchestrator {
|
|
|
11235
11490
|
deadlineMs?: number;
|
|
11236
11491
|
archiveLcm?: boolean;
|
|
11237
11492
|
abortSignal?: AbortSignal;
|
|
11493
|
+
/**
|
|
11494
|
+
* Pin extraction writes to this namespace instead of deriving one from
|
|
11495
|
+
* `defaultNamespaceForPrincipal(resolvePrincipal(sessionKey))` + the
|
|
11496
|
+
* coding overlay (#1495). The access `observe` surface resolves a single
|
|
11497
|
+
* effective scope plan and passes its `writeNamespace` here so the
|
|
11498
|
+
* extracted memories land in the SAME namespace as LCM archival,
|
|
11499
|
+
* objective-state snapshots, and project-scoped recall — without relying
|
|
11500
|
+
* on re-deriving the namespace from a namespace-prefixed session key.
|
|
11501
|
+
* Same hook bulk-import uses (#460).
|
|
11502
|
+
*/
|
|
11503
|
+
writeNamespaceOverride?: string;
|
|
11504
|
+
/**
|
|
11505
|
+
* Pin the provenance PRINCIPAL instead of deriving it from
|
|
11506
|
+
* `resolvePrincipal(turn.sessionKey)` (#1495 thread 1). The access
|
|
11507
|
+
* `observe` surface authenticates the caller at the transport layer and
|
|
11508
|
+
* passes its resolved principal here so extracted-memory provenance uses
|
|
11509
|
+
* the SAME identity the surface authorized — independent of storage
|
|
11510
|
+
* routing (`writeNamespaceOverride`) and of whatever `resolvePrincipal`
|
|
11511
|
+
* would parse from the raw session key. Mirrors the recall path's
|
|
11512
|
+
* `principalOverride` (issue #570 PR 4).
|
|
11513
|
+
*/
|
|
11514
|
+
principalOverride?: string;
|
|
11238
11515
|
} = {},
|
|
11239
11516
|
): Promise<void> {
|
|
11240
11517
|
if (!Array.isArray(turns) || turns.length === 0) return;
|
|
@@ -11321,6 +11598,8 @@ export class Orchestrator {
|
|
|
11321
11598
|
bufferKey,
|
|
11322
11599
|
extractionDeadlineMs: options.deadlineMs,
|
|
11323
11600
|
abortSignal: options.abortSignal,
|
|
11601
|
+
writeNamespaceOverride: options.writeNamespaceOverride,
|
|
11602
|
+
principalOverride: options.principalOverride,
|
|
11324
11603
|
onTaskSettled: (err) => (err ? reject(err) : resolve()),
|
|
11325
11604
|
}).catch(reject);
|
|
11326
11605
|
}),
|
|
@@ -11717,6 +11996,12 @@ export class Orchestrator {
|
|
|
11717
11996
|
* regardless of user-configured principal routing rules.
|
|
11718
11997
|
*/
|
|
11719
11998
|
writeNamespaceOverride?: string;
|
|
11999
|
+
/**
|
|
12000
|
+
* Pin the provenance principal (#1495 thread 1). Forwarded to
|
|
12001
|
+
* `runExtraction` so access `observe` can record provenance under the
|
|
12002
|
+
* authenticated principal instead of `resolvePrincipal(sessionKey)`.
|
|
12003
|
+
*/
|
|
12004
|
+
principalOverride?: string;
|
|
11720
12005
|
} = {},
|
|
11721
12006
|
): Promise<void> {
|
|
11722
12007
|
const bufferKey = options.bufferKey ?? turnsToExtract[0]?.sessionKey ?? "default";
|
|
@@ -11790,6 +12075,7 @@ export class Orchestrator {
|
|
|
11790
12075
|
abortSignal: options.abortSignal,
|
|
11791
12076
|
failOnExtractionFailure: options.failOnExtractionFailure === true,
|
|
11792
12077
|
writeNamespaceOverride: options.writeNamespaceOverride,
|
|
12078
|
+
principalOverride: options.principalOverride,
|
|
11793
12079
|
});
|
|
11794
12080
|
settleTask(undefined, result);
|
|
11795
12081
|
} catch (err) {
|
|
@@ -11946,6 +12232,14 @@ export class Orchestrator {
|
|
|
11946
12232
|
* for provenance; only the storage target is overridden.
|
|
11947
12233
|
*/
|
|
11948
12234
|
writeNamespaceOverride?: string;
|
|
12235
|
+
/**
|
|
12236
|
+
* Pin the provenance principal instead of deriving it from
|
|
12237
|
+
* `resolvePrincipal(sessionKey)` (#1495 thread 1). When set, this is the
|
|
12238
|
+
* identity an access surface already authenticated; used so observed-turn
|
|
12239
|
+
* provenance is correct even though `turn.sessionKey` is the ORIGINAL
|
|
12240
|
+
* (un-prefixed) key and storage is pinned via `writeNamespaceOverride`.
|
|
12241
|
+
*/
|
|
12242
|
+
principalOverride?: string;
|
|
11949
12243
|
} = {},
|
|
11950
12244
|
): Promise<ExtractionRunResult> {
|
|
11951
12245
|
log.debug(`running extraction on ${turns.length} turns`);
|
|
@@ -12039,7 +12333,18 @@ export class Orchestrator {
|
|
|
12039
12333
|
};
|
|
12040
12334
|
}
|
|
12041
12335
|
|
|
12042
|
-
|
|
12336
|
+
// Provenance principal honours the access-surface override (#1495 thread 1,
|
|
12337
|
+
// mirroring the recall path's `principalOverride`, issue #570 PR 4). Access
|
|
12338
|
+
// surfaces that authenticated the caller at the transport layer pass their
|
|
12339
|
+
// resolved principal so provenance uses the SAME identity the surface
|
|
12340
|
+
// authorized, instead of `resolvePrincipal(sessionKey)` — which on a
|
|
12341
|
+
// namespace-prefixed key would collapse to `default`. The ORIGINAL,
|
|
12342
|
+
// un-prefixed session key still drives threading.
|
|
12343
|
+
const principal =
|
|
12344
|
+
typeof options.principalOverride === "string" &&
|
|
12345
|
+
options.principalOverride.length > 0
|
|
12346
|
+
? options.principalOverride
|
|
12347
|
+
: resolvePrincipal(sessionKey, this.config);
|
|
12043
12348
|
// Write path — overlay the coding-agent namespace (issue #569) when the
|
|
12044
12349
|
// session has a codingContext and `codingMode.projectScope` is true.
|
|
12045
12350
|
// Explicit `writeNamespaceOverride` from callers still wins, matching
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { buildEvidencePack, type EvidencePackItem } from "./evidence-pack.js";
|
|
2
2
|
import type { ExplicitCueRecallEngine } from "./explicit-cue-recall.js";
|
|
3
|
+
import {
|
|
4
|
+
gatherAcrossReadSessions,
|
|
5
|
+
resolveLcmReadSessionIds,
|
|
6
|
+
} from "./lcm-fallback-read.js";
|
|
3
7
|
|
|
4
8
|
export interface ResponseGuidanceRecallOptions {
|
|
5
9
|
engine: ExplicitCueRecallEngine | null | undefined;
|
|
6
10
|
sessionId?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Ordered, read-authorized LCM read key set (primary overlay → project/root
|
|
13
|
+
* fallbacks). When present, evidence is gathered across EVERY key and merged
|
|
14
|
+
* under this section's budget (#1505 codex P2). Falls back to `sessionId`.
|
|
15
|
+
*/
|
|
16
|
+
sessionIds?: readonly (string | undefined)[];
|
|
7
17
|
query: string;
|
|
8
18
|
maxChars: number;
|
|
9
19
|
maxItemChars?: number;
|
|
@@ -157,7 +167,17 @@ export async function buildResponseGuidanceRecallSection(
|
|
|
157
167
|
) {
|
|
158
168
|
return "";
|
|
159
169
|
}
|
|
160
|
-
|
|
170
|
+
// #1505 codex P2: gather candidates across the ordered LCM read key set
|
|
171
|
+
// (primary overlay → project/root fallbacks) and UNION them into the existing
|
|
172
|
+
// rank/dedupe/budget pass, so stronger project-fallback guidance is not masked
|
|
173
|
+
// by a weak primary-key hit. `gatherAcrossReadSessions` isolates a per-key read
|
|
174
|
+
// failure so a corrupt/locked fallback index can't discard the primary key's
|
|
175
|
+
// guidance; the single-key path collects exactly once and propagates a failure
|
|
176
|
+
// as before — byte-for-byte the pre-#1505 behavior.
|
|
177
|
+
const items: EvidencePackItem[] = [];
|
|
178
|
+
await gatherAcrossReadSessions(resolveLcmReadSessionIds(options), async (sessionId) => {
|
|
179
|
+
items.push(...(await collectGuidanceItems({ ...options, sessionId }, intents)));
|
|
180
|
+
});
|
|
161
181
|
const ranked = rankAndDedupeGuidanceItems(items, options.query, intents)
|
|
162
182
|
.slice(0, maxResults);
|
|
163
183
|
if (ranked.length === 0) {
|
|
@@ -4,10 +4,20 @@ import {
|
|
|
4
4
|
type EvidencePackItem,
|
|
5
5
|
} from "./evidence-pack.js";
|
|
6
6
|
import type { ExplicitCueRecallEngine } from "./explicit-cue-recall.js";
|
|
7
|
+
import {
|
|
8
|
+
gatherAcrossReadSessions,
|
|
9
|
+
resolveLcmReadSessionIds,
|
|
10
|
+
} from "./lcm-fallback-read.js";
|
|
7
11
|
|
|
8
12
|
export interface TargetedFactRecallOptions {
|
|
9
13
|
engine: ExplicitCueRecallEngine | null | undefined;
|
|
10
14
|
sessionId?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Ordered, read-authorized LCM read key set (primary overlay → project/root
|
|
17
|
+
* fallbacks). When present, evidence is gathered across EVERY key and merged
|
|
18
|
+
* under this section's budget (#1505 codex P2). Falls back to `sessionId`.
|
|
19
|
+
*/
|
|
20
|
+
sessionIds?: readonly (string | undefined)[];
|
|
11
21
|
query: string;
|
|
12
22
|
maxChars: number;
|
|
13
23
|
maxItemChars?: number;
|
|
@@ -43,10 +53,21 @@ export async function buildTargetedFactRecallSection(
|
|
|
43
53
|
return "";
|
|
44
54
|
}
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
// #1505 codex P2: gather candidates across the ordered LCM read key set
|
|
57
|
+
// (primary overlay → project/root fallbacks) and UNION them into the existing
|
|
58
|
+
// rank/dedupe/budget pass, so stronger project-fallback evidence is not masked
|
|
59
|
+
// by a weak primary-key hit. `gatherAcrossReadSessions` isolates a per-key read
|
|
60
|
+
// failure so a corrupt/locked fallback index can't discard the primary key's
|
|
61
|
+
// evidence; the single-key path collects exactly one search+scan pair and
|
|
62
|
+
// propagates a failure as before — byte-for-byte the pre-#1505 behavior.
|
|
63
|
+
const collected: EvidencePackItem[] = [];
|
|
64
|
+
await gatherAcrossReadSessions(resolveLcmReadSessionIds(options), async (sessionId) => {
|
|
65
|
+
const scoped = { ...options, sessionId };
|
|
66
|
+
collected.push(...(await collectTargetedFactSearchItems(scoped)));
|
|
67
|
+
collected.push(...(await collectTargetedFactScanItems(scoped)));
|
|
68
|
+
});
|
|
48
69
|
const ranked = rankAndDedupeTargetedFactItems(
|
|
49
|
-
|
|
70
|
+
collected,
|
|
50
71
|
options.query,
|
|
51
72
|
).slice(0, maxResults);
|
|
52
73
|
|