@remnic/core 9.35.2 → 9.35.4
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 +5 -5
- package/dist/access-admin-ops-surface.js +19 -18
- package/dist/access-authorization-probe.d.ts +5 -5
- package/dist/access-authorization-probe.js +20 -19
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +19 -18
- package/dist/access-cli.js +39 -38
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.d.ts +5 -5
- package/dist/access-extraction-force-flush.js +19 -18
- package/dist/access-http-lcm-compaction.d.ts +5 -5
- package/dist/access-http-lifecycle-flush.d.ts +5 -5
- package/dist/access-http.d.ts +11 -5
- package/dist/access-http.js +24 -23
- package/dist/access-identity-continuity-surface.d.ts +4 -4
- package/dist/access-identity-continuity-surface.js +19 -18
- package/dist/access-lcm-surface.d.ts +5 -5
- package/dist/access-lcm-surface.js +19 -18
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +22 -21
- package/dist/access-namespace-preflight.js +19 -18
- package/dist/access-observe-write-surface.d.ts +5 -5
- package/dist/access-observe-write-surface.js +19 -18
- package/dist/access-operations-batch.js +20 -19
- package/dist/access-operations.d.ts +8 -8
- package/dist/access-operations.js +21 -20
- package/dist/access-recall-concurrency.d.ts +5 -5
- package/dist/access-recall-concurrency.js +19 -18
- package/dist/access-recall-response.d.ts +5 -5
- package/dist/access-recall-response.js +19 -18
- package/dist/access-recall-surface.d.ts +5 -5
- package/dist/access-recall-surface.js +19 -18
- package/dist/access-schema.d.ts +88 -88
- package/dist/{access-service-D9zIBlpp.d.ts → access-service-C2WYT1pO.d.ts} +3 -3
- package/dist/access-service-helpers.d.ts +5 -5
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +19 -18
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/access-wearables-meetings-surface.d.ts +2 -2
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +10 -9
- package/dist/buffer.d.ts +1 -1
- package/dist/{capsule-merge-2H2IUTBT.js → capsule-merge-IDWJPCWK.js} +77 -8
- package/dist/capsule-merge-IDWJPCWK.js.map +1 -0
- package/dist/causal-consolidation.js +11 -10
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-65ZPVYTA.js → chunk-3CE77VAP.js} +55 -5
- package/dist/chunk-3CE77VAP.js.map +1 -0
- package/dist/chunk-4J7G5SY4.js +338 -0
- package/dist/chunk-4J7G5SY4.js.map +1 -0
- package/dist/{chunk-EO7X2LDK.js → chunk-4TAD3B56.js} +2 -2
- package/dist/{chunk-QHYB4MJV.js → chunk-5ZGHUWTT.js} +2 -2
- package/dist/{chunk-TCIWHK3N.js → chunk-6D5D5A4M.js} +5 -5
- package/dist/{chunk-BIXWNHIL.js → chunk-6TF4M2LB.js} +5 -5
- package/dist/{chunk-FBUE6PD3.js → chunk-BEQOZOYZ.js} +21 -4
- package/dist/chunk-BEQOZOYZ.js.map +1 -0
- package/dist/{chunk-SA3WJJF2.js → chunk-BGLDKAYY.js} +2 -2
- package/dist/{chunk-OPRP6Q4Z.js → chunk-DKKZ6SO2.js} +3 -3
- package/dist/{chunk-6TAUHL33.js → chunk-DLVPUX7U.js} +10 -2
- package/dist/chunk-DLVPUX7U.js.map +1 -0
- package/dist/{chunk-QNWXCJ76.js → chunk-ENTCVTQR.js} +18 -1
- package/dist/{chunk-QNWXCJ76.js.map → chunk-ENTCVTQR.js.map} +1 -1
- package/dist/{chunk-AEPYA55F.js → chunk-FE7MUX7M.js} +2 -2
- package/dist/{chunk-GHR7WHZX.js → chunk-GORHSN5C.js} +2 -2
- package/dist/{chunk-7TEZUUGM.js → chunk-HUS2XN3T.js} +2 -2
- package/dist/{chunk-SLPLPGJQ.js → chunk-IE2CS5BM.js} +17 -10
- package/dist/chunk-IE2CS5BM.js.map +1 -0
- package/dist/{chunk-XWDZQIYU.js → chunk-IJAQCBGV.js} +2 -2
- package/dist/{chunk-TJLLYV2X.js → chunk-IMEXE4XS.js} +7 -7
- package/dist/{chunk-IZXTZ7BF.js → chunk-JYLN6KHH.js} +6 -6
- package/dist/{chunk-EH5U2YCX.js → chunk-M3DBB2ND.js} +2 -2
- package/dist/{chunk-XABS455Y.js → chunk-NBAEGFXT.js} +5 -5
- package/dist/{chunk-T4HH5Q3U.js → chunk-OJRLTZQM.js} +3 -3
- package/dist/{chunk-7ZHGPX56.js → chunk-PRP3M5Y3.js} +2 -2
- package/dist/{chunk-Y2ZD2RVG.js → chunk-PYTUBWAS.js} +2 -2
- package/dist/{chunk-4TGZCYHH.js → chunk-Q3CVY7LV.js} +1954 -1642
- package/dist/chunk-Q3CVY7LV.js.map +1 -0
- package/dist/{chunk-PH3ZWL3W.js → chunk-Q53RLEW7.js} +2 -2
- package/dist/{chunk-Y6OWJ3V5.js → chunk-RY2XZTFN.js} +2 -2
- package/dist/{chunk-2SHIWGY7.js → chunk-RY7CHVGE.js} +2 -2
- package/dist/{chunk-7VUAG236.js → chunk-T3FVAHM7.js} +4 -4
- package/dist/{chunk-2LTDWSR6.js → chunk-TSH56VPU.js} +2 -2
- package/dist/{chunk-J6SLNQRO.js → chunk-UBWOCQ3I.js} +6 -2
- package/dist/chunk-UBWOCQ3I.js.map +1 -0
- package/dist/{chunk-5FHE4JI7.js → chunk-VEUVON5I.js} +2 -2
- package/dist/{chunk-KEWUNL7P.js → chunk-WET4M3R6.js} +2 -2
- package/dist/{chunk-HEVWWPTL.js → chunk-Y2UUEB4I.js} +17 -17
- package/dist/{chunk-AHZYNPLX.js → chunk-YVMVVE4B.js} +2 -2
- package/dist/{chunk-5HTAJCTO.js → chunk-ZB3YODID.js} +15 -15
- package/dist/{chunk-FHYGLITW.js → chunk-ZJMVZLPK.js} +217 -45
- package/dist/{chunk-FHYGLITW.js.map → chunk-ZJMVZLPK.js.map} +1 -1
- package/dist/{cli-mFX-QQvx.d.ts → cli-jkmzdT8P.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +34 -33
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +10 -9
- package/dist/connectors/codex-materialize-runner.js +10 -9
- package/dist/connectors/index.js +10 -9
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-provenance-check.js +8 -2
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/consolidation-undo.js +10 -1
- package/dist/consolidation-undo.js.map +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/corpus-watermark.js +12 -11
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +10 -9
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/explicit-capture.js +6 -3
- package/dist/extraction.js +10 -9
- package/dist/{forget-4UY2EOKH.js → forget-DNAJ66ZD.js} +2 -2
- package/dist/index.d.ts +396 -396
- package/dist/index.js +39 -38
- package/dist/maintenance/memory-governance.js +10 -9
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -9
- package/dist/maintenance/rebuild-memory-projection.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-projection.js +11 -10
- package/dist/{maintenance-70Wq118b.d.ts → maintenance-Ha9Dg93M.d.ts} +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +11 -10
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +10 -9
- package/dist/offline-sync-impression-drain.js +3 -3
- package/dist/operator-doctor-corpus.js +13 -12
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +17 -16
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +15 -14
- package/dist/{orchestrator-BH8LT-NX.d.ts → orchestrator-DV_u9bfx.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +39 -38
- package/dist/page-versioning.js +7 -1
- package/dist/schemas.d.ts +54 -54
- package/dist/semantic-consolidation.js +11 -10
- package/dist/semantic-rule-promotion.js +10 -9
- package/dist/semantic-rule-verifier.js +10 -9
- package/dist/{service-CrxXHz5T.d.ts → service-JxXXY-Eh.d.ts} +1 -1
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/source-agent-qualifier.js +10 -9
- package/dist/{storage-CyWa1Jm6.d.ts → storage-BbH4HDUS.d.ts} +30 -26
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +9 -8
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/transfer/capsule-import.js +8 -2
- package/dist/transfer/types.d.ts +66 -66
- package/dist/verified-recall.js +10 -9
- package/package.json +2 -2
- package/src/access-http.ts +14 -9
- package/src/binary-lifecycle/pipeline.ts +52 -1
- package/src/consolidation-undo.ts +4 -0
- package/src/curation/index.ts +41 -5
- package/src/lifecycle/tombstones.ts +40 -0
- package/src/maintenance/memory-governance.ts +4 -0
- package/src/page-versioning.ts +16 -1
- package/src/review/index.ts +97 -13
- package/src/spaces/index.ts +127 -6
- package/src/storage/citation-hash-source.ts +97 -0
- package/src/storage/entity-canonical-id-lock.ts +33 -0
- package/src/storage/entity-canonical-id-migration-adapter.ts +8 -4
- package/src/storage/entity-canonical-id-migration.ts +129 -77
- package/src/storage/entity-canonical-id-references.ts +557 -0
- package/src/storage/entity-ref-repair.ts +171 -0
- package/src/storage/entity-store.ts +163 -2
- package/src/storage/tombstone-blocked-capture-sync.ts +18 -0
- package/src/storage.ts +363 -396
- package/src/transfer/capsule-import.ts +91 -3
- package/src/transfer/capsule-merge.ts +128 -10
- package/dist/capsule-merge-2H2IUTBT.js.map +0 -1
- package/dist/chunk-4TGZCYHH.js.map +0 -1
- package/dist/chunk-65ZPVYTA.js.map +0 -1
- package/dist/chunk-6TAUHL33.js.map +0 -1
- package/dist/chunk-FBUE6PD3.js.map +0 -1
- package/dist/chunk-J6SLNQRO.js.map +0 -1
- package/dist/chunk-SLPLPGJQ.js.map +0 -1
- /package/dist/{chunk-EO7X2LDK.js.map → chunk-4TAD3B56.js.map} +0 -0
- /package/dist/{chunk-QHYB4MJV.js.map → chunk-5ZGHUWTT.js.map} +0 -0
- /package/dist/{chunk-TCIWHK3N.js.map → chunk-6D5D5A4M.js.map} +0 -0
- /package/dist/{chunk-BIXWNHIL.js.map → chunk-6TF4M2LB.js.map} +0 -0
- /package/dist/{chunk-SA3WJJF2.js.map → chunk-BGLDKAYY.js.map} +0 -0
- /package/dist/{chunk-OPRP6Q4Z.js.map → chunk-DKKZ6SO2.js.map} +0 -0
- /package/dist/{chunk-AEPYA55F.js.map → chunk-FE7MUX7M.js.map} +0 -0
- /package/dist/{chunk-GHR7WHZX.js.map → chunk-GORHSN5C.js.map} +0 -0
- /package/dist/{chunk-7TEZUUGM.js.map → chunk-HUS2XN3T.js.map} +0 -0
- /package/dist/{chunk-XWDZQIYU.js.map → chunk-IJAQCBGV.js.map} +0 -0
- /package/dist/{chunk-TJLLYV2X.js.map → chunk-IMEXE4XS.js.map} +0 -0
- /package/dist/{chunk-IZXTZ7BF.js.map → chunk-JYLN6KHH.js.map} +0 -0
- /package/dist/{chunk-EH5U2YCX.js.map → chunk-M3DBB2ND.js.map} +0 -0
- /package/dist/{chunk-XABS455Y.js.map → chunk-NBAEGFXT.js.map} +0 -0
- /package/dist/{chunk-T4HH5Q3U.js.map → chunk-OJRLTZQM.js.map} +0 -0
- /package/dist/{chunk-7ZHGPX56.js.map → chunk-PRP3M5Y3.js.map} +0 -0
- /package/dist/{chunk-Y2ZD2RVG.js.map → chunk-PYTUBWAS.js.map} +0 -0
- /package/dist/{chunk-PH3ZWL3W.js.map → chunk-Q53RLEW7.js.map} +0 -0
- /package/dist/{chunk-Y6OWJ3V5.js.map → chunk-RY2XZTFN.js.map} +0 -0
- /package/dist/{chunk-2SHIWGY7.js.map → chunk-RY7CHVGE.js.map} +0 -0
- /package/dist/{chunk-7VUAG236.js.map → chunk-T3FVAHM7.js.map} +0 -0
- /package/dist/{chunk-2LTDWSR6.js.map → chunk-TSH56VPU.js.map} +0 -0
- /package/dist/{chunk-5FHE4JI7.js.map → chunk-VEUVON5I.js.map} +0 -0
- /package/dist/{chunk-KEWUNL7P.js.map → chunk-WET4M3R6.js.map} +0 -0
- /package/dist/{chunk-HEVWWPTL.js.map → chunk-Y2UUEB4I.js.map} +0 -0
- /package/dist/{chunk-AHZYNPLX.js.map → chunk-YVMVVE4B.js.map} +0 -0
- /package/dist/{chunk-5HTAJCTO.js.map → chunk-ZB3YODID.js.map} +0 -0
- /package/dist/{forget-4UY2EOKH.js.map → forget-DNAJ66ZD.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.35.
|
|
3
|
+
"version": "9.35.4",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -3086,7 +3086,7 @@
|
|
|
3086
3086
|
"core"
|
|
3087
3087
|
],
|
|
3088
3088
|
"peerDependencies": {
|
|
3089
|
-
"@remnic/coding-graph": "^9.35.
|
|
3089
|
+
"@remnic/coding-graph": "^9.35.4"
|
|
3090
3090
|
},
|
|
3091
3091
|
"peerDependenciesMeta": {
|
|
3092
3092
|
"@remnic/coding-graph": {
|
package/src/access-http.ts
CHANGED
|
@@ -66,6 +66,12 @@ export interface AccessHttpReadinessState {
|
|
|
66
66
|
ready: boolean;
|
|
67
67
|
warmupAttempts: number;
|
|
68
68
|
lastError?: string | null;
|
|
69
|
+
/**
|
|
70
|
+
* True when the standalone init gate opened before search warm-up completed
|
|
71
|
+
* (issue #2215): the service is functional (recall serves via fallback
|
|
72
|
+
* retrieval), so health answers 200 with degraded info instead of 503.
|
|
73
|
+
*/
|
|
74
|
+
degraded?: boolean;
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
export interface EngramAccessHttpServerOptions {
|
|
@@ -801,18 +807,13 @@ export class EngramAccessHttpServer {
|
|
|
801
807
|
}
|
|
802
808
|
|
|
803
809
|
if (req.method === "GET" && pathname === "/engram/v1/health") {
|
|
804
|
-
const
|
|
805
|
-
if (!
|
|
810
|
+
const { ready, warmupAttempts, lastError } = this.readiness();
|
|
811
|
+
if (!ready) {
|
|
806
812
|
this.respondJson(res, 503, {
|
|
807
|
-
ok: false,
|
|
808
|
-
ready: false,
|
|
809
|
-
warmupAttempts: readiness.warmupAttempts,
|
|
810
|
-
lastError: readiness.lastError ?? null,
|
|
811
|
-
code: "not_ready",
|
|
813
|
+
ok: false, ready: false, warmupAttempts, lastError: lastError ?? null, code: "not_ready",
|
|
812
814
|
});
|
|
813
815
|
return;
|
|
814
816
|
}
|
|
815
|
-
|
|
816
817
|
}
|
|
817
818
|
|
|
818
819
|
// Run any host-supplied pre-auth request handler. It runs AFTER the
|
|
@@ -888,7 +889,11 @@ export class EngramAccessHttpServer {
|
|
|
888
889
|
}
|
|
889
890
|
|
|
890
891
|
if (req.method === "GET" && pathname === "/engram/v1/health") {
|
|
891
|
-
|
|
892
|
+
const { degraded, warmupAttempts, lastError } = this.readiness();
|
|
893
|
+
const health = await this.service.health();
|
|
894
|
+
this.respondJson(res, 200, degraded !== true ? health : {
|
|
895
|
+
...health, degraded: true, warmupAttempts, lastError: lastError ?? null,
|
|
896
|
+
});
|
|
892
897
|
return;
|
|
893
898
|
}
|
|
894
899
|
if (req.method === "GET" && pathname === "/engram/v1/capabilities") return respondLcmCompactionCapabilitiesHttp(res);
|
|
@@ -20,6 +20,16 @@ import { readManifest, writeManifest } from "./manifest.js";
|
|
|
20
20
|
import { scanForBinaries } from "./scanner.js";
|
|
21
21
|
import { bumpMemoryCorpusVersionForDir } from "../memory-corpus-version.js";
|
|
22
22
|
import { RECALL_FALLBACK_DIRS } from "../utils/category-dir.js";
|
|
23
|
+
import {
|
|
24
|
+
HistoricalEntityCanonicalIdCache,
|
|
25
|
+
canonicalizeEntityRefFrontmatter,
|
|
26
|
+
classifyEntityRefWritePath,
|
|
27
|
+
repairContentAfterJournalMove,
|
|
28
|
+
requestEntityCanonicalIdReconcile,
|
|
29
|
+
} from "../storage/entity-canonical-id-references.js";
|
|
30
|
+
|
|
31
|
+
// Write-boundary journal cache for redirect rewrites (issue #2213).
|
|
32
|
+
const historicalIdCache = new HistoricalEntityCanonicalIdCache();
|
|
23
33
|
|
|
24
34
|
/**
|
|
25
35
|
* True when `mdPath` is an active recall-corpus memory file under `memoryDir`
|
|
@@ -319,7 +329,48 @@ async function stageRedirect(
|
|
|
319
329
|
let writeFailCount = 0;
|
|
320
330
|
for (const update of updates) {
|
|
321
331
|
try {
|
|
322
|
-
|
|
332
|
+
// Whole-record rewrite of bytes read earlier (issue #2213): for
|
|
333
|
+
// memory-tier records, resolve the frontmatter entityRef through the
|
|
334
|
+
// CURRENT journal at the write and REPAIR afterwards — writing back
|
|
335
|
+
// the as-read bytes could revert a rewrite a peer migration performed
|
|
336
|
+
// since the read. findMarkdownFiles also surfaces transcripts,
|
|
337
|
+
// profiles, and other Markdown outside the migration's scope: those
|
|
338
|
+
// keep their bytes (plus the redirect) untouched, and entities/ pages
|
|
339
|
+
// get the bounded reconcile marker instead — their migrated surface
|
|
340
|
+
// is relationship targets, not an entityRef frontmatter field.
|
|
341
|
+
const stateDir = path.join(memoryDir, "state");
|
|
342
|
+
const kind = classifyEntityRefWritePath(memoryDir, update.mdPath);
|
|
343
|
+
if (kind === "memory") {
|
|
344
|
+
const idsAtWrite = historicalIdCache.get(stateDir);
|
|
345
|
+
const written = canonicalizeEntityRefFrontmatter(update.content, idsAtWrite);
|
|
346
|
+
// Snapshot the ORIGINAL bytes: if repair fails after the write, the
|
|
347
|
+
// catch below reports a failed redirect, so the file must return to
|
|
348
|
+
// its pre-redirect state — a retry needs the original reference,
|
|
349
|
+
// and the skipped corpus bump means a warm cache would otherwise
|
|
350
|
+
// keep serving bytes that no longer match disk (AGENTS.md §14).
|
|
351
|
+
const originalBytes = await readMarkdownFile(update.mdPath).catch(() => null);
|
|
352
|
+
await writeMarkdownFile(update.mdPath, written);
|
|
353
|
+
try {
|
|
354
|
+
await repairContentAfterJournalMove({
|
|
355
|
+
stateDir,
|
|
356
|
+
cache: historicalIdCache,
|
|
357
|
+
idsAtWrite,
|
|
358
|
+
rawContent: update.content,
|
|
359
|
+
lastWritten: written,
|
|
360
|
+
rewrite: (next) => writeMarkdownFile(update.mdPath, next),
|
|
361
|
+
});
|
|
362
|
+
} catch (err) {
|
|
363
|
+
if (originalBytes !== null) {
|
|
364
|
+
await writeMarkdownFile(update.mdPath, originalBytes).catch(() => undefined);
|
|
365
|
+
}
|
|
366
|
+
throw err;
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
await writeMarkdownFile(update.mdPath, update.content);
|
|
370
|
+
if (kind === "entity") {
|
|
371
|
+
await requestEntityCanonicalIdReconcile(stateDir);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
323
374
|
// The redirect rewrote an on-disk memory file out-of-band (not through a
|
|
324
375
|
// StorageManager mutation). Bump the corpus sentinel per write so a warm
|
|
325
376
|
// hot-memories cache rescans — mid-batch too, never serving pre-redirect
|
|
@@ -26,6 +26,7 @@ import { constants as fsConstants } from "node:fs";
|
|
|
26
26
|
import type { StorageManager } from "./storage.js";
|
|
27
27
|
import type { VersioningConfig } from "./page-versioning.js";
|
|
28
28
|
import { bumpMemoryCorpusVersionForDir } from "./memory-corpus-version.js";
|
|
29
|
+
import { requestEntityCanonicalIdReconcile } from "./storage/entity-canonical-id-references.js";
|
|
29
30
|
import { getVersion } from "./page-versioning.js";
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -654,6 +655,9 @@ export async function runConsolidationUndo(options: {
|
|
|
654
655
|
// restores would stay invisible to recall until an unrelated mutation or
|
|
655
656
|
// restart (esp. with hotMemoriesCacheTtlMs: 0). Issue #1902, Codex Medium.
|
|
656
657
|
bumpMemoryCorpusVersionForDir(memoryDir);
|
|
658
|
+
// Restored bytes predate the current migration journal and can carry
|
|
659
|
+
// legacy entity references (issue #2213) — request one reconcile pass.
|
|
660
|
+
await requestEntityCanonicalIdReconcile(path.join(memoryDir, "state"));
|
|
657
661
|
result.restores.push({
|
|
658
662
|
entry: p.entry,
|
|
659
663
|
sourcePath: p.sourcePath,
|
package/src/curation/index.ts
CHANGED
|
@@ -10,6 +10,11 @@ import path from "node:path";
|
|
|
10
10
|
import crypto from "node:crypto";
|
|
11
11
|
import { getCategoryDir, ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
|
|
12
12
|
import { bumpMemoryCorpusVersionForDir } from "../memory-corpus-version.js";
|
|
13
|
+
import {
|
|
14
|
+
HistoricalEntityCanonicalIdCache,
|
|
15
|
+
canonicalizeEntityRefFrontmatter,
|
|
16
|
+
repairContentAfterJournalMoveSync,
|
|
17
|
+
} from "../storage/entity-canonical-id-references.js";
|
|
13
18
|
|
|
14
19
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
15
20
|
|
|
@@ -444,6 +449,10 @@ function loadExistingMemories(memoryDir: string): Map<string, ExistingMemory> {
|
|
|
444
449
|
|
|
445
450
|
// ── Writing ──────────────────────────────────────────────────────────────────
|
|
446
451
|
|
|
452
|
+
// Curation writes raw frontmatter directly, so it is a write boundary for
|
|
453
|
+
// entity references too (issue #2213): resolve through the target's journal.
|
|
454
|
+
const historicalIdCache = new HistoricalEntityCanonicalIdCache();
|
|
455
|
+
|
|
447
456
|
function writeStatement(stmt: CuratedStatement, memoryDir: string): string {
|
|
448
457
|
const now = new Date();
|
|
449
458
|
const dateDir = now.toISOString().split("T")[0];
|
|
@@ -454,8 +463,12 @@ function writeStatement(stmt: CuratedStatement, memoryDir: string): string {
|
|
|
454
463
|
|
|
455
464
|
const fileName = `${stmt.category}-${Date.now()}-${stmt.id.slice(0, 8)}.md`;
|
|
456
465
|
const filePath = path.join(dir, fileName);
|
|
466
|
+
const historicalIds = historicalIdCache.get(path.join(memoryDir, "state"));
|
|
457
467
|
|
|
458
|
-
|
|
468
|
+
// Keep the caller's ORIGINAL ref in the raw form: repair-after-write must
|
|
469
|
+
// re-resolve from it, including the direction where a mapping was parked
|
|
470
|
+
// and the resolution falls back to the legacy claimant (issue #2213).
|
|
471
|
+
const rawBody = `${[
|
|
459
472
|
"---",
|
|
460
473
|
`id: ${stmt.id}`,
|
|
461
474
|
`category: ${stmt.category}`,
|
|
@@ -471,14 +484,37 @@ function writeStatement(stmt: CuratedStatement, memoryDir: string): string {
|
|
|
471
484
|
"---",
|
|
472
485
|
]
|
|
473
486
|
.filter(Boolean)
|
|
474
|
-
.join("\n")
|
|
475
|
-
|
|
476
|
-
const body = `${frontmatter}\n\n${stmt.content}\n`;
|
|
487
|
+
.join("\n")}\n\n${stmt.content}\n`;
|
|
488
|
+
const body = canonicalizeEntityRefFrontmatter(rawBody, historicalIds);
|
|
477
489
|
|
|
478
490
|
fs.writeFileSync(filePath, body);
|
|
491
|
+
// Post-write REPAIR (issue #2213): a peer process publishing the journal
|
|
492
|
+
// across this write would otherwise strand the reference — the corpus bump
|
|
493
|
+
// below no longer re-triggers the migration, and a parked mapping cannot be
|
|
494
|
+
// fixed after the fact by the reconcile pass. On repair exhaustion, remove
|
|
495
|
+
// the just-created file so the retryable error leaves no untracked partial
|
|
496
|
+
// statement (a retry would otherwise duplicate it).
|
|
497
|
+
try {
|
|
498
|
+
repairContentAfterJournalMoveSync({
|
|
499
|
+
stateDir: path.join(memoryDir, "state"),
|
|
500
|
+
cache: historicalIdCache,
|
|
501
|
+
idsAtWrite: historicalIds,
|
|
502
|
+
rawContent: rawBody,
|
|
503
|
+
lastWritten: body,
|
|
504
|
+
rewrite: (next) => fs.writeFileSync(filePath, next),
|
|
505
|
+
});
|
|
506
|
+
} catch (err) {
|
|
507
|
+
try {
|
|
508
|
+
fs.unlinkSync(filePath);
|
|
509
|
+
} catch {
|
|
510
|
+
// Best effort — the retryable repair error is the primary signal.
|
|
511
|
+
}
|
|
512
|
+
throw err;
|
|
513
|
+
}
|
|
479
514
|
// Bump the corpus sentinel per write (out-of-band create) so a concurrent
|
|
480
515
|
// same-process readAllMemories rescans and sees this statement immediately,
|
|
481
|
-
// never a stale/partial corpus mid-batch (Cursor Medium, #1902).
|
|
516
|
+
// never a stale/partial corpus mid-batch (Cursor Medium, #1902). AFTER the
|
|
517
|
+
// repair, so a rescan never captures pre-repair bytes.
|
|
482
518
|
bumpMemoryCorpusVersionForDir(memoryDir);
|
|
483
519
|
return filePath;
|
|
484
520
|
}
|
|
@@ -835,6 +835,46 @@ export function collectRetiredMemoriesForRebuild(
|
|
|
835
835
|
return retired;
|
|
836
836
|
}
|
|
837
837
|
|
|
838
|
+
/**
|
|
839
|
+
* One-shot write-time resurrection gate (issue #1579 / #2213). Looks up the
|
|
840
|
+
* fact's identity in the tombstone store and, on a match, downgrades the
|
|
841
|
+
* frontmatter to `pending_review` + `blockedBy` IN PLACE. ADD-ONLY: an
|
|
842
|
+
* already-blocked or terminal-status record is left untouched. Pure over its
|
|
843
|
+
* inputs (no I/O beyond the injected store) so both `writeMemory`'s gate and
|
|
844
|
+
* the chunk-repair re-gate share one lookup + apply.
|
|
845
|
+
*/
|
|
846
|
+
export function applyTombstoneResurrectionGate(
|
|
847
|
+
store: Pick<TombstoneStore, "lookup">,
|
|
848
|
+
fm: {
|
|
849
|
+
id: string;
|
|
850
|
+
status?: string;
|
|
851
|
+
blockedBy?: string;
|
|
852
|
+
tombstoneBlockTier?: TombstoneMatchTier;
|
|
853
|
+
entityRef?: string;
|
|
854
|
+
contentHash?: string;
|
|
855
|
+
},
|
|
856
|
+
query: {
|
|
857
|
+
normalizedText: string;
|
|
858
|
+
supersessionKeys: string[];
|
|
859
|
+
namespace: string;
|
|
860
|
+
},
|
|
861
|
+
): TombstoneMatch | null {
|
|
862
|
+
if (fm.status === "pending_review" && fm.blockedBy) return null;
|
|
863
|
+
if (!(fm.status === undefined || fm.status === "active" || fm.status === "pending_review")) return null;
|
|
864
|
+
const match = store.lookup({
|
|
865
|
+
contentHash: fm.contentHash,
|
|
866
|
+
normalizedText: query.normalizedText,
|
|
867
|
+
...(fm.entityRef ? { entityRef: fm.entityRef } : {}),
|
|
868
|
+
...(query.supersessionKeys.length > 0 ? { supersessionKeys: query.supersessionKeys } : {}),
|
|
869
|
+
namespace: query.namespace,
|
|
870
|
+
});
|
|
871
|
+
if (!match) return null;
|
|
872
|
+
fm.status = "pending_review";
|
|
873
|
+
fm.blockedBy = match.tombstoneId;
|
|
874
|
+
fm.tombstoneBlockTier = match.matchedTier;
|
|
875
|
+
return match;
|
|
876
|
+
}
|
|
877
|
+
|
|
838
878
|
/**
|
|
839
879
|
* Build the live-emission tombstone inputs for a single retired FACT — one
|
|
840
880
|
* input per derived supersession key (or a single keyless record when no
|
|
@@ -6,6 +6,7 @@ import type { MemoryFile, MemoryStatus } from "../types.js";
|
|
|
6
6
|
import { RECALL_FALLBACK_DIRS } from "../utils/category-dir.js";
|
|
7
7
|
import { bumpMemoryCorpusVersionForDir } from "../memory-corpus-version.js";
|
|
8
8
|
import { assertPathInsideRoot } from "../utils/path-containment.js";
|
|
9
|
+
import { requestEntityCanonicalIdReconcile } from "../storage/entity-canonical-id-references.js";
|
|
9
10
|
|
|
10
11
|
export type MemoryGovernanceMode = "shadow" | "apply";
|
|
11
12
|
export type MemoryGovernanceReasonCode =
|
|
@@ -1021,6 +1022,9 @@ export async function restoreMemoryGovernanceRun(
|
|
|
1021
1022
|
// same-process or peer readAllMemories rescans mid-batch and never serves a
|
|
1022
1023
|
// partially-restored corpus (Cursor Medium, #1902).
|
|
1023
1024
|
bumpMemoryCorpusVersionForDir(options.memoryDir);
|
|
1025
|
+
// Restored bytes predate the current migration journal and can carry
|
|
1026
|
+
// legacy entity references (issue #2213) — request one reconcile pass.
|
|
1027
|
+
await requestEntityCanonicalIdReconcile(path.join(options.memoryDir, "state"));
|
|
1024
1028
|
}
|
|
1025
1029
|
|
|
1026
1030
|
return {
|
package/src/page-versioning.ts
CHANGED
|
@@ -27,6 +27,10 @@ import {
|
|
|
27
27
|
} from "node:fs/promises";
|
|
28
28
|
import { ALL_CATEGORY_DIRS, RECALL_FALLBACK_DIRS } from "./utils/category-dir.js";
|
|
29
29
|
import { bumpMemoryCorpusVersionForDir } from "./memory-corpus-version.js";
|
|
30
|
+
import {
|
|
31
|
+
pathMayCarryEntityRefs,
|
|
32
|
+
requestEntityCanonicalIdReconcile,
|
|
33
|
+
} from "./storage/entity-canonical-id-references.js";
|
|
30
34
|
|
|
31
35
|
// ---------------------------------------------------------------------------
|
|
32
36
|
// Public interfaces
|
|
@@ -342,9 +346,20 @@ export async function revertToVersion(
|
|
|
342
346
|
// non-memory queue dirs (questions/). Use RECALL_FALLBACK_DIRS — a revert of a
|
|
343
347
|
// questions/ page must NOT bump the recall corpus sentinel and force a rescan.
|
|
344
348
|
const revertedTop = relPath(pagePath, resolvedMemoryDir).split(path.sep)[0];
|
|
345
|
-
|
|
349
|
+
const inRecallTier = (RECALL_FALLBACK_DIRS as readonly string[]).includes(revertedTop);
|
|
350
|
+
if (inRecallTier) {
|
|
346
351
|
bumpMemoryCorpusVersionForDir(resolvedMemoryDir);
|
|
347
352
|
}
|
|
353
|
+
// A pre-migration snapshot can reintroduce a legacy entityRef — or, under
|
|
354
|
+
// entities/, a legacy relationship target rewriteRelationshipTargets had
|
|
355
|
+
// migrated — that the target's completed journal already renamed (issue
|
|
356
|
+
// #2213); the revert keeps the snapshot bytes faithful, so request one
|
|
357
|
+
// bounded reconcile pass instead. pathMayCarryEntityRefs is the migration's
|
|
358
|
+
// own scan scope (hot recall + cold + archive + entities), which is wider
|
|
359
|
+
// than the corpus bump above on purpose.
|
|
360
|
+
if (pathMayCarryEntityRefs(resolvedMemoryDir, pagePath)) {
|
|
361
|
+
await requestEntityCanonicalIdReconcile(path.join(resolvedMemoryDir, "state"));
|
|
362
|
+
}
|
|
348
363
|
log.debug(`page-versioning: reverted ${pagePath} to version ${versionId}`);
|
|
349
364
|
|
|
350
365
|
return version;
|
package/src/review/index.ts
CHANGED
|
@@ -9,6 +9,58 @@ import fs from "node:fs";
|
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { getCategoryDir, ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
|
|
11
11
|
import { bumpMemoryCorpusVersionForDir } from "../memory-corpus-version.js";
|
|
12
|
+
import {
|
|
13
|
+
HistoricalEntityCanonicalIdCache,
|
|
14
|
+
canonicalizeEntityRefFrontmatter,
|
|
15
|
+
repairContentAfterJournalMoveSync,
|
|
16
|
+
} from "../storage/entity-canonical-id-references.js";
|
|
17
|
+
|
|
18
|
+
// Write-boundary journal cache for review-queue promotions (issue #2213).
|
|
19
|
+
const historicalIdCache = new HistoricalEntityCanonicalIdCache();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* In-place raw rewrite under the uniform write-boundary rule (issue #2213):
|
|
23
|
+
* canonicalize the effective entityRef at the write and REPAIR when the
|
|
24
|
+
* journal moved across it — these rewrites re-serialize whole records, so an
|
|
25
|
+
* unrelated approve/dismiss/flag must not write a legacy ref back out. On
|
|
26
|
+
* repair exhaustion the ORIGINAL bytes are restored before the retryable
|
|
27
|
+
* error propagates: performReview bumps the corpus sentinel only after the
|
|
28
|
+
* action returns, so a half-applied rewrite would be invisible to a warm
|
|
29
|
+
* cache while the caller is told the action failed.
|
|
30
|
+
*/
|
|
31
|
+
function writeReviewFileCanonicalized(memoryDir: string, filePath: string, content: string): void {
|
|
32
|
+
const stateDir = path.join(memoryDir, "state");
|
|
33
|
+
const idsAtWrite = historicalIdCache.get(stateDir);
|
|
34
|
+
let originalBytes: string | null = null;
|
|
35
|
+
try {
|
|
36
|
+
originalBytes = fs.readFileSync(filePath, "utf8");
|
|
37
|
+
} catch {
|
|
38
|
+
originalBytes = null; // new file — rollback is removal
|
|
39
|
+
}
|
|
40
|
+
const written = canonicalizeEntityRefFrontmatter(content, idsAtWrite);
|
|
41
|
+
fs.writeFileSync(filePath, written, "utf8");
|
|
42
|
+
try {
|
|
43
|
+
repairContentAfterJournalMoveSync({
|
|
44
|
+
stateDir,
|
|
45
|
+
cache: historicalIdCache,
|
|
46
|
+
idsAtWrite,
|
|
47
|
+
rawContent: content,
|
|
48
|
+
lastWritten: written,
|
|
49
|
+
rewrite: (next) => fs.writeFileSync(filePath, next, "utf8"),
|
|
50
|
+
});
|
|
51
|
+
} catch (err) {
|
|
52
|
+
try {
|
|
53
|
+
if (originalBytes === null) {
|
|
54
|
+
fs.unlinkSync(filePath);
|
|
55
|
+
} else {
|
|
56
|
+
fs.writeFileSync(filePath, originalBytes, "utf8");
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// Best effort — the retryable repair error is the primary signal.
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
12
64
|
|
|
13
65
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
14
66
|
|
|
@@ -295,13 +347,16 @@ function approveItem(
|
|
|
295
347
|
const fm = parseFrontmatter(content);
|
|
296
348
|
if (!fm) return { itemId, action: "approve", message: "Could not parse frontmatter" };
|
|
297
349
|
|
|
298
|
-
// Issue #1579 —
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
350
|
+
// Issue #1579 — approving a tombstone-blocked memory fires the revocation
|
|
351
|
+
// hook so the content is re-allowed (append-only log: a `kind: "revocation"`
|
|
352
|
+
// entry supersedes the tombstone at lookup). Deferred until the approval's
|
|
353
|
+
// writes commit (Codex P2, round 19): revoking before the promote left a
|
|
354
|
+
// failed repair with the non-resurrection guard already destroyed, letting
|
|
355
|
+
// a later extraction resurrect the content. Fire-and-forget once invoked:
|
|
356
|
+
// a hook failure never fails the approval.
|
|
303
357
|
const blockedBy = typeof fm.blockedBy === "string" ? fm.blockedBy : null;
|
|
304
|
-
|
|
358
|
+
const fireBlockedRevocation = (): void => {
|
|
359
|
+
if (!blockedBy || !options.onApproveBlockedMemory) return;
|
|
305
360
|
try {
|
|
306
361
|
const maybe = options.onApproveBlockedMemory(blockedBy, itemId);
|
|
307
362
|
if (maybe && typeof (maybe as Promise<void>).then === "function") {
|
|
@@ -310,7 +365,7 @@ function approveItem(
|
|
|
310
365
|
} catch {
|
|
311
366
|
/* fire-and-forget — never fail the approval (gotcha #13) */
|
|
312
367
|
}
|
|
313
|
-
}
|
|
368
|
+
};
|
|
314
369
|
|
|
315
370
|
const updatedContent = updateFrontmatterFields(content, {
|
|
316
371
|
confidence: "0.9",
|
|
@@ -324,7 +379,8 @@ function approveItem(
|
|
|
324
379
|
});
|
|
325
380
|
|
|
326
381
|
if (found.location === "category") {
|
|
327
|
-
|
|
382
|
+
writeReviewFileCanonicalized(memoryDir, found.filePath, updatedContent);
|
|
383
|
+
fireBlockedRevocation();
|
|
328
384
|
return {
|
|
329
385
|
itemId,
|
|
330
386
|
action: "approve",
|
|
@@ -342,10 +398,38 @@ function approveItem(
|
|
|
342
398
|
const outputPath = path.join(targetDir, dateDir, path.basename(found.filePath));
|
|
343
399
|
|
|
344
400
|
ensureSafeDirectory(rootReal, path.dirname(outputPath));
|
|
345
|
-
|
|
401
|
+
// The suggestions/review queue dirs sit OUTSIDE the migration's reference
|
|
402
|
+
// scan, so a queued pre-migration item can still carry a legacy entityRef
|
|
403
|
+
// (issue #2213): promote it canonicalized against the target journal, and
|
|
404
|
+
// REPAIR when the journal moved across the write.
|
|
405
|
+
const reviewStateDir = path.join(memoryDir, "state");
|
|
406
|
+
const idsAtWrite = historicalIdCache.get(reviewStateDir);
|
|
407
|
+
const promotedWritten = canonicalizeEntityRefFrontmatter(updatedContent, idsAtWrite);
|
|
408
|
+
const promotedPath = writeFileWithoutClobber(outputPath, promotedWritten, itemId);
|
|
409
|
+
try {
|
|
410
|
+
repairContentAfterJournalMoveSync({
|
|
411
|
+
stateDir: reviewStateDir,
|
|
412
|
+
cache: historicalIdCache,
|
|
413
|
+
idsAtWrite,
|
|
414
|
+
rawContent: updatedContent,
|
|
415
|
+
lastWritten: promotedWritten,
|
|
416
|
+
rewrite: (next) => fs.writeFileSync(promotedPath, next, "utf8"),
|
|
417
|
+
});
|
|
418
|
+
} catch (err) {
|
|
419
|
+
// Roll back so a retried approve does not clobber-fail against a partial
|
|
420
|
+
// promotion; the queue entry survives untouched (Bugbot).
|
|
421
|
+
try {
|
|
422
|
+
fs.unlinkSync(promotedPath);
|
|
423
|
+
} catch {
|
|
424
|
+
// Best effort — the retryable repair error below is the primary signal.
|
|
425
|
+
}
|
|
426
|
+
throw err;
|
|
427
|
+
}
|
|
346
428
|
|
|
347
|
-
// Remove from review
|
|
429
|
+
// Remove from review, THEN revoke: the guard only falls once the approval
|
|
430
|
+
// has fully committed (promoted file repaired + queue entry gone).
|
|
348
431
|
fs.unlinkSync(found.filePath);
|
|
432
|
+
fireBlockedRevocation();
|
|
349
433
|
|
|
350
434
|
return {
|
|
351
435
|
itemId,
|
|
@@ -372,13 +456,13 @@ function dismissItem(
|
|
|
372
456
|
}
|
|
373
457
|
|
|
374
458
|
const content = fs.readFileSync(found.filePath, "utf8");
|
|
375
|
-
|
|
459
|
+
writeReviewFileCanonicalized(
|
|
460
|
+
memoryDir,
|
|
376
461
|
found.filePath,
|
|
377
462
|
updateFrontmatterFields(content, {
|
|
378
463
|
reviewDismissed: "true",
|
|
379
464
|
reviewDismissedAt: new Date().toISOString(),
|
|
380
465
|
}),
|
|
381
|
-
"utf8",
|
|
382
466
|
);
|
|
383
467
|
return {
|
|
384
468
|
itemId,
|
|
@@ -403,7 +487,7 @@ function flagItem(
|
|
|
403
487
|
flagged: "true",
|
|
404
488
|
flaggedAt: new Date().toISOString(),
|
|
405
489
|
});
|
|
406
|
-
|
|
490
|
+
writeReviewFileCanonicalized(memoryDir, found.filePath, fixed);
|
|
407
491
|
return {
|
|
408
492
|
itemId,
|
|
409
493
|
action: "flag",
|