@remnic/core 9.51.0 → 9.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.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
|
@@ -12,12 +12,52 @@
|
|
|
12
12
|
* cached `readAllMemories()` path so cost is amortized with the rest of the
|
|
13
13
|
* write pipeline.
|
|
14
14
|
*/
|
|
15
|
-
import type { MemoryFile, MemoryFrontmatter,
|
|
16
|
-
|
|
17
|
-
import
|
|
15
|
+
import type { MemoryFile, MemoryFrontmatter, MemoryLifecycleEvent } from "./types.js";
|
|
16
|
+
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
18
|
import { log } from "./logger.js";
|
|
19
19
|
import { effectiveValidAt } from "./temporal-validity.js";
|
|
20
|
-
import {
|
|
20
|
+
import type {
|
|
21
|
+
DependencyPropagationDeliveryPort,
|
|
22
|
+
DependencyPropagationPreparationToken,
|
|
23
|
+
} from "./orchestration/dependency-propagation-delivery.js";
|
|
24
|
+
import type { PropagationEvent } from "./orchestration/dependency-propagation.js";
|
|
25
|
+
export interface TemporalSupersessionStorage {
|
|
26
|
+
readAllMemories(): Promise<MemoryFile[]>;
|
|
27
|
+
readAllColdMemories(): Promise<MemoryFile[]>;
|
|
28
|
+
readMemoryByPath(memoryPath: string): Promise<MemoryFile | null>;
|
|
29
|
+
writeMemoryFrontmatterIfUnchanged?: (
|
|
30
|
+
memory: MemoryFile,
|
|
31
|
+
patch: Partial<MemoryFrontmatter>,
|
|
32
|
+
lifecycle?: {
|
|
33
|
+
actor?: string;
|
|
34
|
+
reasonCode?: string;
|
|
35
|
+
relatedMemoryIds?: string[];
|
|
36
|
+
correlationId?: string;
|
|
37
|
+
},
|
|
38
|
+
) => Promise<boolean>;
|
|
39
|
+
appendTombstone(input: {
|
|
40
|
+
reason: "supersession";
|
|
41
|
+
createdBy: "supersession";
|
|
42
|
+
sourceMemoryId: string;
|
|
43
|
+
rawContent: string;
|
|
44
|
+
entityRef?: string;
|
|
45
|
+
supersessionKey?: string;
|
|
46
|
+
createdAt?: string;
|
|
47
|
+
contentHash?: string;
|
|
48
|
+
operationKey?: string;
|
|
49
|
+
}): Promise<string | null>;
|
|
50
|
+
hasExactTombstone?: (input: {
|
|
51
|
+
sourceMemoryId: string;
|
|
52
|
+
contentHash?: string;
|
|
53
|
+
entityRef?: string;
|
|
54
|
+
supersessionKey?: string;
|
|
55
|
+
createdAt?: string;
|
|
56
|
+
operationKey?: string;
|
|
57
|
+
}) => Promise<boolean>;
|
|
58
|
+
getMemoryTimeline: (memoryId: string, limit?: number) => Promise<MemoryLifecycleEvent[]>;
|
|
59
|
+
isTombstonesEnabled?: () => boolean;
|
|
60
|
+
}
|
|
21
61
|
|
|
22
62
|
/**
|
|
23
63
|
* Shared normalization for supersession key components.
|
|
@@ -171,15 +211,254 @@ export function shouldSupersedeExisting(args: {
|
|
|
171
211
|
function normalizeValue(v: string): string {
|
|
172
212
|
return v.trim().toLowerCase();
|
|
173
213
|
}
|
|
214
|
+
function temporalSupersessionOperationKey(
|
|
215
|
+
sourceMemoryId: string,
|
|
216
|
+
replacementId: string,
|
|
217
|
+
mutation: TemporalSupersessionMutation,
|
|
218
|
+
): string {
|
|
219
|
+
return createHash("sha256")
|
|
220
|
+
.update(
|
|
221
|
+
JSON.stringify({
|
|
222
|
+
sourceMemoryId,
|
|
223
|
+
replacementId,
|
|
224
|
+
cause: "temporal_supersession",
|
|
225
|
+
supersededAt: mutation.supersededAt,
|
|
226
|
+
invalidAt: mutation.invalidAt ?? null,
|
|
227
|
+
matchedKeys: [...mutation.matchedKeys].sort(compareByteStable),
|
|
228
|
+
}),
|
|
229
|
+
)
|
|
230
|
+
.digest("hex");
|
|
231
|
+
}
|
|
232
|
+
type TemporalMemorySnapshot = Pick<MemoryFile, "content" | "frontmatter">;
|
|
233
|
+
function compareByteStable(left: string, right: string): number {
|
|
234
|
+
return Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function canonicalTemporalValue(value: unknown): unknown {
|
|
238
|
+
if (Array.isArray(value)) return value.map((entry) => canonicalTemporalValue(entry));
|
|
239
|
+
if (value && typeof value === "object") {
|
|
240
|
+
return Object.fromEntries(
|
|
241
|
+
Object.entries(value as Record<string, unknown>)
|
|
242
|
+
.sort(([left], [right]) => compareByteStable(left, right))
|
|
243
|
+
.map(([key, entry]) => [key, canonicalTemporalValue(entry)]),
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function temporalSnapshotKey(memory: TemporalMemorySnapshot): string {
|
|
250
|
+
return JSON.stringify(
|
|
251
|
+
canonicalTemporalValue({
|
|
252
|
+
content: memory.content,
|
|
253
|
+
frontmatter: memory.frontmatter,
|
|
254
|
+
}),
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function temporalMutationPatch(
|
|
259
|
+
replacementId: string,
|
|
260
|
+
mutation: TemporalSupersessionMutation,
|
|
261
|
+
): Partial<MemoryFrontmatter> {
|
|
262
|
+
return {
|
|
263
|
+
status: "superseded",
|
|
264
|
+
supersededBy: replacementId,
|
|
265
|
+
supersededAt: mutation.supersededAt,
|
|
266
|
+
updated: mutation.supersededAt,
|
|
267
|
+
...(mutation.invalidAt ? { invalid_at: mutation.invalidAt } : {}),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
type TemporalPrimaryCommitState = "committed" | "not-committed" | "unknown";
|
|
272
|
+
|
|
273
|
+
async function classifyTemporalPrimaryMutation(args: {
|
|
274
|
+
storage: TemporalSupersessionStorage;
|
|
275
|
+
path: string;
|
|
276
|
+
before: TemporalMemorySnapshot;
|
|
277
|
+
patch: Partial<MemoryFrontmatter>;
|
|
278
|
+
}): Promise<TemporalPrimaryCommitState> {
|
|
279
|
+
let current: MemoryFile | null;
|
|
280
|
+
try {
|
|
281
|
+
current = await args.storage.readMemoryByPath(args.path);
|
|
282
|
+
} catch {
|
|
283
|
+
return "unknown";
|
|
284
|
+
}
|
|
285
|
+
if (!current) return "unknown";
|
|
286
|
+
const beforeKey = temporalSnapshotKey(args.before);
|
|
287
|
+
const committedKey = temporalSnapshotKey({
|
|
288
|
+
content: args.before.content,
|
|
289
|
+
frontmatter: { ...args.before.frontmatter, ...args.patch },
|
|
290
|
+
});
|
|
291
|
+
const currentKey = temporalSnapshotKey(current);
|
|
292
|
+
if (currentKey === committedKey) return "committed";
|
|
293
|
+
if (currentKey === beforeKey) return "not-committed";
|
|
294
|
+
return "unknown";
|
|
295
|
+
}
|
|
296
|
+
type PreparedTemporalPropagation = {
|
|
297
|
+
delivery: DependencyPropagationDeliveryPort;
|
|
298
|
+
event: PropagationEvent;
|
|
299
|
+
token: DependencyPropagationPreparationToken | null;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
async function settleTemporalPropagationAfterPrimaryFailure(
|
|
303
|
+
pending: PreparedTemporalPropagation,
|
|
304
|
+
commitState: TemporalPrimaryCommitState,
|
|
305
|
+
memoryId: string,
|
|
306
|
+
): Promise<void> {
|
|
307
|
+
let token = pending.token;
|
|
308
|
+
if (commitState === "not-committed") {
|
|
309
|
+
if (token !== null) {
|
|
310
|
+
try {
|
|
311
|
+
await pending.delivery.cancel(token);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
log.warn(
|
|
314
|
+
`temporal-supersession: dependency propagation cancellation failed for ${memoryId}: ${error}`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (token === null && (commitState === "committed" || commitState === "unknown")) {
|
|
321
|
+
try {
|
|
322
|
+
token = await pending.delivery.prepare(pending.event);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
log.warn(
|
|
325
|
+
`temporal-supersession: dependency propagation recovery preparation failed for ${memoryId}: ${error}`,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (token !== null) {
|
|
330
|
+
try {
|
|
331
|
+
await pending.delivery.deferPrepared(token);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
log.warn(`temporal-supersession: dependency propagation defer failed for ${memoryId}: ${error}`);
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (commitState === "committed") {
|
|
338
|
+
try {
|
|
339
|
+
await pending.delivery.afterMutation(null, pending.event);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
log.warn(`temporal-supersession: dependency propagation fallback failed for ${memoryId}: ${error}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
async function hasTemporalLifecycleEffect(args: {
|
|
348
|
+
storage: TemporalSupersessionStorage;
|
|
349
|
+
memoryId: string;
|
|
350
|
+
timestamp: string;
|
|
351
|
+
operationKey: string;
|
|
352
|
+
reasonCode: string;
|
|
353
|
+
relatedMemoryIds: string[];
|
|
354
|
+
}): Promise<boolean> {
|
|
355
|
+
const events = await args.storage.getMemoryTimeline(args.memoryId, 200);
|
|
356
|
+
return events.some((event) => {
|
|
357
|
+
if (
|
|
358
|
+
event.memoryId !== args.memoryId ||
|
|
359
|
+
(event.eventType !== "superseded" && event.eventType !== "updated") ||
|
|
360
|
+
event.timestamp !== args.timestamp ||
|
|
361
|
+
event.after?.status !== "superseded"
|
|
362
|
+
) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
if (event.correlationId !== undefined) return event.correlationId === args.operationKey;
|
|
366
|
+
// Rows written before correlation IDs existed can be replayed only with
|
|
367
|
+
// their complete legacy identity. New rows always take the exact branch.
|
|
368
|
+
return (
|
|
369
|
+
event.reasonCode === args.reasonCode &&
|
|
370
|
+
Array.isArray(event.relatedMemoryIds) &&
|
|
371
|
+
event.relatedMemoryIds.length === args.relatedMemoryIds.length &&
|
|
372
|
+
event.relatedMemoryIds.every((memoryId, index) => memoryId === args.relatedMemoryIds[index])
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async function hasTemporalTombstoneEffect(args: {
|
|
378
|
+
storage: TemporalSupersessionStorage;
|
|
379
|
+
oldMemory: MemoryFile;
|
|
380
|
+
supersessionKey?: string;
|
|
381
|
+
operationKey: string;
|
|
382
|
+
createdAt: string;
|
|
383
|
+
}): Promise<boolean> {
|
|
384
|
+
if (!args.storage.hasExactTombstone) return false;
|
|
385
|
+
return args.storage.hasExactTombstone({
|
|
386
|
+
sourceMemoryId: args.oldMemory.frontmatter.id,
|
|
387
|
+
...(args.oldMemory.frontmatter.contentHash
|
|
388
|
+
? { contentHash: args.oldMemory.frontmatter.contentHash }
|
|
389
|
+
: {}),
|
|
390
|
+
...(args.oldMemory.frontmatter.entityRef
|
|
391
|
+
? { entityRef: args.oldMemory.frontmatter.entityRef }
|
|
392
|
+
: {}),
|
|
393
|
+
...(args.supersessionKey ? { supersessionKey: args.supersessionKey } : {}),
|
|
394
|
+
createdAt: args.createdAt,
|
|
395
|
+
operationKey: args.operationKey,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
async function ensureTemporalLifecycleEffect(args: {
|
|
401
|
+
storage: TemporalSupersessionStorage;
|
|
402
|
+
memory: MemoryFile;
|
|
403
|
+
replacementId: string;
|
|
404
|
+
mutation: TemporalSupersessionMutation;
|
|
405
|
+
operationKey: string;
|
|
406
|
+
child?: boolean;
|
|
407
|
+
}): Promise<boolean> {
|
|
408
|
+
const reasonCode = args.child
|
|
409
|
+
? "structured-attribute-update-child-chunk"
|
|
410
|
+
: "structured-attribute-update";
|
|
411
|
+
const relatedMemoryIds = args.child
|
|
412
|
+
? [args.replacementId, args.memory.frontmatter.parentId ?? ""].filter(Boolean)
|
|
413
|
+
: [args.replacementId];
|
|
414
|
+
const lifecycleEffect = await hasTemporalLifecycleEffect({
|
|
415
|
+
storage: args.storage,
|
|
416
|
+
memoryId: args.memory.frontmatter.id,
|
|
417
|
+
timestamp: args.mutation.supersededAt,
|
|
418
|
+
operationKey: args.operationKey,
|
|
419
|
+
reasonCode,
|
|
420
|
+
relatedMemoryIds,
|
|
421
|
+
});
|
|
422
|
+
if (lifecycleEffect === undefined) return false;
|
|
423
|
+
if (lifecycleEffect === true) return true;
|
|
424
|
+
if (lifecycleEffect === false || args.memory.frontmatter.status !== "superseded") {
|
|
425
|
+
if (!args.storage.writeMemoryFrontmatterIfUnchanged) return false;
|
|
426
|
+
const wrote = await args.storage.writeMemoryFrontmatterIfUnchanged(
|
|
427
|
+
args.memory,
|
|
428
|
+
{
|
|
429
|
+
status: "superseded",
|
|
430
|
+
supersededBy: args.replacementId,
|
|
431
|
+
supersededAt: args.mutation.supersededAt,
|
|
432
|
+
updated: args.mutation.supersededAt,
|
|
433
|
+
...(args.mutation.invalidAt && !args.memory.frontmatter.invalid_at
|
|
434
|
+
? { invalid_at: args.mutation.invalidAt }
|
|
435
|
+
: {}),
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
actor: "temporal-supersession",
|
|
439
|
+
reasonCode: args.child
|
|
440
|
+
? "structured-attribute-update-child-chunk"
|
|
441
|
+
: "structured-attribute-update",
|
|
442
|
+
relatedMemoryIds: args.child
|
|
443
|
+
? [args.replacementId, args.memory.frontmatter.parentId ?? ""].filter(Boolean)
|
|
444
|
+
: [args.replacementId],
|
|
445
|
+
correlationId: args.operationKey,
|
|
446
|
+
},
|
|
447
|
+
);
|
|
448
|
+
return wrote;
|
|
449
|
+
}
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
174
452
|
|
|
175
453
|
async function expireChildChunksForSupersededParent(args: {
|
|
176
|
-
storage:
|
|
454
|
+
storage: TemporalSupersessionStorage;
|
|
177
455
|
allCandidates: MemoryFile[];
|
|
178
456
|
parentId: string;
|
|
179
457
|
newMemoryId: string;
|
|
180
458
|
supersededAt: string;
|
|
181
459
|
invalidAt?: string;
|
|
182
|
-
|
|
460
|
+
operationKey: string;
|
|
461
|
+
}): Promise<boolean> {
|
|
183
462
|
const processedChunkIds = new Set<string>();
|
|
184
463
|
const chunks = args.allCandidates.filter(
|
|
185
464
|
(candidate) => candidate.frontmatter.parentId === args.parentId,
|
|
@@ -194,31 +473,161 @@ async function expireChildChunksForSupersededParent(args: {
|
|
|
194
473
|
if (!freshChunk) continue;
|
|
195
474
|
processedChunkIds.add(chunkKey);
|
|
196
475
|
const freshStatus = freshChunk.frontmatter.status ?? "active";
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
476
|
+
if (
|
|
477
|
+
freshStatus !== "active" &&
|
|
478
|
+
!(
|
|
479
|
+
freshStatus === "superseded" &&
|
|
480
|
+
freshChunk.frontmatter.supersededBy === args.newMemoryId
|
|
481
|
+
)
|
|
482
|
+
) {
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
const completed = await ensureTemporalLifecycleEffect({
|
|
486
|
+
storage: args.storage,
|
|
487
|
+
memory: freshChunk,
|
|
488
|
+
replacementId: args.newMemoryId,
|
|
489
|
+
mutation: {
|
|
204
490
|
supersededAt: args.supersededAt,
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
? { invalid_at: args.invalidAt }
|
|
208
|
-
: {}),
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
actor: "temporal-supersession",
|
|
212
|
-
reasonCode: "structured-attribute-update-child-chunk",
|
|
213
|
-
relatedMemoryIds: [args.newMemoryId, args.parentId],
|
|
491
|
+
...(args.invalidAt ? { invalidAt: args.invalidAt } : {}),
|
|
492
|
+
matchedKeys: [],
|
|
214
493
|
},
|
|
215
|
-
|
|
494
|
+
operationKey: `${args.operationKey}:child:${freshChunk.frontmatter.id}`,
|
|
495
|
+
child: true,
|
|
496
|
+
});
|
|
497
|
+
if (!completed) return false;
|
|
216
498
|
} catch (err) {
|
|
217
499
|
log.warn(
|
|
218
500
|
`temporal-supersession: failed to expire child chunk ${chunk.frontmatter.id} for parent ${args.parentId}: ${err}`,
|
|
219
501
|
);
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export interface TemporalSupersessionMutation {
|
|
509
|
+
supersededAt: string;
|
|
510
|
+
invalidAt?: string;
|
|
511
|
+
matchedKeys: string[];
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export async function applyTemporalSupersessionPrimaryMutation(args: {
|
|
515
|
+
storage: TemporalSupersessionStorage;
|
|
516
|
+
oldMemory: MemoryFile;
|
|
517
|
+
replacementId: string;
|
|
518
|
+
mutation: TemporalSupersessionMutation;
|
|
519
|
+
allCandidates?: MemoryFile[];
|
|
520
|
+
}): Promise<boolean> {
|
|
521
|
+
const { storage, oldMemory, replacementId, mutation } = args;
|
|
522
|
+
if (typeof storage.getMemoryTimeline !== "function") {
|
|
523
|
+
log.warn("temporal-supersession: required memory timeline adapter is missing");
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
const operationKey = temporalSupersessionOperationKey(
|
|
527
|
+
oldMemory.frontmatter.id,
|
|
528
|
+
replacementId,
|
|
529
|
+
mutation,
|
|
530
|
+
);
|
|
531
|
+
const alreadyApplied =
|
|
532
|
+
oldMemory.frontmatter.status === "superseded" &&
|
|
533
|
+
oldMemory.frontmatter.supersededBy === replacementId &&
|
|
534
|
+
oldMemory.frontmatter.supersededAt === mutation.supersededAt &&
|
|
535
|
+
(mutation.invalidAt === undefined || oldMemory.frontmatter.invalid_at === mutation.invalidAt);
|
|
536
|
+
const lifecycleEffect = alreadyApplied
|
|
537
|
+
? await hasTemporalLifecycleEffect({
|
|
538
|
+
storage,
|
|
539
|
+
memoryId: oldMemory.frontmatter.id,
|
|
540
|
+
timestamp: mutation.supersededAt,
|
|
541
|
+
operationKey,
|
|
542
|
+
reasonCode: "structured-attribute-update",
|
|
543
|
+
relatedMemoryIds: [replacementId],
|
|
544
|
+
})
|
|
545
|
+
: undefined;
|
|
546
|
+
if (alreadyApplied && lifecycleEffect === undefined) return false;
|
|
547
|
+
if (!alreadyApplied || lifecycleEffect === false) {
|
|
548
|
+
if (!storage.writeMemoryFrontmatterIfUnchanged) return false;
|
|
549
|
+
const wrote = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
550
|
+
oldMemory,
|
|
551
|
+
temporalMutationPatch(replacementId, mutation),
|
|
552
|
+
{
|
|
553
|
+
actor: "temporal-supersession",
|
|
554
|
+
reasonCode: "structured-attribute-update",
|
|
555
|
+
relatedMemoryIds: [replacementId],
|
|
556
|
+
correlationId: operationKey,
|
|
557
|
+
},
|
|
558
|
+
);
|
|
559
|
+
if (!wrote) return false;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
let allCandidates = args.allCandidates;
|
|
563
|
+
if (!allCandidates) {
|
|
564
|
+
let hotMemories: MemoryFile[];
|
|
565
|
+
let coldMemories: MemoryFile[];
|
|
566
|
+
try {
|
|
567
|
+
hotMemories = await storage.readAllMemories();
|
|
568
|
+
} catch (error) {
|
|
569
|
+
log.warn(`temporal-supersession: recovery hot-memory scan failed: ${error}`);
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
coldMemories = await storage.readAllColdMemories();
|
|
574
|
+
} catch (error) {
|
|
575
|
+
log.warn(`temporal-supersession: recovery cold-memory scan failed: ${error}`);
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
allCandidates = [...hotMemories, ...coldMemories];
|
|
579
|
+
}
|
|
580
|
+
const childrenCompleted = await expireChildChunksForSupersededParent({
|
|
581
|
+
storage,
|
|
582
|
+
allCandidates,
|
|
583
|
+
parentId: oldMemory.frontmatter.id,
|
|
584
|
+
newMemoryId: replacementId,
|
|
585
|
+
supersededAt: mutation.supersededAt,
|
|
586
|
+
invalidAt: mutation.invalidAt ?? oldMemory.frontmatter.invalid_at,
|
|
587
|
+
operationKey,
|
|
588
|
+
});
|
|
589
|
+
if (!childrenCompleted) return false;
|
|
590
|
+
|
|
591
|
+
const keysToTombstone =
|
|
592
|
+
mutation.matchedKeys.length > 0 ? mutation.matchedKeys : [undefined];
|
|
593
|
+
for (const key of keysToTombstone) {
|
|
594
|
+
const tombstoneOperationKey = `${operationKey}:tombstone:${key ?? "content"}`;
|
|
595
|
+
if (
|
|
596
|
+
await hasTemporalTombstoneEffect({
|
|
597
|
+
storage,
|
|
598
|
+
oldMemory,
|
|
599
|
+
supersessionKey: key,
|
|
600
|
+
operationKey: tombstoneOperationKey,
|
|
601
|
+
createdAt: mutation.supersededAt,
|
|
602
|
+
})
|
|
603
|
+
) {
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
try {
|
|
607
|
+
const tombstoneId = await storage.appendTombstone({
|
|
608
|
+
reason: "supersession",
|
|
609
|
+
createdBy: "supersession",
|
|
610
|
+
sourceMemoryId: oldMemory.frontmatter.id,
|
|
611
|
+
...(oldMemory.frontmatter.contentHash
|
|
612
|
+
? { contentHash: oldMemory.frontmatter.contentHash }
|
|
613
|
+
: {}),
|
|
614
|
+
rawContent: oldMemory.content,
|
|
615
|
+
...(oldMemory.frontmatter.entityRef ? { entityRef: oldMemory.frontmatter.entityRef } : {}),
|
|
616
|
+
...(key ? { supersessionKey: key } : {}),
|
|
617
|
+
createdAt: mutation.supersededAt,
|
|
618
|
+
operationKey: tombstoneOperationKey,
|
|
619
|
+
});
|
|
620
|
+
if (tombstoneId === null && storage.isTombstonesEnabled?.() !== false) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
} catch (error) {
|
|
624
|
+
log.warn(
|
|
625
|
+
`temporal-supersession: tombstone emit failed for ${oldMemory.frontmatter.id}${key ? ` (key=${key})` : ""} : ${error}`,
|
|
626
|
+
);
|
|
627
|
+
return false;
|
|
220
628
|
}
|
|
221
629
|
}
|
|
630
|
+
return true;
|
|
222
631
|
}
|
|
223
632
|
|
|
224
633
|
export interface TemporalSupersessionResult {
|
|
@@ -232,7 +641,7 @@ export interface TemporalSupersessionResult {
|
|
|
232
641
|
* on disk, and supersession is a best-effort hygiene step.
|
|
233
642
|
*/
|
|
234
643
|
export async function applyTemporalSupersession(args: {
|
|
235
|
-
storage:
|
|
644
|
+
storage: TemporalSupersessionStorage;
|
|
236
645
|
newMemoryId: string;
|
|
237
646
|
entityRef?: string;
|
|
238
647
|
structuredAttributes?: Record<string, string>;
|
|
@@ -246,16 +655,29 @@ export async function applyTemporalSupersession(args: {
|
|
|
246
655
|
* stale relative to the incoming promotion event (PR #402 Finding Uyui).
|
|
247
656
|
*/
|
|
248
657
|
useCallerTimestamp?: boolean;
|
|
249
|
-
/**
|
|
250
|
-
extraction?: ExtractionEngine;
|
|
251
|
-
config?: PluginConfig;
|
|
658
|
+
/** Namespace used by the dependency propagation worker. */
|
|
252
659
|
namespaceScope?: string;
|
|
660
|
+
/** Optional durable delivery for one-hop propagation. */
|
|
661
|
+
dependencyPropagationDelivery?: DependencyPropagationDeliveryPort;
|
|
253
662
|
}): Promise<TemporalSupersessionResult> {
|
|
254
663
|
const empty: TemporalSupersessionResult = { supersededIds: [], matchedKeys: [] };
|
|
664
|
+
const createdAtMs = typeof args.createdAt === "string" ? Date.parse(args.createdAt) : Number.NaN;
|
|
665
|
+
if (!Number.isFinite(createdAtMs)) {
|
|
666
|
+
log.warn(`temporal-supersession: refusing mutation with invalid createdAt: ${String(args.createdAt)}`);
|
|
667
|
+
return empty;
|
|
668
|
+
}
|
|
669
|
+
// Validate the caller's ordering anchor before any corpus scan or durable
|
|
670
|
+
// side-effect preparation. A malformed timestamp must be a true no-op:
|
|
671
|
+
// otherwise a later monotonic clamp could manufacture a 1970 mutation and
|
|
672
|
+
// leave a prepared propagation job without a replayable temporal identity.
|
|
255
673
|
if (!args.enabled) return empty;
|
|
256
674
|
if (!args.entityRef) return empty;
|
|
257
675
|
if (!args.structuredAttributes) return empty;
|
|
258
676
|
if (Object.keys(args.structuredAttributes).length === 0) return empty;
|
|
677
|
+
if (typeof args.storage.getMemoryTimeline !== "function") {
|
|
678
|
+
log.warn("temporal-supersession: required memory timeline adapter is missing");
|
|
679
|
+
return empty;
|
|
680
|
+
}
|
|
259
681
|
|
|
260
682
|
const newKeys = supersessionKeysForFact({
|
|
261
683
|
entityRef: args.entityRef,
|
|
@@ -335,6 +757,11 @@ export async function applyTemporalSupersession(args: {
|
|
|
335
757
|
// supersessions — the P1 finding is precisely that stale cold facts leak
|
|
336
758
|
// when hot has no hits.
|
|
337
759
|
const allCandidates: MemoryFile[] = [...hotMemories, ...coldMemories];
|
|
760
|
+
// Resolve the replacement from the combined hot and cold snapshot. A
|
|
761
|
+
// hash-dedup promotion can point at a cold-only memory, and replay needs its
|
|
762
|
+
// captured content rather than a null replacement payload.
|
|
763
|
+
const replacementMemory =
|
|
764
|
+
allCandidates.find((memory) => memory.frontmatter.id === args.newMemoryId) ?? newMemoryFile;
|
|
338
765
|
|
|
339
766
|
for (const memory of allCandidates) {
|
|
340
767
|
if (memory.frontmatter.id === args.newMemoryId) continue;
|
|
@@ -371,6 +798,14 @@ export async function applyTemporalSupersession(args: {
|
|
|
371
798
|
continue;
|
|
372
799
|
}
|
|
373
800
|
|
|
801
|
+
let preparedPropagation: {
|
|
802
|
+
delivery: DependencyPropagationDeliveryPort;
|
|
803
|
+
event: PropagationEvent;
|
|
804
|
+
token: DependencyPropagationPreparationToken | null;
|
|
805
|
+
} | null = null;
|
|
806
|
+
let primaryPath: string | null = null;
|
|
807
|
+
let primaryBefore: TemporalMemorySnapshot | null = null;
|
|
808
|
+
let primaryPatch: Partial<MemoryFrontmatter> | null = null;
|
|
374
809
|
try {
|
|
375
810
|
// CAS-style re-read immediately before the write. `readAllMemories()`
|
|
376
811
|
// is a snapshot — with concurrent writers, another run may have already
|
|
@@ -405,6 +840,11 @@ export async function applyTemporalSupersession(args: {
|
|
|
405
840
|
);
|
|
406
841
|
continue;
|
|
407
842
|
}
|
|
843
|
+
primaryPath = fresh.path;
|
|
844
|
+
primaryBefore = {
|
|
845
|
+
content: fresh.content,
|
|
846
|
+
frontmatter: { ...fresh.frontmatter },
|
|
847
|
+
};
|
|
408
848
|
|
|
409
849
|
// Finding 2 fix: the `supersededAt` / `updated` timestamps written to the
|
|
410
850
|
// old fact must never run backwards relative to its own persisted
|
|
@@ -446,99 +886,119 @@ export async function applyTemporalSupersession(args: {
|
|
|
446
886
|
if (args.useCallerTimestamp) {
|
|
447
887
|
invalidAtPatch = persistedCreatedAt;
|
|
448
888
|
} else {
|
|
449
|
-
const newValidAt =
|
|
889
|
+
const newValidAt = replacementMemory?.frontmatter.valid_at?.trim();
|
|
450
890
|
invalidAtPatch =
|
|
451
891
|
newValidAt && newValidAt.length > 0 ? newValidAt : persistedCreatedAt;
|
|
452
892
|
}
|
|
453
893
|
}
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
{
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
894
|
+
const mutation: TemporalSupersessionMutation = {
|
|
895
|
+
supersededAt,
|
|
896
|
+
...(invalidAtPatch ? { invalidAt: invalidAtPatch } : {}),
|
|
897
|
+
matchedKeys: decision.matchedKeys,
|
|
898
|
+
};
|
|
899
|
+
primaryPatch = temporalMutationPatch(args.newMemoryId, mutation);
|
|
900
|
+
const propagationEvent: PropagationEvent | null =
|
|
901
|
+
args.dependencyPropagationDelivery && args.namespaceScope
|
|
902
|
+
? {
|
|
903
|
+
oldMemory: {
|
|
904
|
+
content: fresh.content,
|
|
905
|
+
frontmatter: {
|
|
906
|
+
...fresh.frontmatter,
|
|
907
|
+
...(fresh.frontmatter.links
|
|
908
|
+
? { links: fresh.frontmatter.links.map((link) => ({ ...link })) }
|
|
909
|
+
: {}),
|
|
910
|
+
},
|
|
911
|
+
},
|
|
912
|
+
replacementId: args.newMemoryId,
|
|
913
|
+
replacementContent: replacementMemory?.content ?? null,
|
|
914
|
+
cause: "temporal_supersession",
|
|
915
|
+
namespaceScope: args.namespaceScope,
|
|
916
|
+
temporalMutation: mutation,
|
|
917
|
+
}
|
|
918
|
+
: null;
|
|
919
|
+
if (propagationEvent && args.dependencyPropagationDelivery) {
|
|
920
|
+
preparedPropagation = {
|
|
921
|
+
delivery: args.dependencyPropagationDelivery,
|
|
922
|
+
event: propagationEvent,
|
|
923
|
+
token: null,
|
|
924
|
+
};
|
|
925
|
+
try {
|
|
926
|
+
preparedPropagation.token =
|
|
927
|
+
await args.dependencyPropagationDelivery.prepare(propagationEvent);
|
|
928
|
+
} catch (prepareErr) {
|
|
929
|
+
log.warn(
|
|
930
|
+
`temporal-supersession: dependency propagation preparation failed for ${fresh.frontmatter.id}: ${prepareErr}`,
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
const wrote = await applyTemporalSupersessionPrimaryMutation({
|
|
935
|
+
storage: args.storage,
|
|
936
|
+
oldMemory: fresh,
|
|
937
|
+
replacementId: args.newMemoryId,
|
|
938
|
+
mutation,
|
|
939
|
+
allCandidates,
|
|
940
|
+
});
|
|
941
|
+
if (!wrote) {
|
|
942
|
+
const commitState = await classifyTemporalPrimaryMutation({
|
|
473
943
|
storage: args.storage,
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
supersededAt,
|
|
478
|
-
invalidAt: invalidAtPatch ?? fresh.frontmatter.invalid_at,
|
|
944
|
+
path: fresh.path,
|
|
945
|
+
before: primaryBefore,
|
|
946
|
+
patch: primaryPatch,
|
|
479
947
|
});
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
// just the first. A later paraphrased re-observation may derive the
|
|
488
|
-
// same entity but place the tombstoned attribute at a different
|
|
489
|
-
// position in structuredAttributes; without an entry for every matched
|
|
490
|
-
// key, the keyed tier would miss and the fact could resurrect as
|
|
491
|
-
// active. Pass the canonical contentHash from the retired memory's
|
|
492
|
-
// frontmatter so the tombstone's exact tier matches re-extraction
|
|
493
|
-
// (review: citation-hash alignment). The rawContent is the stored body;
|
|
494
|
-
// the StorageManager.appendTombstone chokepoint strips citation
|
|
495
|
-
// annotations for the normalized-text tier.
|
|
496
|
-
const keysToTombstone =
|
|
497
|
-
decision.matchedKeys.length > 0 ? decision.matchedKeys : [undefined];
|
|
498
|
-
for (const key of keysToTombstone) {
|
|
499
|
-
try {
|
|
500
|
-
await args.storage.appendTombstone({
|
|
501
|
-
reason: "supersession",
|
|
502
|
-
createdBy: "supersession",
|
|
503
|
-
sourceMemoryId: fresh.frontmatter.id,
|
|
504
|
-
...(fresh.frontmatter.contentHash
|
|
505
|
-
? { contentHash: fresh.frontmatter.contentHash }
|
|
506
|
-
: {}),
|
|
507
|
-
rawContent: fresh.content,
|
|
508
|
-
...(fresh.frontmatter.entityRef ? { entityRef: fresh.frontmatter.entityRef } : {}),
|
|
509
|
-
...(key ? { supersessionKey: key } : {}),
|
|
510
|
-
createdAt: supersededAt,
|
|
511
|
-
});
|
|
512
|
-
} catch (tombErr) {
|
|
948
|
+
if (preparedPropagation) {
|
|
949
|
+
await settleTemporalPropagationAfterPrimaryFailure(
|
|
950
|
+
preparedPropagation,
|
|
951
|
+
commitState,
|
|
952
|
+
fresh.frontmatter.id,
|
|
953
|
+
);
|
|
954
|
+
if (commitState !== "not-committed") {
|
|
513
955
|
log.warn(
|
|
514
|
-
`temporal-supersession:
|
|
956
|
+
`temporal-supersession: retaining dependency propagation recovery for ${fresh.frontmatter.id} after ${commitState} primary mutation`,
|
|
515
957
|
);
|
|
516
958
|
}
|
|
517
959
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
);
|
|
534
|
-
} catch (propagationErr) {
|
|
535
|
-
log.warn(
|
|
536
|
-
`temporal-supersession: dependency propagation failed for ${fresh.frontmatter.id}: ${propagationErr}`,
|
|
537
|
-
);
|
|
538
|
-
}
|
|
960
|
+
preparedPropagation = null;
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
if (preparedPropagation) {
|
|
964
|
+
const completedPropagation = preparedPropagation;
|
|
965
|
+
preparedPropagation = null;
|
|
966
|
+
try {
|
|
967
|
+
await completedPropagation.delivery.afterMutation(
|
|
968
|
+
completedPropagation.token,
|
|
969
|
+
completedPropagation.event,
|
|
970
|
+
);
|
|
971
|
+
} catch (afterMutationErr) {
|
|
972
|
+
log.warn(
|
|
973
|
+
`temporal-supersession: dependency propagation delivery failed for ${memory.frontmatter.id}: ${afterMutationErr}`,
|
|
974
|
+
);
|
|
539
975
|
}
|
|
540
976
|
}
|
|
977
|
+
supersededIds.push(memory.frontmatter.id);
|
|
978
|
+
for (const key of decision.matchedKeys) matchedKeys.add(key);
|
|
541
979
|
} catch (err) {
|
|
980
|
+
let commitState: TemporalPrimaryCommitState = "unknown";
|
|
981
|
+
if (primaryPath && primaryBefore && primaryPatch) {
|
|
982
|
+
commitState = await classifyTemporalPrimaryMutation({
|
|
983
|
+
storage: args.storage,
|
|
984
|
+
path: primaryPath,
|
|
985
|
+
before: primaryBefore,
|
|
986
|
+
patch: primaryPatch,
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
if (preparedPropagation) {
|
|
990
|
+
await settleTemporalPropagationAfterPrimaryFailure(
|
|
991
|
+
preparedPropagation,
|
|
992
|
+
commitState,
|
|
993
|
+
memory.frontmatter.id,
|
|
994
|
+
);
|
|
995
|
+
if (commitState !== "not-committed") {
|
|
996
|
+
log.warn(
|
|
997
|
+
`temporal-supersession: retaining dependency propagation recovery for ${memory.frontmatter.id} after ${commitState} primary mutation`,
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
preparedPropagation = null;
|
|
542
1002
|
log.warn(
|
|
543
1003
|
`temporal-supersession: failed to mark ${memory.frontmatter.id} superseded: ${err}`,
|
|
544
1004
|
);
|