@remnic/core 9.51.1 → 9.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +6 -5
- package/dist/access-admin-ops-surface.js +16 -16
- package/dist/access-authorization-probe.d.ts +6 -5
- package/dist/access-authorization-probe.js +17 -17
- package/dist/access-boundary.d.ts +6 -5
- package/dist/access-boundary.js +16 -16
- package/dist/access-cli.js +39 -39
- package/dist/access-extraction-force-flush.d.ts +6 -5
- package/dist/access-extraction-force-flush.js +16 -16
- package/dist/access-http-lcm-compaction.d.ts +6 -5
- package/dist/access-http-lifecycle-flush.d.ts +6 -5
- package/dist/access-http-offline-stream.d.ts +6 -5
- package/dist/access-http-query.js +16 -16
- package/dist/access-http.d.ts +6 -5
- package/dist/access-http.js +22 -22
- package/dist/access-identity-continuity-surface.d.ts +5 -4
- package/dist/access-identity-continuity-surface.js +16 -16
- package/dist/access-lcm-surface.d.ts +6 -5
- package/dist/access-lcm-surface.js +16 -16
- package/dist/access-mcp.d.ts +6 -5
- package/dist/access-mcp.js +20 -20
- package/dist/access-namespace-preflight.js +16 -16
- package/dist/access-observe-write-surface.d.ts +6 -5
- package/dist/access-observe-write-surface.js +16 -16
- package/dist/access-offline-manifest.d.ts +5 -4
- package/dist/access-operations-batch.js +17 -17
- package/dist/access-operations.d.ts +9 -8
- package/dist/access-operations.js +19 -19
- package/dist/access-recall-concurrency.d.ts +6 -5
- package/dist/access-recall-concurrency.js +16 -16
- package/dist/access-recall-response.d.ts +6 -5
- package/dist/access-recall-response.js +16 -16
- package/dist/access-recall-surface.d.ts +6 -5
- package/dist/access-recall-surface.js +16 -16
- package/dist/access-schema.d.ts +88 -88
- package/dist/{access-service-BY0KJ44c.d.ts → access-service-Cbk3bNbD.d.ts} +3 -3
- package/dist/access-service-helpers.d.ts +6 -5
- package/dist/access-service.d.ts +6 -5
- package/dist/access-service.js +16 -16
- package/dist/access-surface-catalog.d.ts +6 -5
- package/dist/access-wearables-meetings-surface.d.ts +2 -2
- package/dist/bootstrap.d.ts +5 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +7 -7
- package/dist/buffer.d.ts +1 -1
- package/dist/{capsule-merge-XMFBXVDP.js → capsule-merge-HCJJMLPS.js} +4 -4
- package/dist/causal-consolidation.js +8 -8
- package/dist/{chunk-PWN32TAG.js → chunk-2ASAS3CQ.js} +2 -2
- package/dist/{chunk-WQM326GS.js → chunk-2LY6GIIE.js} +2 -2
- package/dist/{chunk-JL43BVDE.js → chunk-2OIQDTDC.js} +2 -2
- package/dist/{chunk-33SV3E43.js → chunk-3OY7KXHM.js} +3 -3
- package/dist/{chunk-AIUBBHSF.js → chunk-5UZ6UCOZ.js} +3 -3
- package/dist/chunk-5WITETWU.js +591 -0
- package/dist/chunk-5WITETWU.js.map +1 -0
- package/dist/{chunk-UT5BV5OG.js → chunk-7HXZJAID.js} +2 -2
- package/dist/{chunk-M7BXDKPR.js → chunk-7M6NYQWB.js} +3 -3
- package/dist/{chunk-TPTECFWR.js → chunk-B6LZ4IBH.js} +3 -3
- package/dist/{chunk-J5LLX6IL.js → chunk-BF6SXNQJ.js} +10 -10
- package/dist/{chunk-4PC4PRJE.js → chunk-BKIFTXHR.js} +2 -2
- package/dist/{chunk-VVG2BOKA.js → chunk-BMEYMKWX.js} +2 -2
- package/dist/{chunk-DCZBSE4F.js → chunk-D5YOZ66N.js} +18 -18
- package/dist/{chunk-BHXYAVUM.js → chunk-DLG5V6ND.js} +2 -2
- package/dist/{chunk-52UKFTUK.js → chunk-F4EU6VUC.js} +2 -2
- package/dist/{chunk-M4A44LUA.js → chunk-GSTIBXBW.js} +2 -2
- package/dist/{chunk-W5XVQFLM.js → chunk-GXFSQ6SD.js} +2 -2
- package/dist/{chunk-XADV5IZ3.js → chunk-HDIUYMSB.js} +5 -5
- package/dist/{chunk-FXTYIA46.js → chunk-J272WKX2.js} +2 -2
- package/dist/{chunk-ENTCVTQR.js → chunk-JJRVPWFY.js} +11 -1
- package/dist/chunk-JJRVPWFY.js.map +1 -0
- package/dist/{chunk-JYL3BVVZ.js → chunk-JSEJH2YQ.js} +2 -2
- package/dist/{chunk-5UDMFE3Q.js → chunk-MLXZD4WM.js} +2 -2
- package/dist/{chunk-PUSLMZBQ.js → chunk-NOEMGE56.js} +2 -2
- package/dist/{chunk-C5QLI33R.js → chunk-QINJITDS.js} +2 -2
- package/dist/{chunk-45BRYON6.js → chunk-QPOHMO6M.js} +2 -2
- package/dist/{chunk-ZQOCRIGA.js → chunk-QYN5PXDZ.js} +3 -3
- package/dist/{chunk-PA45XCUD.js → chunk-RANKJI5T.js} +2 -2
- package/dist/{chunk-ZHY5TMJG.js → chunk-RMHMUUBW.js} +2392 -628
- package/dist/chunk-RMHMUUBW.js.map +1 -0
- package/dist/{chunk-37ZO6LLJ.js → chunk-RYLEDKQ2.js} +2 -2
- package/dist/{chunk-CV6BBVYV.js → chunk-TEMSWHQD.js} +2 -2
- package/dist/{chunk-QGR6OVGT.js → chunk-VFSAWRMZ.js} +2 -2
- package/dist/{chunk-NLK2VZYZ.js → chunk-VXCNQYTC.js} +2 -2
- package/dist/{chunk-AZ2QBGYB.js → chunk-W7Y5JDVB.js} +1 -1
- package/dist/{chunk-VBHPOWBJ.js → chunk-WD5B5AIE.js} +1051 -591
- package/dist/chunk-WD5B5AIE.js.map +1 -0
- package/dist/{chunk-OYESWJ43.js → chunk-WLRD2TXF.js} +15 -10
- package/dist/chunk-WLRD2TXF.js.map +1 -0
- package/dist/{chunk-4F6X3RXW.js → chunk-X57M2Q4I.js} +2 -2
- package/dist/{chunk-6MTSFX7A.js → chunk-X7XEDOSL.js} +8 -8
- package/dist/{chunk-UEVLYVNW.js → chunk-YXKVLYXU.js} +2 -2
- package/dist/{chunk-OQAL6SIR.js → chunk-ZMSZVISA.js} +2 -2
- package/dist/{cli-CnOkH9hA.d.ts → cli-DMAFI0qt.d.ts} +3 -3
- package/dist/cli.d.ts +7 -6
- package/dist/cli.js +33 -33
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +7 -7
- package/dist/connectors/codex-materialize-runner.js +7 -7
- package/dist/connectors/index.js +7 -7
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/consolidation-undo.js +3 -3
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +1 -1
- package/dist/convergence-refresh.js +9 -9
- package/dist/corpus-watermark.js +9 -9
- package/dist/dependency-propagation-delivery-D63r24pH.d.ts +41 -0
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +7 -7
- package/dist/explicit-capture.d.ts +5 -4
- package/dist/explicit-capture.js +4 -4
- package/dist/external-wiki-access.d.ts +6 -5
- package/dist/external-wiki-access.js +17 -17
- package/dist/external-wiki-mcp-tools.d.ts +6 -5
- package/dist/extraction.js +7 -7
- package/dist/{forget-UJDJHG6R.js → forget-HDS4N6CG.js} +3 -3
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +687 -686
- package/dist/index.js +39 -39
- package/dist/maintenance/memory-governance.js +7 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-projection.js +8 -8
- package/dist/{maintenance-CKwxpdRx.d.ts → maintenance-kBII2qUI.d.ts} +3 -2
- package/dist/mcp-memory-inspector-app.d.ts +6 -5
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +8 -8
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +7 -7
- package/dist/operator-doctor-corpus.js +10 -10
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +15 -15
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +12 -12
- package/dist/{orchestrator-BshGam-o.d.ts → orchestrator-DHy5LtTy.d.ts} +20 -35
- package/dist/orchestrator.d.ts +5 -4
- package/dist/orchestrator.js +39 -39
- package/dist/page-versioning.js +3 -3
- package/dist/schemas.d.ts +138 -138
- package/dist/semantic-consolidation.js +8 -8
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.js +7 -7
- package/dist/{service-BIgVM7fL.d.ts → service-PT2_z7xm.d.ts} +1 -1
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/source-agent-qualifier.js +7 -7
- package/dist/{storage-CVfGWqCI.d.ts → storage-BmVudhk4.d.ts} +46 -27
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +6 -6
- package/dist/temporal-supersession.d.ts +51 -26
- package/dist/temporal-supersession.js +3 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/transfer/capsule-import.js +4 -4
- package/dist/transfer/types.d.ts +66 -66
- package/dist/verified-recall.js +7 -7
- package/package.json +2 -2
- package/src/lifecycle/tombstones.ts +30 -0
- package/src/orchestration/consolidation-run.ts +324 -106
- package/src/orchestration/contradiction-linking-coordinator.ts +237 -64
- package/src/orchestration/dependency-propagation-delivery.test.ts +2353 -0
- package/src/orchestration/dependency-propagation-delivery.ts +1143 -0
- package/src/orchestration/dependency-propagation-queue-state.ts +220 -0
- package/src/orchestration/dependency-propagation-recovery-replay.ts +237 -0
- package/src/orchestration/dependency-propagation-replay-reconciliation.ts +73 -0
- package/src/orchestration/dependency-propagation-triggers.test.ts +1069 -11
- package/src/orchestration/dependency-propagation.test.ts +82 -31
- package/src/orchestration/dependency-propagation.ts +55 -17
- package/src/orchestration/extraction-persist-deps.ts +2 -2
- package/src/orchestration/extraction-persist.ts +14 -15
- package/src/orchestration/extraction-queue-coordinator.ts +23 -1
- package/src/orchestration/extraction-shutdown.ts +29 -0
- package/src/orchestration/maintenance.ts +13 -7
- package/src/orchestration/turn-ingestion.ts +5 -1
- package/src/orchestrator-background-writes.test.ts +265 -1
- package/src/orchestrator-extraction-queue.test.ts +39 -0
- package/src/orchestrator.ts +59 -58
- package/src/storage/committed-invalidation.ts +34 -0
- package/src/storage/deletion-revision-store.ts +529 -0
- package/src/storage/supersession-audit.ts +41 -0
- package/src/storage/supersession-side-effects.ts +52 -25
- package/src/storage-contract/failure-semantics.test.ts +12 -0
- package/src/storage-contract/round-trip.test.ts +184 -2
- package/src/storage-deletion-revisions.test.ts +213 -1
- package/src/storage-frontmatter-cas.test.ts +141 -0
- package/src/storage-memory-lifecycle-ledger.test.ts +372 -0
- package/src/storage-replay.test.ts +149 -0
- package/src/storage.ts +345 -358
- package/src/temporal-supersession.test.ts +431 -2
- package/src/temporal-supersession.ts +567 -107
- package/src/testing/subjects/dependency-propagation.test.ts +179 -38
- package/dist/chunk-ENTCVTQR.js.map +0 -1
- package/dist/chunk-EWMTOVMY.js +0 -484
- package/dist/chunk-EWMTOVMY.js.map +0 -1
- package/dist/chunk-OYESWJ43.js.map +0 -1
- package/dist/chunk-VBHPOWBJ.js.map +0 -1
- package/dist/chunk-ZHY5TMJG.js.map +0 -1
- /package/dist/{capsule-merge-XMFBXVDP.js.map → capsule-merge-HCJJMLPS.js.map} +0 -0
- /package/dist/{chunk-PWN32TAG.js.map → chunk-2ASAS3CQ.js.map} +0 -0
- /package/dist/{chunk-WQM326GS.js.map → chunk-2LY6GIIE.js.map} +0 -0
- /package/dist/{chunk-JL43BVDE.js.map → chunk-2OIQDTDC.js.map} +0 -0
- /package/dist/{chunk-33SV3E43.js.map → chunk-3OY7KXHM.js.map} +0 -0
- /package/dist/{chunk-AIUBBHSF.js.map → chunk-5UZ6UCOZ.js.map} +0 -0
- /package/dist/{chunk-UT5BV5OG.js.map → chunk-7HXZJAID.js.map} +0 -0
- /package/dist/{chunk-M7BXDKPR.js.map → chunk-7M6NYQWB.js.map} +0 -0
- /package/dist/{chunk-TPTECFWR.js.map → chunk-B6LZ4IBH.js.map} +0 -0
- /package/dist/{chunk-J5LLX6IL.js.map → chunk-BF6SXNQJ.js.map} +0 -0
- /package/dist/{chunk-4PC4PRJE.js.map → chunk-BKIFTXHR.js.map} +0 -0
- /package/dist/{chunk-VVG2BOKA.js.map → chunk-BMEYMKWX.js.map} +0 -0
- /package/dist/{chunk-DCZBSE4F.js.map → chunk-D5YOZ66N.js.map} +0 -0
- /package/dist/{chunk-BHXYAVUM.js.map → chunk-DLG5V6ND.js.map} +0 -0
- /package/dist/{chunk-52UKFTUK.js.map → chunk-F4EU6VUC.js.map} +0 -0
- /package/dist/{chunk-M4A44LUA.js.map → chunk-GSTIBXBW.js.map} +0 -0
- /package/dist/{chunk-W5XVQFLM.js.map → chunk-GXFSQ6SD.js.map} +0 -0
- /package/dist/{chunk-XADV5IZ3.js.map → chunk-HDIUYMSB.js.map} +0 -0
- /package/dist/{chunk-FXTYIA46.js.map → chunk-J272WKX2.js.map} +0 -0
- /package/dist/{chunk-JYL3BVVZ.js.map → chunk-JSEJH2YQ.js.map} +0 -0
- /package/dist/{chunk-5UDMFE3Q.js.map → chunk-MLXZD4WM.js.map} +0 -0
- /package/dist/{chunk-PUSLMZBQ.js.map → chunk-NOEMGE56.js.map} +0 -0
- /package/dist/{chunk-C5QLI33R.js.map → chunk-QINJITDS.js.map} +0 -0
- /package/dist/{chunk-45BRYON6.js.map → chunk-QPOHMO6M.js.map} +0 -0
- /package/dist/{chunk-ZQOCRIGA.js.map → chunk-QYN5PXDZ.js.map} +0 -0
- /package/dist/{chunk-PA45XCUD.js.map → chunk-RANKJI5T.js.map} +0 -0
- /package/dist/{chunk-37ZO6LLJ.js.map → chunk-RYLEDKQ2.js.map} +0 -0
- /package/dist/{chunk-CV6BBVYV.js.map → chunk-TEMSWHQD.js.map} +0 -0
- /package/dist/{chunk-QGR6OVGT.js.map → chunk-VFSAWRMZ.js.map} +0 -0
- /package/dist/{chunk-NLK2VZYZ.js.map → chunk-VXCNQYTC.js.map} +0 -0
- /package/dist/{chunk-AZ2QBGYB.js.map → chunk-W7Y5JDVB.js.map} +0 -0
- /package/dist/{chunk-4F6X3RXW.js.map → chunk-X57M2Q4I.js.map} +0 -0
- /package/dist/{chunk-6MTSFX7A.js.map → chunk-X7XEDOSL.js.map} +0 -0
- /package/dist/{chunk-UEVLYVNW.js.map → chunk-YXKVLYXU.js.map} +0 -0
- /package/dist/{chunk-OQAL6SIR.js.map → chunk-ZMSZVISA.js.map} +0 -0
- /package/dist/{forget-UJDJHG6R.js.map → forget-HDS4N6CG.js.map} +0 -0
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
findDependents,
|
|
6
|
+
isDependencyPropagationEnabled,
|
|
7
|
+
propagateInvalidation,
|
|
8
|
+
} from "./dependency-propagation.js";
|
|
5
9
|
import type { ExtractionEngine } from "../extraction.js";
|
|
6
10
|
import type { MemoryFile, MemoryLinkType, PluginConfig } from "../types.js";
|
|
7
11
|
import type { StorageManager } from "../storage.js";
|
|
12
|
+
import { invalidationCommitFingerprint } from "../storage/deletion-revision-store.js";
|
|
8
13
|
|
|
9
14
|
type Verdict = {
|
|
10
15
|
memoryId: string;
|
|
@@ -25,8 +30,17 @@ type Fixture = {
|
|
|
25
30
|
extraction: ExtractionEngine;
|
|
26
31
|
calls: {
|
|
27
32
|
revalidate: RevalidationCall[];
|
|
28
|
-
supersede: Array<{
|
|
29
|
-
|
|
33
|
+
supersede: Array<{
|
|
34
|
+
id: string;
|
|
35
|
+
replacementId: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
metadata?: Record<string, unknown>;
|
|
38
|
+
options: {
|
|
39
|
+
requireActive?: boolean;
|
|
40
|
+
acceptExactReplay?: boolean;
|
|
41
|
+
expectedSnapshot?: Pick<MemoryFile, "content" | "frontmatter"> & Partial<Pick<MemoryFile, "path">>;
|
|
42
|
+
};
|
|
43
|
+
}>;
|
|
30
44
|
};
|
|
31
45
|
};
|
|
32
46
|
|
|
@@ -77,36 +91,63 @@ function config(overrides: Record<string, unknown> = {}): PluginConfig {
|
|
|
77
91
|
} as unknown as PluginConfig;
|
|
78
92
|
}
|
|
79
93
|
|
|
94
|
+
test("dependency propagation enable gate accepts only normalized true values and a positive limit", () => {
|
|
95
|
+
assert.equal(isDependencyPropagationEnabled(config()), true);
|
|
96
|
+
assert.equal(isDependencyPropagationEnabled(config({ dependencyPropagation: { enabled: "yes" } })), true);
|
|
97
|
+
assert.equal(isDependencyPropagationEnabled(config({ dependencyPropagation: { enabled: "disabled" } })), false);
|
|
98
|
+
assert.equal(isDependencyPropagationEnabled(config({ dependencyPropagation: { maxDependents: 0 } })), false);
|
|
99
|
+
});
|
|
100
|
+
|
|
80
101
|
function fixture(
|
|
81
102
|
initial: MemoryFile[],
|
|
82
103
|
verdicts: Verdict[] | Error | ((signal?: AbortSignal) => Promise<{ verdicts: Verdict[] }>),
|
|
83
104
|
): Fixture {
|
|
84
105
|
const memories = new Map(initial.map((item) => [item.frontmatter.id, item]));
|
|
85
|
-
const calls: Fixture["calls"] = { revalidate: [], supersede: []
|
|
86
|
-
|
|
106
|
+
const calls: Fixture["calls"] = { revalidate: [], supersede: [] };
|
|
87
107
|
const storage = {
|
|
108
|
+
|
|
88
109
|
async readAllMemories(): Promise<MemoryFile[]> {
|
|
89
|
-
return [...memories.values()];
|
|
110
|
+
return structuredClone([...memories.values()]);
|
|
90
111
|
},
|
|
91
112
|
async getMemoryById(id: string): Promise<MemoryFile | null> {
|
|
92
113
|
return memories.get(id) ?? null;
|
|
93
114
|
},
|
|
94
|
-
async supersedeMemory(
|
|
95
|
-
|
|
115
|
+
async supersedeMemory(
|
|
116
|
+
id: string,
|
|
117
|
+
replacementId: string,
|
|
118
|
+
reason: string,
|
|
119
|
+
metadata?: Record<string, unknown>,
|
|
120
|
+
options: {
|
|
121
|
+
requireActive?: boolean;
|
|
122
|
+
acceptExactReplay?: boolean;
|
|
123
|
+
expectedSnapshot?: Pick<MemoryFile, "content" | "frontmatter"> & Partial<Pick<MemoryFile, "path">>;
|
|
124
|
+
} = {},
|
|
125
|
+
): Promise<boolean> {
|
|
126
|
+
calls.supersede.push({ id, replacementId, reason, metadata, options });
|
|
96
127
|
const current = memories.get(id);
|
|
97
128
|
if (!current) return false;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
129
|
+
if (
|
|
130
|
+
options.expectedSnapshot &&
|
|
131
|
+
invalidationCommitFingerprint(current) !== invalidationCommitFingerprint(options.expectedSnapshot)
|
|
132
|
+
) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const exactReplay =
|
|
136
|
+
current.frontmatter.status === "superseded" &&
|
|
137
|
+
current.frontmatter.supersededBy === replacementId &&
|
|
138
|
+
current.frontmatter.supersessionCause === metadata?.supersessionCause &&
|
|
139
|
+
current.frontmatter.invalidatedBy === metadata?.invalidatedBy;
|
|
140
|
+
if (exactReplay) return options.acceptExactReplay === true;
|
|
141
|
+
if (
|
|
142
|
+
options.requireActive === true &&
|
|
143
|
+
(current.frontmatter.status ?? "active") !== "active"
|
|
144
|
+
) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
Object.assign(current.frontmatter, metadata, {
|
|
148
|
+
status: "superseded",
|
|
149
|
+
supersededBy: replacementId,
|
|
150
|
+
});
|
|
110
151
|
return true;
|
|
111
152
|
},
|
|
112
153
|
} as unknown as StorageManager;
|
|
@@ -293,6 +334,7 @@ test("invalidated verdict supersedes the dependent and persists dependency front
|
|
|
293
334
|
const old = memory("old", { links: [{ targetId: "dep", linkType: "supports" }] });
|
|
294
335
|
const dependent = memory("dep");
|
|
295
336
|
const fixtureValue = fixture([old, dependent], [{ memoryId: "dep", verdict: "invalidated", reason: "claim lost support" }]);
|
|
337
|
+
const expectedSnapshot = structuredClone(dependent);
|
|
296
338
|
|
|
297
339
|
const result = await propagateInvalidation(
|
|
298
340
|
deps(fixtureValue),
|
|
@@ -317,18 +359,32 @@ test("invalidated verdict supersedes the dependent and persists dependency front
|
|
|
317
359
|
id: "dep",
|
|
318
360
|
replacementId: "replacement",
|
|
319
361
|
reason: "dependency_propagation:contradiction",
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
assert.deepEqual(fixtureValue.calls.frontmatter, [
|
|
323
|
-
{
|
|
324
|
-
id: "dep",
|
|
325
|
-
patch: { supersessionCause: "dependency", invalidatedBy: "old" },
|
|
362
|
+
metadata: { supersessionCause: "dependency", invalidatedBy: "old" },
|
|
363
|
+
options: { requireActive: true, acceptExactReplay: true, expectedSnapshot },
|
|
326
364
|
},
|
|
327
365
|
]);
|
|
328
366
|
assert.equal(fixtureValue.memories.get("dep")?.frontmatter.status, "superseded");
|
|
329
367
|
assert.equal(fixtureValue.memories.get("dep")?.frontmatter.supersessionCause, "dependency");
|
|
330
368
|
assert.equal(fixtureValue.memories.get("dep")?.frontmatter.invalidatedBy, "old");
|
|
331
369
|
});
|
|
370
|
+
test("supersede fixture rejects a stale expected snapshot after concurrent mutation", async () => {
|
|
371
|
+
const old = memory("old", { links: [{ targetId: "dep", linkType: "supports" }] });
|
|
372
|
+
const dependent = memory("dep");
|
|
373
|
+
const fixtureValue = fixture([old, dependent], []);
|
|
374
|
+
const expectedSnapshot = structuredClone(dependent);
|
|
375
|
+
dependent.content = "concurrent update";
|
|
376
|
+
|
|
377
|
+
const superseded = await fixtureValue.storage.supersedeMemory(
|
|
378
|
+
"dep",
|
|
379
|
+
"replacement",
|
|
380
|
+
"dependency_propagation:contradiction",
|
|
381
|
+
{ supersessionCause: "dependency", invalidatedBy: "old" },
|
|
382
|
+
{ requireActive: true, expectedSnapshot },
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
assert.equal(superseded, false);
|
|
386
|
+
assert.equal(fixtureValue.memories.get("dep")?.frontmatter.status, "active");
|
|
387
|
+
});
|
|
332
388
|
|
|
333
389
|
test("still_valid and uncertain verdicts do not write", async () => {
|
|
334
390
|
const old = memory("old", {
|
|
@@ -356,7 +412,6 @@ test("still_valid and uncertain verdicts do not write", async () => {
|
|
|
356
412
|
assert.equal(result.stillValid, 1);
|
|
357
413
|
assert.equal(result.uncertain, 1);
|
|
358
414
|
assert.equal(fixtureValue.calls.supersede.length, 0);
|
|
359
|
-
assert.equal(fixtureValue.calls.frontmatter.length, 0);
|
|
360
415
|
});
|
|
361
416
|
|
|
362
417
|
test("unknown, missing, and garbage verdicts default to uncertain and drop unknown ids", async () => {
|
|
@@ -412,7 +467,6 @@ test("LLM errors skip the event and perform zero writes", async () => {
|
|
|
412
467
|
assert.equal(typeof result.durationMs, "number");
|
|
413
468
|
assert.equal(result.skipped, "llm_error");
|
|
414
469
|
assert.equal(fixtureValue.calls.supersede.length, 0);
|
|
415
|
-
assert.equal(fixtureValue.calls.frontmatter.length, 0);
|
|
416
470
|
});
|
|
417
471
|
|
|
418
472
|
test("LLM timeout aborts the batch and performs zero writes", async () => {
|
|
@@ -439,7 +493,6 @@ test("LLM timeout aborts the batch and performs zero writes", async () => {
|
|
|
439
493
|
assert.equal(typeof result.durationMs, "number");
|
|
440
494
|
assert.equal(result.skipped, "timeout");
|
|
441
495
|
assert.equal(fixtureValue.calls.supersede.length, 0);
|
|
442
|
-
assert.equal(fixtureValue.calls.frontmatter.length, 0);
|
|
443
496
|
});
|
|
444
497
|
|
|
445
498
|
test("a completion that ignores AbortSignal returns at the shared deadline", async (t) => {
|
|
@@ -470,7 +523,6 @@ test("a completion that ignores AbortSignal returns at the shared deadline", asy
|
|
|
470
523
|
assert.equal(result.route, "fast-completion");
|
|
471
524
|
assert.equal(result.durationMs, 20);
|
|
472
525
|
assert.equal(fixtureValue.calls.supersede.length, 0);
|
|
473
|
-
assert.equal(fixtureValue.calls.frontmatter.length, 0);
|
|
474
526
|
} finally {
|
|
475
527
|
t.mock.timers.reset();
|
|
476
528
|
}
|
|
@@ -515,7 +567,6 @@ test("dryRun computes invalidation verdicts without writing", async () => {
|
|
|
515
567
|
assert.equal(result.invalidated, 1);
|
|
516
568
|
assert.equal(fixtureValue.calls.revalidate.length, 1);
|
|
517
569
|
assert.equal(fixtureValue.calls.supersede.length, 0);
|
|
518
|
-
assert.equal(fixtureValue.calls.frontmatter.length, 0);
|
|
519
570
|
assert.equal(fixtureValue.memories.get("dep")?.frontmatter.status, "active");
|
|
520
571
|
});
|
|
521
572
|
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { ExtractionEngine } from "../extraction.js";
|
|
8
8
|
import { log } from "../logger.js";
|
|
9
|
-
import type { StorageManager } from "../index.js";
|
|
10
9
|
import type {
|
|
11
10
|
DependencyPropagationConfig,
|
|
12
11
|
MemoryFile,
|
|
@@ -15,9 +14,14 @@ import type {
|
|
|
15
14
|
PluginConfig,
|
|
16
15
|
} from "../types.js";
|
|
17
16
|
|
|
17
|
+
export function isDependencyPropagationEnabled(config: Pick<PluginConfig, "dependencyPropagation">): boolean {
|
|
18
|
+
const parsed = propagationConfig(config);
|
|
19
|
+
return isEnabled(parsed.enabled) && parsed.maxDependents > 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
export interface PropagationEvent {
|
|
19
23
|
/** Snapshot captured before the primary supersession or deletion. */
|
|
20
|
-
oldMemory: { content: string; frontmatter: MemoryFrontmatter };
|
|
24
|
+
oldMemory: { content: string; frontmatter: MemoryFrontmatter; path?: string };
|
|
21
25
|
replacementId: string | null;
|
|
22
26
|
replacementContent: string | null;
|
|
23
27
|
cause:
|
|
@@ -26,6 +30,11 @@ export interface PropagationEvent {
|
|
|
26
30
|
| "consolidation_invalidate"
|
|
27
31
|
| "consolidation_merge";
|
|
28
32
|
namespaceScope: string;
|
|
33
|
+
temporalMutation?: {
|
|
34
|
+
supersededAt: string;
|
|
35
|
+
invalidAt?: string;
|
|
36
|
+
matchedKeys: string[];
|
|
37
|
+
};
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
export interface PropagationResult {
|
|
@@ -38,7 +47,27 @@ export interface PropagationResult {
|
|
|
38
47
|
durationMs: number;
|
|
39
48
|
}
|
|
40
49
|
|
|
41
|
-
|
|
50
|
+
export type DependencyPropagationWriteFence = <T>(
|
|
51
|
+
write: () => Promise<T>,
|
|
52
|
+
) => Promise<T | undefined>;
|
|
53
|
+
|
|
54
|
+
export interface DependencyPropagationStorage {
|
|
55
|
+
readAllMemories(): Promise<MemoryFile[]>;
|
|
56
|
+
getMemoryById(id: string): Promise<MemoryFile | null>;
|
|
57
|
+
supersedeMemory(
|
|
58
|
+
id: string,
|
|
59
|
+
replacementId: string,
|
|
60
|
+
reason: string,
|
|
61
|
+
metadata?: Pick<MemoryFrontmatter, "supersessionCause" | "invalidatedBy">,
|
|
62
|
+
options?: {
|
|
63
|
+
requireActive?: boolean;
|
|
64
|
+
acceptExactReplay?: boolean;
|
|
65
|
+
expectedSnapshot?: Pick<MemoryFile, "content" | "frontmatter"> & Partial<Pick<MemoryFile, "path">>;
|
|
66
|
+
},
|
|
67
|
+
): Promise<boolean>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function propagationConfig(config: Pick<PluginConfig, "dependencyPropagation">): DependencyPropagationConfig {
|
|
42
71
|
return config.dependencyPropagation;
|
|
43
72
|
}
|
|
44
73
|
|
|
@@ -142,7 +171,12 @@ function isTimeoutError(error: unknown, signal: AbortSignal): boolean {
|
|
|
142
171
|
* constructs another storage or scans a different namespace.
|
|
143
172
|
*/
|
|
144
173
|
export async function propagateInvalidation(
|
|
145
|
-
deps: {
|
|
174
|
+
deps: {
|
|
175
|
+
storage: DependencyPropagationStorage;
|
|
176
|
+
extraction: ExtractionEngine;
|
|
177
|
+
config: PluginConfig;
|
|
178
|
+
writeFence?: DependencyPropagationWriteFence;
|
|
179
|
+
},
|
|
146
180
|
event: PropagationEvent,
|
|
147
181
|
): Promise<PropagationResult> {
|
|
148
182
|
const startedAt = Date.now();
|
|
@@ -255,19 +289,23 @@ export async function propagateInvalidation(
|
|
|
255
289
|
}
|
|
256
290
|
try {
|
|
257
291
|
const replacementId = event.replacementId ?? event.oldMemory.frontmatter.id;
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
292
|
+
const write = () =>
|
|
293
|
+
deps.storage.supersedeMemory(
|
|
294
|
+
dependent.frontmatter.id,
|
|
295
|
+
replacementId,
|
|
296
|
+
`dependency_propagation:${event.cause}`,
|
|
297
|
+
{
|
|
298
|
+
supersessionCause: "dependency",
|
|
299
|
+
invalidatedBy: event.oldMemory.frontmatter.id,
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
requireActive: true,
|
|
303
|
+
acceptExactReplay: true,
|
|
304
|
+
expectedSnapshot: dependent,
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
const superseded = deps.writeFence ? await deps.writeFence(write) : await write();
|
|
308
|
+
if (superseded !== true) continue;
|
|
271
309
|
invalidated += 1;
|
|
272
310
|
} catch (error) {
|
|
273
311
|
log.warn(`dependency propagation write failed for ${dependent.frontmatter.id}: ${error}`);
|
|
@@ -12,12 +12,12 @@ import type { RouteRule, RoutingEngineOptions } from "../routing/engine.js";
|
|
|
12
12
|
import type { SearchBackend } from "../search/port.js";
|
|
13
13
|
import type { ThreadingManager } from "../threading.js";
|
|
14
14
|
import type { MemoryFile, MemoryLink, PluginConfig } from "../types.js";
|
|
15
|
-
|
|
15
|
+
import type { DependencyPropagationDeliveryPort } from "./dependency-propagation-delivery.js";
|
|
16
16
|
export interface ExtractionPersistDeps {
|
|
17
17
|
config: PluginConfig;
|
|
18
18
|
getStorageRouter: () => NamespaceStorageRouter;
|
|
19
19
|
getThreading: () => ThreadingManager;
|
|
20
|
-
|
|
20
|
+
getDependencyPropagationDelivery: () => DependencyPropagationDeliveryPort;
|
|
21
21
|
getLocalLlm: () => LocalLlmClient;
|
|
22
22
|
getQmd: () => SearchBackend;
|
|
23
23
|
getJudgeVerdictCache: () => Map<string, JudgeVerdict>;
|
|
@@ -84,6 +84,7 @@ import { decideSemanticDedup, type SemanticDedupDecision, type SemanticDedupHit
|
|
|
84
84
|
import { selectRouteRule, type RouteRule, type RoutingEngineOptions } from "../routing/engine.js";
|
|
85
85
|
import { ThreadingManager } from "../threading.js";
|
|
86
86
|
import { NamespaceStorageRouter } from "../namespaces/storage.js";
|
|
87
|
+
import type { ResolvedScopeProfilePlan } from "../namespaces/scope-profiles.js";
|
|
87
88
|
import type { SearchBackend } from "../search/port.js";
|
|
88
89
|
import { inferIntentFromText } from "../intent.js";
|
|
89
90
|
import type {
|
|
@@ -103,7 +104,8 @@ import {
|
|
|
103
104
|
shouldPromoteToShared,
|
|
104
105
|
} from "./extraction-persist-promotion.js";
|
|
105
106
|
import type { ExtractionPersistDeps } from "./extraction-persist-deps.js";
|
|
106
|
-
import type {
|
|
107
|
+
import type { DependencyPropagationDeliveryPort } from "./dependency-propagation-delivery.js";
|
|
108
|
+
import { isDependencyPropagationEnabled } from "./dependency-propagation.js";
|
|
107
109
|
import {
|
|
108
110
|
buildMemoryPathById,
|
|
109
111
|
appendMemoryToGraphContext,
|
|
@@ -114,8 +116,6 @@ import { persistConstructedHarmonicRecords } from "./harmonic-construction-persi
|
|
|
114
116
|
import { ExtractionAnchorSnapshot } from "./extraction-anchor-snapshot.js";
|
|
115
117
|
|
|
116
118
|
|
|
117
|
-
|
|
118
|
-
|
|
119
119
|
export class ExtractionPersistCoordinator {
|
|
120
120
|
constructor(
|
|
121
121
|
private readonly deps: ExtractionPersistDeps,
|
|
@@ -123,6 +123,10 @@ export class ExtractionPersistCoordinator {
|
|
|
123
123
|
private get config(): PluginConfig {
|
|
124
124
|
return this.deps.config;
|
|
125
125
|
}
|
|
126
|
+
private get dependencyPropagationDelivery(): DependencyPropagationDeliveryPort | undefined {
|
|
127
|
+
if (!isDependencyPropagationEnabled(this.config)) return undefined;
|
|
128
|
+
return this.deps.getDependencyPropagationDelivery();
|
|
129
|
+
}
|
|
126
130
|
|
|
127
131
|
async persistExtraction(
|
|
128
132
|
result: ExtractionResult,
|
|
@@ -500,8 +504,7 @@ export class ExtractionPersistCoordinator {
|
|
|
500
504
|
structuredAttributes: { ...targetSurvivingAttrs },
|
|
501
505
|
createdAt: supersessionOrderingAt(options.validAt),
|
|
502
506
|
enabled: !(options.eventTimeSource === "extracted" && !options.validAt),
|
|
503
|
-
|
|
504
|
-
config: this.config,
|
|
507
|
+
dependencyPropagationDelivery: this.dependencyPropagationDelivery,
|
|
505
508
|
namespaceScope: this.deps.storageDirNamespace(targetStorage.dir),
|
|
506
509
|
});
|
|
507
510
|
} catch (profileSupersessionErr) {
|
|
@@ -800,10 +803,9 @@ export class ExtractionPersistCoordinator {
|
|
|
800
803
|
entityRef: sharedPromotionEnvelope.entityRef,
|
|
801
804
|
structuredAttributes: { ...sharedSurvivingAttrs },
|
|
802
805
|
createdAt: supersessionOrderingAt(options.validAt),
|
|
803
|
-
enabled: !(options.eventTimeSource === "extracted" && !options.validAt),
|
|
804
806
|
useCallerTimestamp: true,
|
|
805
|
-
|
|
806
|
-
|
|
807
|
+
enabled: !(options.eventTimeSource === "extracted" && !options.validAt),
|
|
808
|
+
dependencyPropagationDelivery: this.dependencyPropagationDelivery,
|
|
807
809
|
namespaceScope: this.deps.storageDirNamespace(sharedStorage.dir),
|
|
808
810
|
});
|
|
809
811
|
// Catalog touch (issue #1499 — codex P2 NElSf): this dedup branch
|
|
@@ -927,8 +929,7 @@ export class ExtractionPersistCoordinator {
|
|
|
927
929
|
structuredAttributes: { ...sharedSurvivingAttrs },
|
|
928
930
|
createdAt: supersessionOrderingAt(options.validAt),
|
|
929
931
|
enabled: !(options.eventTimeSource === "extracted" && !options.validAt),
|
|
930
|
-
|
|
931
|
-
config: this.config,
|
|
932
|
+
dependencyPropagationDelivery: this.dependencyPropagationDelivery,
|
|
932
933
|
namespaceScope: this.deps.storageDirNamespace(sharedStorage.dir),
|
|
933
934
|
});
|
|
934
935
|
} catch (sharedSupersessionErr) {
|
|
@@ -2358,14 +2359,13 @@ export class ExtractionPersistCoordinator {
|
|
|
2358
2359
|
structuredAttributes: parentWriteEnvelope.rawStructuredAttributes
|
|
2359
2360
|
? { ...parentWriteEnvelope.rawStructuredAttributes }
|
|
2360
2361
|
: undefined,
|
|
2361
|
-
createdAt: supersessionOrderingAt(biTemporal?.validFrom ?? sourceContext?.validAt),
|
|
2362
2362
|
// #1578 r3: an extracted end-only bound (validFrom absent) is
|
|
2363
2363
|
// historical, not a new authoritative state — never let it
|
|
2364
2364
|
// supersede a later active fact (codex P1 on :15534).
|
|
2365
|
+
createdAt: supersessionOrderingAt(biTemporal?.validFrom ?? sourceContext?.validAt),
|
|
2365
2366
|
enabled: lifecycleCaps.temporalSupersession &&
|
|
2366
2367
|
!(biTemporal && !biTemporal.validFrom),
|
|
2367
|
-
|
|
2368
|
-
config: this.config,
|
|
2368
|
+
dependencyPropagationDelivery: this.dependencyPropagationDelivery,
|
|
2369
2369
|
namespaceScope: this.deps.storageDirNamespace(targetStorage.dir),
|
|
2370
2370
|
});
|
|
2371
2371
|
for (const supersededId of temporalSupersession.supersededIds) {
|
|
@@ -2646,8 +2646,7 @@ export class ExtractionPersistCoordinator {
|
|
|
2646
2646
|
createdAt: supersessionOrderingAt(biTemporal?.validFrom ?? sourceContext?.validAt),
|
|
2647
2647
|
enabled: lifecycleCaps.temporalSupersession &&
|
|
2648
2648
|
!(biTemporal && !biTemporal.validFrom),
|
|
2649
|
-
|
|
2650
|
-
config: this.config,
|
|
2649
|
+
dependencyPropagationDelivery: this.dependencyPropagationDelivery,
|
|
2651
2650
|
namespaceScope: this.deps.storageDirNamespace(targetStorage.dir),
|
|
2652
2651
|
});
|
|
2653
2652
|
for (const supersededId of temporalSupersession.supersededIds) {
|
|
@@ -39,6 +39,7 @@ export class ExtractionQueueCoordinator {
|
|
|
39
39
|
private readonly queue: Array<() => Promise<void>> = [];
|
|
40
40
|
/** Whether the serial drain loop is currently running. */
|
|
41
41
|
private processing = false;
|
|
42
|
+
private accepting = true;
|
|
42
43
|
|
|
43
44
|
/** Current queue depth (idle-wait + test seam). */
|
|
44
45
|
get length(): number {
|
|
@@ -55,7 +56,8 @@ export class ExtractionQueueCoordinator {
|
|
|
55
56
|
* the production entry point — the orchestrator's `queueBufferedExtraction`
|
|
56
57
|
* builds each task closure and hands it here.
|
|
57
58
|
*/
|
|
58
|
-
enqueue(task: () => Promise<void>):
|
|
59
|
+
enqueue(task: () => Promise<void>): boolean {
|
|
60
|
+
if (!this.accepting) return false;
|
|
59
61
|
this.queue.push(task);
|
|
60
62
|
if (!this.processing) {
|
|
61
63
|
this.processing = true;
|
|
@@ -64,6 +66,26 @@ export class ExtractionQueueCoordinator {
|
|
|
64
66
|
this.processing = false;
|
|
65
67
|
});
|
|
66
68
|
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Stop new producers from entering the queue. */
|
|
73
|
+
stopAccepting(): void {
|
|
74
|
+
this.accepting = false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Resume producer acceptance after an orchestrator re-initializes. */
|
|
78
|
+
resumeAccepting(): void {
|
|
79
|
+
this.accepting = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Stop new producers and wait for all accepted work to finish. */
|
|
83
|
+
async pauseAndDrain(timeoutMs: number = 60_000): Promise<boolean> {
|
|
84
|
+
this.stopAccepting();
|
|
85
|
+
if (await this.waitForIdle(timeoutMs)) return true;
|
|
86
|
+
log.warn("extraction queue drain exceeded timeout; waiting for accepted work to settle");
|
|
87
|
+
await this.waitForIdle(Number.POSITIVE_INFINITY);
|
|
88
|
+
return true;
|
|
67
89
|
}
|
|
68
90
|
|
|
69
91
|
/**
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { log } from "../logger.js";
|
|
2
|
+
|
|
3
|
+
export interface ExtractionDrainPort {
|
|
4
|
+
pauseAndDrain(): Promise<boolean>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface DependencyPropagationShutdownPort {
|
|
8
|
+
shutdown(): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function drainExtractionAndShutdownDependencyPropagation(
|
|
12
|
+
extractionQueue: ExtractionDrainPort,
|
|
13
|
+
dependencyPropagationDelivery: DependencyPropagationShutdownPort | undefined,
|
|
14
|
+
): Promise<unknown | undefined> {
|
|
15
|
+
let extractionDrainError: unknown;
|
|
16
|
+
try {
|
|
17
|
+
if (!await extractionQueue.pauseAndDrain()) {
|
|
18
|
+
extractionDrainError = new Error("orchestrator.destroy: extraction queue did not drain before teardown");
|
|
19
|
+
}
|
|
20
|
+
} catch (error) {
|
|
21
|
+
extractionDrainError = error;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
await dependencyPropagationDelivery?.shutdown();
|
|
25
|
+
} catch (error) {
|
|
26
|
+
log.warn(`orchestrator.destroy: dependency propagation shutdown failed: ${error}`);
|
|
27
|
+
}
|
|
28
|
+
return extractionDrainError;
|
|
29
|
+
}
|
|
@@ -126,7 +126,8 @@ export class MaintenanceScheduler {
|
|
|
126
126
|
// ── Consolidation scheduling state ──
|
|
127
127
|
private nonZeroExtractionsSinceConsolidation = 0;
|
|
128
128
|
private lastConsolidationRunAtMs = 0;
|
|
129
|
-
private
|
|
129
|
+
private consolidationRunPromise: Promise<unknown> | null = null;
|
|
130
|
+
private disposed = false;
|
|
130
131
|
|
|
131
132
|
// ── QMD maintenance scheduling state ──
|
|
132
133
|
private qmdMaintenanceTimer: NodeJS.Timeout | null = null;
|
|
@@ -442,15 +443,17 @@ export class MaintenanceScheduler {
|
|
|
442
443
|
) {
|
|
443
444
|
return;
|
|
444
445
|
}
|
|
445
|
-
if (this.
|
|
446
|
+
if (this.disposed || this.consolidationRunPromise) return;
|
|
446
447
|
|
|
447
|
-
this.consolidationInFlight = true;
|
|
448
448
|
this.lastConsolidationRunAtMs = now;
|
|
449
449
|
this.nonZeroExtractionsSinceConsolidation = 0;
|
|
450
|
-
|
|
451
|
-
.
|
|
450
|
+
this.consolidationRunPromise = Promise.resolve()
|
|
451
|
+
.then(runConsolidation)
|
|
452
|
+
.catch((err) => {
|
|
453
|
+
log.error("background consolidation failed", err);
|
|
454
|
+
})
|
|
452
455
|
.finally(() => {
|
|
453
|
-
this.
|
|
456
|
+
this.consolidationRunPromise = null;
|
|
454
457
|
});
|
|
455
458
|
}
|
|
456
459
|
|
|
@@ -458,7 +461,7 @@ export class MaintenanceScheduler {
|
|
|
458
461
|
* the orchestrator's waitForConsolidationIdle can poll idle state without
|
|
459
462
|
* owning the cadence state (issue #1526 PR1). */
|
|
460
463
|
isConsolidationInFlight(): boolean {
|
|
461
|
-
return this.
|
|
464
|
+
return this.consolidationRunPromise !== null;
|
|
462
465
|
}
|
|
463
466
|
|
|
464
467
|
/** Whether a debounced QMD maintenance pass is armed and waiting for the
|
|
@@ -1174,6 +1177,8 @@ export class MaintenanceScheduler {
|
|
|
1174
1177
|
* teardown. Async: awaits the scheduler's abort+drain before resolving.
|
|
1175
1178
|
*/
|
|
1176
1179
|
async dispose(): Promise<void> {
|
|
1180
|
+
this.disposed = true;
|
|
1181
|
+
const consolidationDrain = this.consolidationRunPromise;
|
|
1177
1182
|
// The registrar latches teardown first (so a draining tick's onRun cannot
|
|
1178
1183
|
// re-arm QMD maintenance) and aborts+drains the sync. Capture its drain and
|
|
1179
1184
|
// await it last so the rest of teardown runs while the aborted tick unwinds.
|
|
@@ -1190,5 +1195,6 @@ export class MaintenanceScheduler {
|
|
|
1190
1195
|
this.qmdMaintenanceTimer = null;
|
|
1191
1196
|
}
|
|
1192
1197
|
this.qmdMaintenancePending = false;
|
|
1198
|
+
if (consolidationDrain) await consolidationDrain;
|
|
1193
1199
|
}
|
|
1194
1200
|
}
|
|
@@ -727,7 +727,7 @@ export class TurnIngestionCoordinator {
|
|
|
727
727
|
scheduleQueueWaitTimeout();
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
-
this.deps.extractionQueueCoordinator.enqueue(async () => {
|
|
730
|
+
const accepted = this.deps.extractionQueueCoordinator.enqueue(async () => {
|
|
731
731
|
if (settled) return;
|
|
732
732
|
if (
|
|
733
733
|
typeof extractionDeadlineMs === "number" &&
|
|
@@ -761,6 +761,10 @@ export class TurnIngestionCoordinator {
|
|
|
761
761
|
}
|
|
762
762
|
}
|
|
763
763
|
});
|
|
764
|
+
if (!accepted) {
|
|
765
|
+
settleTask(new Error("extraction queue is stopped"));
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
764
768
|
|
|
765
769
|
log.debug(`queued extraction from ${reason}`);
|
|
766
770
|
}
|