@remnic/core 9.50.3 → 9.51.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +13 -11
- package/dist/access-admin-ops-surface.js +41 -39
- package/dist/access-authorization-probe.d.ts +13 -11
- package/dist/access-authorization-probe.js +42 -40
- package/dist/access-boundary.d.ts +13 -11
- package/dist/access-boundary.js +41 -39
- package/dist/access-cli.js +109 -104
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.d.ts +3 -1
- package/dist/access-extraction-force-flush.d.ts +13 -11
- package/dist/access-extraction-force-flush.js +41 -39
- package/dist/access-health-types.d.ts +3 -1
- package/dist/access-http-lcm-compaction.d.ts +13 -11
- package/dist/access-http-lifecycle-flush.d.ts +13 -11
- package/dist/access-http-offline-stream.d.ts +13 -11
- package/dist/access-http-query.js +41 -39
- package/dist/access-http.d.ts +13 -11
- package/dist/access-http.js +52 -50
- package/dist/access-identity-continuity-surface.d.ts +11 -9
- package/dist/access-identity-continuity-surface.js +41 -39
- package/dist/access-lcm-surface.d.ts +13 -11
- package/dist/access-lcm-surface.js +41 -39
- package/dist/access-mcp.d.ts +13 -11
- package/dist/access-mcp.js +47 -45
- package/dist/access-memory-search-fanout.d.ts +4 -2
- package/dist/{access-namespace-preflight-Dj2QolFE.d.ts → access-namespace-preflight-Ds6CGlCP.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +5 -3
- package/dist/access-namespace-preflight.js +41 -39
- package/dist/access-observe-write-surface.d.ts +13 -11
- package/dist/access-observe-write-surface.js +41 -39
- package/dist/access-offline-manifest.d.ts +11 -9
- package/dist/access-operations-batch.js +43 -41
- package/dist/access-operations.d.ts +16 -14
- package/dist/access-operations.js +46 -44
- package/dist/access-recall-concurrency.d.ts +13 -11
- package/dist/access-recall-concurrency.js +41 -39
- package/dist/access-recall-response.d.ts +13 -11
- package/dist/access-recall-response.js +41 -39
- package/dist/access-recall-surface.d.ts +13 -11
- package/dist/access-recall-surface.js +41 -39
- package/dist/access-schema.d.ts +88 -88
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-DesbnTSa.d.ts → access-service-BY0KJ44c.d.ts} +7 -7
- package/dist/access-service-helpers.d.ts +13 -11
- package/dist/access-service.d.ts +13 -11
- package/dist/access-service.js +41 -39
- package/dist/access-surface-catalog.d.ts +13 -11
- package/dist/access-wearables-meetings-surface.d.ts +5 -3
- package/dist/action-confidence.d.ts +3 -1
- package/dist/active-memory-bridge.d.ts +3 -1
- package/dist/active-recall.d.ts +3 -1
- package/dist/active-recall.js +5 -3
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/ambient-provenance.d.ts +3 -1
- package/dist/archive-mutation-version.d.ts +9 -0
- package/dist/archive-mutation-version.js +20 -0
- package/dist/artifact-search.d.ts +3 -1
- package/dist/behavior-learner.d.ts +3 -1
- package/dist/behavior-signals.d.ts +3 -1
- package/dist/bootstrap.d.ts +11 -9
- package/dist/briefing.d.ts +4 -2
- package/dist/briefing.js +10 -9
- package/dist/buffer-surprise-report.d.ts +3 -1
- package/dist/buffer-turn-helpers.d.ts +3 -1
- package/dist/buffer.d.ts +4 -2
- package/dist/buffer.js +5 -5
- package/dist/bulk-import/index.d.ts +5 -3
- package/dist/calibration.d.ts +3 -1
- package/dist/capabilities.d.ts +3 -1
- package/dist/{catalog-D-G7RgXR.d.ts → catalog-D3B4RDy1.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +3 -1
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.d.ts +3 -1
- package/dist/causal-consolidation.js +13 -12
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.d.ts +4 -2
- package/dist/causal-trajectory-graph.js +2 -1
- package/dist/causal-trajectory-graph.js.map +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-KDG2KTWW.js → chunk-33SV3E43.js} +4 -4
- package/dist/{chunk-2HGUDBRJ.js → chunk-37ZO6LLJ.js} +2 -2
- package/dist/{chunk-QW3WFHKU.js → chunk-45BRYON6.js} +2 -2
- package/dist/{chunk-GZICBRWK.js → chunk-4F6X3RXW.js} +2 -2
- package/dist/{chunk-7BAYBYY5.js → chunk-4PC4PRJE.js} +2 -2
- package/dist/{chunk-VJXY5ZCT.js → chunk-52UKFTUK.js} +2 -2
- package/dist/{chunk-ESG7Z255.js → chunk-5UDMFE3Q.js} +2 -2
- package/dist/{chunk-JPHEOE6Z.js → chunk-6MTSFX7A.js} +15 -15
- package/dist/chunk-6ZN34ISG.js +44 -0
- package/dist/chunk-6ZN34ISG.js.map +1 -0
- package/dist/{chunk-M4MP34AQ.js → chunk-AHZOMHWN.js} +1 -1
- package/dist/{chunk-Z3RHPGSY.js → chunk-AIUBBHSF.js} +3 -3
- package/dist/{chunk-S3AYIQCU.js → chunk-AXIDQG5A.js} +12 -12
- package/dist/{chunk-QN7NTKRC.js → chunk-AZ2QBGYB.js} +1 -1
- package/dist/{chunk-47J4EDGR.js → chunk-AZM4CCAC.js} +8 -8
- package/dist/{chunk-IXXABW5A.js → chunk-BHXYAVUM.js} +11 -5
- package/dist/chunk-BHXYAVUM.js.map +1 -0
- package/dist/{chunk-L2DPF4DF.js → chunk-BIJAWZYW.js} +54 -12
- package/dist/chunk-BIJAWZYW.js.map +1 -0
- package/dist/{chunk-LEJPSMKM.js → chunk-C5QLI33R.js} +3 -3
- package/dist/{chunk-SV3PSA7Y.js → chunk-CV6BBVYV.js} +5 -5
- package/dist/{chunk-V7X767RT.js → chunk-DCZBSE4F.js} +52 -52
- package/dist/chunk-FC67T4E6.js +70 -0
- package/dist/chunk-FC67T4E6.js.map +1 -0
- package/dist/{chunk-7PKFPLLZ.js → chunk-FXTYIA46.js} +2 -2
- package/dist/{chunk-64BHFFKZ.js → chunk-HLYR2TQL.js} +5 -5
- package/dist/{chunk-5C2HNJMO.js → chunk-J5LLX6IL.js} +32 -32
- package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
- package/dist/{chunk-PLKFO2HJ.js → chunk-JL43BVDE.js} +2 -2
- package/dist/{chunk-2S72LWKX.js → chunk-JYL3BVVZ.js} +2 -2
- package/dist/{chunk-IPLYGWQF.js → chunk-KQAFEZQX.js} +5 -5
- package/dist/chunk-LXY5VVGM.js +61 -0
- package/dist/chunk-LXY5VVGM.js.map +1 -0
- package/dist/{chunk-PYKKWQZ2.js → chunk-M7BXDKPR.js} +6 -6
- package/dist/{chunk-Z54EM5YZ.js → chunk-MAXVH44E.js} +7 -7
- package/dist/{chunk-LYMNZZIM.js → chunk-NIS256NW.js} +2 -2
- package/dist/{chunk-67QG75MJ.js → chunk-NLK2VZYZ.js} +2 -2
- package/dist/{chunk-A4QMKFFO.js → chunk-O5FFBO2M.js} +14 -28
- package/dist/chunk-O5FFBO2M.js.map +1 -0
- package/dist/{chunk-JYIFGGEM.js → chunk-OUKBMQ2O.js} +2 -2
- package/dist/{chunk-75XOWNRS.js → chunk-OYESWJ43.js} +4 -4
- package/dist/{chunk-TS4AJ2GI.js → chunk-PA45XCUD.js} +3 -3
- package/dist/{chunk-BJYET5TX.js → chunk-PRDJMOST.js} +7 -1
- package/dist/chunk-PRDJMOST.js.map +1 -0
- package/dist/{chunk-4MBPIL4V.js → chunk-PUSLMZBQ.js} +2 -2
- package/dist/{chunk-RVO6TCTU.js → chunk-QCECHYZL.js} +6 -6
- package/dist/{chunk-GFIKG4FR.js → chunk-QGR6OVGT.js} +2 -2
- package/dist/{chunk-NSGU76YO.js → chunk-QHFV6VNU.js} +6 -2
- package/dist/chunk-QHFV6VNU.js.map +1 -0
- package/dist/{chunk-PDEKQWDV.js → chunk-QT2FRYI4.js} +18 -2
- package/dist/chunk-QT2FRYI4.js.map +1 -0
- package/dist/{chunk-HNZKVLAN.js → chunk-S354QO4R.js} +2 -2
- package/dist/{chunk-NO37MR45.js → chunk-SOEN6KCF.js} +2 -2
- package/dist/{chunk-FXX6RQYC.js → chunk-TPTECFWR.js} +2 -2
- package/dist/{chunk-GDDGZVFI.js → chunk-UEVLYVNW.js} +2 -2
- package/dist/{chunk-YQ4IQ2GA.js → chunk-UT5BV5OG.js} +2 -2
- package/dist/{chunk-LAQ33B5D.js → chunk-UU3G6GUP.js} +1 -1
- package/dist/chunk-UU3G6GUP.js.map +1 -0
- package/dist/{chunk-LNTUXKBT.js → chunk-UYYW2KPU.js} +2 -2
- package/dist/{chunk-BOFVT6P3.js → chunk-VBHPOWBJ.js} +175 -98
- package/dist/chunk-VBHPOWBJ.js.map +1 -0
- package/dist/{chunk-QSYL5CXK.js → chunk-VKXWK44Q.js} +3 -3
- package/dist/{chunk-KMWYZTIE.js → chunk-VMMQDEN5.js} +2 -2
- package/dist/{chunk-JB6D7EFB.js → chunk-VVG2BOKA.js} +2 -2
- package/dist/chunk-W5BIQOAJ.js +69 -0
- package/dist/chunk-W5BIQOAJ.js.map +1 -0
- package/dist/{chunk-V5H6OJCR.js → chunk-W5XVQFLM.js} +2 -2
- package/dist/{chunk-OIOZ6MOP.js → chunk-W6FNX2LP.js} +2 -2
- package/dist/{chunk-6QPHF6RR.js → chunk-WQM326GS.js} +2 -2
- package/dist/{chunk-TQFHQPWD.js → chunk-XADV5IZ3.js} +8 -8
- package/dist/{chunk-DATDLJAO.js → chunk-XAPMEU3K.js} +2 -2
- package/dist/chunk-XXSOVESA.js +42 -0
- package/dist/chunk-XXSOVESA.js.map +1 -0
- package/dist/{chunk-IK6X5QPO.js → chunk-ZHY5TMJG.js} +1193 -443
- package/dist/chunk-ZHY5TMJG.js.map +1 -0
- package/dist/{chunk-N3LL5NEU.js → chunk-ZMYDYNI4.js} +2 -2
- package/dist/{cli-D4vvtxjY.d.ts → cli-CnOkH9hA.d.ts} +6 -6
- package/dist/cli.d.ts +15 -13
- package/dist/cli.js +74 -70
- package/dist/coding/pre-action-gate.d.ts +3 -1
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.d.ts +4 -2
- package/dist/compounding/engine.js +10 -9
- package/dist/compounding/preference-consolidator.d.ts +3 -1
- package/dist/compression-optimizer.d.ts +3 -1
- package/dist/config.d.ts +3 -1
- package/dist/config.js +5 -3
- package/dist/connectors/codex-materialize-runner.d.ts +3 -1
- package/dist/connectors/codex-materialize-runner.js +10 -9
- package/dist/connectors/codex-materialize.d.ts +3 -1
- package/dist/connectors/index.d.ts +3 -1
- package/dist/connectors/index.js +10 -9
- package/dist/consolidation-provenance-check.d.ts +4 -2
- package/dist/consolidation-undo.d.ts +4 -2
- package/dist/contradiction/index.d.ts +4 -2
- package/dist/contradiction-config.d.ts +29 -0
- package/dist/contradiction-config.js +12 -0
- package/dist/contradiction-config.js.map +1 -0
- package/dist/converge-config.d.ts +3 -1
- package/dist/convergence-refresh.d.ts +4 -2
- package/dist/convergence-refresh.js +12 -11
- package/dist/conversation-index/backend.d.ts +3 -1
- package/dist/conversation-index/chunker.d.ts +3 -1
- package/dist/conversation-index/faiss-adapter.d.ts +3 -1
- package/dist/conversation-index/indexer.d.ts +3 -1
- package/dist/conversation-index/search.d.ts +3 -1
- package/dist/corpus-watermark.d.ts +3 -1
- package/dist/corpus-watermark.js +21 -20
- package/dist/day-summary.d.ts +3 -1
- package/dist/delinearize.d.ts +3 -1
- package/dist/dependency-propagation-config.d.ts +3 -1
- package/dist/direct-answer-wiring.d.ts +3 -1
- package/dist/direct-answer.d.ts +3 -1
- package/dist/embedding-fallback.d.ts +3 -1
- package/dist/enrichment/index.d.ts +3 -1
- package/dist/entity-retrieval.d.ts +4 -2
- package/dist/entity-retrieval.js +10 -9
- package/dist/entity-schema.d.ts +3 -1
- package/dist/explicit-capture.d.ts +11 -9
- package/dist/explicit-capture.js +3 -2
- package/dist/external-wiki-access.d.ts +13 -11
- package/dist/external-wiki-access.js +42 -40
- package/dist/external-wiki-collection-registration.d.ts +3 -1
- package/dist/external-wiki-collection.d.ts +3 -1
- package/dist/external-wiki-mcp-tools.d.ts +13 -11
- package/dist/extraction-error-classification.d.ts +3 -1
- package/dist/extraction-faithfulness.d.ts +3 -1
- package/dist/extraction-judge-telemetry.d.ts +3 -1
- package/dist/extraction-judge-training.d.ts +3 -1
- package/dist/extraction-judge.d.ts +3 -1
- package/dist/extraction-liveness.d.ts +3 -1
- package/dist/extraction-normalization.d.ts +3 -1
- package/dist/extraction-prompt.d.ts +3 -1
- package/dist/extraction-source-grounding-rules.d.ts +3 -1
- package/dist/extraction-source-grounding.d.ts +3 -1
- package/dist/extraction.d.ts +3 -1
- package/dist/extraction.js +22 -21
- package/dist/fallback-llm.d.ts +3 -1
- package/dist/graph-444E-DrF.d.ts +349 -0
- package/dist/graph-dashboard-diff.d.ts +4 -2
- package/dist/graph-dashboard-key.d.ts +4 -2
- package/dist/graph-dashboard-parser.d.ts +4 -2
- package/dist/{graph-edge-decay-Z6ENALU2.js → graph-edge-decay-65KMOJWY.js} +4 -3
- package/dist/graph-edge-decay-65KMOJWY.js.map +1 -0
- package/dist/graph-edge-reinforcement.d.ts +4 -2
- package/dist/graph-path-reconstruction.d.ts +10 -0
- package/dist/graph-path-reconstruction.js +12 -0
- package/dist/graph-path-reconstruction.js.map +1 -0
- package/dist/graph-path-scoring-config.d.ts +8 -0
- package/dist/graph-path-scoring-config.js +10 -0
- package/dist/graph-path-scoring-config.js.map +1 -0
- package/dist/graph-path-scoring.d.ts +36 -0
- package/dist/graph-path-scoring.js +11 -0
- package/dist/graph-path-scoring.js.map +1 -0
- package/dist/graph-snapshot.d.ts +4 -2
- package/dist/graph-snapshot.js +3 -2
- package/dist/graph.d.ts +5 -318
- package/dist/graph.js +5 -1
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/importance.d.ts +3 -1
- package/dist/importers/index.d.ts +3 -1
- package/dist/in-flight-reads.d.ts +3 -1
- package/dist/index.d.ts +696 -694
- package/dist/index.js +149 -144
- package/dist/intent.d.ts +3 -1
- package/dist/lcm/engine.d.ts +3 -1
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.d.ts +3 -1
- package/dist/lcm/index.js +6 -6
- package/dist/lcm/tools.d.ts +3 -1
- package/dist/lifecycle.d.ts +3 -1
- package/dist/live-connectors-runner.d.ts +3 -1
- package/dist/local-llm.d.ts +3 -1
- package/dist/local-model-endpoint.d.ts +3 -1
- package/dist/maintenance/memory-governance.d.ts +3 -1
- package/dist/maintenance/memory-governance.js +10 -9
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +4 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -9
- package/dist/maintenance/rebuild-memory-projection.d.ts +4 -2
- package/dist/maintenance/rebuild-memory-projection.js +11 -10
- package/dist/{maintenance-ClmUVpGI.d.ts → maintenance-CKwxpdRx.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +13 -11
- package/dist/memory-action-policy.d.ts +3 -1
- package/dist/memory-cache.d.ts +3 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +3 -1
- package/dist/memory-projection-store.d.ts +3 -1
- package/dist/memory-provenance.d.ts +3 -1
- package/dist/memory-snapshot.d.ts +3 -1
- package/dist/memory-worth-outcomes.d.ts +4 -2
- package/dist/models-json.d.ts +3 -1
- package/dist/namespaces/migrate.d.ts +5 -3
- package/dist/namespaces/migrate.js +20 -19
- package/dist/namespaces/principal.d.ts +3 -1
- package/dist/namespaces/search.d.ts +3 -1
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.d.ts +5 -3
- package/dist/namespaces/storage.js +10 -9
- package/dist/native-knowledge.d.ts +3 -1
- package/dist/offline-sync-impression-drain.d.ts +3 -1
- package/dist/operator-doctor-corpus.d.ts +3 -1
- package/dist/operator-doctor-corpus.js +22 -21
- package/dist/operator-doctor-replica.d.ts +3 -1
- package/dist/operator-toolkit.d.ts +6 -4
- package/dist/operator-toolkit.js +33 -29
- package/dist/orchestration/compression-guideline-coordinator.d.ts +4 -2
- package/dist/orchestration/maintenance.d.ts +6 -4
- package/dist/orchestration/maintenance.js +15 -14
- package/dist/{orchestrator-qJ07zI8H.d.ts → orchestrator-BshGam-o.d.ts} +45 -19
- package/dist/orchestrator.d.ts +11 -9
- package/dist/orchestrator.js +109 -104
- package/dist/patterns-cli.d.ts +3 -1
- package/dist/{pipeline-WFCxhHOn.d.ts → pipeline-D21Rs6gW.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +3 -1
- package/dist/proactive-contention.d.ts +3 -1
- package/dist/provenance.d.ts +3 -1
- package/dist/{qmd-5tzkQ44y.d.ts → qmd-Dhle1P4X.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +4 -2
- package/dist/qmd-recall-cache.d.ts +3 -1
- package/dist/qmd.d.ts +4 -2
- package/dist/qmd.js +4 -4
- package/dist/recall-concurrency-config.d.ts +3 -1
- package/dist/recall-disclosure-escalation.d.ts +3 -1
- package/dist/recall-explain-renderer.d.ts +3 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +3 -1
- package/dist/recall-planner-llm.d.ts +3 -1
- package/dist/recall-planner-llm.js +3 -3
- package/dist/recall-state.d.ts +7 -1
- package/dist/recall-state.js +1 -1
- package/dist/recall-tag-filter.d.ts +3 -1
- package/dist/recall-timings.d.ts +3 -1
- package/dist/recall-xray-cli.d.ts +3 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +3 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +7 -1
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/cursor.d.ts +3 -1
- package/dist/reconcile/manifest.d.ts +3 -1
- package/dist/reconcile/plan.d.ts +3 -1
- package/dist/replay/normalizers/chatgpt.d.ts +3 -1
- package/dist/replay/normalizers/claude.d.ts +3 -1
- package/dist/replay/normalizers/openclaw.d.ts +3 -1
- package/dist/replay/normalizers/shared.d.ts +3 -1
- package/dist/replay/runner.d.ts +3 -1
- package/dist/replay/types.d.ts +3 -1
- package/dist/replica-divergence.d.ts +3 -1
- package/dist/replica-peers-config.d.ts +3 -1
- package/dist/resolve-auth-token.d.ts +3 -1
- package/dist/resume-bundles.js +6 -4
- package/dist/retrieval-agents.d.ts +4 -2
- package/dist/retrieval-tiers.d.ts +3 -1
- package/dist/routing/engine.d.ts +3 -1
- package/dist/routing/store.d.ts +3 -1
- package/dist/salvage-envelope.d.ts +3 -1
- package/dist/schemas.d.ts +116 -116
- package/dist/{scope-profiles--vfXPl3X.d.ts → scope-profiles-NzGPDXLn.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +3 -1
- package/dist/search/factory.d.ts +3 -1
- package/dist/search/factory.js +6 -6
- package/dist/search/index.d.ts +3 -1
- package/dist/search/index.js +12 -12
- package/dist/search/lancedb-backend.d.ts +3 -1
- package/dist/search/meilisearch-backend.d.ts +3 -1
- package/dist/search/noop-backend.d.ts +3 -1
- package/dist/search/orama-backend.d.ts +3 -1
- package/dist/search/port.d.ts +3 -1
- package/dist/search/remote-backend.d.ts +3 -1
- package/dist/{semantic-consolidation-BBDuP9nJ.d.ts → semantic-consolidation-DLDKvyy5.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +4 -2
- package/dist/semantic-consolidation.js +11 -10
- package/dist/semantic-rule-promotion.js +10 -9
- package/dist/semantic-rule-verifier.d.ts +3 -1
- package/dist/semantic-rule-verifier.js +10 -9
- package/dist/{service-B8DtbpCM.d.ts → service-BIgVM7fL.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +3 -1
- package/dist/session-observer-state.d.ts +3 -1
- package/dist/shared-context/manager.d.ts +11 -9
- package/dist/signal.d.ts +3 -1
- package/dist/source-agent-qualifier.d.ts +3 -1
- package/dist/source-agent-qualifier.js +18 -17
- package/dist/{storage-BkYN1RIy.d.ts → storage-CVfGWqCI.d.ts} +7 -6
- package/dist/storage.d.ts +4 -2
- package/dist/storage.js +9 -8
- package/dist/summarizer.d.ts +3 -1
- package/dist/summary-snapshot.d.ts +3 -1
- package/dist/temporal-supersession.d.ts +4 -2
- package/dist/temporal-timeline-recall.d.ts +3 -1
- package/dist/temporal-validity.d.ts +3 -1
- package/dist/threading.d.ts +3 -1
- package/dist/tier-migration.d.ts +4 -2
- package/dist/tier-routing.d.ts +3 -1
- package/dist/topics.d.ts +3 -1
- package/dist/transcript.d.ts +3 -1
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +1 -1
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/types.d.ts +66 -66
- package/dist/trust-score-stage.d.ts +3 -1
- package/dist/trust-score.d.ts +3 -1
- package/dist/{types-u2s9j1uD.d.ts → types-CkiDUorT.d.ts} +10 -16
- package/dist/types.d.ts +3 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +3 -1
- package/dist/verified-recall.js +10 -9
- package/dist/write-envelope.d.ts +3 -1
- package/package.json +2 -2
- package/src/archive-mutation-version.ts +59 -0
- package/src/config.test.ts +45 -0
- package/src/config.ts +10 -30
- package/src/contradiction/contradiction.test.ts +86 -1
- package/src/contradiction-config.ts +92 -0
- package/src/graph-path-reconstruction.ts +91 -0
- package/src/graph-path-scoring-config.ts +55 -0
- package/src/graph-path-scoring.test.ts +238 -0
- package/src/graph-path-scoring.ts +77 -0
- package/src/graph.test.ts +225 -5
- package/src/graph.ts +72 -5
- package/src/orchestration/contradiction-linking-coordinator.ts +246 -76
- package/src/orchestration/contradiction-localization.integration.test.ts +1953 -0
- package/src/orchestration/extraction-anchor-snapshot.ts +83 -0
- package/src/orchestration/extraction-persist-deps.ts +8 -2
- package/src/orchestration/extraction-persist.ts +43 -34
- package/src/orchestration/graph-path-state-loader.test.ts +801 -0
- package/src/orchestration/graph-path-state-loader.ts +318 -0
- package/src/orchestration/graph-recall-coordinator.integration.test.ts +1163 -0
- package/src/orchestration/graph-recall-coordinator.ts +206 -29
- package/src/orchestration/graph-recall-seam.ts +19 -0
- package/src/orchestration/recall-internal-deps.ts +7 -13
- package/src/orchestration/recall-internal.ts +5 -0
- package/src/orchestration/recall-introspection.ts +9 -1
- package/src/orchestration/recall-search-pipeline.ts +8 -13
- package/src/orchestration/update-localization.test.ts +784 -0
- package/src/orchestration/update-localization.ts +173 -0
- package/src/orchestrator.ts +19 -18
- package/src/recall-state.test.ts +29 -1
- package/src/recall-state.ts +13 -0
- package/src/recall-xray-renderer.test.ts +45 -0
- package/src/recall-xray-renderer.ts +16 -0
- package/src/recall-xray.ts +13 -0
- package/src/storage/supersession-side-effects.ts +97 -0
- package/src/storage/tombstone-blocked-capture-sync.ts +13 -5
- package/src/storage-contract/cache-coherence.test.ts +299 -0
- package/src/storage.ts +86 -88
- package/src/types.ts +12 -15
- package/dist/chunk-A4QMKFFO.js.map +0 -1
- package/dist/chunk-BJYET5TX.js.map +0 -1
- package/dist/chunk-BOFVT6P3.js.map +0 -1
- package/dist/chunk-IK6X5QPO.js.map +0 -1
- package/dist/chunk-IXXABW5A.js.map +0 -1
- package/dist/chunk-L2DPF4DF.js.map +0 -1
- package/dist/chunk-LAQ33B5D.js.map +0 -1
- package/dist/chunk-NSGU76YO.js.map +0 -1
- package/dist/chunk-PDEKQWDV.js.map +0 -1
- /package/dist/{graph-edge-decay-Z6ENALU2.js.map → archive-mutation-version.js.map} +0 -0
- /package/dist/{chunk-KDG2KTWW.js.map → chunk-33SV3E43.js.map} +0 -0
- /package/dist/{chunk-2HGUDBRJ.js.map → chunk-37ZO6LLJ.js.map} +0 -0
- /package/dist/{chunk-QW3WFHKU.js.map → chunk-45BRYON6.js.map} +0 -0
- /package/dist/{chunk-GZICBRWK.js.map → chunk-4F6X3RXW.js.map} +0 -0
- /package/dist/{chunk-7BAYBYY5.js.map → chunk-4PC4PRJE.js.map} +0 -0
- /package/dist/{chunk-VJXY5ZCT.js.map → chunk-52UKFTUK.js.map} +0 -0
- /package/dist/{chunk-ESG7Z255.js.map → chunk-5UDMFE3Q.js.map} +0 -0
- /package/dist/{chunk-JPHEOE6Z.js.map → chunk-6MTSFX7A.js.map} +0 -0
- /package/dist/{chunk-M4MP34AQ.js.map → chunk-AHZOMHWN.js.map} +0 -0
- /package/dist/{chunk-Z3RHPGSY.js.map → chunk-AIUBBHSF.js.map} +0 -0
- /package/dist/{chunk-S3AYIQCU.js.map → chunk-AXIDQG5A.js.map} +0 -0
- /package/dist/{chunk-QN7NTKRC.js.map → chunk-AZ2QBGYB.js.map} +0 -0
- /package/dist/{chunk-47J4EDGR.js.map → chunk-AZM4CCAC.js.map} +0 -0
- /package/dist/{chunk-LEJPSMKM.js.map → chunk-C5QLI33R.js.map} +0 -0
- /package/dist/{chunk-SV3PSA7Y.js.map → chunk-CV6BBVYV.js.map} +0 -0
- /package/dist/{chunk-V7X767RT.js.map → chunk-DCZBSE4F.js.map} +0 -0
- /package/dist/{chunk-7PKFPLLZ.js.map → chunk-FXTYIA46.js.map} +0 -0
- /package/dist/{chunk-64BHFFKZ.js.map → chunk-HLYR2TQL.js.map} +0 -0
- /package/dist/{chunk-5C2HNJMO.js.map → chunk-J5LLX6IL.js.map} +0 -0
- /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-PLKFO2HJ.js.map → chunk-JL43BVDE.js.map} +0 -0
- /package/dist/{chunk-2S72LWKX.js.map → chunk-JYL3BVVZ.js.map} +0 -0
- /package/dist/{chunk-IPLYGWQF.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-PYKKWQZ2.js.map → chunk-M7BXDKPR.js.map} +0 -0
- /package/dist/{chunk-Z54EM5YZ.js.map → chunk-MAXVH44E.js.map} +0 -0
- /package/dist/{chunk-LYMNZZIM.js.map → chunk-NIS256NW.js.map} +0 -0
- /package/dist/{chunk-67QG75MJ.js.map → chunk-NLK2VZYZ.js.map} +0 -0
- /package/dist/{chunk-JYIFGGEM.js.map → chunk-OUKBMQ2O.js.map} +0 -0
- /package/dist/{chunk-75XOWNRS.js.map → chunk-OYESWJ43.js.map} +0 -0
- /package/dist/{chunk-TS4AJ2GI.js.map → chunk-PA45XCUD.js.map} +0 -0
- /package/dist/{chunk-4MBPIL4V.js.map → chunk-PUSLMZBQ.js.map} +0 -0
- /package/dist/{chunk-RVO6TCTU.js.map → chunk-QCECHYZL.js.map} +0 -0
- /package/dist/{chunk-GFIKG4FR.js.map → chunk-QGR6OVGT.js.map} +0 -0
- /package/dist/{chunk-HNZKVLAN.js.map → chunk-S354QO4R.js.map} +0 -0
- /package/dist/{chunk-NO37MR45.js.map → chunk-SOEN6KCF.js.map} +0 -0
- /package/dist/{chunk-FXX6RQYC.js.map → chunk-TPTECFWR.js.map} +0 -0
- /package/dist/{chunk-GDDGZVFI.js.map → chunk-UEVLYVNW.js.map} +0 -0
- /package/dist/{chunk-YQ4IQ2GA.js.map → chunk-UT5BV5OG.js.map} +0 -0
- /package/dist/{chunk-LNTUXKBT.js.map → chunk-UYYW2KPU.js.map} +0 -0
- /package/dist/{chunk-QSYL5CXK.js.map → chunk-VKXWK44Q.js.map} +0 -0
- /package/dist/{chunk-KMWYZTIE.js.map → chunk-VMMQDEN5.js.map} +0 -0
- /package/dist/{chunk-JB6D7EFB.js.map → chunk-VVG2BOKA.js.map} +0 -0
- /package/dist/{chunk-V5H6OJCR.js.map → chunk-W5XVQFLM.js.map} +0 -0
- /package/dist/{chunk-OIOZ6MOP.js.map → chunk-W6FNX2LP.js.map} +0 -0
- /package/dist/{chunk-6QPHF6RR.js.map → chunk-WQM326GS.js.map} +0 -0
- /package/dist/{chunk-TQFHQPWD.js.map → chunk-XADV5IZ3.js.map} +0 -0
- /package/dist/{chunk-DATDLJAO.js.map → chunk-XAPMEU3K.js.map} +0 -0
- /package/dist/{chunk-N3LL5NEU.js.map → chunk-ZMYDYNI4.js.map} +0 -0
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { lstat, mkdir, mkdtemp, realpath, rm, symlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import type { Dir, Dirent, Stats } from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
|
|
8
|
+
import { GraphPathStateLoader } from "./graph-path-state-loader.js";
|
|
9
|
+
import type { MemoryFile } from "../types.js";
|
|
10
|
+
import type { StorageManager } from "../index.js";
|
|
11
|
+
|
|
12
|
+
type LoaderInternals = {
|
|
13
|
+
archivePathIndexes?: Map<
|
|
14
|
+
string,
|
|
15
|
+
{ version: number; pathsByBasename: Map<string, string[]>; unavailable?: "oversized" }
|
|
16
|
+
>;
|
|
17
|
+
archivePathIndexBuilds?: Map<
|
|
18
|
+
string,
|
|
19
|
+
Promise<{ version: number; pathsByBasename: Map<string, string[]>; unavailable?: "oversized" } | null>
|
|
20
|
+
>;
|
|
21
|
+
buildArchivePathIndex: (
|
|
22
|
+
storageRoot: string,
|
|
23
|
+
version: number,
|
|
24
|
+
) => Promise<{ version: number; pathsByBasename: Map<string, string[]>; unavailable?: "oversized" } | null>;
|
|
25
|
+
openArchiveDirectory?: (directoryPath: string) => Promise<Dir>;
|
|
26
|
+
archiveLstat?: (filePath: string) => Promise<Stats>;
|
|
27
|
+
archiveRealpath?: (filePath: string) => Promise<string>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function memory(filePath: string, content: string, id = "node"): MemoryFile {
|
|
31
|
+
return {
|
|
32
|
+
path: filePath,
|
|
33
|
+
content,
|
|
34
|
+
frontmatter: {
|
|
35
|
+
id,
|
|
36
|
+
category: "fact",
|
|
37
|
+
created: "2026-01-01T00:00:00.000Z",
|
|
38
|
+
updated: "2026-01-01T00:00:00.000Z",
|
|
39
|
+
source: "test",
|
|
40
|
+
confidence: 1,
|
|
41
|
+
status: "archived",
|
|
42
|
+
},
|
|
43
|
+
} as MemoryFile;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function fakeStorage(
|
|
47
|
+
root: string,
|
|
48
|
+
archivePath: string,
|
|
49
|
+
result: MemoryFile,
|
|
50
|
+
archiveVersionRef: { value: number } = { value: 1 },
|
|
51
|
+
corpusVersionRef: { value: string } = { value: "hot:1:cold:1" },
|
|
52
|
+
): StorageManager {
|
|
53
|
+
return {
|
|
54
|
+
dir: root,
|
|
55
|
+
getArchiveMutationVersion: () => archiveVersionRef.value,
|
|
56
|
+
getCorpusScanVersion: async () => corpusVersionRef.value,
|
|
57
|
+
readMemoryByPath: async (filePath: string) =>
|
|
58
|
+
filePath === archivePath ? result : null,
|
|
59
|
+
} as unknown as StorageManager;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
test("does not return archive state when the memory id mismatches", async () => {
|
|
63
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-id-"));
|
|
64
|
+
try {
|
|
65
|
+
const archivePath = path.join(root, "archive", "2026-01-01", "node.md");
|
|
66
|
+
await mkdir(path.dirname(archivePath), { recursive: true });
|
|
67
|
+
await writeFile(archivePath, "archive", "utf8");
|
|
68
|
+
const storage = fakeStorage(
|
|
69
|
+
root,
|
|
70
|
+
archivePath,
|
|
71
|
+
memory(archivePath, "mismatch", "other"),
|
|
72
|
+
);
|
|
73
|
+
const loader = new GraphPathStateLoader();
|
|
74
|
+
|
|
75
|
+
assert.equal(await loader.readNode(storage, "node.md", null, true), null);
|
|
76
|
+
} finally {
|
|
77
|
+
await rm(root, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
test("skips a mismatched hot direct candidate before checking the active cold tier", async () => {
|
|
81
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-direct-id-"));
|
|
82
|
+
try {
|
|
83
|
+
const hotPath = path.join(root, "node.md");
|
|
84
|
+
const coldPath = path.join(root, "cold", "node.md");
|
|
85
|
+
await mkdir(path.dirname(coldPath), { recursive: true });
|
|
86
|
+
await writeFile(hotPath, "hot", "utf8");
|
|
87
|
+
await writeFile(coldPath, "cold", "utf8");
|
|
88
|
+
const hot = memory(hotPath, "wrong", "other");
|
|
89
|
+
hot.frontmatter.status = "active";
|
|
90
|
+
const cold = memory(coldPath, "right", "node");
|
|
91
|
+
cold.frontmatter.status = "active";
|
|
92
|
+
const storage = {
|
|
93
|
+
dir: root,
|
|
94
|
+
getArchiveMutationVersion: () => 1,
|
|
95
|
+
getCorpusScanVersion: async () => "hot:1:cold:1",
|
|
96
|
+
readMemoryByPath: async (filePath: string) =>
|
|
97
|
+
filePath === hotPath ? hot : filePath === coldPath ? cold : null,
|
|
98
|
+
} as unknown as StorageManager;
|
|
99
|
+
|
|
100
|
+
assert.equal((await new GraphPathStateLoader().readNode(storage, "node.md", null, true))?.content, "right");
|
|
101
|
+
} finally {
|
|
102
|
+
await rm(root, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
test("prefers an active cold direct path over an inactive hot duplicate", async () => {
|
|
106
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-duplicate-tier-"));
|
|
107
|
+
try {
|
|
108
|
+
const hotPath = path.join(root, "node.md");
|
|
109
|
+
const coldPath = path.join(root, "cold", "node.md");
|
|
110
|
+
await mkdir(path.dirname(coldPath), { recursive: true });
|
|
111
|
+
await writeFile(hotPath, "hot", "utf8");
|
|
112
|
+
await writeFile(coldPath, "cold", "utf8");
|
|
113
|
+
const hot = memory(hotPath, "inactive");
|
|
114
|
+
hot.frontmatter.status = "superseded";
|
|
115
|
+
const cold = memory(coldPath, "active");
|
|
116
|
+
cold.frontmatter.status = "active";
|
|
117
|
+
const storage = {
|
|
118
|
+
dir: root,
|
|
119
|
+
getArchiveMutationVersion: () => 1,
|
|
120
|
+
getCorpusScanVersion: async () => "hot:1:cold:1",
|
|
121
|
+
readMemoryByPath: async (filePath: string) =>
|
|
122
|
+
filePath === hotPath ? hot : filePath === coldPath ? cold : null,
|
|
123
|
+
} as unknown as StorageManager;
|
|
124
|
+
|
|
125
|
+
assert.equal((await new GraphPathStateLoader().readNode(storage, "node.md", null, true))?.content, "active");
|
|
126
|
+
} finally {
|
|
127
|
+
await rm(root, { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
test("prefers a canonically active cold copy over a raw-active archived direct duplicate", async () => {
|
|
131
|
+
for (const scenario of ["archivedAt", "archive-path"] as const) {
|
|
132
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-canonical-active-"));
|
|
133
|
+
try {
|
|
134
|
+
const relativeNodePath =
|
|
135
|
+
scenario === "archive-path" ? path.join("archive", "2026-01-01", "node.md") : "node.md";
|
|
136
|
+
const hotPath = path.join(root, relativeNodePath);
|
|
137
|
+
const coldPath = path.join(root, "cold", relativeNodePath);
|
|
138
|
+
await mkdir(path.dirname(hotPath), { recursive: true });
|
|
139
|
+
await mkdir(path.dirname(coldPath), { recursive: true });
|
|
140
|
+
await writeFile(hotPath, "hot", "utf8");
|
|
141
|
+
await writeFile(coldPath, "cold", "utf8");
|
|
142
|
+
const hot = memory(hotPath, "raw-active");
|
|
143
|
+
hot.frontmatter.status = "active";
|
|
144
|
+
if (scenario === "archivedAt") {
|
|
145
|
+
hot.frontmatter.archivedAt = "2026-01-01T01:00:00.000Z";
|
|
146
|
+
}
|
|
147
|
+
const cold = memory(coldPath, "canonical-active");
|
|
148
|
+
cold.frontmatter.status = "active";
|
|
149
|
+
const storage = {
|
|
150
|
+
dir: root,
|
|
151
|
+
getArchiveMutationVersion: () => 1,
|
|
152
|
+
getCorpusScanVersion: async () => "hot:1:cold:1",
|
|
153
|
+
readMemoryByPath: async (filePath: string) =>
|
|
154
|
+
filePath === hotPath ? hot : filePath === coldPath ? cold : null,
|
|
155
|
+
} as unknown as StorageManager;
|
|
156
|
+
|
|
157
|
+
assert.equal(
|
|
158
|
+
(await new GraphPathStateLoader().readNode(storage, relativeNodePath, null, true))?.content,
|
|
159
|
+
"canonical-active",
|
|
160
|
+
);
|
|
161
|
+
} finally {
|
|
162
|
+
await rm(root, { recursive: true, force: true });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("does not poison a shared archive build with a short caller deadline", async () => {
|
|
168
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-timeout-"));
|
|
169
|
+
try {
|
|
170
|
+
const archiveRoot = path.join(root, "archive");
|
|
171
|
+
const archivePath = path.join(archiveRoot, "node.md");
|
|
172
|
+
await mkdir(archiveRoot, { recursive: true });
|
|
173
|
+
await writeFile(archivePath, "archive", "utf8");
|
|
174
|
+
await Promise.all(
|
|
175
|
+
Array.from({ length: 4096 }, (_, index) =>
|
|
176
|
+
writeFile(path.join(archiveRoot, `noise-${index}.txt`), "noise", "utf8"),
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
const storage = fakeStorage(root, archivePath, memory(archivePath, "success"));
|
|
180
|
+
const loader = new GraphPathStateLoader();
|
|
181
|
+
|
|
182
|
+
const timedOut = loader.readNode(storage, "node.md", Date.now() + 1, true);
|
|
183
|
+
await Promise.resolve();
|
|
184
|
+
const successful = loader.readNode(storage, "node.md", Date.now() + 2_000, true);
|
|
185
|
+
const [timedOutResult, successfulResult] = await Promise.all([timedOut, successful]);
|
|
186
|
+
|
|
187
|
+
assert.equal(timedOutResult, null);
|
|
188
|
+
assert.equal(successfulResult?.content, "success");
|
|
189
|
+
} finally {
|
|
190
|
+
await rm(root, { recursive: true, force: true });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
test("ignores unrelated corpus version changes", async () => {
|
|
194
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-unrelated-"));
|
|
195
|
+
try {
|
|
196
|
+
const archivePath = path.join(root, "archive", "2026-01-01", "node.md");
|
|
197
|
+
const corpusVersionRef = { value: "hot:1:cold:1" };
|
|
198
|
+
const storage = fakeStorage(root, archivePath, memory(archivePath, "stable"), { value: 1 }, corpusVersionRef);
|
|
199
|
+
const loader = new GraphPathStateLoader();
|
|
200
|
+
const internals = loader as unknown as LoaderInternals;
|
|
201
|
+
let builds = 0;
|
|
202
|
+
internals.buildArchivePathIndex = async (_storageRoot, version) => {
|
|
203
|
+
builds += 1;
|
|
204
|
+
return { version, pathsByBasename: new Map([["node.md", [archivePath]]]) };
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
assert.equal((await loader.readNode(storage, "node.md", null, true))?.content, "stable");
|
|
208
|
+
corpusVersionRef.value = "hot:2:cold:3";
|
|
209
|
+
assert.equal((await loader.readNode(storage, "node.md", null, true))?.content, "stable");
|
|
210
|
+
assert.equal(builds, 1);
|
|
211
|
+
} finally {
|
|
212
|
+
await rm(root, { recursive: true, force: true });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("invalidates archive index when archive mutation version changes", async () => {
|
|
217
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-invalidate-"));
|
|
218
|
+
try {
|
|
219
|
+
const archivePath = path.join(root, "archive", "2026-01-01", "node.md");
|
|
220
|
+
const archiveVersionRef = { value: 1 };
|
|
221
|
+
const storage = fakeStorage(root, archivePath, memory(archivePath, "fresh"), archiveVersionRef);
|
|
222
|
+
const loader = new GraphPathStateLoader();
|
|
223
|
+
const internals = loader as unknown as LoaderInternals;
|
|
224
|
+
let builds = 0;
|
|
225
|
+
internals.buildArchivePathIndex = async (_storageRoot, version) => {
|
|
226
|
+
builds += 1;
|
|
227
|
+
return { version, pathsByBasename: new Map([["node.md", [archivePath]]]) };
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
assert.equal((await loader.readNode(storage, "node.md", null, true))?.content, "fresh");
|
|
231
|
+
archiveVersionRef.value = 2;
|
|
232
|
+
assert.equal((await loader.readNode(storage, "node.md", null, true))?.content, "fresh");
|
|
233
|
+
assert.equal(builds, 2);
|
|
234
|
+
} finally {
|
|
235
|
+
await rm(root, { recursive: true, force: true });
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
test("discards an archive index when generation changes during its build", async () => {
|
|
239
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-generation-race-"));
|
|
240
|
+
try {
|
|
241
|
+
const oldArchivePath = path.join(root, "archive", "2026-01-01", "node.md");
|
|
242
|
+
const newArchivePath = path.join(root, "archive", "2026-01-02", "node.md");
|
|
243
|
+
const archiveVersionRef = { value: 1 };
|
|
244
|
+
const storage = {
|
|
245
|
+
dir: root,
|
|
246
|
+
getArchiveMutationVersion: () => archiveVersionRef.value,
|
|
247
|
+
readMemoryByPath: async (filePath: string) => {
|
|
248
|
+
if (filePath === oldArchivePath) return memory(filePath, "stale");
|
|
249
|
+
if (filePath === newArchivePath) return memory(filePath, "fresh");
|
|
250
|
+
return null;
|
|
251
|
+
},
|
|
252
|
+
} as unknown as StorageManager;
|
|
253
|
+
const loader = new GraphPathStateLoader();
|
|
254
|
+
const internals = loader as unknown as LoaderInternals;
|
|
255
|
+
const started = Promise.withResolvers<void>();
|
|
256
|
+
const release = Promise.withResolvers<void>();
|
|
257
|
+
let builds = 0;
|
|
258
|
+
internals.buildArchivePathIndex = async (_storageRoot, version) => {
|
|
259
|
+
const build = builds++;
|
|
260
|
+
if (build === 0) {
|
|
261
|
+
started.resolve();
|
|
262
|
+
await release.promise;
|
|
263
|
+
}
|
|
264
|
+
const archivePath = version === 1 ? oldArchivePath : newArchivePath;
|
|
265
|
+
return { version, pathsByBasename: new Map([["node.md", [archivePath]]]) };
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const resultPromise = loader.readNode(storage, "node.md", null, true);
|
|
269
|
+
await started.promise;
|
|
270
|
+
archiveVersionRef.value = 2;
|
|
271
|
+
release.resolve();
|
|
272
|
+
|
|
273
|
+
assert.equal((await resultPromise)?.content, "fresh");
|
|
274
|
+
assert.equal(builds, 2);
|
|
275
|
+
assert.equal(internals.archivePathIndexes?.size, 1);
|
|
276
|
+
assert.equal([...internals.archivePathIndexes?.values() ?? []][0]?.version, 2);
|
|
277
|
+
} finally {
|
|
278
|
+
await rm(root, { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
test("bounds retries when archive generation changes during every build", async () => {
|
|
282
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-generation-churn-"));
|
|
283
|
+
try {
|
|
284
|
+
const archiveVersionRef = { value: 1 };
|
|
285
|
+
const storage = {
|
|
286
|
+
dir: root,
|
|
287
|
+
getArchiveMutationVersion: () => archiveVersionRef.value,
|
|
288
|
+
readMemoryByPath: async () => null,
|
|
289
|
+
} as unknown as StorageManager;
|
|
290
|
+
const loader = new GraphPathStateLoader();
|
|
291
|
+
const internals = loader as unknown as LoaderInternals;
|
|
292
|
+
let builds = 0;
|
|
293
|
+
internals.buildArchivePathIndex = async (_storageRoot, version) => {
|
|
294
|
+
builds += 1;
|
|
295
|
+
archiveVersionRef.value += 1;
|
|
296
|
+
return { version, pathsByBasename: new Map([["node.md", []]]) };
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
assert.equal(await loader.readNode(storage, "node.md", null, true), null);
|
|
300
|
+
assert.equal(builds, 2);
|
|
301
|
+
assert.equal(internals.archivePathIndexes?.size ?? 0, 0);
|
|
302
|
+
} finally {
|
|
303
|
+
await rm(root, { recursive: true, force: true });
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
test("discards an index when generation changes after build validation", async () => {
|
|
307
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-post-build-race-"));
|
|
308
|
+
try {
|
|
309
|
+
const oldArchivePath = path.join(root, "archive", "2026-01-01", "node.md");
|
|
310
|
+
const newArchivePath = path.join(root, "archive", "2026-01-02", "node.md");
|
|
311
|
+
const generations = [1, 1, 2, 2, 2, 2];
|
|
312
|
+
let generationReads = 0;
|
|
313
|
+
const storage = {
|
|
314
|
+
dir: root,
|
|
315
|
+
getArchiveMutationVersion: () =>
|
|
316
|
+
generations[Math.min(generationReads++, generations.length - 1)] ?? 2,
|
|
317
|
+
readMemoryByPath: async (filePath: string) => {
|
|
318
|
+
if (filePath === oldArchivePath) return memory(filePath, "stale");
|
|
319
|
+
if (filePath === newArchivePath) return memory(filePath, "fresh");
|
|
320
|
+
return null;
|
|
321
|
+
},
|
|
322
|
+
} as unknown as StorageManager;
|
|
323
|
+
const loader = new GraphPathStateLoader();
|
|
324
|
+
const internals = loader as unknown as LoaderInternals;
|
|
325
|
+
let builds = 0;
|
|
326
|
+
internals.buildArchivePathIndex = async (_storageRoot, version) => {
|
|
327
|
+
builds += 1;
|
|
328
|
+
const archivePath = version === 1 ? oldArchivePath : newArchivePath;
|
|
329
|
+
return { version, pathsByBasename: new Map([["node.md", [archivePath]]]) };
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
assert.equal((await loader.readNode(storage, "node.md", null, true))?.content, "fresh");
|
|
333
|
+
assert.equal(builds, 2);
|
|
334
|
+
assert.equal(internals.archivePathIndexes?.size, 1);
|
|
335
|
+
assert.equal([...internals.archivePathIndexes?.values() ?? []][0]?.version, 2);
|
|
336
|
+
} finally {
|
|
337
|
+
await rm(root, { recursive: true, force: true });
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("coalesces concurrent archive index builds for one key", async () => {
|
|
342
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-coalesce-"));
|
|
343
|
+
try {
|
|
344
|
+
const archivePath = path.join(root, "archive", "2026-01-01", "node.md");
|
|
345
|
+
const storage = fakeStorage(root, archivePath, memory(archivePath, "shared"));
|
|
346
|
+
const loader = new GraphPathStateLoader();
|
|
347
|
+
const internals = loader as unknown as LoaderInternals;
|
|
348
|
+
const started = Promise.withResolvers<void>();
|
|
349
|
+
const release = Promise.withResolvers<void>();
|
|
350
|
+
let builds = 0;
|
|
351
|
+
internals.buildArchivePathIndex = async (_storageRoot, version) => {
|
|
352
|
+
builds += 1;
|
|
353
|
+
started.resolve();
|
|
354
|
+
await release.promise;
|
|
355
|
+
return { version, pathsByBasename: new Map([["node.md", [archivePath]]]) };
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const first = loader.readNode(storage, "node.md", null, true);
|
|
359
|
+
await started.promise;
|
|
360
|
+
const second = loader.readNode(storage, "node.md", null, true);
|
|
361
|
+
release.resolve();
|
|
362
|
+
const [firstResult, secondResult] = await Promise.all([first, second]);
|
|
363
|
+
assert.equal(firstResult?.content, "shared");
|
|
364
|
+
assert.equal(secondResult?.content, "shared");
|
|
365
|
+
assert.equal(builds, 1);
|
|
366
|
+
} finally {
|
|
367
|
+
await rm(root, { recursive: true, force: true });
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test("evicts prior archive indexes for the same storage root", async () => {
|
|
372
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-cache-"));
|
|
373
|
+
try {
|
|
374
|
+
const archiveVersionRef = { value: 1 };
|
|
375
|
+
const archivePath = path.join(root, "archive", "2026-01-01", "node.md");
|
|
376
|
+
await mkdir(path.dirname(archivePath), { recursive: true });
|
|
377
|
+
const storage = fakeStorage(root, archivePath, memory(archivePath, "unused"), archiveVersionRef);
|
|
378
|
+
const loader = new GraphPathStateLoader();
|
|
379
|
+
const internals = loader as unknown as LoaderInternals;
|
|
380
|
+
|
|
381
|
+
await loader.readNode(storage, "node.md", null, true);
|
|
382
|
+
archiveVersionRef.value = 2;
|
|
383
|
+
await loader.readNode(storage, "node.md", null, true);
|
|
384
|
+
|
|
385
|
+
assert.equal(internals.archivePathIndexes?.size, 1);
|
|
386
|
+
} finally {
|
|
387
|
+
await rm(root, { recursive: true, force: true });
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
test("bounds archive indexes across distinct storage roots", async () => {
|
|
391
|
+
const loader = new GraphPathStateLoader();
|
|
392
|
+
const internals = loader as unknown as LoaderInternals;
|
|
393
|
+
const roots: string[] = [];
|
|
394
|
+
internals.buildArchivePathIndex = async (storageRoot, version) => ({
|
|
395
|
+
version,
|
|
396
|
+
pathsByBasename: new Map([
|
|
397
|
+
["node.md", [path.join(storageRoot, "archive", "node.md")]],
|
|
398
|
+
]),
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
for (let index = 0; index < 33; index += 1) {
|
|
403
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-bound-"));
|
|
404
|
+
roots.push(root);
|
|
405
|
+
const archivePath = path.join(root, "archive", "node.md");
|
|
406
|
+
const storage = fakeStorage(root, archivePath, memory(archivePath, String(index)));
|
|
407
|
+
const result = await loader.readNode(storage, "node.md", null, true);
|
|
408
|
+
assert.equal(result?.content, String(index));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
assert.equal(internals.archivePathIndexes?.size, 32);
|
|
412
|
+
} finally {
|
|
413
|
+
await Promise.all(roots.map((root) => rm(root, { recursive: true, force: true })));
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("bounds in-flight admission and coalesces same-key callers behind a full map", async () => {
|
|
418
|
+
const loader = new GraphPathStateLoader();
|
|
419
|
+
const internals = loader as unknown as LoaderInternals;
|
|
420
|
+
const roots: string[] = [];
|
|
421
|
+
const started = Array.from({ length: 34 }, () => Promise.withResolvers<void>());
|
|
422
|
+
const release = Array.from({ length: 34 }, () => Promise.withResolvers<void>());
|
|
423
|
+
let builds = 0;
|
|
424
|
+
internals.buildArchivePathIndex = async (storageRoot, version) => {
|
|
425
|
+
const buildIndex = builds++;
|
|
426
|
+
started[buildIndex]?.resolve();
|
|
427
|
+
await release[buildIndex]!.promise;
|
|
428
|
+
return {
|
|
429
|
+
version,
|
|
430
|
+
pathsByBasename: new Map([["node.md", [path.join(storageRoot, "archive", "node.md")]]]),
|
|
431
|
+
};
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
const requests: Array<Promise<MemoryFile | null>> = [];
|
|
436
|
+
for (let index = 0; index < 32; index += 1) {
|
|
437
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-flight-"));
|
|
438
|
+
roots.push(root);
|
|
439
|
+
const archivePath = path.join(root, "archive", "node.md");
|
|
440
|
+
requests.push(loader.readNode(fakeStorage(root, archivePath, memory(archivePath, String(index))), "node.md", null, true));
|
|
441
|
+
await started[index]!.promise;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const targetRoot = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-flight-target-"));
|
|
445
|
+
roots.push(targetRoot);
|
|
446
|
+
const targetPath = path.join(targetRoot, "archive", "node.md");
|
|
447
|
+
const targetStorage = fakeStorage(targetRoot, targetPath, memory(targetPath, "target"));
|
|
448
|
+
const firstTarget = loader.readNode(targetStorage, "node.md", null, true);
|
|
449
|
+
const secondTarget = loader.readNode(targetStorage, "node.md", null, true);
|
|
450
|
+
for (let tick = 0; tick < 4; tick += 1) await Promise.resolve();
|
|
451
|
+
assert.equal(builds, 32);
|
|
452
|
+
|
|
453
|
+
release[0]!.resolve();
|
|
454
|
+
await started[32]!.promise;
|
|
455
|
+
assert.equal(builds, 33);
|
|
456
|
+
release[32]!.resolve();
|
|
457
|
+
for (let index = 1; index < 34; index += 1) release[index]!.resolve();
|
|
458
|
+
const results = await Promise.all([...requests, firstTarget, secondTarget]);
|
|
459
|
+
assert.equal(results.at(-2)?.content, "target");
|
|
460
|
+
assert.equal(results.at(-1)?.content, "target");
|
|
461
|
+
} finally {
|
|
462
|
+
await Promise.all(roots.map((root) => rm(root, { recursive: true, force: true })));
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("rejects a symlinked configured storage root before scanning its target", async () => {
|
|
467
|
+
if (process.platform === "win32") return;
|
|
468
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-symlink-"));
|
|
469
|
+
try {
|
|
470
|
+
const target = path.join(root, "target");
|
|
471
|
+
const link = path.join(root, "link");
|
|
472
|
+
const directPath = path.join(target, "node.md");
|
|
473
|
+
const archivePath = path.join(target, "archive", "2026-01-01", "node.md");
|
|
474
|
+
await mkdir(path.dirname(archivePath), { recursive: true });
|
|
475
|
+
await writeFile(directPath, "direct", "utf8");
|
|
476
|
+
await writeFile(archivePath, "archive", "utf8");
|
|
477
|
+
await symlink(target, link);
|
|
478
|
+
|
|
479
|
+
let readCount = 0;
|
|
480
|
+
let versionCount = 0;
|
|
481
|
+
const storage = {
|
|
482
|
+
dir: link,
|
|
483
|
+
getCorpusScanVersion: async () => {
|
|
484
|
+
versionCount += 1;
|
|
485
|
+
return "v1";
|
|
486
|
+
},
|
|
487
|
+
readMemoryByPath: async () => {
|
|
488
|
+
readCount += 1;
|
|
489
|
+
return memory(directPath, "target");
|
|
490
|
+
},
|
|
491
|
+
} as unknown as StorageManager;
|
|
492
|
+
|
|
493
|
+
const loader = new GraphPathStateLoader();
|
|
494
|
+
|
|
495
|
+
assert.equal(await loader.readNode(storage, "node.md", null, true), null);
|
|
496
|
+
assert.equal(readCount, 0);
|
|
497
|
+
assert.equal(versionCount, 0);
|
|
498
|
+
} finally {
|
|
499
|
+
await rm(root, { recursive: true, force: true });
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
test("rejects a dangling symlinked configured storage root before scanning", async () => {
|
|
504
|
+
if (process.platform === "win32") return;
|
|
505
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-dangling-"));
|
|
506
|
+
try {
|
|
507
|
+
const link = path.join(root, "link");
|
|
508
|
+
await symlink(path.join(root, "missing-target"), link);
|
|
509
|
+
|
|
510
|
+
let readCount = 0;
|
|
511
|
+
let versionCount = 0;
|
|
512
|
+
const storage = {
|
|
513
|
+
dir: link,
|
|
514
|
+
getCorpusScanVersion: async () => {
|
|
515
|
+
versionCount += 1;
|
|
516
|
+
return "v1";
|
|
517
|
+
},
|
|
518
|
+
readMemoryByPath: async () => {
|
|
519
|
+
readCount += 1;
|
|
520
|
+
return memory(path.join(link, "node.md"), "unexpected");
|
|
521
|
+
},
|
|
522
|
+
} as unknown as StorageManager;
|
|
523
|
+
|
|
524
|
+
const loader = new GraphPathStateLoader();
|
|
525
|
+
|
|
526
|
+
assert.equal(await loader.readNode(storage, "node.md", null, true), null);
|
|
527
|
+
assert.equal(readCount, 0);
|
|
528
|
+
assert.equal(versionCount, 0);
|
|
529
|
+
} finally {
|
|
530
|
+
await rm(root, { recursive: true, force: true });
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
test("rejects a symlinked archive root before enumerating its target", async () => {
|
|
534
|
+
if (process.platform === "win32") return;
|
|
535
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-archive-symlink-"));
|
|
536
|
+
try {
|
|
537
|
+
const storageRoot = path.join(root, "storage");
|
|
538
|
+
const externalArchiveRoot = path.join(root, "external-archive");
|
|
539
|
+
const archiveRoot = path.join(storageRoot, "archive");
|
|
540
|
+
const externalArchivePath = path.join(externalArchiveRoot, "node.md");
|
|
541
|
+
await mkdir(storageRoot, { recursive: true });
|
|
542
|
+
await mkdir(externalArchiveRoot, { recursive: true });
|
|
543
|
+
await writeFile(externalArchivePath, "external", "utf8");
|
|
544
|
+
await symlink(externalArchiveRoot, archiveRoot);
|
|
545
|
+
|
|
546
|
+
const storage = {
|
|
547
|
+
dir: storageRoot,
|
|
548
|
+
getArchiveMutationVersion: () => 1,
|
|
549
|
+
readMemoryByPath: async (filePath: string) =>
|
|
550
|
+
filePath === externalArchivePath ? memory(externalArchivePath, "external") : null,
|
|
551
|
+
} as unknown as StorageManager;
|
|
552
|
+
const loader = new GraphPathStateLoader();
|
|
553
|
+
const internals = loader as unknown as LoaderInternals;
|
|
554
|
+
const buildArchivePathIndex = internals.buildArchivePathIndex;
|
|
555
|
+
let builds = 0;
|
|
556
|
+
internals.buildArchivePathIndex = async (rootPath, version) => {
|
|
557
|
+
builds += 1;
|
|
558
|
+
return buildArchivePathIndex.call(loader, rootPath, version);
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
assert.equal(await loader.readNode(storage, "node.md", null, true), null);
|
|
562
|
+
assert.equal(await loader.readNode(storage, "node.md", null, true), null);
|
|
563
|
+
assert.equal(builds, 1);
|
|
564
|
+
assert.equal(internals.archivePathIndexes?.size ?? 0, 1);
|
|
565
|
+
const cachedIndex = [...(internals.archivePathIndexes?.values() ?? [])][0];
|
|
566
|
+
assert.equal(cachedIndex?.pathsByBasename.size, 0);
|
|
567
|
+
} finally {
|
|
568
|
+
await rm(root, { recursive: true, force: true });
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("publishes an oversized sentinel after the visited-entry cap", async () => {
|
|
573
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-path-cap-"));
|
|
574
|
+
try {
|
|
575
|
+
const archiveRoot = path.join(root, "archive");
|
|
576
|
+
const deepRoot = path.join(archiveRoot, "deep");
|
|
577
|
+
await mkdir(deepRoot, { recursive: true });
|
|
578
|
+
const noiseCount = 100_001;
|
|
579
|
+
for (let start = 0; start < noiseCount; start += 1_000) {
|
|
580
|
+
const end = Math.min(start + 1_000, noiseCount);
|
|
581
|
+
await Promise.all(
|
|
582
|
+
Array.from({ length: end - start }, (_, offset) => {
|
|
583
|
+
const index = start + offset;
|
|
584
|
+
return writeFile(path.join(deepRoot, `noise-${index}.txt`), "noise", "utf8");
|
|
585
|
+
}),
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const storage = {
|
|
590
|
+
dir: root,
|
|
591
|
+
getArchiveMutationVersion: () => 1,
|
|
592
|
+
readMemoryByPath: async () => null,
|
|
593
|
+
} as unknown as StorageManager;
|
|
594
|
+
const loader = new GraphPathStateLoader();
|
|
595
|
+
const internals = loader as unknown as LoaderInternals;
|
|
596
|
+
|
|
597
|
+
assert.equal(await loader.readNode(storage, "missing.md", null, true), null);
|
|
598
|
+
assert.equal(internals.archivePathIndexes?.size ?? 0, 1);
|
|
599
|
+
assert.equal([...internals.archivePathIndexes?.values() ?? []][0]?.unavailable, "oversized");
|
|
600
|
+
} finally {
|
|
601
|
+
await rm(root, { recursive: true, force: true });
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
test("caches an oversized archive scan for one generation and retries after mutation", async () => {
|
|
605
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-cap-cache-"));
|
|
606
|
+
try {
|
|
607
|
+
const archiveRoot = path.join(root, "archive");
|
|
608
|
+
await mkdir(archiveRoot, { recursive: true });
|
|
609
|
+
const archiveVersionRef = { value: 1 };
|
|
610
|
+
const storage = {
|
|
611
|
+
dir: root,
|
|
612
|
+
getArchiveMutationVersion: () => archiveVersionRef.value,
|
|
613
|
+
readMemoryByPath: async () => null,
|
|
614
|
+
} as unknown as StorageManager;
|
|
615
|
+
const loader = new GraphPathStateLoader();
|
|
616
|
+
const internals = loader as unknown as LoaderInternals;
|
|
617
|
+
let openCalls = 0;
|
|
618
|
+
internals.openArchiveDirectory = async () => {
|
|
619
|
+
openCalls += 1;
|
|
620
|
+
return {
|
|
621
|
+
async *[Symbol.asyncIterator]() {
|
|
622
|
+
for (let index = 0; index < 100_000; index += 1) {
|
|
623
|
+
yield {
|
|
624
|
+
name: `noise-${index}.txt`,
|
|
625
|
+
isSymbolicLink: () => false,
|
|
626
|
+
isDirectory: () => false,
|
|
627
|
+
isFile: () => false,
|
|
628
|
+
} as Dirent;
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
} as unknown as Dir;
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
assert.equal(await loader.readNode(storage, "missing.md", null, true), null);
|
|
635
|
+
assert.equal(await loader.readNode(storage, "missing.md", null, true), null);
|
|
636
|
+
assert.equal(openCalls, 1);
|
|
637
|
+
assert.equal([...internals.archivePathIndexes?.values() ?? []][0]?.unavailable, "oversized");
|
|
638
|
+
|
|
639
|
+
archiveVersionRef.value = 2;
|
|
640
|
+
assert.equal(await loader.readNode(storage, "missing.md", null, true), null);
|
|
641
|
+
assert.equal(openCalls, 2);
|
|
642
|
+
} finally {
|
|
643
|
+
await rm(root, { recursive: true, force: true });
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
test("skips a vanished nested archive directory but retries unknown opener failures", async () => {
|
|
647
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-opendir-race-"));
|
|
648
|
+
try {
|
|
649
|
+
const archiveRoot = path.join(root, "archive");
|
|
650
|
+
const vanishedRoot = path.join(archiveRoot, "vanished");
|
|
651
|
+
const validRoot = path.join(archiveRoot, "valid");
|
|
652
|
+
const validPath = path.join(validRoot, "node.md");
|
|
653
|
+
await mkdir(validRoot, { recursive: true });
|
|
654
|
+
await writeFile(validPath, "archive", "utf8");
|
|
655
|
+
const storage = {
|
|
656
|
+
dir: root,
|
|
657
|
+
getArchiveMutationVersion: () => 1,
|
|
658
|
+
readMemoryByPath: async (filePath: string) =>
|
|
659
|
+
filePath === validPath ? memory(filePath, "valid") : null,
|
|
660
|
+
} as unknown as StorageManager;
|
|
661
|
+
const loader = new GraphPathStateLoader();
|
|
662
|
+
const internals = loader as unknown as LoaderInternals;
|
|
663
|
+
const directory = (entries: Dirent[]): Dir =>
|
|
664
|
+
({
|
|
665
|
+
async *[Symbol.asyncIterator]() {
|
|
666
|
+
yield* entries;
|
|
667
|
+
},
|
|
668
|
+
}) as unknown as Dir;
|
|
669
|
+
internals.archiveLstat = async (filePath) =>
|
|
670
|
+
filePath === vanishedRoot
|
|
671
|
+
? ({ isDirectory: () => true, isSymbolicLink: () => false } as Stats)
|
|
672
|
+
: lstat(filePath);
|
|
673
|
+
internals.archiveRealpath = async (filePath) =>
|
|
674
|
+
filePath === vanishedRoot ? filePath : realpath(filePath);
|
|
675
|
+
internals.openArchiveDirectory = async (directoryPath) => {
|
|
676
|
+
if (directoryPath === archiveRoot) {
|
|
677
|
+
return directory([
|
|
678
|
+
{
|
|
679
|
+
name: "vanished",
|
|
680
|
+
isSymbolicLink: () => false,
|
|
681
|
+
isDirectory: () => true,
|
|
682
|
+
isFile: () => false,
|
|
683
|
+
} as Dirent,
|
|
684
|
+
{
|
|
685
|
+
name: "valid",
|
|
686
|
+
isSymbolicLink: () => false,
|
|
687
|
+
isDirectory: () => true,
|
|
688
|
+
isFile: () => false,
|
|
689
|
+
} as Dirent,
|
|
690
|
+
]);
|
|
691
|
+
}
|
|
692
|
+
if (directoryPath === vanishedRoot) {
|
|
693
|
+
throw Object.assign(new Error("vanished directory"), { code: "ENOENT" });
|
|
694
|
+
}
|
|
695
|
+
return directory([
|
|
696
|
+
{
|
|
697
|
+
name: "node.md",
|
|
698
|
+
isSymbolicLink: () => false,
|
|
699
|
+
isDirectory: () => false,
|
|
700
|
+
isFile: () => true,
|
|
701
|
+
} as Dirent,
|
|
702
|
+
]);
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
assert.equal((await loader.readNode(storage, "node.md", null, true))?.content, "valid");
|
|
706
|
+
assert.equal(internals.archivePathIndexes?.size, 1);
|
|
707
|
+
|
|
708
|
+
const unknownFailureLoader = new GraphPathStateLoader();
|
|
709
|
+
const unknownInternals = unknownFailureLoader as unknown as LoaderInternals;
|
|
710
|
+
let unknownOpenCalls = 0;
|
|
711
|
+
unknownInternals.openArchiveDirectory = async () => {
|
|
712
|
+
unknownOpenCalls += 1;
|
|
713
|
+
throw Object.assign(new Error("unknown opener failure"), { code: "EIO" });
|
|
714
|
+
};
|
|
715
|
+
assert.equal(await unknownFailureLoader.readNode(storage, "missing.md", null, true), null);
|
|
716
|
+
assert.equal(await unknownFailureLoader.readNode(storage, "missing.md", null, true), null);
|
|
717
|
+
assert.equal(unknownOpenCalls, 2);
|
|
718
|
+
assert.equal(unknownInternals.archivePathIndexes?.size ?? 0, 0);
|
|
719
|
+
} finally {
|
|
720
|
+
await rm(root, { recursive: true, force: true });
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
test("aborts archive build on async iterator errors without publishing partial paths", async () => {
|
|
725
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-loader-iterator-error-"));
|
|
726
|
+
try {
|
|
727
|
+
const archiveRoot = path.join(root, "archive");
|
|
728
|
+
const archivePath = path.join(archiveRoot, "node.md");
|
|
729
|
+
await mkdir(archiveRoot, { recursive: true });
|
|
730
|
+
await writeFile(archivePath, "archive", "utf8");
|
|
731
|
+
const storage = fakeStorage(root, archivePath, memory(archivePath, "partial"));
|
|
732
|
+
const loader = new GraphPathStateLoader();
|
|
733
|
+
const internals = loader as unknown as LoaderInternals;
|
|
734
|
+
let openCalls = 0;
|
|
735
|
+
internals.openArchiveDirectory = async () => {
|
|
736
|
+
openCalls += 1;
|
|
737
|
+
return {
|
|
738
|
+
async *[Symbol.asyncIterator]() {
|
|
739
|
+
yield {
|
|
740
|
+
name: "node.md",
|
|
741
|
+
isSymbolicLink: () => false,
|
|
742
|
+
isDirectory: () => false,
|
|
743
|
+
isFile: () => true,
|
|
744
|
+
} as Dirent;
|
|
745
|
+
throw new Error("injected iterator error");
|
|
746
|
+
},
|
|
747
|
+
} as unknown as Dir;
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
assert.equal(await loader.readNode(storage, "node.md", null, true), null);
|
|
751
|
+
assert.equal(internals.archivePathIndexes?.size ?? 0, 0);
|
|
752
|
+
assert.equal(await loader.readNode(storage, "node.md", null, true), null);
|
|
753
|
+
assert.equal(openCalls, 2);
|
|
754
|
+
} finally {
|
|
755
|
+
await rm(root, { recursive: true, force: true });
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
test("does not cache partial indexes after injected archive lstat or realpath errors", async () => {
|
|
760
|
+
const failurePoints = ["root-lstat", "root-realpath", "nested-lstat", "nested-realpath", "file-realpath"] as const;
|
|
761
|
+
for (const failurePoint of failurePoints) {
|
|
762
|
+
const root = await mkdtemp(path.join(os.tmpdir(), `remnic-graph-loader-${failurePoint}-`));
|
|
763
|
+
try {
|
|
764
|
+
const archiveRoot = path.join(root, "archive");
|
|
765
|
+
const nestedRoot = path.join(archiveRoot, "nested");
|
|
766
|
+
const archivePath =
|
|
767
|
+
failurePoint === "nested-lstat" || failurePoint === "nested-realpath"
|
|
768
|
+
? path.join(nestedRoot, "node.md")
|
|
769
|
+
: path.join(archiveRoot, "node.md");
|
|
770
|
+
await mkdir(failurePoint.startsWith("nested") ? nestedRoot : archiveRoot, { recursive: true });
|
|
771
|
+
await writeFile(archivePath, "archive", "utf8");
|
|
772
|
+
const storage = fakeStorage(root, archivePath, memory(archivePath, "partial"));
|
|
773
|
+
const loader = new GraphPathStateLoader();
|
|
774
|
+
const internals = loader as unknown as LoaderInternals;
|
|
775
|
+
internals.archiveLstat = async (filePath) => {
|
|
776
|
+
if (
|
|
777
|
+
(failurePoint === "root-lstat" && filePath === archiveRoot) ||
|
|
778
|
+
(failurePoint === "nested-lstat" && filePath === nestedRoot)
|
|
779
|
+
) {
|
|
780
|
+
throw Object.assign(new Error(`injected ${failurePoint}`), { code: "EIO" });
|
|
781
|
+
}
|
|
782
|
+
return lstat(filePath);
|
|
783
|
+
};
|
|
784
|
+
internals.archiveRealpath = async (filePath) => {
|
|
785
|
+
if (
|
|
786
|
+
(failurePoint === "root-realpath" && filePath === archiveRoot) ||
|
|
787
|
+
(failurePoint === "nested-realpath" && filePath === nestedRoot) ||
|
|
788
|
+
(failurePoint === "file-realpath" && filePath === archivePath)
|
|
789
|
+
) {
|
|
790
|
+
throw Object.assign(new Error(`injected ${failurePoint}`), { code: "EIO" });
|
|
791
|
+
}
|
|
792
|
+
return realpath(filePath);
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
assert.equal(await loader.readNode(storage, "node.md", null, true), null, failurePoint);
|
|
796
|
+
assert.equal(internals.archivePathIndexes?.size ?? 0, 0, failurePoint);
|
|
797
|
+
} finally {
|
|
798
|
+
await rm(root, { recursive: true, force: true });
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
});
|