@remnic/core 10.0.94 → 10.0.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.js +1 -1
- package/dist/access-cli.js +1 -1
- package/dist/access-extraction-force-flush.js +1 -1
- package/dist/access-http-briefing.js +1 -1
- package/dist/access-http-query.js +1 -1
- package/dist/access-http.js +1 -1
- package/dist/access-identity-continuity-surface.js +1 -1
- package/dist/access-lcm-surface.js +1 -1
- package/dist/access-mcp.js +1 -1
- package/dist/access-observe-write-surface.js +1 -1
- package/dist/access-operations-batch.js +1 -1
- package/dist/access-operations.js +1 -1
- package/dist/access-recall-concurrency.js +1 -1
- package/dist/access-recall-response.js +1 -1
- package/dist/access-recall-surface.js +1 -1
- package/dist/access-service.js +1 -1
- package/dist/{chunk-D5S6RL32.js → chunk-DDATG2SF.js} +40 -20
- package/dist/{chunk-D5S6RL32.js.map → chunk-DDATG2SF.js.map} +1 -1
- package/dist/{chunk-RUTN5MMC.js → chunk-LS7PT3YD.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/explicit-capture.js +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/backlog-scheduler.js +2 -2
- package/dist/orchestration/buffer-drain-runner.js +2 -2
- package/dist/orchestration/extraction-persist-failure.d.ts +25 -1
- package/dist/orchestration/extraction-persist-failure.d.ts.map +1 -1
- package/dist/orchestration/extraction-persist.d.ts +3 -6
- package/dist/orchestration/extraction-persist.d.ts.map +1 -1
- package/dist/orchestrator.js +1 -1
- package/dist/recall-navigation.js +1 -1
- package/package.json +2 -2
- package/src/orchestration/extraction-persist-failure.test.ts +90 -0
- package/src/orchestration/extraction-persist-failure.ts +39 -1
- package/src/orchestration/extraction-persist.ts +29 -21
- package/src/orchestrator-persist-branch-failure.test.ts +271 -0
- /package/dist/{chunk-RUTN5MMC.js.map → chunk-LS7PT3YD.js.map} +0 -0
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
ExtractionDrain,
|
|
3
3
|
TurnIngestionCoordinator,
|
|
4
4
|
shouldSkipImplicitExtraction
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-DDATG2SF.js";
|
|
6
6
|
import {
|
|
7
7
|
headerConnector,
|
|
8
8
|
renderExtractionConversation,
|
|
@@ -447,4 +447,4 @@ export {
|
|
|
447
447
|
ClaimBufferStorage,
|
|
448
448
|
BufferDrainRunner
|
|
449
449
|
};
|
|
450
|
-
//# sourceMappingURL=chunk-
|
|
450
|
+
//# sourceMappingURL=chunk-LS7PT3YD.js.map
|
package/dist/cli.js
CHANGED
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
runWorkProductStatusCliCommand,
|
|
92
92
|
runWorkProjectCliCommand,
|
|
93
93
|
runWorkTaskCliCommand
|
|
94
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-DDATG2SF.js";
|
|
95
95
|
import "./chunk-B3DR2JBZ.js";
|
|
96
96
|
import "./chunk-6P7U3HWG.js";
|
|
97
97
|
import "./chunk-H4CKS5B2.js";
|
package/dist/explicit-capture.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
shouldSkipImplicitExtraction,
|
|
9
9
|
stripInlineExplicitCaptureNotes,
|
|
10
10
|
validateExplicitCaptureInput
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DDATG2SF.js";
|
|
12
12
|
import "./chunk-B3DR2JBZ.js";
|
|
13
13
|
import "./chunk-6P7U3HWG.js";
|
|
14
14
|
import "./chunk-H4CKS5B2.js";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BufferDrainRunner,
|
|
3
3
|
ClaimBufferStorage
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-LS7PT3YD.js";
|
|
5
|
+
import "../chunk-DDATG2SF.js";
|
|
6
6
|
import "../chunk-B3DR2JBZ.js";
|
|
7
7
|
import "../chunk-6P7U3HWG.js";
|
|
8
8
|
import "../chunk-H4CKS5B2.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
type FinalizeCommittedWrites = (scheduleQmd: boolean) => Promise<void>;
|
|
1
2
|
/**
|
|
2
3
|
* Fail a persistence pass after a durable write rejected (#3227, same
|
|
3
4
|
* contract as the entity write path from #3228). Earlier writes in the batch
|
|
@@ -5,5 +6,28 @@
|
|
|
5
6
|
* original error is then rethrown so the run leaves its input retryable
|
|
6
7
|
* instead of recording the fingerprint and clearing the source turns.
|
|
7
8
|
*/
|
|
8
|
-
export declare function rethrowAfterCommittedWriteFinalization(finalizeCommittedWrites:
|
|
9
|
+
export declare function rethrowAfterCommittedWriteFinalization(finalizeCommittedWrites: FinalizeCommittedWrites, cause: unknown, outputKind: string): Promise<never>;
|
|
10
|
+
/**
|
|
11
|
+
* Batch-end finalization for one persistExtraction pass (#3288).
|
|
12
|
+
*
|
|
13
|
+
* `bind` wraps the pass's finalizer so it runs at most once. The success
|
|
14
|
+
* tail, the per-branch catch sites and the pass boundary can all reach it,
|
|
15
|
+
* and a second run would append the same behavior signals again.
|
|
16
|
+
*
|
|
17
|
+
* `run` is the pass boundary. A throw from a branch with no catch site of its
|
|
18
|
+
* own (a chunked-parent post-write step, the semantic-merge post effects, the
|
|
19
|
+
* profile or question write) still finalizes the writes already committed
|
|
20
|
+
* before the original error propagates. When a catch site or the success tail
|
|
21
|
+
* already ran the finalizer, the boundary only rethrows. Nothing is durable
|
|
22
|
+
* before the finalizer is bound, so an earlier throw propagates as is.
|
|
23
|
+
*/
|
|
24
|
+
export declare class CommittedWriteFinalization {
|
|
25
|
+
private finalizer;
|
|
26
|
+
private started;
|
|
27
|
+
bind(finalize: FinalizeCommittedWrites): FinalizeCommittedWrites;
|
|
28
|
+
run<T>(pass: () => Promise<T>): Promise<T>;
|
|
29
|
+
}
|
|
30
|
+
/** A pass method's parameter list without its leading CommittedWriteFinalization. */
|
|
31
|
+
export type ArgsAfterFinalization<Params> = Params extends [CommittedWriteFinalization, ...infer Rest] ? Rest : never;
|
|
32
|
+
export {};
|
|
9
33
|
//# sourceMappingURL=extraction-persist-failure.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extraction-persist-failure.d.ts","sourceRoot":"","sources":["../../src/orchestration/extraction-persist-failure.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAsB,sCAAsC,CAC1D,uBAAuB,EAAE,
|
|
1
|
+
{"version":3,"file":"extraction-persist-failure.d.ts","sourceRoot":"","sources":["../../src/orchestration/extraction-persist-failure.ts"],"names":[],"mappings":"AAEA,KAAK,uBAAuB,GAAG,CAAC,WAAW,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvE;;;;;;GAMG;AACH,wBAAsB,sCAAsC,CAC1D,uBAAuB,EAAE,uBAAuB,EAChD,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,KAAK,CAAC,CAKhB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,OAAO,CAA4B;IAE3C,IAAI,CAAC,QAAQ,EAAE,uBAAuB,GAAG,uBAAuB;IAK1D,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAQjD;AAED,qFAAqF;AACrF,MAAM,MAAM,qBAAqB,CAAC,MAAM,IAAI,MAAM,SAAS,CAAC,0BAA0B,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC"}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type StorageManager } from "../index.js";
|
|
3
|
-
import { type GraphConstructionCapabilitySet, type MemoryLifecycleCapabilitySet } from "../capabilities.js";
|
|
4
|
-
import type { ResolvedScopeProfilePlan } from "../namespaces/scope-profiles.js";
|
|
5
|
-
import type { ExtractionResult } from "../types.js";
|
|
1
|
+
import { type ArgsAfterFinalization } from "./extraction-persist-failure.js";
|
|
6
2
|
import type { ExtractionPersistDeps } from "./extraction-persist-deps.js";
|
|
7
3
|
/** One extraction run's persist outcome: per-run values, no shared side channels (#1526). */
|
|
8
4
|
export interface ExtractionPersistResult {
|
|
@@ -18,6 +14,7 @@ export declare class ExtractionPersistCoordinator {
|
|
|
18
14
|
constructor(deps: ExtractionPersistDeps);
|
|
19
15
|
private get config();
|
|
20
16
|
private get dependencyPropagationDelivery();
|
|
21
|
-
persistExtraction(
|
|
17
|
+
persistExtraction(...args: ArgsAfterFinalization<Parameters<ExtractionPersistCoordinator["persistExtractionPass"]>>): Promise<ExtractionPersistResult>;
|
|
18
|
+
private persistExtractionPass;
|
|
22
19
|
}
|
|
23
20
|
//# sourceMappingURL=extraction-persist.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extraction-persist.d.ts","sourceRoot":"","sources":["../../src/orchestration/extraction-persist.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"extraction-persist.d.ts","sourceRoot":"","sources":["../../src/orchestration/extraction-persist.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,qBAAqB,EAAsE,MAAM,iCAAiC,CAAC;AA8GjJ,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAmB1E,6FAA6F;AAC7F,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,4EAA4E;IAC5E,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,qBAAa,4BAA4B;IAErC,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,qBAAqB;IAE9C,OAAO,KAAK,MAAM,GAEjB;IACD,OAAO,KAAK,6BAA6B,GAGxC;IAED,iBAAiB,CAAC,GAAG,IAAI,EAAE,qBAAqB,CAAC,UAAU,CAAC,4BAA4B,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,uBAAuB,CAAC;YAKxI,qBAAqB;CAmxFpC"}
|
package/dist/orchestrator.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
mergeGraphExpandedResults,
|
|
7
7
|
qmdStartupCollectionChecksWithTimeout,
|
|
8
8
|
resolveEffectiveIdentityInjectionMode
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-DDATG2SF.js";
|
|
10
10
|
import "./chunk-B3DR2JBZ.js";
|
|
11
11
|
import "./chunk-6P7U3HWG.js";
|
|
12
12
|
import "./chunk-H4CKS5B2.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.95",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -3386,7 +3386,7 @@
|
|
|
3386
3386
|
"core"
|
|
3387
3387
|
],
|
|
3388
3388
|
"peerDependencies": {
|
|
3389
|
-
"@remnic/coding-graph": "^10.0.
|
|
3389
|
+
"@remnic/coding-graph": "^10.0.95"
|
|
3390
3390
|
},
|
|
3391
3391
|
"peerDependenciesMeta": {
|
|
3392
3392
|
"@remnic/coding-graph": {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { CommittedWriteFinalization } from "./extraction-persist-failure.js";
|
|
5
|
+
|
|
6
|
+
function recordingFinalizer() {
|
|
7
|
+
const calls: boolean[] = [];
|
|
8
|
+
return {
|
|
9
|
+
calls,
|
|
10
|
+
finalize: async (scheduleQmd: boolean) => {
|
|
11
|
+
calls.push(scheduleQmd);
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test("#3288: the bound finalizer runs once across the success tail and catch sites", async () => {
|
|
17
|
+
const finalization = new CommittedWriteFinalization();
|
|
18
|
+
const { calls, finalize } = recordingFinalizer();
|
|
19
|
+
const once = finalization.bind(finalize);
|
|
20
|
+
|
|
21
|
+
const value = await finalization.run(async () => {
|
|
22
|
+
await once(true);
|
|
23
|
+
await once(false);
|
|
24
|
+
return "persisted";
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
assert.equal(value, "persisted");
|
|
28
|
+
assert.deepEqual(calls, [true], "a second caller reuses the first run instead of finalizing again");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("#3288: the pass boundary finalizes with QMD scheduling, then rethrows the original error", async () => {
|
|
32
|
+
const finalization = new CommittedWriteFinalization();
|
|
33
|
+
const { calls, finalize } = recordingFinalizer();
|
|
34
|
+
const cause = new Error("branch without its own catch site");
|
|
35
|
+
|
|
36
|
+
await assert.rejects(
|
|
37
|
+
finalization.run(async () => {
|
|
38
|
+
finalization.bind(finalize);
|
|
39
|
+
throw cause;
|
|
40
|
+
}),
|
|
41
|
+
(error: Error) => error === cause,
|
|
42
|
+
);
|
|
43
|
+
assert.deepEqual(calls, [true]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("#3288: the pass boundary does not finalize again after a catch site already did", async () => {
|
|
47
|
+
const finalization = new CommittedWriteFinalization();
|
|
48
|
+
const { calls, finalize } = recordingFinalizer();
|
|
49
|
+
const cause = new Error("catch site rethrow");
|
|
50
|
+
|
|
51
|
+
await assert.rejects(
|
|
52
|
+
finalization.run(async () => {
|
|
53
|
+
const once = finalization.bind(finalize);
|
|
54
|
+
await once(true);
|
|
55
|
+
throw cause;
|
|
56
|
+
}),
|
|
57
|
+
(error: Error) => error === cause,
|
|
58
|
+
);
|
|
59
|
+
assert.deepEqual(calls, [true]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("#3288: a throw before the finalizer is bound propagates without finalizing", async () => {
|
|
63
|
+
const finalization = new CommittedWriteFinalization();
|
|
64
|
+
const cause = new Error("failed before any write");
|
|
65
|
+
|
|
66
|
+
await assert.rejects(
|
|
67
|
+
finalization.run(async () => {
|
|
68
|
+
throw cause;
|
|
69
|
+
}),
|
|
70
|
+
(error: Error) => error === cause,
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("#3288: a failing finalizer does not mask the original error", async () => {
|
|
75
|
+
const finalization = new CommittedWriteFinalization();
|
|
76
|
+
const cause = new Error("branch failure");
|
|
77
|
+
let attempts = 0;
|
|
78
|
+
|
|
79
|
+
await assert.rejects(
|
|
80
|
+
finalization.run(async () => {
|
|
81
|
+
finalization.bind(async () => {
|
|
82
|
+
attempts += 1;
|
|
83
|
+
throw new Error("finalization failure");
|
|
84
|
+
});
|
|
85
|
+
throw cause;
|
|
86
|
+
}),
|
|
87
|
+
(error: Error) => error === cause,
|
|
88
|
+
);
|
|
89
|
+
assert.equal(attempts, 1);
|
|
90
|
+
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { log } from "../logger.js";
|
|
2
2
|
|
|
3
|
+
type FinalizeCommittedWrites = (scheduleQmd: boolean) => Promise<void>;
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Fail a persistence pass after a durable write rejected (#3227, same
|
|
5
7
|
* contract as the entity write path from #3228). Earlier writes in the batch
|
|
@@ -8,7 +10,7 @@ import { log } from "../logger.js";
|
|
|
8
10
|
* instead of recording the fingerprint and clearing the source turns.
|
|
9
11
|
*/
|
|
10
12
|
export async function rethrowAfterCommittedWriteFinalization(
|
|
11
|
-
finalizeCommittedWrites:
|
|
13
|
+
finalizeCommittedWrites: FinalizeCommittedWrites,
|
|
12
14
|
cause: unknown,
|
|
13
15
|
outputKind: string,
|
|
14
16
|
): Promise<never> {
|
|
@@ -17,3 +19,39 @@ export async function rethrowAfterCommittedWriteFinalization(
|
|
|
17
19
|
);
|
|
18
20
|
throw cause;
|
|
19
21
|
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Batch-end finalization for one persistExtraction pass (#3288).
|
|
25
|
+
*
|
|
26
|
+
* `bind` wraps the pass's finalizer so it runs at most once. The success
|
|
27
|
+
* tail, the per-branch catch sites and the pass boundary can all reach it,
|
|
28
|
+
* and a second run would append the same behavior signals again.
|
|
29
|
+
*
|
|
30
|
+
* `run` is the pass boundary. A throw from a branch with no catch site of its
|
|
31
|
+
* own (a chunked-parent post-write step, the semantic-merge post effects, the
|
|
32
|
+
* profile or question write) still finalizes the writes already committed
|
|
33
|
+
* before the original error propagates. When a catch site or the success tail
|
|
34
|
+
* already ran the finalizer, the boundary only rethrows. Nothing is durable
|
|
35
|
+
* before the finalizer is bound, so an earlier throw propagates as is.
|
|
36
|
+
*/
|
|
37
|
+
export class CommittedWriteFinalization {
|
|
38
|
+
private finalizer: FinalizeCommittedWrites | undefined;
|
|
39
|
+
private started: Promise<void> | undefined;
|
|
40
|
+
|
|
41
|
+
bind(finalize: FinalizeCommittedWrites): FinalizeCommittedWrites {
|
|
42
|
+
this.finalizer = (scheduleQmd) => (this.started ??= finalize(scheduleQmd));
|
|
43
|
+
return this.finalizer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async run<T>(pass: () => Promise<T>): Promise<T> {
|
|
47
|
+
try {
|
|
48
|
+
return await pass();
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (!this.finalizer || this.started) throw err;
|
|
51
|
+
return rethrowAfterCommittedWriteFinalization(this.finalizer, err, "persist pass");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A pass method's parameter list without its leading CommittedWriteFinalization. */
|
|
57
|
+
export type ArgsAfterFinalization<Params> = Params extends [CommittedWriteFinalization, ...infer Rest] ? Rest : never;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { classifyExtractionOrigin, type ExtractionSourceContext } from "./extraction-origin-context.js";
|
|
2
2
|
import { confirmActiveFactDuplicate, persistUnderFactDedupWindow } from "./fact-dedup-window.js";
|
|
3
3
|
import { writePromotionUnderFactDedupWindow } from "./promotion-dedup-window.js";
|
|
4
|
-
import { rethrowAfterCommittedWriteFinalization } from "./extraction-persist-failure.js";
|
|
4
|
+
import { type ArgsAfterFinalization, CommittedWriteFinalization, rethrowAfterCommittedWriteFinalization } from "./extraction-persist-failure.js";
|
|
5
5
|
import { evaluateInjectionScreen, screenEntityForIndex, screenPersistStrings } from "./extraction-injection-gate.js";
|
|
6
6
|
/**
|
|
7
7
|
* Extraction persistence coordinator (issue #1526, seam 16).
|
|
@@ -152,7 +152,13 @@ export class ExtractionPersistCoordinator {
|
|
|
152
152
|
return this.deps.getDependencyPropagationDelivery();
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
persistExtraction(...args: ArgsAfterFinalization<Parameters<ExtractionPersistCoordinator["persistExtractionPass"]>>): Promise<ExtractionPersistResult> {
|
|
156
|
+
const finalization = new CommittedWriteFinalization(); // #3288: one batch-end finalization boundary per pass
|
|
157
|
+
return finalization.run(() => this.persistExtractionPass(finalization, ...args));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private async persistExtractionPass(
|
|
161
|
+
finalization: CommittedWriteFinalization,
|
|
156
162
|
result: ExtractionResult,
|
|
157
163
|
storage: StorageManager,
|
|
158
164
|
threadIdForExtraction?: string | null,
|
|
@@ -1125,7 +1131,7 @@ export class ExtractionPersistCoordinator {
|
|
|
1125
1131
|
0,
|
|
1126
1132
|
this.deps.config.extractionMaxProfileUpdatesPerRun,
|
|
1127
1133
|
);
|
|
1128
|
-
const finalizeCommittedWrites = async (scheduleQmd: boolean): Promise<void> => {
|
|
1134
|
+
const finalizeCommittedWrites = finalization.bind(async (scheduleQmd: boolean): Promise<void> => {
|
|
1129
1135
|
await finalizeExtractionCommittedWrites({
|
|
1130
1136
|
saveContentHashIndexes: () => this.deps.saveContentHashIndexes(),
|
|
1131
1137
|
behaviorSignalsByStorage: behaviorSignalsByStorage.values(),
|
|
@@ -1150,7 +1156,7 @@ export class ExtractionPersistCoordinator {
|
|
|
1150
1156
|
requestQmdMaintenance: () => this.deps.requestQmdMaintenance(),
|
|
1151
1157
|
scheduleQmd,
|
|
1152
1158
|
});
|
|
1153
|
-
};
|
|
1159
|
+
});
|
|
1154
1160
|
|
|
1155
1161
|
if (
|
|
1156
1162
|
facts.length < result.facts.length ||
|
|
@@ -2211,6 +2217,24 @@ export class ExtractionPersistCoordinator {
|
|
|
2211
2217
|
const tombstoneBlocked = parentWrite.tombstoneBlocked;
|
|
2212
2218
|
const postWriteGuard =
|
|
2213
2219
|
faithfulnessEnforceStatus === "pending_review" || tombstoneBlocked;
|
|
2220
|
+
// #3288: the parent is durable now. Track it before any post-write step
|
|
2221
|
+
// that can throw (non-chunked rule, #3231); a retry exact-dedupes it.
|
|
2222
|
+
trackBehaviorSignals(
|
|
2223
|
+
targetStorage,
|
|
2224
|
+
buildBehaviorSignalsForMemory({
|
|
2225
|
+
memoryId: parentId,
|
|
2226
|
+
category: writeCategory,
|
|
2227
|
+
content: fact.content,
|
|
2228
|
+
namespace: this.deps.storageDirNamespace(targetStorage.dir),
|
|
2229
|
+
confidence: fact.confidence,
|
|
2230
|
+
source: "extraction",
|
|
2231
|
+
}),
|
|
2232
|
+
);
|
|
2233
|
+
trackPersistedId(targetStorage, parentId, {
|
|
2234
|
+
pendingReview: postWriteGuard,
|
|
2235
|
+
category: writeCategory,
|
|
2236
|
+
harmonicFact,
|
|
2237
|
+
});
|
|
2214
2238
|
// #1645: defer contradiction auto-resolve until tombstone status is
|
|
2215
2239
|
// known (see applyDeferredContradictionResolve).
|
|
2216
2240
|
const contradictionOutcome = await this.deps.applyDeferredContradictionResolve(
|
|
@@ -2306,11 +2330,6 @@ export class ExtractionPersistCoordinator {
|
|
|
2306
2330
|
log.debug(
|
|
2307
2331
|
`chunked memory ${parentId} into ${chunkResult.chunks.length} chunks`,
|
|
2308
2332
|
);
|
|
2309
|
-
trackPersistedId(targetStorage, parentId, {
|
|
2310
|
-
pendingReview: postWriteGuard,
|
|
2311
|
-
category: writeCategory,
|
|
2312
|
-
harmonicFact,
|
|
2313
|
-
});
|
|
2314
2333
|
await anchorSnapshots.replace(targetStorage, parentId, writeCategory, memoryPathById);
|
|
2315
2334
|
// #1576 (cursor Medium): keep pending_review ids out of threadEpisodeIdsForGraph — else later active facts build thread-predecessor edges to an unfaithful memory.
|
|
2316
2335
|
if (
|
|
@@ -2490,17 +2509,6 @@ export class ExtractionPersistCoordinator {
|
|
|
2490
2509
|
} finally {
|
|
2491
2510
|
// #1522: the catalog write touch lives in the storage chokepoint.
|
|
2492
2511
|
}
|
|
2493
|
-
trackBehaviorSignals(
|
|
2494
|
-
targetStorage,
|
|
2495
|
-
buildBehaviorSignalsForMemory({
|
|
2496
|
-
memoryId: parentId,
|
|
2497
|
-
category: writeCategory,
|
|
2498
|
-
content: fact.content,
|
|
2499
|
-
namespace: this.deps.storageDirNamespace(targetStorage.dir),
|
|
2500
|
-
confidence: fact.confidence,
|
|
2501
|
-
source: "extraction",
|
|
2502
|
-
}),
|
|
2503
|
-
);
|
|
2504
2512
|
continue; // Skip the normal write below
|
|
2505
2513
|
}
|
|
2506
2514
|
}
|
|
@@ -2967,7 +2975,7 @@ export class ExtractionPersistCoordinator {
|
|
|
2967
2975
|
}
|
|
2968
2976
|
|
|
2969
2977
|
// Shared batch-end finalization (hash, behavior, harmonic, temporal).
|
|
2970
|
-
// Failure paths call
|
|
2978
|
+
// Failure paths call it with scheduleQmd=true (#3231); it runs once per pass (#3288).
|
|
2971
2979
|
await finalizeCommittedWrites(false);
|
|
2972
2980
|
|
|
2973
2981
|
const dedupSuffix = `${dedupedCount > 0 ? ` (${dedupedCount} deduped)` : ""}${semanticMergedCount > 0 ? ` (${semanticMergedCount} merged)` : ""}`;
|