@remnic/core 9.33.0 → 9.35.0
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-admin-ops-surface.js +16 -16
- package/dist/access-authorization-probe.js +17 -17
- package/dist/access-boundary.js +16 -16
- package/dist/access-cli.js +39 -38
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +16 -16
- package/dist/access-http.js +25 -25
- package/dist/access-identity-continuity-surface.js +16 -16
- package/dist/access-lcm-surface.js +16 -16
- package/dist/access-mcp.js +20 -20
- package/dist/access-namespace-preflight.js +16 -16
- package/dist/access-observe-write-surface.js +16 -16
- package/dist/access-operations-batch.js +17 -17
- package/dist/access-operations.js +19 -19
- package/dist/access-recall-concurrency.js +16 -16
- package/dist/access-recall-response.js +16 -16
- package/dist/access-recall-surface.js +16 -16
- package/dist/access-schema.js +3 -3
- package/dist/access-service.js +16 -16
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/{chunk-BXLOS5AJ.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-SPPL77NI.js → chunk-2NUC473W.js} +49 -29
- package/dist/chunk-2NUC473W.js.map +1 -0
- package/dist/{chunk-N2B56SIS.js → chunk-2SHIWGY7.js} +2 -2
- package/dist/{chunk-I5DQ6KZK.js → chunk-3F2LKUMU.js} +2 -2
- package/dist/{chunk-ATUURSST.js → chunk-5FHE4JI7.js} +3 -3
- package/dist/{chunk-YFQMW2TH.js → chunk-5HTAJCTO.js} +11 -11
- package/dist/{chunk-6NPZ3FP2.js → chunk-65ZPVYTA.js} +2 -2
- package/dist/{chunk-MPH263X2.js → chunk-AHZYNPLX.js} +2 -2
- package/dist/{chunk-77CNV7DY.js → chunk-BLAKS3CP.js} +4 -4
- package/dist/{chunk-J6BYDVPL.js → chunk-EH5U2YCX.js} +2 -2
- package/dist/{chunk-PTN6UAUA.js → chunk-ES36HTSC.js} +4 -4
- package/dist/{chunk-SIKCTPRK.js → chunk-GHR7WHZX.js} +3 -3
- package/dist/{chunk-TITQYVKD.js → chunk-HEVWWPTL.js} +16 -16
- package/dist/{chunk-V2MVDDJF.js → chunk-IOQFPVGY.js} +2 -2
- package/dist/{chunk-VDX2J7OX.js → chunk-KQAFEZQX.js} +2 -2
- package/dist/chunk-KWMXR7XS.js +138 -0
- package/dist/chunk-KWMXR7XS.js.map +1 -0
- package/dist/{chunk-7RZJWZ46.js → chunk-LEPOP46W.js} +5 -5
- package/dist/{chunk-PYHOAXTI.js → chunk-LRGSLV7M.js} +4 -4
- package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
- package/dist/{chunk-RYHWZ52A.js → chunk-S2BBISHU.js} +4 -4
- package/dist/{chunk-YJ7PWEWI.js → chunk-SA3WJJF2.js} +2 -2
- package/dist/{chunk-C4PEMCP4.js → chunk-SLPLPGJQ.js} +7 -7
- package/dist/{chunk-6JE3HGS4.js → chunk-T4HH5Q3U.js} +2 -2
- package/dist/{chunk-EHNENZHZ.js → chunk-TJLLYV2X.js} +3 -3
- package/dist/{chunk-SFW2NABJ.js → chunk-UZQCNOPS.js} +7 -7
- package/dist/{chunk-DQEMWVMT.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-6NNNURLK.js → chunk-Z54EM5YZ.js} +4 -4
- package/dist/cli.js +33 -33
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +11 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +54 -53
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +2 -2
- package/dist/namespaces/migrate.js +10 -10
- package/dist/namespaces/search.js +9 -9
- package/dist/operator-doctor-corpus.js +12 -12
- package/dist/operator-toolkit.js +13 -13
- package/dist/orchestrator.js +39 -38
- package/dist/reconcile/plan.d.ts +175 -0
- package/dist/reconcile/plan.js +420 -0
- package/dist/reconcile/plan.js.map +1 -0
- package/dist/search/factory.js +8 -8
- package/dist/search/index.js +12 -12
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/tool-scoped-memory.d.ts +47 -0
- package/dist/tool-scoped-memory.js +12 -0
- package/dist/tool-scoped-memory.js.map +1 -0
- 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/package.json +12 -2
- package/src/extraction-redaction-rules.test.ts +53 -0
- package/src/index.ts +1 -1
- package/src/orchestration/extraction-persist.ts +31 -32
- package/src/reconcile/plan.test.ts +1013 -0
- package/src/reconcile/plan.ts +813 -0
- package/src/tool-scoped-memory.test.ts +199 -0
- package/src/tool-scoped-memory.ts +190 -0
- package/dist/chunk-SPPL77NI.js.map +0 -1
- /package/dist/{capsule-crypto-YO5QJ6L3.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
- /package/dist/{chunk-BXLOS5AJ.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-N2B56SIS.js.map → chunk-2SHIWGY7.js.map} +0 -0
- /package/dist/{chunk-I5DQ6KZK.js.map → chunk-3F2LKUMU.js.map} +0 -0
- /package/dist/{chunk-ATUURSST.js.map → chunk-5FHE4JI7.js.map} +0 -0
- /package/dist/{chunk-YFQMW2TH.js.map → chunk-5HTAJCTO.js.map} +0 -0
- /package/dist/{chunk-6NPZ3FP2.js.map → chunk-65ZPVYTA.js.map} +0 -0
- /package/dist/{chunk-MPH263X2.js.map → chunk-AHZYNPLX.js.map} +0 -0
- /package/dist/{chunk-77CNV7DY.js.map → chunk-BLAKS3CP.js.map} +0 -0
- /package/dist/{chunk-J6BYDVPL.js.map → chunk-EH5U2YCX.js.map} +0 -0
- /package/dist/{chunk-PTN6UAUA.js.map → chunk-ES36HTSC.js.map} +0 -0
- /package/dist/{chunk-SIKCTPRK.js.map → chunk-GHR7WHZX.js.map} +0 -0
- /package/dist/{chunk-TITQYVKD.js.map → chunk-HEVWWPTL.js.map} +0 -0
- /package/dist/{chunk-V2MVDDJF.js.map → chunk-IOQFPVGY.js.map} +0 -0
- /package/dist/{chunk-VDX2J7OX.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-7RZJWZ46.js.map → chunk-LEPOP46W.js.map} +0 -0
- /package/dist/{chunk-PYHOAXTI.js.map → chunk-LRGSLV7M.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-RYHWZ52A.js.map → chunk-S2BBISHU.js.map} +0 -0
- /package/dist/{chunk-YJ7PWEWI.js.map → chunk-SA3WJJF2.js.map} +0 -0
- /package/dist/{chunk-C4PEMCP4.js.map → chunk-SLPLPGJQ.js.map} +0 -0
- /package/dist/{chunk-6JE3HGS4.js.map → chunk-T4HH5Q3U.js.map} +0 -0
- /package/dist/{chunk-EHNENZHZ.js.map → chunk-TJLLYV2X.js.map} +0 -0
- /package/dist/{chunk-SFW2NABJ.js.map → chunk-UZQCNOPS.js.map} +0 -0
- /package/dist/{chunk-DQEMWVMT.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-6NNNURLK.js.map → chunk-Z54EM5YZ.js.map} +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-scoped memory guard (issue #2183).
|
|
3
|
+
*
|
|
4
|
+
* A tool-related fact extracted from a specific agent integration carries an
|
|
5
|
+
* UNQUALIFIED tool name — "use the search tool" — with no agent identity
|
|
6
|
+
* attached (the conversation handed to the extraction LLM is rendered as
|
|
7
|
+
* `[role] content` only). Promoting such a fact to the shared namespace lets a
|
|
8
|
+
* DIFFERENT integration that exposes a same-named but incompatible tool (Pi
|
|
9
|
+
* `search` = repo code search vs OpenClaw `search` = web search) consume it.
|
|
10
|
+
*
|
|
11
|
+
* `referencesAgentSpecificTool` flags fact text that references a specific
|
|
12
|
+
* tool/command invocation rather than portable knowledge. The promotion guard
|
|
13
|
+
* in `orchestration/extraction-persist.ts` uses it — when the producing
|
|
14
|
+
* integration is known (`sourceConnector`) — to keep such a fact in that
|
|
15
|
+
* integration's own namespace.
|
|
16
|
+
*
|
|
17
|
+
* Tuning bias: a FALSE POSITIVE is cheap — the fact stays in the narrower
|
|
18
|
+
* namespace, the documented safe default. A FALSE NEGATIVE is the actual
|
|
19
|
+
* cross-integration collision bug, so detection leans permissive — but it must
|
|
20
|
+
* not fire on ordinary prose.
|
|
21
|
+
*/
|
|
22
|
+
declare function referencesAgentSpecificTool(content: string): boolean;
|
|
23
|
+
interface ToolScopeWithholdInputs {
|
|
24
|
+
content: string;
|
|
25
|
+
sourceConnector?: string;
|
|
26
|
+
procedureSteps?: ReadonlyArray<{
|
|
27
|
+
intent?: string;
|
|
28
|
+
expectedOutcome?: string;
|
|
29
|
+
toolCall?: {
|
|
30
|
+
kind?: string;
|
|
31
|
+
};
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
declare function withholdToolScopedFromSharedNamespace({ content, sourceConnector, procedureSteps, }: ToolScopeWithholdInputs): boolean;
|
|
35
|
+
interface GlobalFactPromotionInputs {
|
|
36
|
+
scope: string | null | undefined;
|
|
37
|
+
content: string;
|
|
38
|
+
sourceConnector?: string;
|
|
39
|
+
procedureSteps?: ReadonlyArray<{
|
|
40
|
+
toolCall?: {
|
|
41
|
+
kind?: string;
|
|
42
|
+
};
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
declare function shouldPromoteGlobalFactToShared(inputs: GlobalFactPromotionInputs): boolean;
|
|
46
|
+
|
|
47
|
+
export { type GlobalFactPromotionInputs, type ToolScopeWithholdInputs, referencesAgentSpecificTool, shouldPromoteGlobalFactToShared, withholdToolScopedFromSharedNamespace };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
referencesAgentSpecificTool,
|
|
3
|
+
shouldPromoteGlobalFactToShared,
|
|
4
|
+
withholdToolScopedFromSharedNamespace
|
|
5
|
+
} from "./chunk-KWMXR7XS.js";
|
|
6
|
+
import "./chunk-PZ5AY32C.js";
|
|
7
|
+
export {
|
|
8
|
+
referencesAgentSpecificTool,
|
|
9
|
+
shouldPromoteGlobalFactToShared,
|
|
10
|
+
withholdToolScopedFromSharedNamespace
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=tool-scoped-memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/transfer/backup.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
backupMemoryDir
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-2NLLXCJG.js";
|
|
4
4
|
import "../chunk-765K3SAT.js";
|
|
5
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-X7Y7WX73.js";
|
|
6
6
|
import "../chunk-J4EB7DNW.js";
|
|
7
7
|
import "../chunk-BJMBJZ2Y.js";
|
|
8
8
|
import "../chunk-UKJAGEXH.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
exportCapsule,
|
|
3
3
|
isValidCapsuleSince
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-KQAFEZQX.js";
|
|
5
5
|
import "../chunk-WEHSQBFR.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-X7Y7WX73.js";
|
|
7
7
|
import "../chunk-J4EB7DNW.js";
|
|
8
8
|
import "../chunk-BJMBJZ2Y.js";
|
|
9
9
|
import "../chunk-UKJAGEXH.js";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
importCapsule
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-65ZPVYTA.js";
|
|
4
4
|
import "../chunk-6TAUHL33.js";
|
|
5
5
|
import "../chunk-IJ7HK452.js";
|
|
6
6
|
import "../chunk-PDJTHRAW.js";
|
|
7
7
|
import "../chunk-XMMESCPD.js";
|
|
8
8
|
import "../chunk-42JKGUFJ.js";
|
|
9
9
|
import "../chunk-WEHSQBFR.js";
|
|
10
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-X7Y7WX73.js";
|
|
11
11
|
import "../chunk-BJMBJZ2Y.js";
|
|
12
12
|
import "../chunk-UKJAGEXH.js";
|
|
13
13
|
import "../chunk-FP2373TW.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
importSqlite
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-Z734BLO3.js";
|
|
3
|
+
} from "../chunk-LRGSLV7M.js";
|
|
5
4
|
import "../chunk-G4IAEX6D.js";
|
|
5
|
+
import "../chunk-Z734BLO3.js";
|
|
6
6
|
import "../chunk-RL6NEVXT.js";
|
|
7
7
|
import "../chunk-GCGJW34D.js";
|
|
8
8
|
import "../chunk-PZ5AY32C.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.35.0",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -156,6 +156,16 @@
|
|
|
156
156
|
"remnic-source": "./src/adapters/claude-code.ts",
|
|
157
157
|
"import": "./dist/adapters/claude-code.js"
|
|
158
158
|
},
|
|
159
|
+
"./reconcile/plan": {
|
|
160
|
+
"types": "./dist/reconcile/plan.d.ts",
|
|
161
|
+
"remnic-source": "./src/reconcile/plan.ts",
|
|
162
|
+
"import": "./dist/reconcile/plan.js"
|
|
163
|
+
},
|
|
164
|
+
"./reconcile/plan.js": {
|
|
165
|
+
"types": "./dist/reconcile/plan.d.ts",
|
|
166
|
+
"remnic-source": "./src/reconcile/plan.ts",
|
|
167
|
+
"import": "./dist/reconcile/plan.js"
|
|
168
|
+
},
|
|
159
169
|
"./adapters/codex": {
|
|
160
170
|
"types": "./dist/adapters/codex.d.ts",
|
|
161
171
|
"remnic-source": "./src/adapters/codex.ts",
|
|
@@ -3076,7 +3086,7 @@
|
|
|
3076
3086
|
"core"
|
|
3077
3087
|
],
|
|
3078
3088
|
"peerDependencies": {
|
|
3079
|
-
"@remnic/coding-graph": "^9.
|
|
3089
|
+
"@remnic/coding-graph": "^9.35.0"
|
|
3080
3090
|
},
|
|
3081
3091
|
"peerDependenciesMeta": {
|
|
3082
3092
|
"@remnic/coding-graph": {
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type CompiledRedactionRule,
|
|
21
21
|
} from "./extraction-redaction-rules.js";
|
|
22
22
|
import { validateRedactionPattern } from "./correction/correction-contract.js";
|
|
23
|
+
import { shouldPromoteGlobalFactToShared } from "./tool-scoped-memory.js";
|
|
23
24
|
|
|
24
25
|
const withTempDir = <T>(fn: (dir: string) => Promise<T>): Promise<T> =>
|
|
25
26
|
managedWithTempDir(fn, "remnic-redact-");
|
|
@@ -378,3 +379,55 @@ test("#1713 (codex): non-global fact is NOT scope-routed to shared", async () =>
|
|
|
378
379
|
});
|
|
379
380
|
});
|
|
380
381
|
});
|
|
382
|
+
test("#2183: pre-judge consults the SESSION namespace rules for a tool-scoped global fact (not shared)", async () => {
|
|
383
|
+
// The pre-judge namespace prediction and the write-loop scope-routing block
|
|
384
|
+
// share shouldPromoteGlobalFactToShared, so a tool-scoped global fact from a
|
|
385
|
+
// known integration is predicted to stay in the session namespace: its
|
|
386
|
+
// redaction is evaluated against the SESSION rules, not the shared ones. A
|
|
387
|
+
// portable global fact is still predicted shared and consults shared rules.
|
|
388
|
+
// This is the read/write namespace-invariant coupling the guard preserves.
|
|
389
|
+
await withTempDir(async (sourceDir) => {
|
|
390
|
+
await withTempDir(async (sharedDir) => {
|
|
391
|
+
await writeRule(sourceDir, "session-only-secret");
|
|
392
|
+
await writeRule(sharedDir, "shared-only-secret");
|
|
393
|
+
|
|
394
|
+
const sourceNamespace: string = "default";
|
|
395
|
+
const sharedNamespace: string = "shared";
|
|
396
|
+
const sourceConnector = "pi";
|
|
397
|
+
|
|
398
|
+
// Mirror the pre-judge pass: predicted target namespace = shared ONLY
|
|
399
|
+
// when shouldPromoteGlobalFactToShared says so; load that namespace's
|
|
400
|
+
// rules and combine with the base (source) rules.
|
|
401
|
+
async function consultedRulesFor(fact: { scope: string; content: string }): Promise<CompiledRedactionRule[]> {
|
|
402
|
+
const baseRules = await loadRedactionRules(sourceDir);
|
|
403
|
+
const promote = shouldPromoteGlobalFactToShared({
|
|
404
|
+
scope: fact.scope,
|
|
405
|
+
content: fact.content,
|
|
406
|
+
sourceConnector,
|
|
407
|
+
});
|
|
408
|
+
const factNs = promote ? sharedNamespace : undefined;
|
|
409
|
+
const targetRules =
|
|
410
|
+
factNs && factNs !== sourceNamespace ? await loadRedactionRules(sharedDir) : [];
|
|
411
|
+
return [...baseRules, ...targetRules];
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// (a) tool-scoped global fact + sourceConnector -> predicted SESSION.
|
|
415
|
+
const toolRules = await consultedRulesFor({ scope: "global", content: "Prefer the search tool when locating code." });
|
|
416
|
+
assert.ok(
|
|
417
|
+
contentMatchesRedactionRules("session-only-secret here", toolRules),
|
|
418
|
+
"session rules are consulted for a tool-scoped global fact",
|
|
419
|
+
);
|
|
420
|
+
assert.ok(
|
|
421
|
+
!contentMatchesRedactionRules("shared-only-secret here", toolRules),
|
|
422
|
+
"shared rules are NOT consulted for a tool-scoped global fact (the #2183 fix)",
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
// (b) portable global fact + sourceConnector -> predicted SHARED.
|
|
426
|
+
const portableRules = await consultedRulesFor({ scope: "global", content: "User prefers dark mode in all editors" });
|
|
427
|
+
assert.ok(
|
|
428
|
+
contentMatchesRedactionRules("shared-only-secret here", portableRules),
|
|
429
|
+
"shared rules ARE consulted for a portable global fact (no regression)",
|
|
430
|
+
);
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1439,7 +1439,6 @@ export {
|
|
|
1439
1439
|
type ForkLineage,
|
|
1440
1440
|
} from "./transfer/capsule-fork.js";
|
|
1441
1441
|
|
|
1442
|
-
|
|
1443
1442
|
// ---------------------------------------------------------------------------
|
|
1444
1443
|
// Coding-graph engine contract (#1551 PR1) — types owned by core,
|
|
1445
1444
|
// implemented by @remnic/coding-graph (à-la-carte optional).
|
|
@@ -1460,3 +1459,4 @@ export type {
|
|
|
1460
1459
|
ParseResult,
|
|
1461
1460
|
SymbolIR,
|
|
1462
1461
|
} from "./coding/coding-graph-types.js";
|
|
1462
|
+
export type { ResolvedScopeProfilePlan } from "./namespaces/scope-profiles.js";
|
|
@@ -82,6 +82,7 @@ import {
|
|
|
82
82
|
stripCitationForTemplate,
|
|
83
83
|
} from "../source-attribution.js";
|
|
84
84
|
import { classifyMemoryKind } from "../himem.js";
|
|
85
|
+
import { shouldPromoteGlobalFactToShared, withholdToolScopedFromSharedNamespace } from "../tool-scoped-memory.js";
|
|
85
86
|
import {
|
|
86
87
|
buildBehaviorSignalsForMemory,
|
|
87
88
|
dedupeBehaviorSignalsByMemoryAndHash,
|
|
@@ -394,6 +395,7 @@ export class ExtractionPersistCoordinator {
|
|
|
394
395
|
// (pre-judge + write-loop) so no new scattered config.*Enabled read is
|
|
395
396
|
// introduced (ratchet scatteredConfigFlagReads; see #1523).
|
|
396
397
|
const namespacesEnabled = resolveNamespaceCapabilities(this.deps.config).namespaces;
|
|
398
|
+
const sourceConnector = sourceContext?.sourceConnector; // #2183 tool-scope guard input
|
|
397
399
|
const promoteMemoryToProfileTargets = async (options: {
|
|
398
400
|
sourceStorage: StorageManager;
|
|
399
401
|
category: string;
|
|
@@ -607,11 +609,14 @@ export class ExtractionPersistCoordinator {
|
|
|
607
609
|
observedAt?: string;
|
|
608
610
|
eventTimeSource?: "extracted" | "assumed";
|
|
609
611
|
source: string;
|
|
612
|
+
sourceConnector?: string;
|
|
613
|
+
procedureSteps?: ReadonlyArray<{ toolCall?: { kind?: string } }>;
|
|
610
614
|
/** Claim-level provenance spans (issue #1575 PR 2). */
|
|
611
615
|
sources?: ProvenanceSource[];
|
|
612
616
|
provenance?: "verified" | "unverified" | "none";
|
|
613
617
|
}): Promise<void> => {
|
|
614
618
|
await promoteMemoryToProfileTargets(options);
|
|
619
|
+
if (lifecycleCaps.extractionScopeClassification && withholdToolScopedFromSharedNamespace(options)) return;
|
|
615
620
|
if (
|
|
616
621
|
!shouldPromoteToShared(
|
|
617
622
|
this.deps.config,
|
|
@@ -629,7 +634,6 @@ export class ExtractionPersistCoordinator {
|
|
|
629
634
|
this.deps.config.sharedNamespace,
|
|
630
635
|
);
|
|
631
636
|
// Dedup gate: canonicalize content before hashing.
|
|
632
|
-
//
|
|
633
637
|
// Issue #369 (PR #401): When inline attribution is enabled,
|
|
634
638
|
// `applyInlineCitation` appends a timestamp-bearing marker (e.g.
|
|
635
639
|
// `[Source: ..., ts=2026-04-11T...]`). Because the timestamp changes
|
|
@@ -639,7 +643,6 @@ export class ExtractionPersistCoordinator {
|
|
|
639
643
|
// `fact.content`, which can already carry an inline citation (e.g. a
|
|
640
644
|
// relayed or reprocessed fact). Strip any pre-existing citation so the
|
|
641
645
|
// dedup key matches the hash stored from the original un-cited write.
|
|
642
|
-
//
|
|
643
646
|
// PR #402 round-6 (Fix #2 / chatgpt-codex P1 PRRT_kwDORJXyws56U74n):
|
|
644
647
|
// Compute the enriched content before the hash-dedup check so the
|
|
645
648
|
// lookup uses the same content that writeMemory will actually store.
|
|
@@ -652,7 +655,6 @@ export class ExtractionPersistCoordinator {
|
|
|
652
655
|
// key order and casing are canonical — identical to the enrichment
|
|
653
656
|
// applied by storage.writeMemory — preventing spurious hash misses
|
|
654
657
|
// when attribute maps arrive with different insertion orders or casing.
|
|
655
|
-
//
|
|
656
658
|
// Fix #4 (Low PRRT_kwDORJXyws56VHth): sanitize the base content before
|
|
657
659
|
// building dedupContent. writeMemory runs sanitizeMemoryContent on the
|
|
658
660
|
// enriched body before hashing; if sanitization redacts the content to
|
|
@@ -660,7 +662,6 @@ export class ExtractionPersistCoordinator {
|
|
|
660
662
|
// the raw form. Computing dedupContent from sanitized.text here ensures
|
|
661
663
|
// the hash lookup and the normalizedIncoming comparison both use the
|
|
662
664
|
// same content that writeMemory will actually store.
|
|
663
|
-
//
|
|
664
665
|
// Combined fix: strip any pre-existing citation FIRST to obtain
|
|
665
666
|
// rawContent (the canonical body), then sanitize rawContent (not
|
|
666
667
|
// options.content) when building dedupContent, so that citation
|
|
@@ -1350,20 +1351,18 @@ export class ExtractionPersistCoordinator {
|
|
|
1350
1351
|
// + PBJEe/PBKAj).
|
|
1351
1352
|
let factRedactionRules = preJudgeRedactionRules;
|
|
1352
1353
|
let factNs = preRoutedNamespaceByFact[fi];
|
|
1353
|
-
// #1713
|
|
1354
|
-
//
|
|
1355
|
-
//
|
|
1356
|
-
// namespace's rules too, or a never-store pattern under shared is
|
|
1357
|
-
// missed at pre-judge and only caught at the write gate — letting the
|
|
1358
|
-
// content reach the extraction judge/training path. Mirror the write
|
|
1359
|
-
// loop's scope-routing conditions exactly.
|
|
1354
|
+
// #1713: predict the same target namespace the write loop will route
|
|
1355
|
+
// this global fact to (shared, via shouldPromoteGlobalFactToShared) so
|
|
1356
|
+
// the pre-judge redaction filter consults that namespace's rules.
|
|
1360
1357
|
if (
|
|
1361
1358
|
!factNs &&
|
|
1362
1359
|
lifecycleCaps.extractionScopeClassification &&
|
|
1363
1360
|
namespacesEnabled &&
|
|
1364
1361
|
f.scope === "global" &&
|
|
1365
1362
|
profileAllowsSharedWrites &&
|
|
1366
|
-
this.deps.storageDirNamespace(storage.dir) !== this.deps.config.sharedNamespace
|
|
1363
|
+
this.deps.storageDirNamespace(storage.dir) !== this.deps.config.sharedNamespace &&
|
|
1364
|
+
shouldPromoteGlobalFactToShared({ scope: f.scope, content: f.content,
|
|
1365
|
+
sourceConnector, procedureSteps: f.procedureSteps })
|
|
1367
1366
|
) {
|
|
1368
1367
|
factNs = this.deps.config.sharedNamespace;
|
|
1369
1368
|
}
|
|
@@ -1611,14 +1610,9 @@ export class ExtractionPersistCoordinator {
|
|
|
1611
1610
|
}
|
|
1612
1611
|
}
|
|
1613
1612
|
|
|
1614
|
-
// Scope-based namespace routing:
|
|
1615
|
-
//
|
|
1616
|
-
// namespace
|
|
1617
|
-
// applies when namespaces are enabled and the fact was not already
|
|
1618
|
-
// routed to a specific namespace by a routing rule (routing rules
|
|
1619
|
-
// that set an explicit namespace take precedence; category-only rules
|
|
1620
|
-
// do not block scope routing). Rule 30: gated by
|
|
1621
|
-
// extractionScopeClassificationEnabled.
|
|
1613
|
+
// Scope-based namespace routing: a `global` fact (scope classification
|
|
1614
|
+
// on, no explicit routing-rule namespace) is promoted to the shared
|
|
1615
|
+
// namespace unless the tool-scope guard withholds it (#2183).
|
|
1622
1616
|
if (
|
|
1623
1617
|
lifecycleCaps.extractionScopeClassification &&
|
|
1624
1618
|
namespacesEnabled &&
|
|
@@ -1627,18 +1621,21 @@ export class ExtractionPersistCoordinator {
|
|
|
1627
1621
|
) {
|
|
1628
1622
|
const currentNs = this.deps.storageDirNamespace(targetStorage.dir);
|
|
1629
1623
|
if (currentNs !== this.deps.config.sharedNamespace && profileAllowsSharedWrites) {
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1624
|
+
if (shouldPromoteGlobalFactToShared({ scope: fact.scope, content: fact.content,
|
|
1625
|
+
sourceConnector, procedureSteps: fact.procedureSteps })) {
|
|
1626
|
+
try {
|
|
1627
|
+
targetStorage = await this.deps.getStorageRouter().storageFor(
|
|
1628
|
+
this.deps.config.sharedNamespace,
|
|
1629
|
+
);
|
|
1630
|
+
targetNamespaceName = this.deps.config.sharedNamespace;
|
|
1631
|
+
log.debug(
|
|
1632
|
+
`scope-routing: fact "${fact.content.slice(0, 60)}…" routed to shared namespace (scope=global)`,
|
|
1633
|
+
);
|
|
1634
|
+
} catch (scopeRouteErr) {
|
|
1635
|
+
log.warn(
|
|
1636
|
+
`scope-routing: failed to resolve shared namespace storage; writing to session namespace (fail-open): ${scopeRouteErr}`,
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1642
1639
|
}
|
|
1643
1640
|
} else if (currentNs !== this.deps.config.sharedNamespace) {
|
|
1644
1641
|
log.debug(
|
|
@@ -2404,6 +2401,7 @@ export class ExtractionPersistCoordinator {
|
|
|
2404
2401
|
: {}),
|
|
2405
2402
|
source: extractionWriteSource,
|
|
2406
2403
|
...(extractionSourceConnector ? { sourceConnector: extractionSourceConnector } : {}),
|
|
2404
|
+
...(fact.procedureSteps && fact.procedureSteps.length ? { procedureSteps: fact.procedureSteps } : {}),
|
|
2407
2405
|
...(fact.sources && fact.sources.length > 0 ? { sources: fact.sources } : {}),
|
|
2408
2406
|
...(fact.provenance ? { provenance: fact.provenance } : {}),
|
|
2409
2407
|
});
|
|
@@ -2704,6 +2702,7 @@ export class ExtractionPersistCoordinator {
|
|
|
2704
2702
|
: {}),
|
|
2705
2703
|
source: extractionWriteSource,
|
|
2706
2704
|
...(extractionSourceConnector ? { sourceConnector: extractionSourceConnector } : {}),
|
|
2705
|
+
...(fact.procedureSteps && fact.procedureSteps.length ? { procedureSteps: fact.procedureSteps } : {}),
|
|
2707
2706
|
...(fact.sources && fact.sources.length > 0 ? { sources: fact.sources } : {}),
|
|
2708
2707
|
...(fact.provenance ? { provenance: fact.provenance } : {}),
|
|
2709
2708
|
});
|