@remnic/core 9.52.2 → 9.54.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 +11 -11
- package/dist/access-admin-ops-surface.js +22 -22
- package/dist/access-authorization-probe.d.ts +11 -11
- package/dist/access-authorization-probe.js +23 -23
- package/dist/access-boundary.d.ts +11 -11
- package/dist/access-boundary.js +22 -22
- package/dist/access-cli.js +55 -55
- package/dist/access-coding-context-resolution.d.ts +1 -1
- package/dist/access-extraction-force-flush.d.ts +11 -11
- package/dist/access-extraction-force-flush.js +22 -22
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-lcm-compaction.d.ts +11 -11
- package/dist/access-http-lifecycle-flush.d.ts +11 -11
- package/dist/access-http-offline-stream.d.ts +11 -11
- package/dist/access-http-query.js +22 -22
- package/dist/access-http.d.ts +11 -11
- package/dist/access-http.js +29 -29
- package/dist/access-identity-continuity-surface.d.ts +9 -9
- package/dist/access-identity-continuity-surface.js +22 -22
- package/dist/access-lcm-surface.d.ts +11 -11
- package/dist/access-lcm-surface.js +22 -22
- package/dist/access-mcp.d.ts +11 -11
- package/dist/access-mcp.js +27 -27
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-Ds6CGlCP.d.ts → access-namespace-preflight-C7n4tgWC.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +22 -22
- package/dist/access-observe-write-surface.d.ts +11 -11
- package/dist/access-observe-write-surface.js +22 -22
- package/dist/access-offline-manifest.d.ts +9 -9
- package/dist/access-offline-manifest.js +8 -4
- package/dist/access-operations-batch.js +24 -24
- package/dist/access-operations.d.ts +14 -14
- package/dist/access-operations.js +26 -26
- package/dist/access-recall-concurrency.d.ts +11 -11
- package/dist/access-recall-concurrency.js +22 -22
- package/dist/access-recall-response.d.ts +11 -11
- package/dist/access-recall-response.js +22 -22
- package/dist/access-recall-surface.d.ts +11 -11
- package/dist/access-recall-surface.js +22 -22
- package/dist/access-schema.d.ts +88 -88
- package/dist/{access-service-Cbk3bNbD.d.ts → access-service-CXvbxLK-.d.ts} +7 -7
- package/dist/access-service-helpers.d.ts +11 -11
- package/dist/access-service.d.ts +11 -11
- package/dist/access-service.js +22 -22
- package/dist/access-surface-catalog.d.ts +11 -11
- package/dist/access-wearables-meetings-surface.d.ts +3 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +5 -5
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/{auto-sync-QFQGOEO2.js → auto-sync-TDHLZPVP.js} +7 -7
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +9 -9
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +12 -12
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-turn-helpers.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-D3B4RDy1.d.ts → catalog-BOOxl-I2.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +13 -13
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-MLXZD4WM.js → chunk-2GFK22OZ.js} +2 -2
- package/dist/{chunk-RANKJI5T.js → chunk-3JLPGJ43.js} +2 -2
- package/dist/{chunk-O5FFBO2M.js → chunk-47P3RYZP.js} +70 -8
- package/dist/{chunk-O5FFBO2M.js.map → chunk-47P3RYZP.js.map} +1 -1
- package/dist/{chunk-X7XEDOSL.js → chunk-4IIFL7GC.js} +8 -8
- package/dist/chunk-5FVVY2EB.js +27 -0
- package/dist/chunk-5FVVY2EB.js.map +1 -0
- package/dist/{chunk-GXSOUIA4.js → chunk-5WQMORU5.js} +4 -4
- package/dist/{chunk-J272WKX2.js → chunk-6HPHEFXT.js} +2 -2
- package/dist/{chunk-B6LZ4IBH.js → chunk-6LAPLZOS.js} +2 -2
- package/dist/{chunk-WD5B5AIE.js → chunk-6R4G7N4S.js} +118 -173
- package/dist/chunk-6R4G7N4S.js.map +1 -0
- package/dist/{chunk-VXCNQYTC.js → chunk-APPFNGX6.js} +2 -2
- package/dist/{chunk-GXFSQ6SD.js → chunk-C4ZNGVK3.js} +2 -2
- package/dist/{chunk-JSEJH2YQ.js → chunk-CGVHPCP2.js} +2 -2
- package/dist/{chunk-BQIFZ2MP.js → chunk-CX5SW2EW.js} +41 -24
- package/dist/chunk-CX5SW2EW.js.map +1 -0
- package/dist/{chunk-RMHMUUBW.js → chunk-CYF6WVE7.js} +105 -49
- package/dist/{chunk-RMHMUUBW.js.map → chunk-CYF6WVE7.js.map} +1 -1
- package/dist/{chunk-TEMSWHQD.js → chunk-DZD576ZU.js} +5 -5
- package/dist/{chunk-AMTEYNYK.js → chunk-EVWWT3KE.js} +4 -4
- package/dist/{chunk-P2KBDJJ6.js → chunk-G2LUMAUF.js} +4 -3
- package/dist/{chunk-P2KBDJJ6.js.map → chunk-G2LUMAUF.js.map} +1 -1
- package/dist/{chunk-AXIDQG5A.js → chunk-G4T7L557.js} +5 -5
- package/dist/{chunk-7M6NYQWB.js → chunk-G7Z6EENO.js} +4 -4
- package/dist/{chunk-IARXXZU4.js → chunk-HOIRB77A.js} +99 -20
- package/dist/chunk-HOIRB77A.js.map +1 -0
- package/dist/{chunk-NDAH7BJ5.js → chunk-HVP33QVW.js} +103 -6
- package/dist/chunk-HVP33QVW.js.map +1 -0
- package/dist/{chunk-YXKVLYXU.js → chunk-HWRD23DW.js} +2 -2
- package/dist/{chunk-SOEN6KCF.js → chunk-IBKR2TFR.js} +2 -2
- package/dist/{chunk-R3DJVOSM.js → chunk-JDSVLVCG.js} +8 -1
- package/dist/chunk-JDSVLVCG.js.map +1 -0
- package/dist/{chunk-F4EU6VUC.js → chunk-JDZPNCMA.js} +2 -2
- package/dist/{chunk-D5YOZ66N.js → chunk-JV5YUBIZ.js} +20 -20
- package/dist/{chunk-DLG5V6ND.js → chunk-K4QS7LXU.js} +4 -7
- package/dist/chunk-K4QS7LXU.js.map +1 -0
- package/dist/{chunk-BAKXNQ3C.js → chunk-KRTY3ENI.js} +2 -2
- package/dist/{chunk-NK5RGJ65.js → chunk-L4DLLKPN.js} +2 -2
- package/dist/{chunk-VFSAWRMZ.js → chunk-LJS7KUPV.js} +2 -2
- package/dist/{chunk-JJRVPWFY.js → chunk-MLPTFODV.js} +166 -37
- package/dist/chunk-MLPTFODV.js.map +1 -0
- package/dist/{chunk-NOEMGE56.js → chunk-NH5IR2WM.js} +2 -2
- package/dist/{chunk-5V6IXHPI.js → chunk-NSVDUN76.js} +2 -2
- package/dist/{chunk-W7Y5JDVB.js → chunk-PDDTI7IC.js} +4 -4
- package/dist/{chunk-QINJITDS.js → chunk-PX2LSHYM.js} +2 -2
- package/dist/{chunk-HDIUYMSB.js → chunk-QWGE2PUH.js} +6 -6
- package/dist/{chunk-X57M2Q4I.js → chunk-RDJRFDJY.js} +3 -3
- package/dist/{chunk-RYLEDKQ2.js → chunk-RUMNY5DT.js} +3 -3
- package/dist/{chunk-BKIFTXHR.js → chunk-RZ4XGKOJ.js} +2 -2
- package/dist/{chunk-66SE3BNH.js → chunk-SUPEYLH2.js} +2 -2
- package/dist/{chunk-BF6SXNQJ.js → chunk-T2JHZWH6.js} +20 -20
- package/dist/{chunk-VHFXP27N.js → chunk-THPPCISA.js} +60 -23
- package/dist/chunk-THPPCISA.js.map +1 -0
- package/dist/{chunk-QPOHMO6M.js → chunk-UES4BHA2.js} +2 -2
- package/dist/{chunk-XAPMEU3K.js → chunk-UU7TRFNE.js} +2 -2
- package/dist/{chunk-QBSFOKY6.js → chunk-VOELBUS4.js} +4 -4
- package/dist/{chunk-2LY6GIIE.js → chunk-WDBX6VER.js} +2 -2
- package/dist/{chunk-BMEYMKWX.js → chunk-WM65LU67.js} +2 -2
- package/dist/{chunk-WLRD2TXF.js → chunk-WOPA3SGI.js} +4 -4
- package/dist/{chunk-5UZ6UCOZ.js → chunk-XUAMCLY4.js} +3 -3
- package/dist/{chunk-7HXZJAID.js → chunk-YGLY6QFV.js} +3 -3
- package/dist/{chunk-2OIQDTDC.js → chunk-ZPJ4QSGP.js} +2 -2
- package/dist/{chunk-3OY7KXHM.js → chunk-ZYLC7NFO.js} +3 -3
- package/dist/{cli-DMAFI0qt.d.ts → cli-DVUFIa5E.d.ts} +5 -5
- package/dist/cli.d.ts +13 -13
- package/dist/cli.js +42 -42
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +13 -13
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +5 -5
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +12 -12
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +12 -12
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/content-hash.d.ts +12 -2
- package/dist/content-hash.js +9 -3
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/contradiction/index.js +3 -3
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +14 -14
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +1 -1
- package/dist/corpus-watermark.js +14 -14
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/dependency-propagation-config.d.ts +1 -1
- package/dist/{dependency-propagation-delivery-D63r24pH.d.ts → dependency-propagation-delivery-XzF76C9n.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +12 -12
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +9 -9
- package/dist/explicit-capture.js +8 -8
- package/dist/external-wiki-access.d.ts +11 -11
- package/dist/external-wiki-access.js +23 -23
- package/dist/external-wiki-collection-registration.d.ts +1 -1
- package/dist/external-wiki-collection.d.ts +1 -1
- package/dist/external-wiki-mcp-tools.d.ts +11 -11
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-normalization.d.ts +1 -1
- package/dist/extraction-normalization.js +2 -2
- package/dist/extraction-prompt.d.ts +1 -1
- package/dist/extraction-source-grounding-rules.d.ts +1 -1
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction-source-grounding.js +3 -3
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +16 -16
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{forget-HDS4N6CG.js → forget-EHGGJZLX.js} +3 -2
- package/dist/{forget-HDS4N6CG.js.map → forget-EHGGJZLX.js.map} +1 -1
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-path-reconstruction.d.ts +1 -1
- package/dist/graph-path-scoring.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/harmonic-construction.js +2 -2
- package/dist/harmonic-retrieval.js +4 -4
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +777 -705
- package/dist/index.js +77 -67
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +12 -12
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -12
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +13 -13
- package/dist/{maintenance-kBII2qUI.d.ts → maintenance-BJzNIKBE.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +11 -11
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-snapshot.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +13 -13
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +12 -12
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +15 -15
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +21 -21
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +17 -17
- package/dist/{orchestrator-DHy5LtTy.d.ts → orchestrator-BiXgBJBQ.d.ts} +8 -8
- package/dist/orchestrator.d.ts +9 -9
- package/dist/orchestrator.js +55 -55
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-D21Rs6gW.d.ts → pipeline-sz_dcuLg.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/provenance.js +2 -2
- package/dist/{qmd-Dhle1P4X.d.ts → qmd-eAWObK4M.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/reconcile/cursor.d.ts +1 -1
- package/dist/reconcile/manifest.d.ts +4 -2
- package/dist/reconcile/manifest.js +7 -3
- package/dist/reconcile/plan.d.ts +1 -1
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/{resolution-MDPNOEG6.js → resolution-MMOD5WC4.js} +4 -4
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +6 -6
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/salvage-envelope.js +3 -3
- package/dist/schemas.d.ts +138 -138
- package/dist/{scope-profiles-NzGPDXLn.d.ts → scope-profiles-BBQsXuyj.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-DLDKvyy5.d.ts → semantic-consolidation-HD8RkzVA.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +13 -13
- package/dist/semantic-rule-promotion.js +12 -12
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +12 -12
- package/dist/{service-PT2_z7xm.d.ts → service-DIrtHHEg.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +14 -14
- package/dist/{storage-BmVudhk4.d.ts → storage-DqCzXi56.d.ts} +37 -33
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +14 -14
- package/dist/structured-attributes.d.ts +3 -1
- package/dist/structured-attributes.js +3 -1
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +66 -66
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-CkiDUorT.d.ts → types-d_j4vC6N.d.ts} +26 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +12 -12
- package/dist/write-envelope.d.ts +1 -1
- package/dist/write-envelope.js +2 -2
- package/package.json +2 -2
- package/src/access-offline-manifest.ts +1 -0
- package/src/access-service-offline-file-content.test.ts +1 -0
- package/src/content-hash.ts +34 -1
- package/src/dedup/index.test.ts +56 -38
- package/src/dedup/index.ts +85 -38
- package/src/lifecycle/tombstones.test.ts +763 -16
- package/src/lifecycle/tombstones.ts +217 -50
- package/src/reconcile/manifest.test.ts +158 -1
- package/src/reconcile/manifest.ts +76 -22
- package/src/storage/content-hash-index.ts +56 -40
- package/src/storage/entity-ref-repair.ts +3 -3
- package/src/storage/memory-read-store.ts +9 -0
- package/src/storage/tombstone-blocked-capture-index.ts +4 -9
- package/src/storage-contract/content-hash.test.ts +13 -0
- package/src/storage-fact-hash-index-defer.test.ts +1 -1
- package/src/storage.ts +107 -93
- package/src/structured-attributes.ts +11 -0
- package/src/wearables/cleanup.test.ts +71 -0
- package/src/wearables/cleanup.ts +163 -16
- package/src/wearables/config.test.ts +62 -0
- package/src/wearables/config.ts +99 -5
- package/src/wearables/index.ts +10 -0
- package/src/wearables/pipeline.test.ts +36 -0
- package/src/wearables/pipeline.ts +18 -3
- package/src/wearables/redaction.test.ts +156 -0
- package/src/wearables/redaction.ts +102 -10
- package/src/wearables/text-language.ts +146 -0
- package/src/wearables/types.ts +26 -0
- package/dist/chunk-BQIFZ2MP.js.map +0 -1
- package/dist/chunk-DLG5V6ND.js.map +0 -1
- package/dist/chunk-IARXXZU4.js.map +0 -1
- package/dist/chunk-JJRVPWFY.js.map +0 -1
- package/dist/chunk-NDAH7BJ5.js.map +0 -1
- package/dist/chunk-R3DJVOSM.js.map +0 -1
- package/dist/chunk-VHFXP27N.js.map +0 -1
- package/dist/chunk-W5H6ZRPT.js +0 -14
- package/dist/chunk-W5H6ZRPT.js.map +0 -1
- package/dist/chunk-WD5B5AIE.js.map +0 -1
- /package/dist/{auto-sync-QFQGOEO2.js.map → auto-sync-TDHLZPVP.js.map} +0 -0
- /package/dist/{chunk-MLXZD4WM.js.map → chunk-2GFK22OZ.js.map} +0 -0
- /package/dist/{chunk-RANKJI5T.js.map → chunk-3JLPGJ43.js.map} +0 -0
- /package/dist/{chunk-X7XEDOSL.js.map → chunk-4IIFL7GC.js.map} +0 -0
- /package/dist/{chunk-GXSOUIA4.js.map → chunk-5WQMORU5.js.map} +0 -0
- /package/dist/{chunk-J272WKX2.js.map → chunk-6HPHEFXT.js.map} +0 -0
- /package/dist/{chunk-B6LZ4IBH.js.map → chunk-6LAPLZOS.js.map} +0 -0
- /package/dist/{chunk-VXCNQYTC.js.map → chunk-APPFNGX6.js.map} +0 -0
- /package/dist/{chunk-GXFSQ6SD.js.map → chunk-C4ZNGVK3.js.map} +0 -0
- /package/dist/{chunk-JSEJH2YQ.js.map → chunk-CGVHPCP2.js.map} +0 -0
- /package/dist/{chunk-TEMSWHQD.js.map → chunk-DZD576ZU.js.map} +0 -0
- /package/dist/{chunk-AMTEYNYK.js.map → chunk-EVWWT3KE.js.map} +0 -0
- /package/dist/{chunk-AXIDQG5A.js.map → chunk-G4T7L557.js.map} +0 -0
- /package/dist/{chunk-7M6NYQWB.js.map → chunk-G7Z6EENO.js.map} +0 -0
- /package/dist/{chunk-YXKVLYXU.js.map → chunk-HWRD23DW.js.map} +0 -0
- /package/dist/{chunk-SOEN6KCF.js.map → chunk-IBKR2TFR.js.map} +0 -0
- /package/dist/{chunk-F4EU6VUC.js.map → chunk-JDZPNCMA.js.map} +0 -0
- /package/dist/{chunk-D5YOZ66N.js.map → chunk-JV5YUBIZ.js.map} +0 -0
- /package/dist/{chunk-BAKXNQ3C.js.map → chunk-KRTY3ENI.js.map} +0 -0
- /package/dist/{chunk-NK5RGJ65.js.map → chunk-L4DLLKPN.js.map} +0 -0
- /package/dist/{chunk-VFSAWRMZ.js.map → chunk-LJS7KUPV.js.map} +0 -0
- /package/dist/{chunk-NOEMGE56.js.map → chunk-NH5IR2WM.js.map} +0 -0
- /package/dist/{chunk-5V6IXHPI.js.map → chunk-NSVDUN76.js.map} +0 -0
- /package/dist/{chunk-W7Y5JDVB.js.map → chunk-PDDTI7IC.js.map} +0 -0
- /package/dist/{chunk-QINJITDS.js.map → chunk-PX2LSHYM.js.map} +0 -0
- /package/dist/{chunk-HDIUYMSB.js.map → chunk-QWGE2PUH.js.map} +0 -0
- /package/dist/{chunk-X57M2Q4I.js.map → chunk-RDJRFDJY.js.map} +0 -0
- /package/dist/{chunk-RYLEDKQ2.js.map → chunk-RUMNY5DT.js.map} +0 -0
- /package/dist/{chunk-BKIFTXHR.js.map → chunk-RZ4XGKOJ.js.map} +0 -0
- /package/dist/{chunk-66SE3BNH.js.map → chunk-SUPEYLH2.js.map} +0 -0
- /package/dist/{chunk-BF6SXNQJ.js.map → chunk-T2JHZWH6.js.map} +0 -0
- /package/dist/{chunk-QPOHMO6M.js.map → chunk-UES4BHA2.js.map} +0 -0
- /package/dist/{chunk-XAPMEU3K.js.map → chunk-UU7TRFNE.js.map} +0 -0
- /package/dist/{chunk-QBSFOKY6.js.map → chunk-VOELBUS4.js.map} +0 -0
- /package/dist/{chunk-2LY6GIIE.js.map → chunk-WDBX6VER.js.map} +0 -0
- /package/dist/{chunk-BMEYMKWX.js.map → chunk-WM65LU67.js.map} +0 -0
- /package/dist/{chunk-WLRD2TXF.js.map → chunk-WOPA3SGI.js.map} +0 -0
- /package/dist/{chunk-5UZ6UCOZ.js.map → chunk-XUAMCLY4.js.map} +0 -0
- /package/dist/{chunk-7HXZJAID.js.map → chunk-YGLY6QFV.js.map} +0 -0
- /package/dist/{chunk-2OIQDTDC.js.map → chunk-ZPJ4QSGP.js.map} +0 -0
- /package/dist/{chunk-3OY7KXHM.js.map → chunk-ZYLC7NFO.js.map} +0 -0
- /package/dist/{resolution-MDPNOEG6.js.map → resolution-MMOD5WC4.js.map} +0 -0
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
import { describe, it } from "node:test";
|
|
2
1
|
import assert from "node:assert/strict";
|
|
3
|
-
import { mkdtemp, readFile, writeFile, rm, mkdir, appendFile, utimes } from "node:fs/promises";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
6
4
|
import { statSync } from "node:fs";
|
|
5
|
+
import { appendFile, mkdir, mkdtemp, readFile, rm, utimes, writeFile } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import {
|
|
8
|
+
import { describe, it } from "node:test";
|
|
9
|
+
import {
|
|
10
|
+
computeContentHash as computeHash,
|
|
11
|
+
computeLegacyContentHash,
|
|
12
|
+
normalizeContent,
|
|
13
|
+
normalizeLegacyContent,
|
|
14
|
+
} from "../content-hash.js";
|
|
9
15
|
import {
|
|
10
16
|
TombstoneStore,
|
|
11
17
|
parseTombstoneLine,
|
|
12
18
|
type TombstoneFileIo,
|
|
13
19
|
} from "./tombstones.js";
|
|
14
20
|
|
|
15
|
-
// Mirror the dedup normalizer EXACTLY — these are the wired injections.
|
|
16
|
-
function normalizeContent(content: string): string {
|
|
17
|
-
return content
|
|
18
|
-
.toLowerCase()
|
|
19
|
-
.replace(/[^a-z0-9\s]/g, " ")
|
|
20
|
-
.replace(/\s+/g, " ")
|
|
21
|
-
.trim();
|
|
22
|
-
}
|
|
23
|
-
function computeHash(content: string): string {
|
|
24
|
-
return createHash("sha256").update(normalizeContent(content)).digest("hex");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
21
|
function makeIo(): TombstoneFileIo {
|
|
28
22
|
return {
|
|
29
23
|
read: (p) => readFile(p, "utf8"),
|
|
@@ -134,6 +128,663 @@ describe("TombstoneStore — append/lookup round-trip", () => {
|
|
|
134
128
|
});
|
|
135
129
|
});
|
|
136
130
|
|
|
131
|
+
describe("TombstoneStore — Unicode normalizer migration", () => {
|
|
132
|
+
it("migrates a legacy Japanese identity before indexing, then survives restart", async () => {
|
|
133
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-unicode-migration-"));
|
|
134
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
135
|
+
const japanese = "利用者は紅茶を好む。";
|
|
136
|
+
const legacyNormalize = (content: string) =>
|
|
137
|
+
content.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
138
|
+
const legacyEntry = {
|
|
139
|
+
id: "tomb-legacy-japanese",
|
|
140
|
+
kind: "tombstone" as const,
|
|
141
|
+
reason: "correction" as const,
|
|
142
|
+
sourceMemoryId: "fact-japanese",
|
|
143
|
+
contentHash: createHash("sha256").update(legacyNormalize(japanese)).digest("hex"),
|
|
144
|
+
normalizedText: legacyNormalize(japanese),
|
|
145
|
+
namespace: "default",
|
|
146
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
147
|
+
createdBy: "user_correction" as const,
|
|
148
|
+
};
|
|
149
|
+
const legacyAscii = {
|
|
150
|
+
...legacyEntry,
|
|
151
|
+
id: "tomb-legacy-ascii",
|
|
152
|
+
sourceMemoryId: "fact-ascii",
|
|
153
|
+
contentHash: createHash("sha256").update(legacyNormalize("ASCII survives")).digest("hex"),
|
|
154
|
+
normalizedText: legacyNormalize("ASCII survives"),
|
|
155
|
+
};
|
|
156
|
+
await writeFile(
|
|
157
|
+
filePath,
|
|
158
|
+
`${JSON.stringify(legacyEntry)}\n${JSON.stringify(legacyAscii)}\n`,
|
|
159
|
+
"utf8",
|
|
160
|
+
);
|
|
161
|
+
const options = {
|
|
162
|
+
enabled: true,
|
|
163
|
+
semanticMatch: false,
|
|
164
|
+
semanticThreshold: 0.9,
|
|
165
|
+
hashContent: computeHash,
|
|
166
|
+
normalizeText: normalizeContent,
|
|
167
|
+
sourceContentsForMemoryIds: async (ids: readonly string[]) =>
|
|
168
|
+
new Map(ids.map((id) => [id, id === "fact-japanese" ? japanese : "ASCII survives"])),
|
|
169
|
+
};
|
|
170
|
+
const store = new TombstoneStore(filePath, "default", options, makeIo());
|
|
171
|
+
await store.load();
|
|
172
|
+
assert.equal(
|
|
173
|
+
store.lookup({ namespace: "default", contentHash: computeHash(japanese) })?.matchedTier,
|
|
174
|
+
"exact",
|
|
175
|
+
);
|
|
176
|
+
assert.equal(
|
|
177
|
+
store.lookup({ namespace: "default", normalizedText: normalizeContent(japanese) })?.matchedTier,
|
|
178
|
+
"normalized",
|
|
179
|
+
);
|
|
180
|
+
assert.equal(
|
|
181
|
+
store.lookup({ namespace: "default", contentHash: computeHash("ASCII survives") })?.matchedTier,
|
|
182
|
+
"exact",
|
|
183
|
+
);
|
|
184
|
+
assert.equal(store.snapshot().find((entry) => entry.id === legacyEntry.id)?.normalizerVersion, 2);
|
|
185
|
+
|
|
186
|
+
const restarted = new TombstoneStore(filePath, "default", options, makeIo());
|
|
187
|
+
await restarted.load();
|
|
188
|
+
assert.equal(
|
|
189
|
+
restarted.lookup({ namespace: "default", contentHash: computeHash(japanese) })?.matchedTier,
|
|
190
|
+
"exact",
|
|
191
|
+
);
|
|
192
|
+
assert.equal(
|
|
193
|
+
restarted.lookup({ namespace: "default", normalizedText: normalizeContent(japanese) })?.matchedTier,
|
|
194
|
+
"normalized",
|
|
195
|
+
);
|
|
196
|
+
assert.equal(
|
|
197
|
+
restarted.lookup({ namespace: "default", contentHash: computeHash("ASCII survives") })?.matchedTier,
|
|
198
|
+
"exact",
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("preserves an explicit pre-upgrade contentHashSource identity after migration and restart", async () => {
|
|
203
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-content-hash-source-migration-"));
|
|
204
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
205
|
+
const source = "利用者は紅茶を好む。";
|
|
206
|
+
const override = computeHash("external contentHashSource identity");
|
|
207
|
+
const legacyNormalize = (content: string) =>
|
|
208
|
+
content.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
209
|
+
const legacyEntry = {
|
|
210
|
+
id: "tomb-legacy-override",
|
|
211
|
+
kind: "tombstone" as const,
|
|
212
|
+
reason: "correction" as const,
|
|
213
|
+
sourceMemoryId: "fact-override",
|
|
214
|
+
contentHash: override,
|
|
215
|
+
normalizedText: legacyNormalize(source),
|
|
216
|
+
namespace: "default",
|
|
217
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
218
|
+
createdBy: "user_correction" as const,
|
|
219
|
+
};
|
|
220
|
+
await writeFile(filePath, `${JSON.stringify(legacyEntry)}\n`, "utf8");
|
|
221
|
+
const options = {
|
|
222
|
+
enabled: true,
|
|
223
|
+
semanticMatch: false,
|
|
224
|
+
semanticThreshold: 0.9,
|
|
225
|
+
hashContent: computeHash,
|
|
226
|
+
normalizeText: normalizeContent,
|
|
227
|
+
sourceContentsForMemoryIds: async () =>
|
|
228
|
+
new Map<string, string>([["fact-override", source]]),
|
|
229
|
+
};
|
|
230
|
+
const store = new TombstoneStore(filePath, "default", options, makeIo());
|
|
231
|
+
await store.load();
|
|
232
|
+
const query = {
|
|
233
|
+
namespace: "default",
|
|
234
|
+
contentHash: override,
|
|
235
|
+
normalizedText: normalizeContent(source),
|
|
236
|
+
};
|
|
237
|
+
assert.equal(store.lookup(query)?.matchedTier, "exact");
|
|
238
|
+
|
|
239
|
+
const restarted = new TombstoneStore(filePath, "default", options, makeIo());
|
|
240
|
+
await restarted.load();
|
|
241
|
+
assert.equal(restarted.lookup(query)?.matchedTier, "exact");
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe("TombstoneStore — Unicode migration safety", () => {
|
|
246
|
+
it("does not cross-block unrelated pure-CJK tombstones through an empty legacy alias", async () => {
|
|
247
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-cjk-alias-safety-"));
|
|
248
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
249
|
+
const first = "利用者は紅茶を好む。";
|
|
250
|
+
const unrelated = "利用者は珈琲を好む。";
|
|
251
|
+
const legacyHash = computeLegacyContentHash(first);
|
|
252
|
+
await writeFile(
|
|
253
|
+
filePath,
|
|
254
|
+
`${JSON.stringify({
|
|
255
|
+
id: "tomb-cjk-first",
|
|
256
|
+
kind: "tombstone",
|
|
257
|
+
reason: "correction",
|
|
258
|
+
sourceMemoryId: "fact-cjk-first",
|
|
259
|
+
contentHash: legacyHash,
|
|
260
|
+
normalizedText: normalizeLegacyContent(first),
|
|
261
|
+
namespace: "default",
|
|
262
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
263
|
+
createdBy: "user_correction",
|
|
264
|
+
})}\n`,
|
|
265
|
+
"utf8",
|
|
266
|
+
);
|
|
267
|
+
const store = new TombstoneStore(
|
|
268
|
+
filePath,
|
|
269
|
+
"default",
|
|
270
|
+
{
|
|
271
|
+
enabled: true,
|
|
272
|
+
semanticMatch: false,
|
|
273
|
+
semanticThreshold: 0.9,
|
|
274
|
+
hashContent: computeHash,
|
|
275
|
+
normalizeText: normalizeContent,
|
|
276
|
+
sourceContentsForMemoryIds: async () =>
|
|
277
|
+
new Map<string, string>([["fact-cjk-first", first]]),
|
|
278
|
+
},
|
|
279
|
+
makeIo(),
|
|
280
|
+
);
|
|
281
|
+
await store.load();
|
|
282
|
+
assert.equal(
|
|
283
|
+
store.lookup({ namespace: "default", contentHash: computeHash(first) })?.matchedTier,
|
|
284
|
+
"exact",
|
|
285
|
+
);
|
|
286
|
+
assert.equal(
|
|
287
|
+
store.lookup({
|
|
288
|
+
namespace: "default",
|
|
289
|
+
contentHash: computeHash(unrelated),
|
|
290
|
+
}),
|
|
291
|
+
null,
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("does not publish legacy identities when migration fails and retries on the next load", async () => {
|
|
296
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-migration-retry-"));
|
|
297
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
298
|
+
const source = "利用者は緑茶を好む。";
|
|
299
|
+
const legacyEntry = {
|
|
300
|
+
id: "tomb-migration-retry",
|
|
301
|
+
kind: "tombstone" as const,
|
|
302
|
+
reason: "correction" as const,
|
|
303
|
+
sourceMemoryId: "fact-migration-retry",
|
|
304
|
+
contentHash: computeLegacyContentHash(source),
|
|
305
|
+
normalizedText: normalizeLegacyContent(source),
|
|
306
|
+
namespace: "default",
|
|
307
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
308
|
+
createdBy: "user_correction" as const,
|
|
309
|
+
};
|
|
310
|
+
await writeFile(filePath, `${JSON.stringify(legacyEntry)}\n`, "utf8");
|
|
311
|
+
let sourceReadAttempts = 0;
|
|
312
|
+
const store = new TombstoneStore(
|
|
313
|
+
filePath,
|
|
314
|
+
"default",
|
|
315
|
+
{
|
|
316
|
+
enabled: true,
|
|
317
|
+
semanticMatch: false,
|
|
318
|
+
semanticThreshold: 0.9,
|
|
319
|
+
hashContent: computeHash,
|
|
320
|
+
normalizeText: normalizeContent,
|
|
321
|
+
sourceContentsForMemoryIds: async () => {
|
|
322
|
+
sourceReadAttempts += 1;
|
|
323
|
+
if (sourceReadAttempts === 1) throw new Error("transient source read failure");
|
|
324
|
+
return new Map<string, string>([[legacyEntry.sourceMemoryId, source]]);
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
makeIo(),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
await assert.rejects(store.load(), /transient source read failure/);
|
|
331
|
+
assert.deepEqual(store.snapshot(), []);
|
|
332
|
+
assert.equal(
|
|
333
|
+
store.lookup({ namespace: "default", contentHash: computeHash(source) }),
|
|
334
|
+
null,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
await store.load();
|
|
338
|
+
assert.equal(sourceReadAttempts, 2);
|
|
339
|
+
assert.equal(
|
|
340
|
+
store.lookup({ namespace: "default", contentHash: computeHash(source) })?.matchedTier,
|
|
341
|
+
"exact",
|
|
342
|
+
);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("does not publish an empty index after a transient ledger read failure", async () => {
|
|
346
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-load-retry-"));
|
|
347
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
348
|
+
const source = "Current tombstone identity";
|
|
349
|
+
const entry = {
|
|
350
|
+
id: "tomb-load-retry",
|
|
351
|
+
kind: "tombstone" as const,
|
|
352
|
+
reason: "correction" as const,
|
|
353
|
+
sourceMemoryId: "fact-load-retry",
|
|
354
|
+
contentHash: computeHash(source),
|
|
355
|
+
normalizedText: normalizeContent(source),
|
|
356
|
+
normalizerVersion: 2,
|
|
357
|
+
namespace: "default",
|
|
358
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
359
|
+
createdBy: "user_correction" as const,
|
|
360
|
+
};
|
|
361
|
+
await writeFile(filePath, `${JSON.stringify(entry)}\n`, "utf8");
|
|
362
|
+
let readAttempts = 0;
|
|
363
|
+
const io = makeIo();
|
|
364
|
+
io.read = async (target) => {
|
|
365
|
+
readAttempts += 1;
|
|
366
|
+
if (readAttempts === 1) {
|
|
367
|
+
const error = new Error("transient ledger read failure") as NodeJS.ErrnoException;
|
|
368
|
+
error.code = "EACCES";
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
return readFile(target, "utf8");
|
|
372
|
+
};
|
|
373
|
+
const store = new TombstoneStore(
|
|
374
|
+
filePath,
|
|
375
|
+
"default",
|
|
376
|
+
{
|
|
377
|
+
enabled: true,
|
|
378
|
+
semanticMatch: false,
|
|
379
|
+
semanticThreshold: 0.9,
|
|
380
|
+
hashContent: computeHash,
|
|
381
|
+
normalizeText: normalizeContent,
|
|
382
|
+
},
|
|
383
|
+
io,
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
await assert.rejects(store.load(), /transient ledger read failure/);
|
|
387
|
+
assert.deepEqual(store.snapshot(), []);
|
|
388
|
+
|
|
389
|
+
await store.load();
|
|
390
|
+
assert.equal(readAttempts, 2);
|
|
391
|
+
assert.equal(
|
|
392
|
+
store.lookup({ namespace: "default", contentHash: computeHash(source) })?.matchedTier,
|
|
393
|
+
"exact",
|
|
394
|
+
);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("withholds unverified legacy hash tiers until the source becomes available", async () => {
|
|
398
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-unverified-legacy-"));
|
|
399
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
400
|
+
const source = "The user prefers café.";
|
|
401
|
+
const collision = "The user prefers caf.";
|
|
402
|
+
const legacyEntry = {
|
|
403
|
+
id: "tomb-unverified-legacy",
|
|
404
|
+
kind: "tombstone" as const,
|
|
405
|
+
reason: "correction" as const,
|
|
406
|
+
sourceMemoryId: "fact-unverified-legacy",
|
|
407
|
+
contentHash: computeLegacyContentHash(source),
|
|
408
|
+
normalizedText: normalizeLegacyContent(source),
|
|
409
|
+
namespace: "default",
|
|
410
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
411
|
+
createdBy: "user_correction" as const,
|
|
412
|
+
};
|
|
413
|
+
await writeFile(filePath, `${JSON.stringify(legacyEntry)}\n`, "utf8");
|
|
414
|
+
let sourceAvailable = false;
|
|
415
|
+
const store = new TombstoneStore(
|
|
416
|
+
filePath,
|
|
417
|
+
"default",
|
|
418
|
+
{
|
|
419
|
+
enabled: true,
|
|
420
|
+
semanticMatch: true,
|
|
421
|
+
semanticThreshold: 0.9,
|
|
422
|
+
hashContent: computeHash,
|
|
423
|
+
normalizeText: normalizeContent,
|
|
424
|
+
semanticSimilarity: () => 1,
|
|
425
|
+
sourceContentsForMemoryIds: async () =>
|
|
426
|
+
sourceAvailable
|
|
427
|
+
? new Map<string, string>([[legacyEntry.sourceMemoryId, source]])
|
|
428
|
+
: new Map(),
|
|
429
|
+
},
|
|
430
|
+
makeIo(),
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
await store.load();
|
|
434
|
+
assert.equal(store.snapshot()[0]?.normalizerVersion, undefined);
|
|
435
|
+
assert.equal(
|
|
436
|
+
store.lookup({ namespace: "default", contentHash: computeHash(collision) }),
|
|
437
|
+
null,
|
|
438
|
+
);
|
|
439
|
+
assert.equal(
|
|
440
|
+
store.lookup({ namespace: "default", normalizedText: normalizeContent(collision) }),
|
|
441
|
+
null,
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
sourceAvailable = true;
|
|
445
|
+
store.invalidate();
|
|
446
|
+
await store.load();
|
|
447
|
+
assert.equal(
|
|
448
|
+
store.lookup({ namespace: "default", contentHash: computeHash(source) })?.matchedTier,
|
|
449
|
+
"exact",
|
|
450
|
+
);
|
|
451
|
+
assert.equal(
|
|
452
|
+
store.lookup({ namespace: "default", contentHash: computeHash(collision) }),
|
|
453
|
+
null,
|
|
454
|
+
);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it("replaces a mixed-Unicode legacy hash instead of blocking its ASCII collision", async () => {
|
|
458
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-mixed-unicode-alias-safety-"));
|
|
459
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
460
|
+
const source = "The user prefers café.";
|
|
461
|
+
const asciiCollision = "The user prefers caf.";
|
|
462
|
+
await writeFile(
|
|
463
|
+
filePath,
|
|
464
|
+
`${JSON.stringify({
|
|
465
|
+
id: "tomb-mixed-unicode",
|
|
466
|
+
kind: "tombstone",
|
|
467
|
+
reason: "correction",
|
|
468
|
+
sourceMemoryId: "fact-mixed-unicode",
|
|
469
|
+
contentHash: computeLegacyContentHash(source),
|
|
470
|
+
normalizedText: normalizeLegacyContent(source),
|
|
471
|
+
namespace: "default",
|
|
472
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
473
|
+
createdBy: "user_correction",
|
|
474
|
+
})}\n`,
|
|
475
|
+
"utf8",
|
|
476
|
+
);
|
|
477
|
+
const store = new TombstoneStore(
|
|
478
|
+
filePath,
|
|
479
|
+
"default",
|
|
480
|
+
{
|
|
481
|
+
enabled: true,
|
|
482
|
+
semanticMatch: false,
|
|
483
|
+
semanticThreshold: 0.9,
|
|
484
|
+
hashContent: computeHash,
|
|
485
|
+
normalizeText: normalizeContent,
|
|
486
|
+
sourceContentsForMemoryIds: async () =>
|
|
487
|
+
new Map<string, string>([["fact-mixed-unicode", source]]),
|
|
488
|
+
},
|
|
489
|
+
makeIo(),
|
|
490
|
+
);
|
|
491
|
+
await store.load();
|
|
492
|
+
|
|
493
|
+
assert.equal(
|
|
494
|
+
store.lookup({ namespace: "default", contentHash: computeHash(source) })?.matchedTier,
|
|
495
|
+
"exact",
|
|
496
|
+
);
|
|
497
|
+
assert.equal(
|
|
498
|
+
store.lookup({ namespace: "default", contentHash: computeHash(asciiCollision) }),
|
|
499
|
+
null,
|
|
500
|
+
);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("migrates the raw source identity when a stored fact has structured attributes", async () => {
|
|
504
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-attribute-source-migration-"));
|
|
505
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
506
|
+
const source = "利用者は紅茶を好む。";
|
|
507
|
+
const storedBody = `${source}\n[Attributes: topic: 紅茶]`;
|
|
508
|
+
await writeFile(
|
|
509
|
+
filePath,
|
|
510
|
+
`${JSON.stringify({
|
|
511
|
+
id: "tomb-attribute-source",
|
|
512
|
+
kind: "tombstone",
|
|
513
|
+
reason: "correction",
|
|
514
|
+
sourceMemoryId: "fact-attribute-source",
|
|
515
|
+
contentHash: computeLegacyContentHash(source),
|
|
516
|
+
normalizedText: normalizeLegacyContent(source),
|
|
517
|
+
namespace: "default",
|
|
518
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
519
|
+
createdBy: "user_correction",
|
|
520
|
+
})}\n`,
|
|
521
|
+
"utf8"
|
|
522
|
+
);
|
|
523
|
+
const store = new TombstoneStore(
|
|
524
|
+
filePath,
|
|
525
|
+
"default",
|
|
526
|
+
{
|
|
527
|
+
enabled: true,
|
|
528
|
+
semanticMatch: false,
|
|
529
|
+
semanticThreshold: 0.9,
|
|
530
|
+
hashContent: computeHash,
|
|
531
|
+
normalizeText: normalizeContent,
|
|
532
|
+
sourceContentsForMemoryIds: async () => new Map([["fact-attribute-source", [storedBody, source]]]),
|
|
533
|
+
},
|
|
534
|
+
makeIo()
|
|
535
|
+
);
|
|
536
|
+
await store.load();
|
|
537
|
+
|
|
538
|
+
assert.equal(store.lookup({ namespace: "default", contentHash: computeHash(source) })?.matchedTier, "exact");
|
|
539
|
+
assert.equal(store.snapshot()[0]?.normalizerVersion, 2);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it("does not finalize migration from a body with an unrecognized stale citation", async () => {
|
|
543
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-stale-citation-migration-"));
|
|
544
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
545
|
+
const source = "The user prefers café.";
|
|
546
|
+
const storedBody = `${source} [legacy-source:planner]`;
|
|
547
|
+
await writeFile(
|
|
548
|
+
filePath,
|
|
549
|
+
`${JSON.stringify({
|
|
550
|
+
id: "tomb-stale-citation",
|
|
551
|
+
kind: "tombstone",
|
|
552
|
+
reason: "correction",
|
|
553
|
+
sourceMemoryId: "fact-stale-citation",
|
|
554
|
+
contentHash: computeLegacyContentHash(source),
|
|
555
|
+
normalizedText: normalizeLegacyContent(source),
|
|
556
|
+
namespace: "default",
|
|
557
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
558
|
+
createdBy: "user_correction",
|
|
559
|
+
})}\n`,
|
|
560
|
+
"utf8",
|
|
561
|
+
);
|
|
562
|
+
const store = new TombstoneStore(
|
|
563
|
+
filePath,
|
|
564
|
+
"default",
|
|
565
|
+
{
|
|
566
|
+
enabled: true,
|
|
567
|
+
semanticMatch: false,
|
|
568
|
+
semanticThreshold: 0.9,
|
|
569
|
+
hashContent: computeHash,
|
|
570
|
+
normalizeText: normalizeContent,
|
|
571
|
+
sourceContentsForMemoryIds: async () =>
|
|
572
|
+
new Map<string, string>([["fact-stale-citation", storedBody]]),
|
|
573
|
+
},
|
|
574
|
+
makeIo(),
|
|
575
|
+
);
|
|
576
|
+
await store.load();
|
|
577
|
+
|
|
578
|
+
const entry = store.snapshot()[0];
|
|
579
|
+
assert.equal(entry?.normalizerVersion, undefined);
|
|
580
|
+
assert.equal(entry?.currentContentHashAlias, undefined);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("migrates valid rows while preserving malformed JSONL rows", async () => {
|
|
584
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-corrupt-migration-"));
|
|
585
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
586
|
+
const source = "The user prefers café.";
|
|
587
|
+
const valid = JSON.stringify({
|
|
588
|
+
id: "tomb-corrupt-migration",
|
|
589
|
+
kind: "tombstone",
|
|
590
|
+
reason: "correction",
|
|
591
|
+
sourceMemoryId: "fact-corrupt-migration",
|
|
592
|
+
contentHash: computeLegacyContentHash(source),
|
|
593
|
+
normalizedText: normalizeLegacyContent(source),
|
|
594
|
+
namespace: "default",
|
|
595
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
596
|
+
createdBy: "user_correction",
|
|
597
|
+
});
|
|
598
|
+
const original = `${valid}\n{not-json}\n`;
|
|
599
|
+
await writeFile(filePath, original, "utf8");
|
|
600
|
+
const store = new TombstoneStore(
|
|
601
|
+
filePath,
|
|
602
|
+
"default",
|
|
603
|
+
{
|
|
604
|
+
enabled: true,
|
|
605
|
+
semanticMatch: false,
|
|
606
|
+
semanticThreshold: 0.9,
|
|
607
|
+
hashContent: computeHash,
|
|
608
|
+
normalizeText: normalizeContent,
|
|
609
|
+
sourceContentsForMemoryIds: async () =>
|
|
610
|
+
new Map<string, string>([["fact-corrupt-migration", source]]),
|
|
611
|
+
},
|
|
612
|
+
makeIo(),
|
|
613
|
+
);
|
|
614
|
+
await store.load();
|
|
615
|
+
|
|
616
|
+
const rewritten = await readFile(filePath, "utf8");
|
|
617
|
+
assert.equal(rewritten.split("\n")[1], "{not-json}");
|
|
618
|
+
assert.equal(rewritten.endsWith("{not-json}\n"), true);
|
|
619
|
+
assert.equal(store.snapshot()[0]?.normalizerVersion, 2);
|
|
620
|
+
assert.equal(store.snapshot()[0]?.contentHash, computeHash(source));
|
|
621
|
+
assert.equal(store.stats().corruptedLines, 1);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it("blocks the same pure-CJK body without cross-blocking an unrelated body", async () => {
|
|
625
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-cjk-override-safety-"));
|
|
626
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
627
|
+
const source = "東京の会社は紅茶を好む。";
|
|
628
|
+
await writeFile(
|
|
629
|
+
filePath,
|
|
630
|
+
`${JSON.stringify({
|
|
631
|
+
id: "tomb-cjk-override",
|
|
632
|
+
kind: "tombstone",
|
|
633
|
+
reason: "correction",
|
|
634
|
+
sourceMemoryId: "fact-cjk-override",
|
|
635
|
+
contentHash: computeLegacyContentHash(source),
|
|
636
|
+
normalizedText: normalizeLegacyContent(source),
|
|
637
|
+
namespace: "default",
|
|
638
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
639
|
+
createdBy: "user_correction",
|
|
640
|
+
})}\n`,
|
|
641
|
+
"utf8",
|
|
642
|
+
);
|
|
643
|
+
const store = new TombstoneStore(
|
|
644
|
+
filePath,
|
|
645
|
+
"default",
|
|
646
|
+
{
|
|
647
|
+
enabled: true,
|
|
648
|
+
semanticMatch: false,
|
|
649
|
+
semanticThreshold: 0.9,
|
|
650
|
+
hashContent: computeHash,
|
|
651
|
+
normalizeText: normalizeContent,
|
|
652
|
+
sourceContentsForMemoryIds: async () =>
|
|
653
|
+
new Map<string, string>([["fact-cjk-override", source]]),
|
|
654
|
+
},
|
|
655
|
+
makeIo(),
|
|
656
|
+
);
|
|
657
|
+
await store.load();
|
|
658
|
+
assert.equal(
|
|
659
|
+
store.lookup({ namespace: "default", normalizedText: normalizeContent(source) })?.matchedTier,
|
|
660
|
+
"normalized",
|
|
661
|
+
);
|
|
662
|
+
assert.equal(
|
|
663
|
+
store.lookup({ namespace: "default", normalizedText: normalizeContent("大阪の会社は珈琲を好む。") }),
|
|
664
|
+
null,
|
|
665
|
+
);
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it("rejects invalid legacy migration limits and accepts zero", async () => {
|
|
669
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-migration-limit-"));
|
|
670
|
+
const options = {
|
|
671
|
+
enabled: true,
|
|
672
|
+
semanticMatch: false,
|
|
673
|
+
semanticThreshold: 0.9,
|
|
674
|
+
hashContent: computeHash,
|
|
675
|
+
normalizeText: normalizeContent,
|
|
676
|
+
};
|
|
677
|
+
for (const limit of [-1, 1.5, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
678
|
+
assert.throws(
|
|
679
|
+
() => new TombstoneStore(path.join(dir, `${String(limit)}.jsonl`), "default", { ...options, legacyMigrationLimit: limit }, makeIo()),
|
|
680
|
+
/legacyMigrationLimit must be a finite non-negative integer/,
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
assert.doesNotThrow(
|
|
684
|
+
() => new TombstoneStore(path.join(dir, "zero.jsonl"), "default", { ...options, legacyMigrationLimit: 0 }, makeIo()),
|
|
685
|
+
);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it("retries a missing migration source after restart and upgrades when it appears", async () => {
|
|
689
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-migration-missing-source-"));
|
|
690
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
691
|
+
const source = "The user prefers café.";
|
|
692
|
+
await writeFile(
|
|
693
|
+
filePath,
|
|
694
|
+
`${JSON.stringify({
|
|
695
|
+
id: "tomb-missing-source",
|
|
696
|
+
kind: "tombstone",
|
|
697
|
+
reason: "correction",
|
|
698
|
+
sourceMemoryId: "fact-missing-source",
|
|
699
|
+
contentHash: computeLegacyContentHash(source),
|
|
700
|
+
normalizedText: normalizeLegacyContent(source),
|
|
701
|
+
namespace: "default",
|
|
702
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
703
|
+
createdBy: "user_correction",
|
|
704
|
+
})}\n`,
|
|
705
|
+
"utf8",
|
|
706
|
+
);
|
|
707
|
+
let requests = 0;
|
|
708
|
+
let sourceAvailable = false;
|
|
709
|
+
const options = {
|
|
710
|
+
enabled: true,
|
|
711
|
+
semanticMatch: false,
|
|
712
|
+
semanticThreshold: 0.9,
|
|
713
|
+
hashContent: computeHash,
|
|
714
|
+
normalizeText: normalizeContent,
|
|
715
|
+
sourceContentsForMemoryIds: async () => {
|
|
716
|
+
requests += 1;
|
|
717
|
+
return new Map<string, string>(
|
|
718
|
+
sourceAvailable ? [["fact-missing-source", source]] : [],
|
|
719
|
+
);
|
|
720
|
+
},
|
|
721
|
+
};
|
|
722
|
+
const first = new TombstoneStore(filePath, "default", options, makeIo());
|
|
723
|
+
await first.load();
|
|
724
|
+
assert.equal(requests, 1);
|
|
725
|
+
assert.equal(first.lookup({ namespace: "default", contentHash: computeHash(source) }), null);
|
|
726
|
+
|
|
727
|
+
sourceAvailable = true;
|
|
728
|
+
const restarted = new TombstoneStore(filePath, "default", options, makeIo());
|
|
729
|
+
await restarted.load();
|
|
730
|
+
assert.equal(requests, 2);
|
|
731
|
+
assert.equal(
|
|
732
|
+
restarted.lookup({ namespace: "default", contentHash: computeHash(source) })?.matchedTier,
|
|
733
|
+
"exact",
|
|
734
|
+
);
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it("migrates every legacy entry before one load becomes authoritative", async (t) => {
|
|
738
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-migration-all-batches-"));
|
|
739
|
+
t.after(() => rm(dir, { recursive: true, force: true }));
|
|
740
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
741
|
+
const sources = new Map([
|
|
742
|
+
["fact-first", "利用者は紅茶を好む。"],
|
|
743
|
+
["fact-second", "利用者は珈琲を好む。"],
|
|
744
|
+
]);
|
|
745
|
+
const entries = [...sources].map(([sourceMemoryId, content], index) => ({
|
|
746
|
+
id: `tomb-batch-${index}`,
|
|
747
|
+
kind: "tombstone",
|
|
748
|
+
reason: "correction",
|
|
749
|
+
sourceMemoryId,
|
|
750
|
+
contentHash: computeLegacyContentHash(content),
|
|
751
|
+
normalizedText: normalizeLegacyContent(content),
|
|
752
|
+
namespace: "default",
|
|
753
|
+
createdAt: `2026-01-0${index + 1}T00:00:00.000Z`,
|
|
754
|
+
createdBy: "user_correction",
|
|
755
|
+
}));
|
|
756
|
+
await writeFile(filePath, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf8");
|
|
757
|
+
const requestedBatches: string[][] = [];
|
|
758
|
+
const store = new TombstoneStore(
|
|
759
|
+
filePath,
|
|
760
|
+
"default",
|
|
761
|
+
{
|
|
762
|
+
enabled: true,
|
|
763
|
+
semanticMatch: false,
|
|
764
|
+
semanticThreshold: 0.9,
|
|
765
|
+
hashContent: computeHash,
|
|
766
|
+
normalizeText: normalizeContent,
|
|
767
|
+
legacyMigrationLimit: 1,
|
|
768
|
+
sourceContentsForMemoryIds: async (sourceMemoryIds) => {
|
|
769
|
+
requestedBatches.push([...sourceMemoryIds]);
|
|
770
|
+
return new Map(sourceMemoryIds.map((id) => [id, sources.get(id) as string]));
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
makeIo(),
|
|
774
|
+
);
|
|
775
|
+
|
|
776
|
+
await store.load();
|
|
777
|
+
|
|
778
|
+
assert.deepEqual(requestedBatches, [["fact-first"], ["fact-second"]]);
|
|
779
|
+
for (const content of sources.values()) {
|
|
780
|
+
assert.equal(
|
|
781
|
+
store.lookup({ namespace: "default", contentHash: computeHash(content) })?.matchedTier,
|
|
782
|
+
"exact",
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
|
|
137
788
|
describe("TombstoneStore — revocation supersedes tombstone", () => {
|
|
138
789
|
it("a revocation entry re-allows the fact (newest wins)", async () => {
|
|
139
790
|
const env = await makeStore("default");
|
|
@@ -251,6 +902,7 @@ describe("TombstoneStore — corrupted line skipped with counter, not crash", ()
|
|
|
251
902
|
sourceMemoryId: "fact-1",
|
|
252
903
|
contentHash: computeHash("good content"),
|
|
253
904
|
normalizedText: normalizeContent("good content"),
|
|
905
|
+
normalizerVersion: 2,
|
|
254
906
|
namespace: "default",
|
|
255
907
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
256
908
|
createdBy: "user_correction",
|
|
@@ -502,6 +1154,101 @@ describe("TombstoneStore — cross-process write lock (issue #1639)", () => {
|
|
|
502
1154
|
await rm(lockPath, { force: true });
|
|
503
1155
|
});
|
|
504
1156
|
|
|
1157
|
+
it("does not deadlock when mtime freshness sees legacy entries before append or rebuild", async () => {
|
|
1158
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-migration-deadlock-"));
|
|
1159
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
1160
|
+
const japanese = "利用者は紅茶を好む。";
|
|
1161
|
+
const legacyNormalize = (content: string) =>
|
|
1162
|
+
content.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
1163
|
+
const legacyEntry = {
|
|
1164
|
+
id: "tomb-legacy-before-append",
|
|
1165
|
+
kind: "tombstone" as const,
|
|
1166
|
+
reason: "correction" as const,
|
|
1167
|
+
sourceMemoryId: "fact-legacy-before-append",
|
|
1168
|
+
contentHash: createHash("sha256").update(legacyNormalize(japanese)).digest("hex"),
|
|
1169
|
+
normalizedText: legacyNormalize(japanese),
|
|
1170
|
+
namespace: "default",
|
|
1171
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
1172
|
+
createdBy: "user_correction" as const,
|
|
1173
|
+
};
|
|
1174
|
+
let resolveSources = false;
|
|
1175
|
+
const options = {
|
|
1176
|
+
enabled: true,
|
|
1177
|
+
semanticMatch: false,
|
|
1178
|
+
semanticThreshold: 0.9,
|
|
1179
|
+
hashContent: computeHash,
|
|
1180
|
+
normalizeText: normalizeContent,
|
|
1181
|
+
sourceContentsForMemoryIds: async (ids: readonly string[]) =>
|
|
1182
|
+
resolveSources
|
|
1183
|
+
? new Map(ids.map((id) => [id, japanese]))
|
|
1184
|
+
: new Map<string, string>(),
|
|
1185
|
+
};
|
|
1186
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
1187
|
+
const withTurnTimeout = async <T>(operation: Promise<T>, message: string): Promise<T> => {
|
|
1188
|
+
let settled = false;
|
|
1189
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
1190
|
+
let turns = 0;
|
|
1191
|
+
const check = () => {
|
|
1192
|
+
if (settled) return;
|
|
1193
|
+
turns += 1;
|
|
1194
|
+
if (turns >= 100_000) reject(new Error(message));
|
|
1195
|
+
else setImmediate(check);
|
|
1196
|
+
};
|
|
1197
|
+
setImmediate(check);
|
|
1198
|
+
});
|
|
1199
|
+
try {
|
|
1200
|
+
return await Promise.race([operation, timeout]);
|
|
1201
|
+
} finally {
|
|
1202
|
+
settled = true;
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
await writeFile(filePath, `${JSON.stringify(legacyEntry)}\n`, "utf8");
|
|
1206
|
+
const store = new TombstoneStore(filePath, "default", options, makeReadMergeWriteIo());
|
|
1207
|
+
await store.load();
|
|
1208
|
+
resolveSources = true;
|
|
1209
|
+
|
|
1210
|
+
const peerEntry = {
|
|
1211
|
+
...legacyEntry,
|
|
1212
|
+
id: "tomb-peer-before-append",
|
|
1213
|
+
sourceMemoryId: "fact-peer",
|
|
1214
|
+
};
|
|
1215
|
+
await appendFile(filePath, `${JSON.stringify(peerEntry)}\n`, "utf8");
|
|
1216
|
+
const appendMtime = new Date(Date.now() + 2_000);
|
|
1217
|
+
await utimes(filePath, appendMtime, appendMtime);
|
|
1218
|
+
const appended = await withTurnTimeout(
|
|
1219
|
+
store.appendTombstone({
|
|
1220
|
+
reason: "correction",
|
|
1221
|
+
createdBy: "user_correction",
|
|
1222
|
+
sourceMemoryId: "fact-new",
|
|
1223
|
+
rawContent: "A new fact after the peer append.",
|
|
1224
|
+
}),
|
|
1225
|
+
"append migration deadlock",
|
|
1226
|
+
);
|
|
1227
|
+
assert.match(appended, /^tomb-/);
|
|
1228
|
+
|
|
1229
|
+
const rebuildPeer = {
|
|
1230
|
+
...legacyEntry,
|
|
1231
|
+
id: "tomb-peer-before-rebuild",
|
|
1232
|
+
sourceMemoryId: "fact-peer-rebuild",
|
|
1233
|
+
};
|
|
1234
|
+
await appendFile(filePath, `${JSON.stringify(rebuildPeer)}\n`, "utf8");
|
|
1235
|
+
const rebuildMtime = new Date(Date.now() + 4_000);
|
|
1236
|
+
await utimes(filePath, rebuildMtime, rebuildMtime);
|
|
1237
|
+
const rebuilt = await withTurnTimeout(
|
|
1238
|
+
store.rebuild([
|
|
1239
|
+
{
|
|
1240
|
+
memoryId: "fact-rebuild",
|
|
1241
|
+
rawContent: japanese,
|
|
1242
|
+
reason: "correction" as const,
|
|
1243
|
+
createdBy: "user_correction" as const,
|
|
1244
|
+
createdAt: "2026-01-02T00:00:00.000Z",
|
|
1245
|
+
},
|
|
1246
|
+
]),
|
|
1247
|
+
"rebuild migration deadlock",
|
|
1248
|
+
);
|
|
1249
|
+
assert.equal(rebuilt, 1);
|
|
1250
|
+
});
|
|
1251
|
+
|
|
505
1252
|
it("rebuild preserves a peer's revocation appended before rebuild acquired the lock (cursor/codex #1639)", async () => {
|
|
506
1253
|
// Regression for the rebuild race: rebuild must re-read the log under the
|
|
507
1254
|
// lock (ensureFreshAgainstDisk) before computing its payload, otherwise a
|