@remnic/core 9.3.708 → 9.3.710
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
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* correction/correction-executor.ts — the only writer for corrections
|
|
3
|
+
* (issue #1580 PR 2).
|
|
4
|
+
*
|
|
5
|
+
* Applies a {@link CorrectionPlan} in NON-DESTRUCTIVE ORDER
|
|
6
|
+
* (rule 25 / checklist §14 — new state confirmed before old state destroyed):
|
|
7
|
+
*
|
|
8
|
+
* 1. Write replacement / edited memories first (through the injected
|
|
9
|
+
* persist pipeline so catalog/reindex/dedup fire — rule 43; edits go
|
|
10
|
+
* through page-versioning so every change is revertable).
|
|
11
|
+
* 2. Then supersede / retract losers: status flip + `validUntil` stamp
|
|
12
|
+
* (#1578, when gated on) + tombstone append (#1579).
|
|
13
|
+
* 3. Then propagation: QMD reindex for touched files, graph edge updates,
|
|
14
|
+
* belief-ledger claim status (optional-package dynamic import, rule 57),
|
|
15
|
+
* profile.md line removal when an affected memory was profile-sourced.
|
|
16
|
+
* 4. Append an audit record to `corrections/` capturing plan + outcome —
|
|
17
|
+
* corrections are themselves memories, searchable and namespaced.
|
|
18
|
+
* 5. Partial failure → tagged partial result (rule 34; checklist §22):
|
|
19
|
+
* never a half-applied plan reported as success, and never old state
|
|
20
|
+
* destroyed for an action whose replacement write failed.
|
|
21
|
+
*
|
|
22
|
+
* Concurrency: a plan may be applied exactly once. A second apply of the same
|
|
23
|
+
* plan is rejected (plan consumed). Backed by the planner's atomic plan-store
|
|
24
|
+
* (`markConsumed`) under `serializeMutations` keyed by plan id (rule 40).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { serializeMutations } from "../utils/serialize-mutations.js";
|
|
28
|
+
import type { CorrectionPlanner } from "./correction-planner.js";
|
|
29
|
+
import {
|
|
30
|
+
CorrectionContractError,
|
|
31
|
+
validateCorrectionAction,
|
|
32
|
+
validateRedactionPattern,
|
|
33
|
+
type CorrectionAction,
|
|
34
|
+
type CorrectionActionResult,
|
|
35
|
+
type CorrectionOutcome,
|
|
36
|
+
type CorrectionPlan,
|
|
37
|
+
} from "./correction-contract.js";
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Injected collaborators
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/** A memory the executor is operating on (subset of MemoryFile). */
|
|
44
|
+
export interface ExecutorMemory {
|
|
45
|
+
memoryId: string;
|
|
46
|
+
content: string;
|
|
47
|
+
category: string;
|
|
48
|
+
/** True when the memory has frontmatter provenance (#1575 sourceQuote). */
|
|
49
|
+
sourceQuote?: string;
|
|
50
|
+
/** Structured-attribute supersession key, when one exists. */
|
|
51
|
+
supersessionKey?: string;
|
|
52
|
+
entityRef?: string;
|
|
53
|
+
/** Raw content used for the tombstone hash (rule 23). */
|
|
54
|
+
rawContent: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ExecutorDeps {
|
|
58
|
+
/** Lookup a memory by id within the plan's namespace. null if not found. */
|
|
59
|
+
getMemory(namespace: string, memoryId: string): Promise<ExecutorMemory | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Persist a NEW memory through the orchestrator's normal write pipeline
|
|
62
|
+
* (catalog/reindex/dedup fire — rule 43). Returns the new memory id.
|
|
63
|
+
* Used by `supersede` (the replacement).
|
|
64
|
+
*/
|
|
65
|
+
writeReplacement(
|
|
66
|
+
namespace: string,
|
|
67
|
+
draft: {
|
|
68
|
+
content: string;
|
|
69
|
+
category?: string;
|
|
70
|
+
confidence?: number;
|
|
71
|
+
tags?: string[];
|
|
72
|
+
entityRef?: string;
|
|
73
|
+
validAt?: string;
|
|
74
|
+
observedAt?: string;
|
|
75
|
+
structuredAttributes?: Record<string, string>;
|
|
76
|
+
supersedes?: string;
|
|
77
|
+
},
|
|
78
|
+
): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Apply a versioned edit to an existing memory (page-versioning — every
|
|
81
|
+
* change revertable). Returns the memory id. The patch is the NEW full
|
|
82
|
+
* content; the implementation snapshots the prior version.
|
|
83
|
+
*/
|
|
84
|
+
applyEdit(
|
|
85
|
+
namespace: string,
|
|
86
|
+
memoryId: string,
|
|
87
|
+
patch: string,
|
|
88
|
+
): Promise<string>;
|
|
89
|
+
/**
|
|
90
|
+
* Flip a memory's status to superseded/retracted and stamp `validUntil`
|
|
91
|
+
* when bi-temporal is gated on (#1578). Idempotent.
|
|
92
|
+
*/
|
|
93
|
+
retireMemory(
|
|
94
|
+
namespace: string,
|
|
95
|
+
memoryId: string,
|
|
96
|
+
opts: {
|
|
97
|
+
status: "superseded" | "retracted";
|
|
98
|
+
supersededBy?: string;
|
|
99
|
+
validUntil?: string;
|
|
100
|
+
},
|
|
101
|
+
): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Move a memory to a different namespace (rescope). The destination
|
|
104
|
+
* namespace is re-authorized by the service before the executor runs; the
|
|
105
|
+
* implementation performs the move atomically (write-then-unlink).
|
|
106
|
+
*/
|
|
107
|
+
rescopeMemory(namespace: string, memoryId: string, toNamespace: string): Promise<string>;
|
|
108
|
+
/**
|
|
109
|
+
* Append a tombstone (#1579) for a retired memory. Returns the tombstone
|
|
110
|
+
* id, or null if tombstones are disabled (off = pre-feature behavior).
|
|
111
|
+
*/
|
|
112
|
+
appendTombstone(
|
|
113
|
+
namespace: string,
|
|
114
|
+
input: {
|
|
115
|
+
reason: "correction" | "supersession" | "retraction";
|
|
116
|
+
sourceMemoryId: string;
|
|
117
|
+
rawContent: string;
|
|
118
|
+
entityRef?: string;
|
|
119
|
+
supersessionKey?: string;
|
|
120
|
+
},
|
|
121
|
+
): Promise<string | null>;
|
|
122
|
+
/**
|
|
123
|
+
* Persist a redaction rule so extraction consults it the same way tombstones
|
|
124
|
+
* are consulted (route through the same chokepoint check). Idempotent.
|
|
125
|
+
*/
|
|
126
|
+
registerRedactionRule(namespace: string, pattern: string): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Append an audit record under `corrections/` (existing storage category)
|
|
129
|
+
* capturing plan + outcome. Returns the audit memory id.
|
|
130
|
+
*/
|
|
131
|
+
appendAuditRecord(
|
|
132
|
+
namespace: string,
|
|
133
|
+
record: {
|
|
134
|
+
planId: string;
|
|
135
|
+
classification: CorrectionPlan["classification"];
|
|
136
|
+
outcome: CorrectionOutcome;
|
|
137
|
+
requestText: string;
|
|
138
|
+
},
|
|
139
|
+
): Promise<string>;
|
|
140
|
+
/**
|
|
141
|
+
* Post-write propagation: QMD reindex for touched files (checklist §31),
|
|
142
|
+
* graph edge updates, belief-ledger claim status. Best-effort — a failure
|
|
143
|
+
* here is recorded as a warning, never as a failed action (propagation is
|
|
144
|
+
* not part of the §14 non-destructive-order guarantee; it runs after).
|
|
145
|
+
*/
|
|
146
|
+
propagate(namespace: string, touchedMemoryIds: readonly string[]): Promise<void>;
|
|
147
|
+
/** Whether the bi-temporal gate (#1578) is on. When off, validUntil is omitted. */
|
|
148
|
+
readonly biTemporalEnabled: boolean;
|
|
149
|
+
/** Injected clock for deterministic tests. */
|
|
150
|
+
now(): Date;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Executor
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
export class CorrectionExecutor {
|
|
158
|
+
constructor(
|
|
159
|
+
private readonly deps: ExecutorDeps,
|
|
160
|
+
private readonly planner: CorrectionPlanner,
|
|
161
|
+
) {}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Apply a persisted plan by id. Idempotent-once: a second apply of the same
|
|
165
|
+
* plan is rejected. Returns the {@link CorrectionOutcome}.
|
|
166
|
+
*/
|
|
167
|
+
async apply(
|
|
168
|
+
namespace: string,
|
|
169
|
+
planId: string,
|
|
170
|
+
opts: {
|
|
171
|
+
confirm: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Authorize a rescope destination namespace. Bound per-request by the
|
|
174
|
+
* service from the namespace policy + principal so a plan can never
|
|
175
|
+
* write into a namespace the caller lacks write scope for (review
|
|
176
|
+
* thread: authorize-rescope-destination). Defaults to allow when the
|
|
177
|
+
* source namespace already resolves to a writable scope (single-tenant).
|
|
178
|
+
*/
|
|
179
|
+
canWriteDestination?: (namespace: string) => Promise<boolean>;
|
|
180
|
+
},
|
|
181
|
+
): Promise<CorrectionOutcome> {
|
|
182
|
+
if (!opts.confirm) {
|
|
183
|
+
throw new CorrectionContractError(
|
|
184
|
+
"Correction apply requires explicit confirmation (correction.applyRequiresConfirm).",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
// serializeMutations on the plan id so two concurrent applies of the SAME
|
|
188
|
+
// plan serialize — the second observes the consumed status and rejects.
|
|
189
|
+
return serializeMutations(`correction-apply:${namespace}:${planId}`, () =>
|
|
190
|
+
this.applyInternal(namespace, planId, opts.canWriteDestination),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private async applyInternal(namespace: string, planId: string, canWriteDestination?: (namespace: string) => Promise<boolean>): Promise<CorrectionOutcome> {
|
|
195
|
+
const plan = await this.planner.loadPlan(namespace, planId);
|
|
196
|
+
if (!plan) {
|
|
197
|
+
throw new CorrectionContractError(`Correction plan not found: ${planId}`);
|
|
198
|
+
}
|
|
199
|
+
if (plan.namespace !== namespace) {
|
|
200
|
+
// Cross-namespace foreign-id guard (rule 42 / checklist §16).
|
|
201
|
+
throw new CorrectionContractError(
|
|
202
|
+
`Correction plan ${planId} belongs to namespace '${plan.namespace}', not '${namespace}'.`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
if (plan.status === "applied" || plan.status === "partial" || plan.status === "applying") {
|
|
206
|
+
throw new CorrectionContractError(
|
|
207
|
+
`Correction plan ${planId} has already been applied or is in progress (status=${plan.status}).`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
if (plan.status === "discarded") {
|
|
211
|
+
throw new CorrectionContractError(`Correction plan ${planId} has been discarded.`);
|
|
212
|
+
}
|
|
213
|
+
// TTL check — expired plans are rejected with a clear error.
|
|
214
|
+
if (this.deps.now().getTime() > new Date(plan.expiresAt).getTime()) {
|
|
215
|
+
await this.planner.markConsumed(namespace, planId, "discarded");
|
|
216
|
+
throw new CorrectionContractError(
|
|
217
|
+
`Correction plan ${planId} expired at ${plan.expiresAt} and has been discarded.`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Re-validate every action shape before applying (defense in depth).
|
|
222
|
+
for (const action of plan.actions) {
|
|
223
|
+
validateCorrectionAction(action);
|
|
224
|
+
if (action.kind === "redaction_rule") {
|
|
225
|
+
validateRedactionPattern(action.pattern);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Optimistically mark the plan `applying` BEFORE any mutation (review
|
|
230
|
+
// thread OgIqt). If the process dies mid-apply — or the final
|
|
231
|
+
// markConsumed("applied"|"partial") fails — the plan stays `applying`
|
|
232
|
+
// and is NOT silently retryable. A partially-applied plan must never be
|
|
233
|
+
// re-applied wholesale: re-running succeeded actions would duplicate
|
|
234
|
+
// replacements, tombstones, and audits. The operator inspects the
|
|
235
|
+
// outcome and files a NEW plan for any failed actions. This mark runs
|
|
236
|
+
// inside the serializeMutations lock so concurrent applies serialize.
|
|
237
|
+
try {
|
|
238
|
+
await this.planner.markConsumed(namespace, planId, "applying");
|
|
239
|
+
} catch {
|
|
240
|
+
// If we cannot even mark the plan in-progress, the filesystem is
|
|
241
|
+
// likely unwritable and mutations would fail too — fail closed now.
|
|
242
|
+
throw new CorrectionContractError(
|
|
243
|
+
`Correction plan ${planId}: cannot mark in-progress (filesystem unwritable?) — aborting before any mutation.`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const results: CorrectionActionResult[] = [];
|
|
248
|
+
const appliedTouched: string[] = [];
|
|
249
|
+
|
|
250
|
+
// ── Phase 1: replacement / edit writes (new state first) ───────────────
|
|
251
|
+
// For each supersede with a replacement, write the replacement FIRST. If
|
|
252
|
+
// the write fails, the loser is NOT superseded (§14: never destroy old
|
|
253
|
+
// state for an action whose replacement write failed).
|
|
254
|
+
for (const action of plan.actions) {
|
|
255
|
+
if (action.kind === "supersede" && action.replacement) {
|
|
256
|
+
// Preflight the loser BEFORE writing the replacement (review thread
|
|
257
|
+
// Of0pz): if the loser was deleted between plan and apply, writing a
|
|
258
|
+
// replacement creates an orphan fact that supersedes nothing. Phase 2
|
|
259
|
+
// (retireAndTombstone) re-checks via getMemory, so verifying here
|
|
260
|
+
// keeps the two phases in agreement and avoids the orphan write.
|
|
261
|
+
const loser = await this.deps.getMemory(namespace, action.loserId);
|
|
262
|
+
if (!loser) {
|
|
263
|
+
results.push({
|
|
264
|
+
action,
|
|
265
|
+
status: "failed",
|
|
266
|
+
error: `supersede loser not found: ${action.loserId}`,
|
|
267
|
+
});
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
const newId = await this.deps.writeReplacement(namespace, {
|
|
272
|
+
content: action.replacement.content,
|
|
273
|
+
...(action.replacement.category ? { category: action.replacement.category } : {}),
|
|
274
|
+
...(action.replacement.confidence !== undefined ? { confidence: action.replacement.confidence } : {}),
|
|
275
|
+
...(action.replacement.tags ? { tags: action.replacement.tags } : {}),
|
|
276
|
+
...(action.replacement.entityRef ? { entityRef: action.replacement.entityRef } : {}),
|
|
277
|
+
...(action.replacement.validAt ? { validAt: action.replacement.validAt } : {}),
|
|
278
|
+
...(action.replacement.observedAt ? { observedAt: action.replacement.observedAt } : {}),
|
|
279
|
+
...(action.replacement.structuredAttributes
|
|
280
|
+
? { structuredAttributes: action.replacement.structuredAttributes }
|
|
281
|
+
: {}),
|
|
282
|
+
supersedes: action.loserId,
|
|
283
|
+
});
|
|
284
|
+
results.push({ action, status: "applied", memoryId: newId });
|
|
285
|
+
appliedTouched.push(newId);
|
|
286
|
+
} catch (err) {
|
|
287
|
+
results.push({
|
|
288
|
+
action,
|
|
289
|
+
status: "failed",
|
|
290
|
+
error: errMsg(err),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
} else if (action.kind === "edit") {
|
|
294
|
+
try {
|
|
295
|
+
const editedId = await this.deps.applyEdit(namespace, action.memoryId, action.patch);
|
|
296
|
+
results.push({ action, status: "applied", memoryId: editedId });
|
|
297
|
+
appliedTouched.push(editedId);
|
|
298
|
+
} catch (err) {
|
|
299
|
+
results.push({ action, status: "failed", error: errMsg(err) });
|
|
300
|
+
}
|
|
301
|
+
} else if (action.kind === "redaction_rule") {
|
|
302
|
+
try {
|
|
303
|
+
await this.deps.registerRedactionRule(namespace, action.pattern);
|
|
304
|
+
results.push({ action, status: "applied" });
|
|
305
|
+
} catch (err) {
|
|
306
|
+
results.push({ action, status: "failed", error: errMsg(err) });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ── Phase 2: retire losers + tombstones ───────────────────────────────
|
|
312
|
+
// Only run for supersede/retract actions whose replacement write (if any)
|
|
313
|
+
// succeeded. A supersede WITHOUT a replacement is a pure retract.
|
|
314
|
+
for (const action of plan.actions) {
|
|
315
|
+
if (action.kind === "supersede") {
|
|
316
|
+
const replacementResult = results.find(
|
|
317
|
+
(r) => r.action === action && r.status === "applied",
|
|
318
|
+
);
|
|
319
|
+
// If the replacement write failed, skip retirement (§14).
|
|
320
|
+
if (action.replacement && !replacementResult) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
await this.retireAndTombstone(namespace, action, "supersession", results, appliedTouched, {
|
|
324
|
+
supersededBy: replacementResult?.memoryId,
|
|
325
|
+
});
|
|
326
|
+
} else if (action.kind === "retract") {
|
|
327
|
+
await this.retireAndTombstone(namespace, action, "retraction", results, appliedTouched);
|
|
328
|
+
} else if (action.kind === "rescope") {
|
|
329
|
+
try {
|
|
330
|
+
// Authorize the destination namespace BEFORE the move — the plan's
|
|
331
|
+
// toNamespace comes from the LLM/persisted plan and must not bypass
|
|
332
|
+
// the write ACL (review thread: authorize-rescope-destination).
|
|
333
|
+
// canWriteDestination defaults to allow when absent (single-tenant,
|
|
334
|
+
// where the source namespace already resolved to a writable scope).
|
|
335
|
+
const allowed = canWriteDestination ? await canWriteDestination(action.toNamespace) : true;
|
|
336
|
+
if (!allowed) {
|
|
337
|
+
results.push({
|
|
338
|
+
action,
|
|
339
|
+
status: "failed",
|
|
340
|
+
error: `rescope destination namespace not writable: ${action.toNamespace}`,
|
|
341
|
+
});
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const destId = await this.deps.rescopeMemory(namespace, action.memoryId, action.toNamespace);
|
|
345
|
+
results.push({ action, status: "applied", memoryId: action.memoryId });
|
|
346
|
+
appliedTouched.push(action.memoryId);
|
|
347
|
+
// Propagate the destination memory in its namespace too (review
|
|
348
|
+
// thread: propagate-rescoped-destination) — best-effort.
|
|
349
|
+
try {
|
|
350
|
+
await this.deps.propagate(action.toNamespace, [destId]);
|
|
351
|
+
} catch {
|
|
352
|
+
// non-fatal — the source propagation still fires.
|
|
353
|
+
}
|
|
354
|
+
} catch (err) {
|
|
355
|
+
results.push({ action, status: "failed", error: errMsg(err) });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ── Phase 3: propagation (post-write reindex + graph) ─────────────────
|
|
361
|
+
// Best-effort: a propagation failure is recorded as a warning on the
|
|
362
|
+
// outcome, never as a failed action (it runs AFTER the §14 guarantee).
|
|
363
|
+
const propagationWarnings: string[] = [];
|
|
364
|
+
if (appliedTouched.length > 0) {
|
|
365
|
+
try {
|
|
366
|
+
await this.deps.propagate(namespace, appliedTouched);
|
|
367
|
+
} catch (err) {
|
|
368
|
+
propagationWarnings.push(`propagation failed (non-fatal): ${errMsg(err)}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ── Phase 4: audit record ─────────────────────────────────────────────
|
|
373
|
+
const anyFailed = results.some((r) => r.status === "failed");
|
|
374
|
+
const status: CorrectionOutcome["status"] = anyFailed ? "partial" : "applied";
|
|
375
|
+
const appliedAt = this.deps.now().toISOString();
|
|
376
|
+
const outcome: CorrectionOutcome = {
|
|
377
|
+
planId,
|
|
378
|
+
status,
|
|
379
|
+
results,
|
|
380
|
+
auditMemoryId: "", // filled after the audit write
|
|
381
|
+
appliedAt,
|
|
382
|
+
};
|
|
383
|
+
if (propagationWarnings.length > 0) {
|
|
384
|
+
(outcome as CorrectionOutcome & { warnings?: string[] }).warnings = propagationWarnings;
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
const auditId = await this.deps.appendAuditRecord(namespace, {
|
|
388
|
+
planId,
|
|
389
|
+
classification: plan.classification,
|
|
390
|
+
outcome,
|
|
391
|
+
requestText: plan.request.text,
|
|
392
|
+
});
|
|
393
|
+
outcome.auditMemoryId = auditId;
|
|
394
|
+
} catch (err) {
|
|
395
|
+
// The audit record is part of the contract but a failure to write it
|
|
396
|
+
// must NOT undo the applied corrections. Record as a warning.
|
|
397
|
+
(outcome as CorrectionOutcome & { warnings?: string[] }).warnings = [
|
|
398
|
+
...propagationWarnings,
|
|
399
|
+
`audit record write failed (non-fatal): ${errMsg(err)}`,
|
|
400
|
+
];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ── Phase 5: mark plan consumed ───────────────────────────────────────
|
|
404
|
+
// Corrections are already applied (phases 1-4 succeeded). A markConsumed
|
|
405
|
+
// failure must NOT propagate — that would make a client retry re-apply all
|
|
406
|
+
// corrections (review thread: applyInternal-plan-unconsumed). Record as a
|
|
407
|
+
// warning; the pending plan is reconciled by TTL discard on the next pass.
|
|
408
|
+
try {
|
|
409
|
+
await this.planner.markConsumed(namespace, planId, status);
|
|
410
|
+
} catch (err) {
|
|
411
|
+
const w = outcome as CorrectionOutcome & { warnings?: string[] };
|
|
412
|
+
w.warnings = [...(w.warnings ?? []), `plan mark-consumed failed (non-fatal): ${errMsg(err)}`];
|
|
413
|
+
}
|
|
414
|
+
return outcome;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private async retireAndTombstone(
|
|
418
|
+
namespace: string,
|
|
419
|
+
action: CorrectionAction,
|
|
420
|
+
reason: "supersession" | "retraction",
|
|
421
|
+
results: CorrectionActionResult[],
|
|
422
|
+
appliedTouched: string[],
|
|
423
|
+
opts: { supersededBy?: string } = {},
|
|
424
|
+
): Promise<void> {
|
|
425
|
+
const memoryId =
|
|
426
|
+
action.kind === "supersede" ? action.loserId : action.kind === "retract" ? action.memoryId : null;
|
|
427
|
+
if (!memoryId) return;
|
|
428
|
+
try {
|
|
429
|
+
const memory = await this.deps.getMemory(namespace, memoryId);
|
|
430
|
+
if (!memory) {
|
|
431
|
+
results.push({
|
|
432
|
+
action,
|
|
433
|
+
status: "failed",
|
|
434
|
+
error: `memory not found: ${memoryId}`,
|
|
435
|
+
});
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const validUntil = this.deps.biTemporalEnabled ? this.deps.now().toISOString() : undefined;
|
|
439
|
+
// Write the tombstone BEFORE retiring the source memory (review thread
|
|
440
|
+
// PG9): if appendTombstone throws here, retireMemory has not run yet, so
|
|
441
|
+
// the source stays active, the action fails cleanly, and a retry
|
|
442
|
+
// operates on un-mutated state with no resurrection window. A tombstone
|
|
443
|
+
// for a still-active memory (if retire later fails) is benign — it only
|
|
444
|
+
// blocks re-ingestion of the same content, which is exactly the intent.
|
|
445
|
+
const tombstoneId = await this.deps.appendTombstone(namespace, {
|
|
446
|
+
reason,
|
|
447
|
+
sourceMemoryId: memoryId,
|
|
448
|
+
rawContent: memory.rawContent,
|
|
449
|
+
...(memory.entityRef ? { entityRef: memory.entityRef } : {}),
|
|
450
|
+
...(memory.supersessionKey ? { supersessionKey: memory.supersessionKey } : {}),
|
|
451
|
+
});
|
|
452
|
+
await this.deps.retireMemory(namespace, memoryId, {
|
|
453
|
+
status: reason === "supersession" ? "superseded" : "retracted",
|
|
454
|
+
...(opts.supersededBy ? { supersededBy: opts.supersededBy } : {}),
|
|
455
|
+
...(validUntil ? { validUntil } : {}),
|
|
456
|
+
});
|
|
457
|
+
results.push({
|
|
458
|
+
action,
|
|
459
|
+
status: "applied",
|
|
460
|
+
memoryId,
|
|
461
|
+
...(tombstoneId ? { tombstoneId } : {}),
|
|
462
|
+
});
|
|
463
|
+
appliedTouched.push(memoryId);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
results.push({ action, status: "failed", error: errMsg(err) });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function errMsg(err: unknown): string {
|
|
471
|
+
if (err instanceof Error) return err.message;
|
|
472
|
+
return String(err);
|
|
473
|
+
}
|