@remnic/core 9.3.708 → 9.3.709
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-boundary.d.ts +7 -7
- package/dist/access-boundary.js +8 -8
- package/dist/access-cli.js +111 -24
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +6 -6
- package/dist/access-http.js +12 -12
- package/dist/access-mcp.d.ts +14 -6
- package/dist/access-mcp.js +11 -11
- package/dist/access-operations-batch.js +9 -9
- package/dist/access-operations.d.ts +28 -8
- package/dist/access-operations.js +14 -10
- package/dist/access-schema.d.ts +6 -6
- package/dist/{access-service-j1c1gptF.d.ts → access-service-CoIA0NrG.d.ts} +186 -3
- package/dist/access-service.d.ts +6 -6
- package/dist/access-service.js +7 -7
- package/dist/access-surface-catalog.d.ts +6 -6
- package/dist/access-surface-catalog.js +7 -0
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +3 -3
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-CKxilpzS.d.ts → catalog-DQCZrBjw.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +4 -4
- package/dist/{chunk-PDJQVAGM.js → chunk-2T4TDXPC.js} +14 -14
- package/dist/{chunk-W2WQ4LE7.js → chunk-35YJ6KCV.js} +2 -2
- package/dist/{chunk-C63MK3WL.js → chunk-3TCRU4JA.js} +2 -2
- package/dist/{chunk-WRGPE6AW.js → chunk-3XHD3XGK.js} +44 -1
- package/dist/chunk-3XHD3XGK.js.map +1 -0
- package/dist/{chunk-GKI6LX5L.js → chunk-54TI5GLV.js} +2 -2
- package/dist/{chunk-IVCQW4C4.js → chunk-7IBEWQLG.js} +2 -2
- package/dist/{chunk-Y6YHAGQP.js → chunk-7NDYFAJS.js} +2 -2
- package/dist/{chunk-SPETAWFE.js → chunk-AZVHBFI3.js} +2 -2
- package/dist/{chunk-MLLTU5FX.js → chunk-BOGENF7P.js} +1 -1
- package/dist/chunk-BOGENF7P.js.map +1 -0
- package/dist/{chunk-3PTOZJOI.js → chunk-BQCFXAMN.js} +2 -2
- package/dist/{chunk-4E2QCH46.js → chunk-CNVIWMQI.js} +2 -2
- package/dist/{chunk-4DZATVK5.js → chunk-CXMXAC5R.js} +3 -3
- package/dist/{chunk-STYMKPFT.js → chunk-EVX52NCY.js} +1459 -21
- package/dist/chunk-EVX52NCY.js.map +1 -0
- package/dist/{chunk-W2S3Z5MT.js → chunk-GMRNKPWO.js} +2 -2
- package/dist/{chunk-HIV5E57C.js → chunk-HSDJCT3V.js} +2 -2
- package/dist/{chunk-NM6TSEGQ.js → chunk-JNOYYWCA.js} +2 -2
- package/dist/{chunk-IONFO7UK.js → chunk-JQ7XVM4V.js} +3 -3
- package/dist/{chunk-2VQYHHWB.js → chunk-JSDZMOT7.js} +11 -2
- package/dist/chunk-JSDZMOT7.js.map +1 -0
- package/dist/{chunk-WSWYIKXX.js → chunk-KOEKDZ6A.js} +62 -5
- package/dist/chunk-KOEKDZ6A.js.map +1 -0
- package/dist/{chunk-WQADZ3ZY.js → chunk-NK3SPJLM.js} +20 -19
- package/dist/chunk-NK3SPJLM.js.map +1 -0
- package/dist/{chunk-WDH3KUZU.js → chunk-NXL5CVE7.js} +2 -2
- package/dist/{chunk-BGAHTI4C.js → chunk-OK7FUX6R.js} +6 -6
- package/dist/{chunk-24FGNOQS.js → chunk-P6PRSI3W.js} +85 -4
- package/dist/chunk-P6PRSI3W.js.map +1 -0
- package/dist/{chunk-66TSLESZ.js → chunk-PKE7EJMX.js} +2 -2
- package/dist/chunk-PKE7EJMX.js.map +1 -0
- package/dist/{chunk-MOXFPLD6.js → chunk-QXNFQKWU.js} +2 -2
- package/dist/{chunk-MRX6ZXHZ.js → chunk-S6FQLQGH.js} +2 -2
- package/dist/{chunk-6HPJMR5I.js → chunk-SKQCFAYU.js} +2 -2
- package/dist/{chunk-EJDAXR7O.js → chunk-VWB3HDY6.js} +56 -8
- package/dist/chunk-VWB3HDY6.js.map +1 -0
- package/dist/{chunk-N75N5SNX.js → chunk-XD33EX2F.js} +3 -3
- package/dist/{chunk-ESMY4RJ4.js → chunk-XKU4YE6Z.js} +2 -2
- package/dist/{chunk-XKMDDM7P.js → chunk-Y6PIFKXX.js} +2 -2
- package/dist/{chunk-RH2OSRQY.js → chunk-Z56IHRVV.js} +2 -2
- package/dist/{cli-CbT-pyM4.d.ts → cli-qex-L3GT.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +26 -26
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +3 -3
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +3 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +32 -32
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +6 -6
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +4 -4
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +9 -9
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +5 -5
- package/dist/{orchestrator-D4ovYV3x.d.ts → orchestrator-DsVKLEBk.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +15 -15
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/schemas.d.ts +2 -2
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-DyMUCsfN.d.ts → semantic-consolidation-_hVxkTuF.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +8 -1
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +12 -12
- package/dist/{types-Couvz-L3.d.ts → types-DUK4vVnN.d.ts} +27 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +3 -3
- package/package.json +2 -2
- package/src/access-boundary.ts +2 -0
- package/src/access-cli.ts +107 -2
- package/src/access-http.ts +70 -1
- package/src/access-mcp.ts +65 -0
- package/src/access-operations.ts +122 -0
- package/src/access-service.ts +92 -0
- package/src/access-surface-catalog.test.ts +6 -1
- package/src/access-surface-catalog.ts +7 -0
- package/src/cli.ts +1 -0
- package/src/config.test.ts +10 -0
- package/src/config.ts +43 -0
- package/src/correction/correction-access-wiring.ts +887 -0
- package/src/correction/correction-contract.ts +416 -0
- package/src/correction/correction-executor.test.ts +742 -0
- package/src/correction/correction-executor.ts +473 -0
- package/src/correction/correction-planner.test.ts +446 -0
- package/src/correction/correction-planner.ts +546 -0
- package/src/correction/correction-service.ts +180 -0
- package/src/correction/correction-surfaces.test.ts +245 -0
- package/src/correction/index.ts +43 -0
- package/src/storage.ts +10 -0
- package/src/types.ts +27 -0
- package/dist/chunk-24FGNOQS.js.map +0 -1
- package/dist/chunk-2VQYHHWB.js.map +0 -1
- package/dist/chunk-66TSLESZ.js.map +0 -1
- package/dist/chunk-EJDAXR7O.js.map +0 -1
- package/dist/chunk-MLLTU5FX.js.map +0 -1
- package/dist/chunk-STYMKPFT.js.map +0 -1
- package/dist/chunk-WQADZ3ZY.js.map +0 -1
- package/dist/chunk-WRGPE6AW.js.map +0 -1
- package/dist/chunk-WSWYIKXX.js.map +0 -1
- /package/dist/{chunk-PDJQVAGM.js.map → chunk-2T4TDXPC.js.map} +0 -0
- /package/dist/{chunk-W2WQ4LE7.js.map → chunk-35YJ6KCV.js.map} +0 -0
- /package/dist/{chunk-C63MK3WL.js.map → chunk-3TCRU4JA.js.map} +0 -0
- /package/dist/{chunk-GKI6LX5L.js.map → chunk-54TI5GLV.js.map} +0 -0
- /package/dist/{chunk-IVCQW4C4.js.map → chunk-7IBEWQLG.js.map} +0 -0
- /package/dist/{chunk-Y6YHAGQP.js.map → chunk-7NDYFAJS.js.map} +0 -0
- /package/dist/{chunk-SPETAWFE.js.map → chunk-AZVHBFI3.js.map} +0 -0
- /package/dist/{chunk-3PTOZJOI.js.map → chunk-BQCFXAMN.js.map} +0 -0
- /package/dist/{chunk-4E2QCH46.js.map → chunk-CNVIWMQI.js.map} +0 -0
- /package/dist/{chunk-4DZATVK5.js.map → chunk-CXMXAC5R.js.map} +0 -0
- /package/dist/{chunk-W2S3Z5MT.js.map → chunk-GMRNKPWO.js.map} +0 -0
- /package/dist/{chunk-HIV5E57C.js.map → chunk-HSDJCT3V.js.map} +0 -0
- /package/dist/{chunk-NM6TSEGQ.js.map → chunk-JNOYYWCA.js.map} +0 -0
- /package/dist/{chunk-IONFO7UK.js.map → chunk-JQ7XVM4V.js.map} +0 -0
- /package/dist/{chunk-WDH3KUZU.js.map → chunk-NXL5CVE7.js.map} +0 -0
- /package/dist/{chunk-BGAHTI4C.js.map → chunk-OK7FUX6R.js.map} +0 -0
- /package/dist/{chunk-MOXFPLD6.js.map → chunk-QXNFQKWU.js.map} +0 -0
- /package/dist/{chunk-MRX6ZXHZ.js.map → chunk-S6FQLQGH.js.map} +0 -0
- /package/dist/{chunk-6HPJMR5I.js.map → chunk-SKQCFAYU.js.map} +0 -0
- /package/dist/{chunk-N75N5SNX.js.map → chunk-XD33EX2F.js.map} +0 -0
- /package/dist/{chunk-ESMY4RJ4.js.map → chunk-XKU4YE6Z.js.map} +0 -0
- /package/dist/{chunk-XKMDDM7P.js.map → chunk-Y6PIFKXX.js.map} +0 -0
- /package/dist/{chunk-RH2OSRQY.js.map → chunk-Z56IHRVV.js.map} +0 -0
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from "./chunk-H7XKCNR6.js";
|
|
23
23
|
import {
|
|
24
24
|
toRecallExplainJson
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-Y6PIFKXX.js";
|
|
26
26
|
import {
|
|
27
27
|
buildProposedActions,
|
|
28
28
|
buildQualityScore,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
listMemoryGovernanceRuns,
|
|
31
31
|
readMemoryGovernanceRunArtifact,
|
|
32
32
|
runMemoryGovernance
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-XKU4YE6Z.js";
|
|
34
34
|
import {
|
|
35
35
|
clusterByKey,
|
|
36
36
|
combineNamespaces,
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
} from "./chunk-FF4KLI5W.js";
|
|
59
59
|
import {
|
|
60
60
|
estimateRecallTokens
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-AZVHBFI3.js";
|
|
62
62
|
import {
|
|
63
63
|
namespaceCollectionName
|
|
64
64
|
} from "./chunk-K22IJO4R.js";
|
|
@@ -107,17 +107,20 @@ import {
|
|
|
107
107
|
buildBriefing,
|
|
108
108
|
parseBriefingFocus,
|
|
109
109
|
parseBriefingWindow
|
|
110
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-35YJ6KCV.js";
|
|
111
111
|
import {
|
|
112
112
|
parseEntityFile
|
|
113
|
-
} from "./chunk-
|
|
113
|
+
} from "./chunk-JSDZMOT7.js";
|
|
114
114
|
import {
|
|
115
115
|
stripAttributesSuffix
|
|
116
116
|
} from "./chunk-SVOZFLIQ.js";
|
|
117
|
+
import {
|
|
118
|
+
serializeMutations
|
|
119
|
+
} from "./chunk-LTJAMRGI.js";
|
|
117
120
|
import {
|
|
118
121
|
DEFAULT_RECALL_DISCLOSURE,
|
|
119
122
|
isRecallDisclosure
|
|
120
|
-
} from "./chunk-
|
|
123
|
+
} from "./chunk-BOGENF7P.js";
|
|
121
124
|
import {
|
|
122
125
|
inferMemoryStatus,
|
|
123
126
|
toMemoryPathRel
|
|
@@ -1144,12 +1147,12 @@ async function findArchitectureCardMemory(storage) {
|
|
|
1144
1147
|
});
|
|
1145
1148
|
return cards[0] ?? null;
|
|
1146
1149
|
}
|
|
1147
|
-
function createArchitectureVersioningHook(enabled, maxVersionsPerPage, sidecarDir, memoryDir,
|
|
1150
|
+
function createArchitectureVersioningHook(enabled, maxVersionsPerPage, sidecarDir, memoryDir, readFile5, writeVersion) {
|
|
1148
1151
|
return {
|
|
1149
1152
|
async snapshotIfExists(memory) {
|
|
1150
1153
|
if (!enabled) return;
|
|
1151
1154
|
try {
|
|
1152
|
-
const fullContent = await
|
|
1155
|
+
const fullContent = await readFile5(memory.path);
|
|
1153
1156
|
await writeVersion(
|
|
1154
1157
|
memory.path,
|
|
1155
1158
|
fullContent,
|
|
@@ -2131,10 +2134,1381 @@ async function deltaGet(request, ctx, codingContext) {
|
|
|
2131
2134
|
}
|
|
2132
2135
|
}
|
|
2133
2136
|
|
|
2137
|
+
// src/correction/correction-contract.ts
|
|
2138
|
+
var MEMORY_CATEGORIES = [
|
|
2139
|
+
"fact",
|
|
2140
|
+
"preference",
|
|
2141
|
+
"correction",
|
|
2142
|
+
"entity",
|
|
2143
|
+
"decision",
|
|
2144
|
+
"relationship",
|
|
2145
|
+
"principle",
|
|
2146
|
+
"commitment",
|
|
2147
|
+
"moment",
|
|
2148
|
+
"skill",
|
|
2149
|
+
"rule",
|
|
2150
|
+
"procedure",
|
|
2151
|
+
"reasoning_trace"
|
|
2152
|
+
];
|
|
2153
|
+
var CORRECTION_ACTION_KINDS = [
|
|
2154
|
+
"supersede",
|
|
2155
|
+
"edit",
|
|
2156
|
+
"retract",
|
|
2157
|
+
"rescope",
|
|
2158
|
+
"redaction_rule"
|
|
2159
|
+
];
|
|
2160
|
+
var CORRECTION_TEXT_MAX = 1e4;
|
|
2161
|
+
var REDACTION_PATTERN_MAX = 256;
|
|
2162
|
+
function validateCorrectionRequest(request) {
|
|
2163
|
+
if (!request || typeof request !== "object") {
|
|
2164
|
+
throw new CorrectionContractError("CorrectionRequest must be an object.");
|
|
2165
|
+
}
|
|
2166
|
+
const text = typeof request.text === "string" ? request.text.trim() : "";
|
|
2167
|
+
if (text.length === 0) {
|
|
2168
|
+
throw new CorrectionContractError("CorrectionRequest.text is required and must be non-empty.");
|
|
2169
|
+
}
|
|
2170
|
+
if (text.length > CORRECTION_TEXT_MAX) {
|
|
2171
|
+
throw new CorrectionContractError(
|
|
2172
|
+
`CorrectionRequest.text exceeds the ${CORRECTION_TEXT_MAX}-character limit (${text.length}).`
|
|
2173
|
+
);
|
|
2174
|
+
}
|
|
2175
|
+
const targetIds = Array.isArray(request.targetIds) ? request.targetIds.filter((id) => typeof id === "string" && id.length > 0) : void 0;
|
|
2176
|
+
if (targetIds !== void 0 && targetIds.length === 0) {
|
|
2177
|
+
return {
|
|
2178
|
+
text,
|
|
2179
|
+
...request.sessionKey ? { sessionKey: request.sessionKey } : {},
|
|
2180
|
+
...request.principal ? { principal: request.principal } : {},
|
|
2181
|
+
...request.namespace ? { namespace: request.namespace } : {}
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
return {
|
|
2185
|
+
text,
|
|
2186
|
+
...targetIds ? { targetIds } : {},
|
|
2187
|
+
...request.sessionKey ? { sessionKey: request.sessionKey } : {},
|
|
2188
|
+
...request.principal ? { principal: request.principal } : {},
|
|
2189
|
+
...request.namespace ? { namespace: request.namespace } : {}
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
function validateRedactionPattern(pattern) {
|
|
2193
|
+
if (typeof pattern !== "string") {
|
|
2194
|
+
throw new CorrectionContractError("redaction_rule.pattern must be a string.");
|
|
2195
|
+
}
|
|
2196
|
+
const trimmed = pattern.trim();
|
|
2197
|
+
if (trimmed.length === 0) {
|
|
2198
|
+
throw new CorrectionContractError("redaction_rule.pattern is required.");
|
|
2199
|
+
}
|
|
2200
|
+
if (trimmed.length > REDACTION_PATTERN_MAX) {
|
|
2201
|
+
throw new CorrectionContractError(
|
|
2202
|
+
`redaction_rule.pattern exceeds the ${REDACTION_PATTERN_MAX}-character bound.`
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
if (isRegexLike(trimmed) && isOverlyBroadRegex(trimmed)) {
|
|
2206
|
+
throw new CorrectionContractError(
|
|
2207
|
+
"redaction_rule.pattern is too broad \u2014 use a bounded literal or a more specific pattern."
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
return trimmed;
|
|
2211
|
+
}
|
|
2212
|
+
function isRegexLike(pattern) {
|
|
2213
|
+
if (pattern.startsWith("/") && pattern.endsWith("/") && pattern.length >= 2) return true;
|
|
2214
|
+
return /[\\^$.|?*+()[\]{}]/.test(pattern);
|
|
2215
|
+
}
|
|
2216
|
+
function isOverlyBroadRegex(pattern) {
|
|
2217
|
+
const body = pattern.startsWith("/") && pattern.endsWith("/") ? pattern.slice(1, -1) : pattern;
|
|
2218
|
+
if (/(?:^|[^\\])\(\.\*\)|(?:^|[^\\])\.\*|(?:^|[^\\])\.\+|^\.([^*+]?)$/.test(body)) {
|
|
2219
|
+
return true;
|
|
2220
|
+
}
|
|
2221
|
+
return false;
|
|
2222
|
+
}
|
|
2223
|
+
function validateCorrectionAction(action) {
|
|
2224
|
+
if (!action || typeof action !== "object") {
|
|
2225
|
+
throw new CorrectionContractError("CorrectionAction must be an object.");
|
|
2226
|
+
}
|
|
2227
|
+
const a = action;
|
|
2228
|
+
if (typeof a.kind !== "string" || !CORRECTION_ACTION_KINDS.includes(a.kind)) {
|
|
2229
|
+
throw new CorrectionContractError(
|
|
2230
|
+
`CorrectionAction.kind must be one of: ${CORRECTION_ACTION_KINDS.join(", ")}.`
|
|
2231
|
+
);
|
|
2232
|
+
}
|
|
2233
|
+
switch (a.kind) {
|
|
2234
|
+
case "supersede":
|
|
2235
|
+
if (typeof a.loserId !== "string" || a.loserId.length === 0) {
|
|
2236
|
+
throw new CorrectionContractError("supersede.loserId is required.");
|
|
2237
|
+
}
|
|
2238
|
+
if (a.replacement !== void 0 && a.replacement !== null) {
|
|
2239
|
+
validateMemoryDraft(a.replacement);
|
|
2240
|
+
}
|
|
2241
|
+
break;
|
|
2242
|
+
case "edit":
|
|
2243
|
+
if (typeof a.memoryId !== "string" || a.memoryId.length === 0) {
|
|
2244
|
+
throw new CorrectionContractError("edit.memoryId is required.");
|
|
2245
|
+
}
|
|
2246
|
+
if (typeof a.patch !== "string" || a.patch.length === 0) {
|
|
2247
|
+
throw new CorrectionContractError("edit.patch is required and must be non-empty.");
|
|
2248
|
+
}
|
|
2249
|
+
break;
|
|
2250
|
+
case "retract":
|
|
2251
|
+
if (typeof a.memoryId !== "string" || a.memoryId.length === 0) {
|
|
2252
|
+
throw new CorrectionContractError("retract.memoryId is required.");
|
|
2253
|
+
}
|
|
2254
|
+
break;
|
|
2255
|
+
case "rescope":
|
|
2256
|
+
if (typeof a.memoryId !== "string" || a.memoryId.length === 0) {
|
|
2257
|
+
throw new CorrectionContractError("rescope.memoryId is required.");
|
|
2258
|
+
}
|
|
2259
|
+
if (typeof a.toNamespace !== "string" || a.toNamespace.trim().length === 0) {
|
|
2260
|
+
throw new CorrectionContractError("rescope.toNamespace is required.");
|
|
2261
|
+
}
|
|
2262
|
+
break;
|
|
2263
|
+
case "redaction_rule":
|
|
2264
|
+
if (typeof a.pattern !== "string") {
|
|
2265
|
+
throw new CorrectionContractError("redaction_rule.pattern is required.");
|
|
2266
|
+
}
|
|
2267
|
+
validateRedactionPattern(a.pattern);
|
|
2268
|
+
break;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
function validateMemoryDraft(draft) {
|
|
2272
|
+
if (!draft || typeof draft !== "object") {
|
|
2273
|
+
throw new CorrectionContractError("MemoryDraft must be an object.");
|
|
2274
|
+
}
|
|
2275
|
+
const d = draft;
|
|
2276
|
+
if (typeof d.content !== "string" || d.content.trim().length === 0) {
|
|
2277
|
+
throw new CorrectionContractError("MemoryDraft.content is required and must be non-empty.");
|
|
2278
|
+
}
|
|
2279
|
+
if (d.category !== void 0) {
|
|
2280
|
+
if (typeof d.category !== "string" || !MEMORY_CATEGORIES.includes(d.category)) {
|
|
2281
|
+
throw new CorrectionContractError(
|
|
2282
|
+
`MemoryDraft.category must be one of: ${MEMORY_CATEGORIES.join(", ")}.`
|
|
2283
|
+
);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
if (d.confidence !== void 0 && (typeof d.confidence !== "number" || d.confidence < 0 || d.confidence > 1)) {
|
|
2287
|
+
throw new CorrectionContractError("MemoryDraft.confidence must be a number in [0, 1].");
|
|
2288
|
+
}
|
|
2289
|
+
if (d.tags !== void 0 && !Array.isArray(d.tags)) {
|
|
2290
|
+
throw new CorrectionContractError("MemoryDraft.tags must be an array.");
|
|
2291
|
+
}
|
|
2292
|
+
if (d.structuredAttributes !== void 0 && (typeof d.structuredAttributes !== "object" || d.structuredAttributes === null)) {
|
|
2293
|
+
throw new CorrectionContractError("MemoryDraft.structuredAttributes must be an object.");
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
function deterministicFallbackPlan(args) {
|
|
2297
|
+
return {
|
|
2298
|
+
planId: newPlanId(),
|
|
2299
|
+
request: args.request,
|
|
2300
|
+
namespace: args.namespace,
|
|
2301
|
+
affected: args.affected,
|
|
2302
|
+
classification: "outdated",
|
|
2303
|
+
actions: [],
|
|
2304
|
+
diff: "",
|
|
2305
|
+
confidence: 0,
|
|
2306
|
+
warnings: [...args.warnings, "planner LLM unavailable \u2014 manual action selection required"],
|
|
2307
|
+
createdAt: args.createdAt,
|
|
2308
|
+
expiresAt: args.expiresAt,
|
|
2309
|
+
status: "pending"
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
function newPlanId() {
|
|
2313
|
+
return `corr-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
2314
|
+
}
|
|
2315
|
+
var CorrectionContractError = class extends Error {
|
|
2316
|
+
constructor(message) {
|
|
2317
|
+
super(message);
|
|
2318
|
+
this.name = "CorrectionContractError";
|
|
2319
|
+
}
|
|
2320
|
+
};
|
|
2321
|
+
|
|
2322
|
+
// src/correction/correction-planner.ts
|
|
2323
|
+
import { mkdir as mkdir2, readFile as readFile3, rename as rename2, unlink, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
|
|
2324
|
+
import path4 from "path";
|
|
2325
|
+
var CorrectionPlanner = class {
|
|
2326
|
+
constructor(deps) {
|
|
2327
|
+
this.deps = deps;
|
|
2328
|
+
}
|
|
2329
|
+
deps;
|
|
2330
|
+
/**
|
|
2331
|
+
* Plan a correction. `readableNamespaces` is the AUTHORIZED read scope the
|
|
2332
|
+
* service resolved for this caller; the planner never trusts a caller string.
|
|
2333
|
+
*/
|
|
2334
|
+
async plan(request, readableNamespaces) {
|
|
2335
|
+
const cleaned = validateCorrectionRequest(request);
|
|
2336
|
+
if (cleaned.text.length > CORRECTION_TEXT_MAX) {
|
|
2337
|
+
throw new CorrectionContractError("CorrectionRequest.text too long.");
|
|
2338
|
+
}
|
|
2339
|
+
if (cleaned.targetIds && cleaned.targetIds.length > this.deps.maxAffected) {
|
|
2340
|
+
throw new CorrectionContractError(
|
|
2341
|
+
`Correction target list (${cleaned.targetIds.length}) exceeds maxAffected (${this.deps.maxAffected}) \u2014 narrow the target set.`
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
const namespaces = readableNamespaces;
|
|
2345
|
+
if (namespaces.length === 0) {
|
|
2346
|
+
throw new CorrectionContractError(
|
|
2347
|
+
"Correction requires at least one readable namespace \u2014 principal has no read scope."
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
const located = await this.locateCandidates(cleaned, namespaces);
|
|
2351
|
+
if (located.candidates.length === 0) {
|
|
2352
|
+
return this.persist(
|
|
2353
|
+
this.emptyPlan(cleaned, namespaces[0], "no matching memories found for the correction text")
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
const llm = await this.deps.classifyAndDraft({
|
|
2357
|
+
text: cleaned.text,
|
|
2358
|
+
candidates: located.candidates
|
|
2359
|
+
});
|
|
2360
|
+
for (const action of llm.actions) {
|
|
2361
|
+
validateCorrectionAction(action);
|
|
2362
|
+
if (action.kind === "redaction_rule") {
|
|
2363
|
+
validateRedactionPattern(action.pattern);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
const affected = this.deriveAffected(located.candidates, llm);
|
|
2367
|
+
const touchedCount = new Set(
|
|
2368
|
+
llm.actions.map(
|
|
2369
|
+
(a) => a.kind === "supersede" ? a.loserId : a.kind === "edit" || a.kind === "retract" || a.kind === "rescope" ? a.memoryId : null
|
|
2370
|
+
).filter((id) => id !== null)
|
|
2371
|
+
).size;
|
|
2372
|
+
if (touchedCount > this.deps.maxAffected) {
|
|
2373
|
+
throw new CorrectionContractError(
|
|
2374
|
+
`Correction touches ${touchedCount} memories, exceeding the maxAffected limit of ${this.deps.maxAffected}. Narrow the correction text or supply explicit targetIds.`
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
const candidateIds = new Set(located.candidates.map((c) => c.memoryId));
|
|
2378
|
+
if (llm.actions.length > 0 && candidateIds.size > 0) {
|
|
2379
|
+
const filtered = llm.actions.filter((action) => {
|
|
2380
|
+
const id = action.kind === "supersede" ? action.loserId : action.kind === "edit" || action.kind === "retract" || action.kind === "rescope" ? action.memoryId : null;
|
|
2381
|
+
return id === null || candidateIds.has(id);
|
|
2382
|
+
});
|
|
2383
|
+
if (filtered.length < llm.actions.length) {
|
|
2384
|
+
const dropped = llm.actions.length - filtered.length;
|
|
2385
|
+
llm.warnings = [...llm.warnings, `${dropped} action(s) targeted memories outside the located candidate set and were dropped (prompt-injection guard).`];
|
|
2386
|
+
llm.actions = filtered;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
const diff = llm.fallback ? "" : await this.deps.renderDiff({ candidates: located.candidates, actions: llm.actions });
|
|
2390
|
+
const createdAt = this.deps.now().toISOString();
|
|
2391
|
+
const expiresAt = new Date(
|
|
2392
|
+
this.deps.now().getTime() + this.deps.planTtlHours * 60 * 60 * 1e3
|
|
2393
|
+
).toISOString();
|
|
2394
|
+
const plan = llm.fallback ? deterministicFallbackPlan({
|
|
2395
|
+
request: cleaned,
|
|
2396
|
+
namespace: namespaces[0],
|
|
2397
|
+
affected,
|
|
2398
|
+
warnings: llm.warnings,
|
|
2399
|
+
createdAt,
|
|
2400
|
+
expiresAt
|
|
2401
|
+
}) : {
|
|
2402
|
+
planId: newPlanId(),
|
|
2403
|
+
request: cleaned,
|
|
2404
|
+
namespace: namespaces[0],
|
|
2405
|
+
affected,
|
|
2406
|
+
classification: llm.classification,
|
|
2407
|
+
actions: llm.actions,
|
|
2408
|
+
diff,
|
|
2409
|
+
confidence: clampConfidence(llm.confidence),
|
|
2410
|
+
warnings: llm.warnings,
|
|
2411
|
+
createdAt,
|
|
2412
|
+
expiresAt,
|
|
2413
|
+
status: "pending"
|
|
2414
|
+
};
|
|
2415
|
+
return this.persist(plan);
|
|
2416
|
+
}
|
|
2417
|
+
async locateCandidates(request, namespaces) {
|
|
2418
|
+
if (request.targetIds && request.targetIds.length > 0) {
|
|
2419
|
+
const targets = await this.deps.resolveTargets({
|
|
2420
|
+
targetIds: request.targetIds,
|
|
2421
|
+
namespaces
|
|
2422
|
+
});
|
|
2423
|
+
const seedIds2 = targets.map((c) => c.memoryId);
|
|
2424
|
+
const neighbors2 = await this.deps.expandNeighbors({
|
|
2425
|
+
seedIds: seedIds2,
|
|
2426
|
+
namespaces,
|
|
2427
|
+
limit: this.deps.maxAffected
|
|
2428
|
+
});
|
|
2429
|
+
return { candidates: mergeCandidates(targets, neighbors2).slice(0, this.deps.maxAffected) };
|
|
2430
|
+
}
|
|
2431
|
+
const searched = await this.deps.searchCorpus({
|
|
2432
|
+
text: request.text,
|
|
2433
|
+
namespaces,
|
|
2434
|
+
limit: this.deps.maxAffected
|
|
2435
|
+
});
|
|
2436
|
+
if (searched.length === 0) return { candidates: [] };
|
|
2437
|
+
const seedIds = searched.slice(0, Math.min(5, searched.length)).map((c) => c.memoryId);
|
|
2438
|
+
const neighbors = await this.deps.expandNeighbors({
|
|
2439
|
+
seedIds,
|
|
2440
|
+
namespaces,
|
|
2441
|
+
limit: this.deps.maxAffected
|
|
2442
|
+
});
|
|
2443
|
+
return { candidates: mergeCandidates(searched, neighbors).slice(0, this.deps.maxAffected) };
|
|
2444
|
+
}
|
|
2445
|
+
deriveAffected(candidates, llm) {
|
|
2446
|
+
const byId = new Map(candidates.map((c) => [c.memoryId, c]));
|
|
2447
|
+
const out = [];
|
|
2448
|
+
for (const rel of llm.relevance) {
|
|
2449
|
+
const c = byId.get(rel.memoryId);
|
|
2450
|
+
if (!c) continue;
|
|
2451
|
+
out.push({
|
|
2452
|
+
memoryId: c.memoryId,
|
|
2453
|
+
path: c.path,
|
|
2454
|
+
excerpt: c.excerpt,
|
|
2455
|
+
why: rel.why,
|
|
2456
|
+
...c.sourceQuote ? { sourceQuote: c.sourceQuote } : {}
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
const annotated = new Set(out.map((e) => e.memoryId));
|
|
2460
|
+
for (const action of llm.actions) {
|
|
2461
|
+
const id = action.kind === "supersede" ? action.loserId : action.kind === "edit" || action.kind === "retract" || action.kind === "rescope" ? action.memoryId : null;
|
|
2462
|
+
if (id && !annotated.has(id)) {
|
|
2463
|
+
const c = byId.get(id);
|
|
2464
|
+
if (c) {
|
|
2465
|
+
out.push({
|
|
2466
|
+
memoryId: c.memoryId,
|
|
2467
|
+
path: c.path,
|
|
2468
|
+
excerpt: c.excerpt,
|
|
2469
|
+
why: "touched by a drafted action",
|
|
2470
|
+
...c.sourceQuote ? { sourceQuote: c.sourceQuote } : {}
|
|
2471
|
+
});
|
|
2472
|
+
annotated.add(id);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
return out;
|
|
2477
|
+
}
|
|
2478
|
+
emptyPlan(request, namespace, warning) {
|
|
2479
|
+
const createdAt = this.deps.now().toISOString();
|
|
2480
|
+
const expiresAt = new Date(
|
|
2481
|
+
this.deps.now().getTime() + this.deps.planTtlHours * 60 * 60 * 1e3
|
|
2482
|
+
).toISOString();
|
|
2483
|
+
return {
|
|
2484
|
+
planId: newPlanId(),
|
|
2485
|
+
request,
|
|
2486
|
+
namespace,
|
|
2487
|
+
affected: [],
|
|
2488
|
+
classification: "outdated",
|
|
2489
|
+
actions: [],
|
|
2490
|
+
diff: "",
|
|
2491
|
+
confidence: 0,
|
|
2492
|
+
warnings: [warning],
|
|
2493
|
+
createdAt,
|
|
2494
|
+
expiresAt,
|
|
2495
|
+
status: "pending"
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
// -------------------------------------------------------------------------
|
|
2499
|
+
// Plan persistence — atomic write (rule 54), serialized per plan id (rule 40).
|
|
2500
|
+
// -------------------------------------------------------------------------
|
|
2501
|
+
/** Directory holding pending plans for one namespace. */
|
|
2502
|
+
async pendingDir(namespace) {
|
|
2503
|
+
return path4.join(await this.deps.storageDir(namespace), "state", "corrections", "pending");
|
|
2504
|
+
}
|
|
2505
|
+
async persist(plan) {
|
|
2506
|
+
const dir = await this.pendingDir(plan.namespace);
|
|
2507
|
+
const target = path4.join(dir, `${plan.planId}.json`);
|
|
2508
|
+
await serializeMutations(`correction-plan:${target}`, async () => {
|
|
2509
|
+
await mkdir2(dir, { recursive: true });
|
|
2510
|
+
const tmp = `${target}.${process.pid}.${Date.now().toString(36)}.tmp`;
|
|
2511
|
+
await writeFile2(tmp, `${JSON.stringify(plan)}
|
|
2512
|
+
`, "utf-8");
|
|
2513
|
+
await rename2(tmp, target);
|
|
2514
|
+
});
|
|
2515
|
+
return plan;
|
|
2516
|
+
}
|
|
2517
|
+
/** Load a pending plan by id (used by the service / executor). */
|
|
2518
|
+
async loadPlan(namespace, planId) {
|
|
2519
|
+
assertSafePlanId(planId);
|
|
2520
|
+
const file = path4.join(await this.pendingDir(namespace), `${planId}.json`);
|
|
2521
|
+
return serializeMutations(`correction-plan:${file}`, async () => {
|
|
2522
|
+
let raw;
|
|
2523
|
+
try {
|
|
2524
|
+
raw = await readFile3(file, "utf-8");
|
|
2525
|
+
} catch (err) {
|
|
2526
|
+
const code = err?.code;
|
|
2527
|
+
if (code === "ENOENT") return null;
|
|
2528
|
+
throw err;
|
|
2529
|
+
}
|
|
2530
|
+
return parsePlan(raw);
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
/** List pending plans (newest first), excluding consumed (applied/discarded). */
|
|
2534
|
+
async listPending(namespace) {
|
|
2535
|
+
const dir = await this.pendingDir(namespace);
|
|
2536
|
+
let files;
|
|
2537
|
+
try {
|
|
2538
|
+
files = await readdir2(dir);
|
|
2539
|
+
} catch (err) {
|
|
2540
|
+
const code = err?.code;
|
|
2541
|
+
if (code === "ENOENT") return [];
|
|
2542
|
+
throw err;
|
|
2543
|
+
}
|
|
2544
|
+
const plans = [];
|
|
2545
|
+
for (const f of files) {
|
|
2546
|
+
if (!f.endsWith(".json")) continue;
|
|
2547
|
+
let plan = null;
|
|
2548
|
+
try {
|
|
2549
|
+
plan = await this.loadPlan(namespace, f.replace(/\.json$/, ""));
|
|
2550
|
+
} catch {
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
if (plan && plan.status === "pending") plans.push(plan);
|
|
2554
|
+
}
|
|
2555
|
+
plans.sort((a, b) => a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0);
|
|
2556
|
+
return plans;
|
|
2557
|
+
}
|
|
2558
|
+
async markConsumed(namespace, planId, status) {
|
|
2559
|
+
assertSafePlanId(planId);
|
|
2560
|
+
const file = path4.join(await this.pendingDir(namespace), `${planId}.json`);
|
|
2561
|
+
await serializeMutations(`correction-plan:${file}`, async () => {
|
|
2562
|
+
let raw;
|
|
2563
|
+
try {
|
|
2564
|
+
raw = await readFile3(file, "utf-8");
|
|
2565
|
+
} catch (err) {
|
|
2566
|
+
const code = err?.code;
|
|
2567
|
+
if (code === "ENOENT") return;
|
|
2568
|
+
throw err;
|
|
2569
|
+
}
|
|
2570
|
+
const plan = parsePlan(raw);
|
|
2571
|
+
if (!plan) return;
|
|
2572
|
+
plan.status = status;
|
|
2573
|
+
const tmp = `${file}.${process.pid}.${Date.now().toString(36)}.tmp`;
|
|
2574
|
+
await writeFile2(tmp, `${JSON.stringify(plan)}
|
|
2575
|
+
`, "utf-8");
|
|
2576
|
+
await rename2(tmp, file);
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
async deletePlan(namespace, planId) {
|
|
2580
|
+
assertSafePlanId(planId);
|
|
2581
|
+
const file = path4.join(await this.pendingDir(namespace), `${planId}.json`);
|
|
2582
|
+
await serializeMutations(`correction-plan:${file}`, async () => {
|
|
2583
|
+
try {
|
|
2584
|
+
await unlink(file);
|
|
2585
|
+
} catch (err) {
|
|
2586
|
+
const code = err?.code;
|
|
2587
|
+
if (code === "ENOENT") return;
|
|
2588
|
+
throw err;
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
};
|
|
2593
|
+
function assertSafePlanId(planId) {
|
|
2594
|
+
if (typeof planId !== "string" || planId.length === 0 || planId.includes("/") || planId.includes("\\") || planId.includes("\0") || planId === "." || planId === ".." || planId.startsWith(".") || planId.includes("..")) {
|
|
2595
|
+
throw new CorrectionContractError(
|
|
2596
|
+
`Invalid plan id: ${JSON.stringify(planId)} \u2014 must be a bare file basename with no path separators or parent segments.`
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
function clampConfidence(n) {
|
|
2601
|
+
if (!Number.isFinite(n)) return 0;
|
|
2602
|
+
return Math.min(1, Math.max(0, n));
|
|
2603
|
+
}
|
|
2604
|
+
function mergeCandidates(a, b) {
|
|
2605
|
+
const map = /* @__PURE__ */ new Map();
|
|
2606
|
+
for (const c of a) {
|
|
2607
|
+
const existing = map.get(c.memoryId);
|
|
2608
|
+
if (!existing || c.score > existing.score) map.set(c.memoryId, c);
|
|
2609
|
+
}
|
|
2610
|
+
for (const c of b) {
|
|
2611
|
+
const existing = map.get(c.memoryId);
|
|
2612
|
+
if (!existing || c.score > existing.score) map.set(c.memoryId, c);
|
|
2613
|
+
}
|
|
2614
|
+
return [...map.values()].sort((x, y) => y.score - x.score);
|
|
2615
|
+
}
|
|
2616
|
+
function parsePlan(raw) {
|
|
2617
|
+
let parsed;
|
|
2618
|
+
try {
|
|
2619
|
+
parsed = JSON.parse(raw);
|
|
2620
|
+
} catch {
|
|
2621
|
+
return null;
|
|
2622
|
+
}
|
|
2623
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
2624
|
+
const p = parsed;
|
|
2625
|
+
if (typeof p.planId !== "string" || typeof p.namespace !== "string") return null;
|
|
2626
|
+
if (typeof p.createdAt !== "string" || typeof p.expiresAt !== "string") return null;
|
|
2627
|
+
if (!Array.isArray(p.actions) || !Array.isArray(p.affected)) return null;
|
|
2628
|
+
for (const action of p.actions) {
|
|
2629
|
+
try {
|
|
2630
|
+
validateCorrectionAction(action);
|
|
2631
|
+
} catch {
|
|
2632
|
+
return null;
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
return parsed;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
// src/correction/correction-executor.ts
|
|
2639
|
+
var CorrectionExecutor = class {
|
|
2640
|
+
constructor(deps, planner) {
|
|
2641
|
+
this.deps = deps;
|
|
2642
|
+
this.planner = planner;
|
|
2643
|
+
}
|
|
2644
|
+
deps;
|
|
2645
|
+
planner;
|
|
2646
|
+
/**
|
|
2647
|
+
* Apply a persisted plan by id. Idempotent-once: a second apply of the same
|
|
2648
|
+
* plan is rejected. Returns the {@link CorrectionOutcome}.
|
|
2649
|
+
*/
|
|
2650
|
+
async apply(namespace, planId, opts) {
|
|
2651
|
+
if (!opts.confirm) {
|
|
2652
|
+
throw new CorrectionContractError(
|
|
2653
|
+
"Correction apply requires explicit confirmation (correction.applyRequiresConfirm)."
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
return serializeMutations(
|
|
2657
|
+
`correction-apply:${namespace}:${planId}`,
|
|
2658
|
+
() => this.applyInternal(namespace, planId, opts.canWriteDestination)
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
async applyInternal(namespace, planId, canWriteDestination) {
|
|
2662
|
+
const plan = await this.planner.loadPlan(namespace, planId);
|
|
2663
|
+
if (!plan) {
|
|
2664
|
+
throw new CorrectionContractError(`Correction plan not found: ${planId}`);
|
|
2665
|
+
}
|
|
2666
|
+
if (plan.namespace !== namespace) {
|
|
2667
|
+
throw new CorrectionContractError(
|
|
2668
|
+
`Correction plan ${planId} belongs to namespace '${plan.namespace}', not '${namespace}'.`
|
|
2669
|
+
);
|
|
2670
|
+
}
|
|
2671
|
+
if (plan.status === "applied" || plan.status === "partial" || plan.status === "applying") {
|
|
2672
|
+
throw new CorrectionContractError(
|
|
2673
|
+
`Correction plan ${planId} has already been applied or is in progress (status=${plan.status}).`
|
|
2674
|
+
);
|
|
2675
|
+
}
|
|
2676
|
+
if (plan.status === "discarded") {
|
|
2677
|
+
throw new CorrectionContractError(`Correction plan ${planId} has been discarded.`);
|
|
2678
|
+
}
|
|
2679
|
+
if (this.deps.now().getTime() > new Date(plan.expiresAt).getTime()) {
|
|
2680
|
+
await this.planner.markConsumed(namespace, planId, "discarded");
|
|
2681
|
+
throw new CorrectionContractError(
|
|
2682
|
+
`Correction plan ${planId} expired at ${plan.expiresAt} and has been discarded.`
|
|
2683
|
+
);
|
|
2684
|
+
}
|
|
2685
|
+
for (const action of plan.actions) {
|
|
2686
|
+
validateCorrectionAction(action);
|
|
2687
|
+
if (action.kind === "redaction_rule") {
|
|
2688
|
+
validateRedactionPattern(action.pattern);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
try {
|
|
2692
|
+
await this.planner.markConsumed(namespace, planId, "applying");
|
|
2693
|
+
} catch {
|
|
2694
|
+
throw new CorrectionContractError(
|
|
2695
|
+
`Correction plan ${planId}: cannot mark in-progress (filesystem unwritable?) \u2014 aborting before any mutation.`
|
|
2696
|
+
);
|
|
2697
|
+
}
|
|
2698
|
+
const results = [];
|
|
2699
|
+
const appliedTouched = [];
|
|
2700
|
+
for (const action of plan.actions) {
|
|
2701
|
+
if (action.kind === "supersede" && action.replacement) {
|
|
2702
|
+
const loser = await this.deps.getMemory(namespace, action.loserId);
|
|
2703
|
+
if (!loser) {
|
|
2704
|
+
results.push({
|
|
2705
|
+
action,
|
|
2706
|
+
status: "failed",
|
|
2707
|
+
error: `supersede loser not found: ${action.loserId}`
|
|
2708
|
+
});
|
|
2709
|
+
continue;
|
|
2710
|
+
}
|
|
2711
|
+
try {
|
|
2712
|
+
const newId = await this.deps.writeReplacement(namespace, {
|
|
2713
|
+
content: action.replacement.content,
|
|
2714
|
+
...action.replacement.category ? { category: action.replacement.category } : {},
|
|
2715
|
+
...action.replacement.confidence !== void 0 ? { confidence: action.replacement.confidence } : {},
|
|
2716
|
+
...action.replacement.tags ? { tags: action.replacement.tags } : {},
|
|
2717
|
+
...action.replacement.entityRef ? { entityRef: action.replacement.entityRef } : {},
|
|
2718
|
+
...action.replacement.validAt ? { validAt: action.replacement.validAt } : {},
|
|
2719
|
+
...action.replacement.observedAt ? { observedAt: action.replacement.observedAt } : {},
|
|
2720
|
+
...action.replacement.structuredAttributes ? { structuredAttributes: action.replacement.structuredAttributes } : {},
|
|
2721
|
+
supersedes: action.loserId
|
|
2722
|
+
});
|
|
2723
|
+
results.push({ action, status: "applied", memoryId: newId });
|
|
2724
|
+
appliedTouched.push(newId);
|
|
2725
|
+
} catch (err) {
|
|
2726
|
+
results.push({
|
|
2727
|
+
action,
|
|
2728
|
+
status: "failed",
|
|
2729
|
+
error: errMsg(err)
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
} else if (action.kind === "edit") {
|
|
2733
|
+
try {
|
|
2734
|
+
const editedId = await this.deps.applyEdit(namespace, action.memoryId, action.patch);
|
|
2735
|
+
results.push({ action, status: "applied", memoryId: editedId });
|
|
2736
|
+
appliedTouched.push(editedId);
|
|
2737
|
+
} catch (err) {
|
|
2738
|
+
results.push({ action, status: "failed", error: errMsg(err) });
|
|
2739
|
+
}
|
|
2740
|
+
} else if (action.kind === "redaction_rule") {
|
|
2741
|
+
try {
|
|
2742
|
+
await this.deps.registerRedactionRule(namespace, action.pattern);
|
|
2743
|
+
results.push({ action, status: "applied" });
|
|
2744
|
+
} catch (err) {
|
|
2745
|
+
results.push({ action, status: "failed", error: errMsg(err) });
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
for (const action of plan.actions) {
|
|
2750
|
+
if (action.kind === "supersede") {
|
|
2751
|
+
const replacementResult = results.find(
|
|
2752
|
+
(r) => r.action === action && r.status === "applied"
|
|
2753
|
+
);
|
|
2754
|
+
if (action.replacement && !replacementResult) {
|
|
2755
|
+
continue;
|
|
2756
|
+
}
|
|
2757
|
+
await this.retireAndTombstone(namespace, action, "supersession", results, appliedTouched, {
|
|
2758
|
+
supersededBy: replacementResult?.memoryId
|
|
2759
|
+
});
|
|
2760
|
+
} else if (action.kind === "retract") {
|
|
2761
|
+
await this.retireAndTombstone(namespace, action, "retraction", results, appliedTouched);
|
|
2762
|
+
} else if (action.kind === "rescope") {
|
|
2763
|
+
try {
|
|
2764
|
+
const allowed = canWriteDestination ? await canWriteDestination(action.toNamespace) : true;
|
|
2765
|
+
if (!allowed) {
|
|
2766
|
+
results.push({
|
|
2767
|
+
action,
|
|
2768
|
+
status: "failed",
|
|
2769
|
+
error: `rescope destination namespace not writable: ${action.toNamespace}`
|
|
2770
|
+
});
|
|
2771
|
+
continue;
|
|
2772
|
+
}
|
|
2773
|
+
const destId = await this.deps.rescopeMemory(namespace, action.memoryId, action.toNamespace);
|
|
2774
|
+
results.push({ action, status: "applied", memoryId: action.memoryId });
|
|
2775
|
+
appliedTouched.push(action.memoryId);
|
|
2776
|
+
try {
|
|
2777
|
+
await this.deps.propagate(action.toNamespace, [destId]);
|
|
2778
|
+
} catch {
|
|
2779
|
+
}
|
|
2780
|
+
} catch (err) {
|
|
2781
|
+
results.push({ action, status: "failed", error: errMsg(err) });
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
const propagationWarnings = [];
|
|
2786
|
+
if (appliedTouched.length > 0) {
|
|
2787
|
+
try {
|
|
2788
|
+
await this.deps.propagate(namespace, appliedTouched);
|
|
2789
|
+
} catch (err) {
|
|
2790
|
+
propagationWarnings.push(`propagation failed (non-fatal): ${errMsg(err)}`);
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
const anyFailed = results.some((r) => r.status === "failed");
|
|
2794
|
+
const status = anyFailed ? "partial" : "applied";
|
|
2795
|
+
const appliedAt = this.deps.now().toISOString();
|
|
2796
|
+
const outcome = {
|
|
2797
|
+
planId,
|
|
2798
|
+
status,
|
|
2799
|
+
results,
|
|
2800
|
+
auditMemoryId: "",
|
|
2801
|
+
// filled after the audit write
|
|
2802
|
+
appliedAt
|
|
2803
|
+
};
|
|
2804
|
+
if (propagationWarnings.length > 0) {
|
|
2805
|
+
outcome.warnings = propagationWarnings;
|
|
2806
|
+
}
|
|
2807
|
+
try {
|
|
2808
|
+
const auditId = await this.deps.appendAuditRecord(namespace, {
|
|
2809
|
+
planId,
|
|
2810
|
+
classification: plan.classification,
|
|
2811
|
+
outcome,
|
|
2812
|
+
requestText: plan.request.text
|
|
2813
|
+
});
|
|
2814
|
+
outcome.auditMemoryId = auditId;
|
|
2815
|
+
} catch (err) {
|
|
2816
|
+
outcome.warnings = [
|
|
2817
|
+
...propagationWarnings,
|
|
2818
|
+
`audit record write failed (non-fatal): ${errMsg(err)}`
|
|
2819
|
+
];
|
|
2820
|
+
}
|
|
2821
|
+
try {
|
|
2822
|
+
await this.planner.markConsumed(namespace, planId, status);
|
|
2823
|
+
} catch (err) {
|
|
2824
|
+
const w = outcome;
|
|
2825
|
+
w.warnings = [...w.warnings ?? [], `plan mark-consumed failed (non-fatal): ${errMsg(err)}`];
|
|
2826
|
+
}
|
|
2827
|
+
return outcome;
|
|
2828
|
+
}
|
|
2829
|
+
async retireAndTombstone(namespace, action, reason, results, appliedTouched, opts = {}) {
|
|
2830
|
+
const memoryId = action.kind === "supersede" ? action.loserId : action.kind === "retract" ? action.memoryId : null;
|
|
2831
|
+
if (!memoryId) return;
|
|
2832
|
+
try {
|
|
2833
|
+
const memory = await this.deps.getMemory(namespace, memoryId);
|
|
2834
|
+
if (!memory) {
|
|
2835
|
+
results.push({
|
|
2836
|
+
action,
|
|
2837
|
+
status: "failed",
|
|
2838
|
+
error: `memory not found: ${memoryId}`
|
|
2839
|
+
});
|
|
2840
|
+
return;
|
|
2841
|
+
}
|
|
2842
|
+
const validUntil = this.deps.biTemporalEnabled ? this.deps.now().toISOString() : void 0;
|
|
2843
|
+
const tombstoneId = await this.deps.appendTombstone(namespace, {
|
|
2844
|
+
reason,
|
|
2845
|
+
sourceMemoryId: memoryId,
|
|
2846
|
+
rawContent: memory.rawContent,
|
|
2847
|
+
...memory.entityRef ? { entityRef: memory.entityRef } : {},
|
|
2848
|
+
...memory.supersessionKey ? { supersessionKey: memory.supersessionKey } : {}
|
|
2849
|
+
});
|
|
2850
|
+
await this.deps.retireMemory(namespace, memoryId, {
|
|
2851
|
+
status: reason === "supersession" ? "superseded" : "retracted",
|
|
2852
|
+
...opts.supersededBy ? { supersededBy: opts.supersededBy } : {},
|
|
2853
|
+
...validUntil ? { validUntil } : {}
|
|
2854
|
+
});
|
|
2855
|
+
results.push({
|
|
2856
|
+
action,
|
|
2857
|
+
status: "applied",
|
|
2858
|
+
memoryId,
|
|
2859
|
+
...tombstoneId ? { tombstoneId } : {}
|
|
2860
|
+
});
|
|
2861
|
+
appliedTouched.push(memoryId);
|
|
2862
|
+
} catch (err) {
|
|
2863
|
+
results.push({ action, status: "failed", error: errMsg(err) });
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
};
|
|
2867
|
+
function errMsg(err) {
|
|
2868
|
+
if (err instanceof Error) return err.message;
|
|
2869
|
+
return String(err);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
// src/correction/correction-service.ts
|
|
2873
|
+
var CorrectionService = class {
|
|
2874
|
+
constructor(deps) {
|
|
2875
|
+
this.deps = deps;
|
|
2876
|
+
this.planner = new CorrectionPlanner(deps.plannerDeps());
|
|
2877
|
+
this.executor = new CorrectionExecutor(deps.executorDeps(), this.planner);
|
|
2878
|
+
}
|
|
2879
|
+
deps;
|
|
2880
|
+
planner;
|
|
2881
|
+
executor;
|
|
2882
|
+
/**
|
|
2883
|
+
* Plan a correction. Read-only — produces + persists a {@link CorrectionPlan}.
|
|
2884
|
+
* Safe to run with the gate ON (default) since the plan writes no memory state.
|
|
2885
|
+
*
|
|
2886
|
+
* The plan is stored under the AUTHORIZED (writable) namespace — the same
|
|
2887
|
+
* namespace apply/listPending/discard resolve — so the plan is always
|
|
2888
|
+
* found by the caller that created it (review threads: plan-stored-wrong-
|
|
2889
|
+
* namespace + cross-namespace candidate mismatch). Candidates are located
|
|
2890
|
+
* within that single namespace too: a correction can only affect memories
|
|
2891
|
+
* the principal can write, so reading from a non-writable namespace to
|
|
2892
|
+
* draft a correction that can never be applied would be misleading.
|
|
2893
|
+
*/
|
|
2894
|
+
async plan(request) {
|
|
2895
|
+
this.requireEnabled();
|
|
2896
|
+
const authorized = await this.deps.policy.resolveAuthorizedNamespace(request);
|
|
2897
|
+
return this.planner.plan(request, [authorized]);
|
|
2898
|
+
}
|
|
2899
|
+
/**
|
|
2900
|
+
* Apply a persisted plan by id. Requires `confirm: true` (or interactive
|
|
2901
|
+
* yes at the CLI) when `applyRequiresConfirm` is on (default).
|
|
2902
|
+
*/
|
|
2903
|
+
async apply(planId, opts) {
|
|
2904
|
+
this.requireEnabled();
|
|
2905
|
+
if (this.deps.applyRequiresConfirm() && opts.confirm !== true) {
|
|
2906
|
+
throw new CorrectionContractError(
|
|
2907
|
+
"Correction apply requires explicit confirmation (pass `confirm: true`)."
|
|
2908
|
+
);
|
|
2909
|
+
}
|
|
2910
|
+
const namespace = await this.deps.policy.resolveAuthorizedNamespace({
|
|
2911
|
+
...opts.namespace ? { namespace: opts.namespace } : {},
|
|
2912
|
+
...opts.sessionKey ? { sessionKey: opts.sessionKey } : {},
|
|
2913
|
+
...opts.principal ? { principal: opts.principal } : {}
|
|
2914
|
+
});
|
|
2915
|
+
return this.executor.apply(namespace, planId, {
|
|
2916
|
+
confirm: true,
|
|
2917
|
+
// Authorize rescope destinations through the namespace policy bound to
|
|
2918
|
+
// THIS caller's principal (review thread: authorize-rescope-destination).
|
|
2919
|
+
canWriteDestination: (dest) => this.deps.policy.canWriteNamespace({
|
|
2920
|
+
namespace: dest,
|
|
2921
|
+
...opts.sessionKey ? { sessionKey: opts.sessionKey } : {},
|
|
2922
|
+
...opts.principal ? { principal: opts.principal } : {}
|
|
2923
|
+
})
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2926
|
+
/** List pending (not-yet-applied) plans for the caller's namespace. */
|
|
2927
|
+
async listPending(opts) {
|
|
2928
|
+
this.requireEnabled();
|
|
2929
|
+
const namespace = await this.deps.policy.resolveAuthorizedNamespace(opts);
|
|
2930
|
+
return this.planner.listPending(namespace);
|
|
2931
|
+
}
|
|
2932
|
+
/** Discard a pending plan. Idempotent. */
|
|
2933
|
+
async discard(planId, opts) {
|
|
2934
|
+
this.requireEnabled();
|
|
2935
|
+
const namespace = await this.deps.policy.resolveAuthorizedNamespace(opts);
|
|
2936
|
+
await this.planner.markConsumed(namespace, planId, "discarded");
|
|
2937
|
+
await this.planner.deletePlan(namespace, planId);
|
|
2938
|
+
}
|
|
2939
|
+
requireEnabled() {
|
|
2940
|
+
if (!this.deps.isEnabled()) {
|
|
2941
|
+
throw new CorrectionContractError(
|
|
2942
|
+
"Correction Contract is disabled (set correction.enabled = true to enable)."
|
|
2943
|
+
);
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
};
|
|
2947
|
+
|
|
2948
|
+
// src/correction/correction-access-wiring.ts
|
|
2949
|
+
import path5 from "path";
|
|
2950
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
2951
|
+
function createCorrectionService(wiring) {
|
|
2952
|
+
const cfg = wiring.orchestrator.config;
|
|
2953
|
+
const correctionEnabled = isCorrectionFeatureEnabled(cfg);
|
|
2954
|
+
const applyRequiresConfirm = typeof cfg.correctionApplyRequiresConfirm === "boolean" ? cfg.correctionApplyRequiresConfirm : readCorrectionFlag(cfg, "applyRequiresConfirm", true);
|
|
2955
|
+
const maxAffected = typeof cfg.correctionMaxAffected === "number" && cfg.correctionMaxAffected >= 1 ? Math.floor(cfg.correctionMaxAffected) : readCorrectionNumber(cfg, "maxAffected", 10);
|
|
2956
|
+
const planTtlHours = typeof cfg.correctionPlanTtlHours === "number" && cfg.correctionPlanTtlHours > 0 ? cfg.correctionPlanTtlHours : readCorrectionNumber(cfg, "planTtlHours", 24);
|
|
2957
|
+
const biTemporalEnabled = cfg.temporalBiTemporal === true;
|
|
2958
|
+
const serviceDeps = {
|
|
2959
|
+
policy: {
|
|
2960
|
+
resolveAuthorizedNamespace: (req) => wiring.resolveAuthorizedNamespace(req),
|
|
2961
|
+
canWriteNamespace: (req) => wiring.canWriteNamespace(req),
|
|
2962
|
+
readableNamespaces: (req) => Promise.resolve(wiring.resolveReadableNamespaces(req))
|
|
2963
|
+
},
|
|
2964
|
+
plannerDeps: () => makePlannerDeps(wiring, { maxAffected, planTtlHours }),
|
|
2965
|
+
executorDeps: () => makeExecutorDeps(wiring, { biTemporalEnabled }),
|
|
2966
|
+
isEnabled: () => correctionEnabled,
|
|
2967
|
+
applyRequiresConfirm: () => applyRequiresConfirm
|
|
2968
|
+
};
|
|
2969
|
+
return new CorrectionService(serviceDeps);
|
|
2970
|
+
}
|
|
2971
|
+
function makePlannerDeps(wiring, opts) {
|
|
2972
|
+
return {
|
|
2973
|
+
searchCorpus: async ({ text, namespaces, limit }) => searchMemories(wiring, text, namespaces, limit),
|
|
2974
|
+
resolveTargets: async ({ targetIds, namespaces }) => resolveTargetMemories(wiring, targetIds, namespaces),
|
|
2975
|
+
expandNeighbors: async ({ seedIds, namespaces, limit }) => expandEntityNeighbors(wiring, seedIds, namespaces, limit),
|
|
2976
|
+
classifyAndDraft: async ({ text, candidates }) => classifyAndDraft(wiring, text, candidates),
|
|
2977
|
+
renderDiff: async ({ candidates, actions }) => renderCorrectionDiff(wiring, candidates, actions),
|
|
2978
|
+
storageDir: async (namespace) => (await wiring.orchestrator.getStorage(namespace)).dir,
|
|
2979
|
+
maxAffected: opts.maxAffected,
|
|
2980
|
+
planTtlHours: opts.planTtlHours,
|
|
2981
|
+
now: () => /* @__PURE__ */ new Date()
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
async function searchMemories(wiring, text, namespaces, limit) {
|
|
2985
|
+
const tokens = tokenize(text);
|
|
2986
|
+
if (tokens.length === 0) return [];
|
|
2987
|
+
const scored = [];
|
|
2988
|
+
for (const ns of namespaces) {
|
|
2989
|
+
const storage = await wiring.orchestrator.getStorage(ns);
|
|
2990
|
+
const all = await storage.readAllMemories();
|
|
2991
|
+
for (const m of all) {
|
|
2992
|
+
if (m.frontmatter.status && m.frontmatter.status !== "active") continue;
|
|
2993
|
+
const hay = `${m.content} ${m.frontmatter.tags?.join(" ") ?? ""}`.toLowerCase();
|
|
2994
|
+
let hits = 0;
|
|
2995
|
+
for (const tok of tokens) {
|
|
2996
|
+
if (hay.includes(tok)) hits++;
|
|
2997
|
+
}
|
|
2998
|
+
if (hits === 0) continue;
|
|
2999
|
+
scored.push({ m, ns, score: hits / tokens.length });
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
scored.sort((a, b) => b.score - a.score);
|
|
3003
|
+
return scored.slice(0, limit).map((s, i) => toCandidate(s.m, s.ns, s.score - i * 0.01));
|
|
3004
|
+
}
|
|
3005
|
+
function tokenize(text) {
|
|
3006
|
+
const STOP = /* @__PURE__ */ new Set([
|
|
3007
|
+
"the",
|
|
3008
|
+
"a",
|
|
3009
|
+
"an",
|
|
3010
|
+
"is",
|
|
3011
|
+
"are",
|
|
3012
|
+
"was",
|
|
3013
|
+
"were",
|
|
3014
|
+
"be",
|
|
3015
|
+
"been",
|
|
3016
|
+
"being",
|
|
3017
|
+
"to",
|
|
3018
|
+
"of",
|
|
3019
|
+
"in",
|
|
3020
|
+
"on",
|
|
3021
|
+
"at",
|
|
3022
|
+
"by",
|
|
3023
|
+
"for",
|
|
3024
|
+
"with",
|
|
3025
|
+
"from",
|
|
3026
|
+
"into",
|
|
3027
|
+
"and",
|
|
3028
|
+
"or",
|
|
3029
|
+
"but",
|
|
3030
|
+
"not",
|
|
3031
|
+
"no",
|
|
3032
|
+
"yes",
|
|
3033
|
+
"this",
|
|
3034
|
+
"that",
|
|
3035
|
+
"these",
|
|
3036
|
+
"those",
|
|
3037
|
+
"it",
|
|
3038
|
+
"its",
|
|
3039
|
+
"we",
|
|
3040
|
+
"you",
|
|
3041
|
+
"i",
|
|
3042
|
+
"he",
|
|
3043
|
+
"she",
|
|
3044
|
+
"they",
|
|
3045
|
+
"them",
|
|
3046
|
+
"our",
|
|
3047
|
+
"your",
|
|
3048
|
+
"my",
|
|
3049
|
+
"his",
|
|
3050
|
+
"her",
|
|
3051
|
+
"their",
|
|
3052
|
+
"as",
|
|
3053
|
+
"so",
|
|
3054
|
+
"if",
|
|
3055
|
+
"then",
|
|
3056
|
+
"than",
|
|
3057
|
+
"too",
|
|
3058
|
+
"very",
|
|
3059
|
+
"can",
|
|
3060
|
+
"will",
|
|
3061
|
+
"just",
|
|
3062
|
+
"should",
|
|
3063
|
+
"now",
|
|
3064
|
+
"has",
|
|
3065
|
+
"have",
|
|
3066
|
+
"had",
|
|
3067
|
+
"do",
|
|
3068
|
+
"does",
|
|
3069
|
+
"did",
|
|
3070
|
+
"about",
|
|
3071
|
+
"which",
|
|
3072
|
+
"what",
|
|
3073
|
+
"who",
|
|
3074
|
+
"when",
|
|
3075
|
+
"where",
|
|
3076
|
+
"why",
|
|
3077
|
+
"how",
|
|
3078
|
+
"all",
|
|
3079
|
+
"each",
|
|
3080
|
+
"every",
|
|
3081
|
+
"both",
|
|
3082
|
+
"few",
|
|
3083
|
+
"more",
|
|
3084
|
+
"most",
|
|
3085
|
+
"other",
|
|
3086
|
+
"some",
|
|
3087
|
+
"such",
|
|
3088
|
+
"only",
|
|
3089
|
+
"own",
|
|
3090
|
+
"same",
|
|
3091
|
+
"up"
|
|
3092
|
+
]);
|
|
3093
|
+
const raw = text.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
3094
|
+
const out = raw.filter((w) => w.length >= 3 && !STOP.has(w));
|
|
3095
|
+
return [...new Set(out)];
|
|
3096
|
+
}
|
|
3097
|
+
async function resolveTargetMemories(wiring, targetIds, namespaces) {
|
|
3098
|
+
const out = [];
|
|
3099
|
+
const missing = [];
|
|
3100
|
+
for (const id of targetIds) {
|
|
3101
|
+
let found = null;
|
|
3102
|
+
for (const ns of namespaces) {
|
|
3103
|
+
const storage = await wiring.orchestrator.getStorage(ns);
|
|
3104
|
+
const m = await storage.getMemoryById(id);
|
|
3105
|
+
if (m) {
|
|
3106
|
+
found = toCandidate(m, ns, 1);
|
|
3107
|
+
break;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
if (found) out.push(found);
|
|
3111
|
+
else missing.push(id);
|
|
3112
|
+
}
|
|
3113
|
+
if (missing.length > 0) {
|
|
3114
|
+
throw new CorrectionContractError(`target memory not found: ${missing[0]}`);
|
|
3115
|
+
}
|
|
3116
|
+
return out;
|
|
3117
|
+
}
|
|
3118
|
+
async function expandEntityNeighbors(wiring, seedIds, namespaces, limit) {
|
|
3119
|
+
if (seedIds.length === 0 || limit <= 0 || namespaces.length === 0) return [];
|
|
3120
|
+
const out = [];
|
|
3121
|
+
const seen = new Set(seedIds);
|
|
3122
|
+
const seedRefs = /* @__PURE__ */ new Set();
|
|
3123
|
+
for (const ns of namespaces) {
|
|
3124
|
+
const storage = await wiring.orchestrator.getStorage(ns);
|
|
3125
|
+
const all = await storage.readAllMemories();
|
|
3126
|
+
for (const m of all) {
|
|
3127
|
+
if (seedIds.includes(m.frontmatter.id) && m.frontmatter.entityRef) {
|
|
3128
|
+
seedRefs.add(m.frontmatter.entityRef);
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
if (seedRefs.size === 0) return [];
|
|
3133
|
+
for (const ns of namespaces) {
|
|
3134
|
+
if (out.length >= limit) break;
|
|
3135
|
+
const storage = await wiring.orchestrator.getStorage(ns);
|
|
3136
|
+
const all = await storage.readAllMemories();
|
|
3137
|
+
for (const m of all) {
|
|
3138
|
+
if (out.length >= limit) break;
|
|
3139
|
+
if (seen.has(m.frontmatter.id)) continue;
|
|
3140
|
+
if (m.frontmatter.status && m.frontmatter.status !== "active") continue;
|
|
3141
|
+
if (m.frontmatter.entityRef && seedRefs.has(m.frontmatter.entityRef)) {
|
|
3142
|
+
out.push(toCandidate(m, ns, 0.5));
|
|
3143
|
+
seen.add(m.frontmatter.id);
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
return out;
|
|
3148
|
+
}
|
|
3149
|
+
async function classifyAndDraft(wiring, text, candidates) {
|
|
3150
|
+
try {
|
|
3151
|
+
if (!wiring.llmComplete) {
|
|
3152
|
+
return fallbackClassification(candidates, "no LLM client available");
|
|
3153
|
+
}
|
|
3154
|
+
const user = buildClassifyPrompt(text, candidates);
|
|
3155
|
+
const raw = await wiring.llmComplete({ system: CLASSIFY_SYSTEM_PROMPT, user });
|
|
3156
|
+
return parseClassifyResponse(raw, candidates);
|
|
3157
|
+
} catch (err) {
|
|
3158
|
+
return fallbackClassification(candidates, `LLM unavailable: ${errMsg2(err)}`);
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
async function renderCorrectionDiff(_wiring, candidates, actions) {
|
|
3162
|
+
const lines = [];
|
|
3163
|
+
for (const action of actions) {
|
|
3164
|
+
const id = action.kind === "supersede" ? action.loserId : action.kind === "edit" || action.kind === "retract" || action.kind === "rescope" ? action.memoryId : null;
|
|
3165
|
+
const candidate = id ? candidates.find((c) => c.memoryId === id) : null;
|
|
3166
|
+
const before = candidate?.content ?? "(new)";
|
|
3167
|
+
const after = describeAfterState(action);
|
|
3168
|
+
lines.push(`--- ${id ?? "redaction-rule"} (${action.kind})`);
|
|
3169
|
+
lines.push(`- ${before.slice(0, 120)}`);
|
|
3170
|
+
lines.push(`+ ${after.slice(0, 120)}`);
|
|
3171
|
+
}
|
|
3172
|
+
return lines.join("\n");
|
|
3173
|
+
}
|
|
3174
|
+
function describeAfterState(action) {
|
|
3175
|
+
switch (action.kind) {
|
|
3176
|
+
case "supersede":
|
|
3177
|
+
return action.replacement?.content ?? "(superseded without replacement)";
|
|
3178
|
+
case "edit":
|
|
3179
|
+
return action.patch;
|
|
3180
|
+
case "retract":
|
|
3181
|
+
return "(retracted + tombstoned)";
|
|
3182
|
+
case "rescope":
|
|
3183
|
+
return `(moved to namespace '${action.toNamespace}')`;
|
|
3184
|
+
case "redaction_rule":
|
|
3185
|
+
return `(redaction rule persisted for pattern '${action.pattern}')`;
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
function makeExecutorDeps(wiring, opts) {
|
|
3189
|
+
return {
|
|
3190
|
+
getMemory: async (namespace, memoryId) => getExecutorMemory(wiring, namespace, memoryId),
|
|
3191
|
+
writeReplacement: async (namespace, draft) => writeReplacementMemory(wiring, namespace, draft),
|
|
3192
|
+
applyEdit: async (namespace, memoryId, patch) => applyEditMemory(wiring, namespace, memoryId, patch),
|
|
3193
|
+
retireMemory: async (namespace, memoryId, retireOpts) => retireMemoryFn(wiring, namespace, memoryId, retireOpts),
|
|
3194
|
+
rescopeMemory: async (namespace, memoryId, toNamespace) => rescopeMemoryFn(wiring, namespace, memoryId, toNamespace),
|
|
3195
|
+
appendTombstone: async (namespace, input) => appendTombstoneFn(wiring, namespace, input),
|
|
3196
|
+
registerRedactionRule: async (namespace, pattern) => registerRedactionRuleFn(wiring, namespace, pattern),
|
|
3197
|
+
appendAuditRecord: async (namespace, record) => appendAuditRecordFn(wiring, namespace, record),
|
|
3198
|
+
propagate: async (namespace, touchedMemoryIds) => propagateFn(wiring, namespace, touchedMemoryIds),
|
|
3199
|
+
biTemporalEnabled: opts.biTemporalEnabled,
|
|
3200
|
+
now: () => /* @__PURE__ */ new Date()
|
|
3201
|
+
};
|
|
3202
|
+
}
|
|
3203
|
+
async function getExecutorMemory(wiring, namespace, memoryId) {
|
|
3204
|
+
const storage = await wiring.orchestrator.getStorage(namespace);
|
|
3205
|
+
const m = await storage.getMemoryById(memoryId);
|
|
3206
|
+
if (!m) return null;
|
|
3207
|
+
return toExecutorMemory(m);
|
|
3208
|
+
}
|
|
3209
|
+
async function writeReplacementMemory(wiring, namespace, draft) {
|
|
3210
|
+
const storage = await wiring.orchestrator.getStorage(namespace);
|
|
3211
|
+
const id = await storage.writeMemory(
|
|
3212
|
+
draft.category ?? "fact",
|
|
3213
|
+
draft.content,
|
|
3214
|
+
{
|
|
3215
|
+
source: "correction",
|
|
3216
|
+
confidence: draft.confidence ?? 0.9,
|
|
3217
|
+
tags: draft.tags ?? [],
|
|
3218
|
+
...draft.entityRef ? { entityRef: draft.entityRef } : {},
|
|
3219
|
+
...draft.validAt ? { validAt: draft.validAt } : {},
|
|
3220
|
+
...draft.observedAt ? { observedAt: draft.observedAt } : {},
|
|
3221
|
+
...draft.structuredAttributes ? { structuredAttributes: draft.structuredAttributes } : {},
|
|
3222
|
+
...draft.supersedes ? { supersedes: draft.supersedes } : {}
|
|
3223
|
+
}
|
|
3224
|
+
);
|
|
3225
|
+
return id;
|
|
3226
|
+
}
|
|
3227
|
+
async function applyEditMemory(wiring, namespace, memoryId, patch) {
|
|
3228
|
+
const storage = await wiring.orchestrator.getStorage(namespace);
|
|
3229
|
+
const existing = await storage.getMemoryById(memoryId);
|
|
3230
|
+
if (!existing) throw new CorrectionContractError(`memory not found for edit: ${memoryId}`);
|
|
3231
|
+
await storage.writeMemoryFrontmatter(
|
|
3232
|
+
{ ...existing, content: patch },
|
|
3233
|
+
{ updated: (/* @__PURE__ */ new Date()).toISOString() }
|
|
3234
|
+
);
|
|
3235
|
+
return memoryId;
|
|
3236
|
+
}
|
|
3237
|
+
async function retireMemoryFn(wiring, namespace, memoryId, opts) {
|
|
3238
|
+
const storage = await wiring.orchestrator.getStorage(namespace);
|
|
3239
|
+
const memory = await storage.getMemoryById(memoryId);
|
|
3240
|
+
if (!memory) throw new CorrectionContractError(`memory not found for retire: ${memoryId}`);
|
|
3241
|
+
const storageStatus = opts.status === "retracted" ? "forgotten" : "superseded";
|
|
3242
|
+
await storage.writeMemoryFrontmatter(memory, {
|
|
3243
|
+
status: storageStatus,
|
|
3244
|
+
...opts.supersededBy ? { supersededBy: opts.supersededBy } : {},
|
|
3245
|
+
...opts.status === "superseded" ? { supersededAt: (/* @__PURE__ */ new Date()).toISOString() } : {},
|
|
3246
|
+
// validUntil is the bi-temporal end; absent when the gate is off.
|
|
3247
|
+
...opts.validUntil ? { invalid_at: opts.validUntil } : {}
|
|
3248
|
+
});
|
|
3249
|
+
}
|
|
3250
|
+
async function rescopeMemoryFn(wiring, namespace, memoryId, toNamespace) {
|
|
3251
|
+
const sourceStorage = await wiring.orchestrator.getStorage(namespace);
|
|
3252
|
+
const memory = await sourceStorage.getMemoryById(memoryId);
|
|
3253
|
+
if (!memory) throw new CorrectionContractError(`memory not found for rescope: ${memoryId}`);
|
|
3254
|
+
if (memory.frontmatter.status && memory.frontmatter.status !== "active") {
|
|
3255
|
+
throw new CorrectionContractError(
|
|
3256
|
+
`cannot rescope memory ${memoryId}: source is ${memory.frontmatter.status}, not active`
|
|
3257
|
+
);
|
|
3258
|
+
}
|
|
3259
|
+
const destStorage = await wiring.orchestrator.getStorage(toNamespace);
|
|
3260
|
+
const fm = memory.frontmatter;
|
|
3261
|
+
const destContent = fm.structuredAttributes ? stripAttributesSuffix(memory.content) : memory.content;
|
|
3262
|
+
const destId = await destStorage.writeMemory(fm.category, destContent, {
|
|
3263
|
+
source: `correction:rescope:${namespace}`,
|
|
3264
|
+
...typeof fm.confidence === "number" ? { confidence: fm.confidence } : {},
|
|
3265
|
+
...Array.isArray(fm.tags) ? { tags: fm.tags } : {},
|
|
3266
|
+
...fm.entityRef ? { entityRef: fm.entityRef } : {},
|
|
3267
|
+
...fm.structuredAttributes ? { structuredAttributes: fm.structuredAttributes } : {},
|
|
3268
|
+
...fm.valid_at ? { validAt: fm.valid_at } : {},
|
|
3269
|
+
...fm.observedAt ? { observedAt: fm.observedAt } : {},
|
|
3270
|
+
...fm.memoryKind ? { memoryKind: fm.memoryKind } : {},
|
|
3271
|
+
...Array.isArray(fm.links) ? { links: fm.links } : {},
|
|
3272
|
+
...fm.intentGoal ? { intentGoal: fm.intentGoal } : {}
|
|
3273
|
+
});
|
|
3274
|
+
try {
|
|
3275
|
+
await sourceStorage.writeMemoryFrontmatter(memory, {
|
|
3276
|
+
status: "archived",
|
|
3277
|
+
archivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3278
|
+
});
|
|
3279
|
+
} catch (err) {
|
|
3280
|
+
try {
|
|
3281
|
+
const destMem = await destStorage.getMemoryById(destId);
|
|
3282
|
+
if (destMem) {
|
|
3283
|
+
await destStorage.writeMemoryFrontmatter(destMem, {
|
|
3284
|
+
status: "archived",
|
|
3285
|
+
archivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3288
|
+
} catch {
|
|
3289
|
+
}
|
|
3290
|
+
throw err;
|
|
3291
|
+
}
|
|
3292
|
+
return destId;
|
|
3293
|
+
}
|
|
3294
|
+
async function appendTombstoneFn(wiring, namespace, input) {
|
|
3295
|
+
const storage = await wiring.orchestrator.getStorage(namespace);
|
|
3296
|
+
const enabled = typeof storage.isTombstonesEnabled === "function" ? storage.isTombstonesEnabled() : true;
|
|
3297
|
+
const result = await storage.appendTombstone({
|
|
3298
|
+
reason: input.reason,
|
|
3299
|
+
createdBy: "user_correction",
|
|
3300
|
+
sourceMemoryId: input.sourceMemoryId,
|
|
3301
|
+
rawContent: input.rawContent,
|
|
3302
|
+
...input.entityRef ? { entityRef: input.entityRef } : {},
|
|
3303
|
+
...input.supersessionKey ? { supersessionKey: input.supersessionKey } : {}
|
|
3304
|
+
});
|
|
3305
|
+
if (result === null && enabled) {
|
|
3306
|
+
throw new CorrectionContractError(
|
|
3307
|
+
`tombstone persistence failed for memory ${input.sourceMemoryId} (tombstones enabled but store returned null \u2014 I/O error swallowed)`
|
|
3308
|
+
);
|
|
3309
|
+
}
|
|
3310
|
+
return result;
|
|
3311
|
+
}
|
|
3312
|
+
async function registerRedactionRuleFn(wiring, namespace, pattern) {
|
|
3313
|
+
const storage = await wiring.orchestrator.getStorage(namespace);
|
|
3314
|
+
const dir = path5.join(storage.dir, "state", "corrections", "redaction-rules");
|
|
3315
|
+
await mkdir3(dir, { recursive: true });
|
|
3316
|
+
const slug = pattern.replace(/[^a-zA-Z0-9]+/g, "-").slice(0, 64) || "rule";
|
|
3317
|
+
await writeFile3(
|
|
3318
|
+
path5.join(dir, `${slug}.json`),
|
|
3319
|
+
`${JSON.stringify({ pattern, namespace, createdAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
3320
|
+
`,
|
|
3321
|
+
"utf-8"
|
|
3322
|
+
);
|
|
3323
|
+
}
|
|
3324
|
+
async function appendAuditRecordFn(wiring, namespace, record) {
|
|
3325
|
+
const storage = await wiring.orchestrator.getStorage(namespace);
|
|
3326
|
+
const id = await storage.writeMemory("correction", buildAuditBody(record), {
|
|
3327
|
+
source: "correction-contract",
|
|
3328
|
+
confidence: 1,
|
|
3329
|
+
tags: ["correction-audit", `plan:${record.planId}`, `classification:${record.classification}`]
|
|
3330
|
+
});
|
|
3331
|
+
return id;
|
|
3332
|
+
}
|
|
3333
|
+
function buildAuditBody(record) {
|
|
3334
|
+
const sensitive = record.classification === "never_store" || record.outcome.results.some((r) => r.action.kind === "redaction_rule");
|
|
3335
|
+
const safeRequest = sensitive ? "[redacted \u2014 never-store/redaction correction text withheld from the audit trail]" : record.requestText.slice(0, 200);
|
|
3336
|
+
const lines = [
|
|
3337
|
+
`Correction plan ${record.planId} applied (${record.outcome.status}).`,
|
|
3338
|
+
"",
|
|
3339
|
+
`Request: ${safeRequest}`,
|
|
3340
|
+
`Classification: ${record.classification}`,
|
|
3341
|
+
`Applied at: ${record.outcome.appliedAt}`,
|
|
3342
|
+
"",
|
|
3343
|
+
"Actions:"
|
|
3344
|
+
];
|
|
3345
|
+
for (const r of record.outcome.results) {
|
|
3346
|
+
const withhold = r.action.kind === "redaction_rule" || record.classification === "never_store";
|
|
3347
|
+
const errPart = r.error ? withhold ? " (error withheld)" : ` (${r.error})` : "";
|
|
3348
|
+
lines.push(` - ${r.action.kind}: ${r.status}${errPart}`);
|
|
3349
|
+
}
|
|
3350
|
+
return lines.join("\n");
|
|
3351
|
+
}
|
|
3352
|
+
async function propagateFn(wiring, namespace, touchedMemoryIds) {
|
|
3353
|
+
for (const id of touchedMemoryIds) {
|
|
3354
|
+
try {
|
|
3355
|
+
const storage = await wiring.orchestrator.getStorage(namespace);
|
|
3356
|
+
const orchestrator = wiring.orchestrator;
|
|
3357
|
+
if (typeof orchestrator.indexPersistedMemory === "function") {
|
|
3358
|
+
await orchestrator.indexPersistedMemory(storage, id);
|
|
3359
|
+
}
|
|
3360
|
+
} catch {
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
function toCandidate(m, namespace, score) {
|
|
3365
|
+
return {
|
|
3366
|
+
memoryId: m.frontmatter.id,
|
|
3367
|
+
path: m.path,
|
|
3368
|
+
content: m.content,
|
|
3369
|
+
excerpt: m.content.slice(0, 160),
|
|
3370
|
+
...m.frontmatter.entityRef ? { entityRef: m.frontmatter.entityRef } : {},
|
|
3371
|
+
score
|
|
3372
|
+
// namespace is implicit (the planner scopes by namespace); keep it on the
|
|
3373
|
+
// candidate for diff rendering if needed.
|
|
3374
|
+
};
|
|
3375
|
+
}
|
|
3376
|
+
function toExecutorMemory(m) {
|
|
3377
|
+
const fm = m.frontmatter;
|
|
3378
|
+
const rawBody = fm.structuredAttributes ? stripAttributesSuffix(m.content) : m.content;
|
|
3379
|
+
return {
|
|
3380
|
+
memoryId: fm.id,
|
|
3381
|
+
content: m.content,
|
|
3382
|
+
category: fm.category,
|
|
3383
|
+
rawContent: rawBody,
|
|
3384
|
+
...fm.entityRef ? { entityRef: fm.entityRef } : {}
|
|
3385
|
+
};
|
|
3386
|
+
}
|
|
3387
|
+
function isCorrectionFeatureEnabled(config) {
|
|
3388
|
+
if (typeof config.correctionEnabled === "boolean") return config.correctionEnabled;
|
|
3389
|
+
const nested = config.correction;
|
|
3390
|
+
if (nested && typeof nested.enabled === "boolean") return nested.enabled;
|
|
3391
|
+
if (nested && typeof nested.enabled === "string") return nested.enabled === "true" || nested.enabled === "1";
|
|
3392
|
+
return readCorrectionFlag(config, "enabled", true);
|
|
3393
|
+
}
|
|
3394
|
+
function readCorrectionFlag(config, key, fallback) {
|
|
3395
|
+
const nested = config.correction;
|
|
3396
|
+
if (nested && typeof nested[key] === "boolean") return nested[key];
|
|
3397
|
+
if (nested && typeof nested[key] === "string") return nested[key] === "true" || nested[key] === "1";
|
|
3398
|
+
const raw = config[`correction${capitalize(key)}`];
|
|
3399
|
+
if (typeof raw === "boolean") return raw;
|
|
3400
|
+
if (typeof raw === "string") return raw === "true" || raw === "1";
|
|
3401
|
+
return fallback;
|
|
3402
|
+
}
|
|
3403
|
+
function readCorrectionNumber(config, key, fallback) {
|
|
3404
|
+
const nested = config.correction;
|
|
3405
|
+
if (nested) {
|
|
3406
|
+
const nv = nested[key];
|
|
3407
|
+
if (typeof nv === "number" && Number.isFinite(nv)) return nv;
|
|
3408
|
+
if (typeof nv === "string") {
|
|
3409
|
+
const nn = Number(nv);
|
|
3410
|
+
if (Number.isFinite(nn)) return nn;
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
const raw = config[`correction${capitalize(key)}`];
|
|
3414
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
|
3415
|
+
if (typeof raw === "string") {
|
|
3416
|
+
const n = Number(raw);
|
|
3417
|
+
if (Number.isFinite(n)) return n;
|
|
3418
|
+
}
|
|
3419
|
+
return fallback;
|
|
3420
|
+
}
|
|
3421
|
+
function capitalize(s) {
|
|
3422
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3423
|
+
}
|
|
3424
|
+
function errMsg2(err) {
|
|
3425
|
+
if (err instanceof Error) return err.message;
|
|
3426
|
+
return String(err);
|
|
3427
|
+
}
|
|
3428
|
+
var CLASSIFY_SYSTEM_PROMPT = `You classify memory corrections and draft per-memory actions.
|
|
3429
|
+
|
|
3430
|
+
Given a correction statement and candidate memories, respond with a JSON object:
|
|
3431
|
+
{
|
|
3432
|
+
"classification": "wrong" | "outdated" | "incomplete" | "wrong_scope" | "never_store",
|
|
3433
|
+
"confidence": <number 0..1>,
|
|
3434
|
+
"actions": [<one or more correction actions>],
|
|
3435
|
+
"relevance": [{"memoryId": "<id>", "why": "<one short sentence>"}]
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
Action shapes:
|
|
3439
|
+
- {"kind":"supersede","loserId":"<id>","replacement":{"content":"<new fact>"}}
|
|
3440
|
+
- {"kind":"edit","memoryId":"<id>","patch":"<new full content>"}
|
|
3441
|
+
- {"kind":"retract","memoryId":"<id>"}
|
|
3442
|
+
- {"kind":"rescope","memoryId":"<id>","toNamespace":"<ns>"}
|
|
3443
|
+
- {"kind":"redaction_rule","pattern":"<bounded literal or regex>"}
|
|
3444
|
+
|
|
3445
|
+
Only emit actions you are confident in. If uncertain, return confidence < 0.5 and few actions.`;
|
|
3446
|
+
function buildClassifyPrompt(text, candidates) {
|
|
3447
|
+
const lines = [
|
|
3448
|
+
`Correction: ${text}`,
|
|
3449
|
+
"",
|
|
3450
|
+
"Candidate memories:"
|
|
3451
|
+
];
|
|
3452
|
+
for (const c of candidates.slice(0, 20)) {
|
|
3453
|
+
lines.push(`[${c.memoryId}] ${c.excerpt}`);
|
|
3454
|
+
}
|
|
3455
|
+
lines.push("", "Respond with the JSON object only.");
|
|
3456
|
+
return lines.join("\n");
|
|
3457
|
+
}
|
|
3458
|
+
function parseClassifyResponse(raw, candidates) {
|
|
3459
|
+
let parsed;
|
|
3460
|
+
try {
|
|
3461
|
+
parsed = JSON.parse(raw);
|
|
3462
|
+
} catch {
|
|
3463
|
+
return fallbackClassification(candidates, "LLM returned non-JSON response");
|
|
3464
|
+
}
|
|
3465
|
+
if (!parsed || typeof parsed !== "object") {
|
|
3466
|
+
return fallbackClassification(candidates, "LLM returned non-object response");
|
|
3467
|
+
}
|
|
3468
|
+
const obj = parsed;
|
|
3469
|
+
const classification = isClassification(obj.classification) ? obj.classification : "outdated";
|
|
3470
|
+
const confidence = typeof obj.confidence === "number" ? Math.min(1, Math.max(0, obj.confidence)) : 0.5;
|
|
3471
|
+
const rawActions = Array.isArray(obj.actions) ? obj.actions : [];
|
|
3472
|
+
const actions = [];
|
|
3473
|
+
const warnings = [];
|
|
3474
|
+
for (const rawAction of rawActions) {
|
|
3475
|
+
try {
|
|
3476
|
+
validateCorrectionAction(rawAction);
|
|
3477
|
+
actions.push(rawAction);
|
|
3478
|
+
} catch (err) {
|
|
3479
|
+
warnings.push(`dropped malformed action: ${errMsg2(err)}`);
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
const relevance = Array.isArray(obj.relevance) ? obj.relevance.filter((r) => !!r && typeof r === "object").map((r) => ({
|
|
3483
|
+
memoryId: typeof r.memoryId === "string" ? r.memoryId : "",
|
|
3484
|
+
why: typeof r.why === "string" ? r.why : ""
|
|
3485
|
+
})).filter((r) => r.memoryId.length > 0) : [];
|
|
3486
|
+
return {
|
|
3487
|
+
classification,
|
|
3488
|
+
confidence,
|
|
3489
|
+
actions,
|
|
3490
|
+
relevance,
|
|
3491
|
+
warnings
|
|
3492
|
+
};
|
|
3493
|
+
}
|
|
3494
|
+
function isClassification(value) {
|
|
3495
|
+
return value === "wrong" || value === "outdated" || value === "incomplete" || value === "wrong_scope" || value === "never_store";
|
|
3496
|
+
}
|
|
3497
|
+
function fallbackClassification(candidates, reason) {
|
|
3498
|
+
return {
|
|
3499
|
+
classification: "outdated",
|
|
3500
|
+
confidence: 0,
|
|
3501
|
+
actions: [],
|
|
3502
|
+
relevance: candidates.map((c) => ({ memoryId: c.memoryId, why: "located for review" })),
|
|
3503
|
+
warnings: [reason],
|
|
3504
|
+
fallback: true
|
|
3505
|
+
};
|
|
3506
|
+
}
|
|
3507
|
+
|
|
2134
3508
|
// src/procedural/procedure-miner.ts
|
|
2135
3509
|
import { createHash as createHash2 } from "crypto";
|
|
2136
|
-
import { mkdir as
|
|
2137
|
-
import
|
|
3510
|
+
import { mkdir as mkdir4, rm, stat } from "fs/promises";
|
|
3511
|
+
import path6 from "path";
|
|
2138
3512
|
var PROCEDURE_CLUSTER_ATTR_MAX = 500;
|
|
2139
3513
|
var PROCEDURE_CLUSTER_LOCK_STALE_MS = 12e4;
|
|
2140
3514
|
var PROCEDURE_CLUSTER_LOCK_TIMEOUT_MS = 3e4;
|
|
@@ -2154,14 +3528,14 @@ function sleep(ms) {
|
|
|
2154
3528
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2155
3529
|
}
|
|
2156
3530
|
async function acquireClusterWriteLock(memoryDir, clusterHash) {
|
|
2157
|
-
const lockRoot =
|
|
2158
|
-
const lockDir =
|
|
2159
|
-
await
|
|
3531
|
+
const lockRoot = path6.join(path6.resolve(memoryDir), "state", "procedure-miner-locks");
|
|
3532
|
+
const lockDir = path6.join(lockRoot, `${clusterHash}.lock`);
|
|
3533
|
+
await mkdir4(lockRoot, { recursive: true });
|
|
2160
3534
|
const startedAt = Date.now();
|
|
2161
3535
|
let attempts = 0;
|
|
2162
3536
|
while (true) {
|
|
2163
3537
|
try {
|
|
2164
|
-
await
|
|
3538
|
+
await mkdir4(lockDir);
|
|
2165
3539
|
return async () => {
|
|
2166
3540
|
await rm(lockDir, { recursive: true, force: true });
|
|
2167
3541
|
};
|
|
@@ -2186,7 +3560,7 @@ async function acquireClusterWriteLock(memoryDir, clusterHash) {
|
|
|
2186
3560
|
}
|
|
2187
3561
|
}
|
|
2188
3562
|
async function withClusterWriteLock(memoryDir, clusterHash, fn) {
|
|
2189
|
-
const inProcessKey = `${
|
|
3563
|
+
const inProcessKey = `${path6.resolve(memoryDir)}:${clusterHash}`;
|
|
2190
3564
|
const previous = inProcessClusterWriteLocks.get(inProcessKey) ?? Promise.resolve();
|
|
2191
3565
|
let releaseInProcess;
|
|
2192
3566
|
const current = previous.catch(() => void 0).then(
|
|
@@ -2425,7 +3799,7 @@ function formatProcedureStatsText(report) {
|
|
|
2425
3799
|
}
|
|
2426
3800
|
|
|
2427
3801
|
// src/admin/admin-surfaces.ts
|
|
2428
|
-
import
|
|
3802
|
+
import path7 from "path";
|
|
2429
3803
|
var REDACTED = "<redacted>";
|
|
2430
3804
|
var SENSITIVE_KEY_RE = /(token|secret|password|api[_-]?key|bearer|authorization|credential|private[_-]?key)/i;
|
|
2431
3805
|
var BEARER_VALUE_RE = /^(bearer|sk-|pk-|xox[bp]-|AKIA)/i;
|
|
@@ -2656,7 +4030,7 @@ async function auditTranscripts(memoryDir) {
|
|
|
2656
4030
|
movedEntries: file.groups.reduce((sum, group) => sum + group.entryCount, 0),
|
|
2657
4031
|
unmovableLines: file.unmovableLines
|
|
2658
4032
|
}));
|
|
2659
|
-
const mixedOtherDefault = files.some((file) => file.sourceRelPath.split(
|
|
4033
|
+
const mixedOtherDefault = files.some((file) => file.sourceRelPath.split(path7.sep).includes("other"));
|
|
2660
4034
|
const summary = plan.movedEntries === 0 ? "No mixed-session transcript data detected; nothing to migrate." : `Found ${plan.movedEntries} entries across ${plan.distinctSessions} distinct sessions stranded in shared/legacy directories. Run the CLI migration command to re-home them.`;
|
|
2661
4035
|
return {
|
|
2662
4036
|
generatedAt: plan.generatedAt,
|
|
@@ -5293,6 +6667,69 @@ var EngramAccessService = class {
|
|
|
5293
6667
|
}
|
|
5294
6668
|
});
|
|
5295
6669
|
}
|
|
6670
|
+
// -------------------------------------------------------------------------
|
|
6671
|
+
// Correction Contract (issue #1580) — one plan/apply pipeline for every
|
|
6672
|
+
// memory correction. The CorrectionService owns the planner + executor; the
|
|
6673
|
+
// access-service constructs it lazily via the wiring helper and delegates.
|
|
6674
|
+
// Each method below is thin wiring (≤4 lines) per the god-file ratchet.
|
|
6675
|
+
// -------------------------------------------------------------------------
|
|
6676
|
+
/** Whether the memory_correct_plan / memory_correct_apply tools should appear in tools/list (rule 39). Same reader as the runtime gate so visibility + enforcement stay in sync. */
|
|
6677
|
+
get correctionSurfaceVisible() {
|
|
6678
|
+
return isCorrectionFeatureEnabled(this.orchestrator.config);
|
|
6679
|
+
}
|
|
6680
|
+
_correctionService = null;
|
|
6681
|
+
/** Lazily construct + cache the CorrectionService with deps wired from the orchestrator. */
|
|
6682
|
+
correctionService() {
|
|
6683
|
+
if (this._correctionService) return this._correctionService;
|
|
6684
|
+
const service = createCorrectionService({
|
|
6685
|
+
orchestrator: this.orchestrator,
|
|
6686
|
+
resolveAuthorizedNamespace: async (req) => this.resolveWritableNamespace(req.namespace, req.sessionKey, req.principal),
|
|
6687
|
+
resolveReadableNamespaces: (req) => {
|
|
6688
|
+
const principal = this.resolveRequestPrincipal(req.sessionKey, req.principal);
|
|
6689
|
+
return recallNamespacesForPrincipal(principal, this.orchestrator.config);
|
|
6690
|
+
},
|
|
6691
|
+
canWriteNamespace: (req) => {
|
|
6692
|
+
try {
|
|
6693
|
+
this.resolveWritableNamespace(req.namespace, req.sessionKey, req.principal);
|
|
6694
|
+
return Promise.resolve(true);
|
|
6695
|
+
} catch {
|
|
6696
|
+
return Promise.resolve(false);
|
|
6697
|
+
}
|
|
6698
|
+
},
|
|
6699
|
+
// Wire the orchestrator's extraction LLM so classify+draft actually
|
|
6700
|
+
// drafts actions instead of always hitting the deterministic fallback
|
|
6701
|
+
// (review thread PG5). The planner's classifyAndDraft already falls back
|
|
6702
|
+
// on any LLM outage, so returning null (LLM disabled/cooldown) or a
|
|
6703
|
+
// thrown error both degrade safely to the deterministic path (rule 13).
|
|
6704
|
+
llmComplete: async ({ system, user }) => {
|
|
6705
|
+
const result = await this.orchestrator.localLlm.chatCompletion(
|
|
6706
|
+
[
|
|
6707
|
+
{ role: "system", content: system },
|
|
6708
|
+
{ role: "user", content: user }
|
|
6709
|
+
],
|
|
6710
|
+
{ operation: "correction-classify", priority: "background" }
|
|
6711
|
+
);
|
|
6712
|
+
if (!result) {
|
|
6713
|
+
throw new Error("correction classify+draft: local LLM unavailable (disabled or in cooldown)");
|
|
6714
|
+
}
|
|
6715
|
+
return result.content;
|
|
6716
|
+
}
|
|
6717
|
+
});
|
|
6718
|
+
this._correctionService = service;
|
|
6719
|
+
return service;
|
|
6720
|
+
}
|
|
6721
|
+
async correctionPlan(request) {
|
|
6722
|
+
return this.correctionService().plan(request);
|
|
6723
|
+
}
|
|
6724
|
+
async correctionApply(planId, opts) {
|
|
6725
|
+
return this.correctionService().apply(planId, opts);
|
|
6726
|
+
}
|
|
6727
|
+
async correctionListPending(opts) {
|
|
6728
|
+
return this.correctionService().listPending(opts);
|
|
6729
|
+
}
|
|
6730
|
+
async correctionDiscard(planId, opts) {
|
|
6731
|
+
return this.correctionService().discard(planId, opts);
|
|
6732
|
+
}
|
|
5296
6733
|
async memoryBrowse(request = {}) {
|
|
5297
6734
|
const resolvedNamespace = this.resolveReadableNamespace(
|
|
5298
6735
|
request.namespace,
|
|
@@ -7355,7 +8792,7 @@ ${next}`);
|
|
|
7355
8792
|
const resolvedNs = this.resolveWritableNamespace(request.namespace, request.sessionKey, request.principal);
|
|
7356
8793
|
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
7357
8794
|
const storageDir = storage.dir;
|
|
7358
|
-
const { writeFile:
|
|
8795
|
+
const { writeFile: writeFile4, mkdir: mkdir5 } = await import("fs/promises");
|
|
7359
8796
|
const { join: join2, resolve: resolve2 } = await import("path");
|
|
7360
8797
|
const safeKey = request.sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
7361
8798
|
if (!safeKey) throw new EngramAccessInputError("sessionKey is required");
|
|
@@ -7364,10 +8801,10 @@ ${next}`);
|
|
|
7364
8801
|
if (!resolved.startsWith(resolve2(storageDir))) {
|
|
7365
8802
|
throw new EngramAccessInputError("Invalid sessionKey");
|
|
7366
8803
|
}
|
|
7367
|
-
await
|
|
8804
|
+
await mkdir5(checkpointDir, { recursive: true });
|
|
7368
8805
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7369
8806
|
const filePath = join2(checkpointDir, `checkpoint-${ts}.md`);
|
|
7370
|
-
await
|
|
8807
|
+
await writeFile4(filePath, request.context, "utf-8");
|
|
7371
8808
|
return { saved: true };
|
|
7372
8809
|
}
|
|
7373
8810
|
async lcmStatus() {
|
|
@@ -8256,6 +9693,7 @@ export {
|
|
|
8256
9693
|
DECISION_SUBCOMMANDS,
|
|
8257
9694
|
isCodegraphToolName,
|
|
8258
9695
|
DELTA_SUBCOMMANDS,
|
|
9696
|
+
CorrectionContractError,
|
|
8259
9697
|
computeProcedureStats,
|
|
8260
9698
|
formatProcedureStatsText,
|
|
8261
9699
|
EngramAccessInputError,
|
|
@@ -8263,4 +9701,4 @@ export {
|
|
|
8263
9701
|
shapeMemorySummary,
|
|
8264
9702
|
EngramAccessService
|
|
8265
9703
|
};
|
|
8266
|
-
//# sourceMappingURL=chunk-
|
|
9704
|
+
//# sourceMappingURL=chunk-EVX52NCY.js.map
|