@remnic/core 9.69.87 → 9.69.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.js +61 -55
- package/dist/access-authorization-probe.js +62 -56
- package/dist/access-boundary.js +61 -55
- package/dist/access-citation-namespace.js +2 -2
- package/dist/access-cli.js +140 -134
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +61 -55
- package/dist/access-http-briefing.js +62 -56
- package/dist/access-http-offline-stream.d.ts.map +1 -1
- package/dist/access-http-offline-stream.js +1 -1
- package/dist/access-http-query.js +63 -57
- package/dist/access-http.js +85 -79
- package/dist/access-identity-continuity-surface.js +61 -55
- package/dist/access-lcm-surface.js +61 -55
- package/dist/access-mcp-output-schemas.d.ts.map +1 -1
- package/dist/access-mcp-output-schemas.js +1 -1
- package/dist/access-mcp.js +74 -68
- package/dist/access-namespace-preflight.js +61 -55
- package/dist/access-observe-write-category.js +19 -13
- package/dist/access-observe-write-surface.js +61 -55
- package/dist/access-offline-manifest.js +15 -10
- package/dist/access-operations-batch.js +65 -59
- package/dist/access-operations.js +72 -66
- package/dist/access-recall-concurrency.js +61 -55
- package/dist/access-recall-response.js +61 -55
- package/dist/access-recall-surface.js +61 -55
- package/dist/access-schema.js +15 -7
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +61 -55
- package/dist/access-surface-catalog.js +63 -57
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +27 -22
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{auto-sync-6IPX2BAC.js → auto-sync-U5N3CB5Y.js} +7 -7
- package/dist/briefing.js +15 -15
- package/dist/buffer.js +7 -7
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/{capsule-merge-3MNURAQ3.js → capsule-merge-ZCXUPLNJ.js} +2 -2
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +18 -18
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-JE334SIA.js → chunk-22DYJLFN.js} +2 -2
- package/dist/{chunk-JGSQ6P4D.js → chunk-2DLWLNO4.js} +3 -3
- package/dist/{chunk-LS3XH3UE.js → chunk-2NAD4YCI.js} +2 -2
- package/dist/{chunk-SY5KDWGU.js → chunk-2WLMWP2T.js} +2 -2
- package/dist/{chunk-P6WFK4ZJ.js → chunk-3O5BB45T.js} +4 -4
- package/dist/{chunk-SOZUJCME.js → chunk-3SGTV7RD.js} +6 -6
- package/dist/{chunk-YITN5WXI.js → chunk-4KN2JLQV.js} +2 -2
- package/dist/{chunk-JKMHZF3Q.js → chunk-4MP7XR5J.js} +2 -2
- package/dist/chunk-4NSLQ7EU.js +137 -0
- package/dist/chunk-4NSLQ7EU.js.map +1 -0
- package/dist/{chunk-7DE2NOON.js → chunk-4O4XCBOS.js} +3 -3
- package/dist/{chunk-SLEAJ67D.js → chunk-4OQET73E.js} +4 -4
- package/dist/{chunk-GDYU6VF4.js → chunk-5AYUHSAU.js} +2 -2
- package/dist/{chunk-QWW2JDDX.js → chunk-5E6RYRYW.js} +6 -6
- package/dist/{chunk-ICZQG4XE.js → chunk-5FAGCOZD.js} +4 -4
- package/dist/{chunk-4IEOGIPB.js → chunk-5MNUBT7T.js} +2 -2
- package/dist/{chunk-CK5KOVHW.js → chunk-5R627MYY.js} +3 -2
- package/dist/{chunk-CK5KOVHW.js.map → chunk-5R627MYY.js.map} +1 -1
- package/dist/chunk-5X4MXNEU.js +64 -0
- package/dist/chunk-5X4MXNEU.js.map +1 -0
- package/dist/{chunk-52II7PB6.js → chunk-645WOU5I.js} +4 -4
- package/dist/{chunk-DUJYRVUD.js → chunk-6O2VUWV6.js} +3 -3
- package/dist/{chunk-CS52MFDV.js → chunk-6UQFJULG.js} +2 -2
- package/dist/chunk-75PT4N7Q.js +75 -0
- package/dist/chunk-75PT4N7Q.js.map +1 -0
- package/dist/{chunk-HESDNYRB.js → chunk-76M5OK7V.js} +2 -2
- package/dist/{chunk-4CDYONWO.js → chunk-A4VSOUCB.js} +3 -3
- package/dist/{chunk-HBBHL4YF.js → chunk-AE5U5NMS.js} +2 -2
- package/dist/{chunk-CUCWX3MO.js → chunk-AEHVZY3X.js} +2 -2
- package/dist/{chunk-DZ525SNQ.js → chunk-AEU4EQGV.js} +1 -1
- package/dist/{chunk-VLLD4TOB.js → chunk-AWG3TIAY.js} +2 -2
- package/dist/{chunk-72JNNJJJ.js → chunk-AXC4WSF3.js} +3 -3
- package/dist/{chunk-N4S4NUX5.js → chunk-B3O3IRZN.js} +1 -1
- package/dist/{chunk-FAN73COB.js → chunk-BGI4NUAO.js} +2 -2
- package/dist/{chunk-XTYX6V52.js → chunk-BJVO4H5C.js} +2 -2
- package/dist/{chunk-EHS5MJEF.js → chunk-BLVR4WYU.js} +3 -3
- package/dist/{chunk-KFCMMDHH.js → chunk-BOZJD5PK.js} +2 -2
- package/dist/{chunk-TM3RXL4G.js → chunk-BUQKSQXL.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-EVBI7S4J.js → chunk-C6U57KG4.js} +4 -4
- package/dist/{chunk-RB776UH3.js → chunk-C7V3KPKJ.js} +235 -93
- package/dist/chunk-C7V3KPKJ.js.map +1 -0
- package/dist/{chunk-VX6GCKBS.js → chunk-C7ZDKWPS.js} +4 -4
- package/dist/{chunk-ZI7QA4MK.js → chunk-CRXRZA3G.js} +2 -2
- package/dist/{chunk-VQNONC4E.js → chunk-CZJA2EQE.js} +2 -2
- package/dist/{chunk-M536WNTQ.js → chunk-D5BSQR63.js} +2 -2
- package/dist/{chunk-37S3IMYB.js → chunk-DGS34QSY.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/{chunk-3BTFBQLO.js → chunk-EFHNFTWB.js} +49 -3
- package/dist/chunk-EFHNFTWB.js.map +1 -0
- package/dist/{chunk-C3QXITIP.js → chunk-EQUD7FP4.js} +2 -2
- package/dist/chunk-ETUZFFPU.js +218 -0
- package/dist/chunk-ETUZFFPU.js.map +1 -0
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-EQ6YOIKO.js → chunk-FJLQXMTU.js} +3 -3
- package/dist/{chunk-SRZW7FNU.js → chunk-FKD656D2.js} +2 -2
- package/dist/{chunk-CLXTDOKV.js → chunk-FUNBJQYR.js} +2 -2
- package/dist/{chunk-CLSYE5OZ.js → chunk-G2STTRYB.js} +2 -2
- package/dist/{chunk-KP7SANCF.js → chunk-GWM6T5EQ.js} +2 -2
- package/dist/{chunk-UA5DX5C3.js → chunk-GZDTVYK2.js} +2 -2
- package/dist/{chunk-3GQC3NMB.js → chunk-H56VY4BY.js} +5 -5
- package/dist/{chunk-L6UZEAID.js → chunk-H5BJWPUI.js} +4 -4
- package/dist/{chunk-YSJTCOHR.js → chunk-HLB7GKGG.js} +2 -2
- package/dist/{chunk-PUKTBULQ.js → chunk-HWLIMQE2.js} +2 -2
- package/dist/{chunk-CWHV574S.js → chunk-IOSZHHJC.js} +6 -6
- package/dist/{chunk-P2WZHLUF.js → chunk-JVPWRNHA.js} +2 -2
- package/dist/{chunk-6ET2VII7.js → chunk-JYG2334B.js} +2 -2
- package/dist/chunk-K6T33R4Q.js +535 -0
- package/dist/chunk-K6T33R4Q.js.map +1 -0
- package/dist/{chunk-WIKO4SCR.js → chunk-LHRQBMJN.js} +2 -2
- package/dist/{chunk-K4J4E7WV.js → chunk-LO4UKPKX.js} +2 -2
- package/dist/{chunk-LF5IGOOD.js → chunk-LU7E2MXE.js} +17 -17
- package/dist/{chunk-RJLV4BKR.js → chunk-LV5H6FTN.js} +2 -2
- package/dist/{chunk-3C3J243X.js → chunk-LW3HKRL7.js} +3 -3
- package/dist/{chunk-4BZP2B4U.js → chunk-M6C7SOR6.js} +2 -2
- package/dist/{chunk-JN72EPOE.js → chunk-MAAL6OZV.js} +7 -7
- package/dist/chunk-MAAL6OZV.js.map +1 -0
- package/dist/{chunk-MY2PLXSM.js → chunk-MCTM4YPL.js} +2 -1
- package/dist/{chunk-MY2PLXSM.js.map → chunk-MCTM4YPL.js.map} +1 -1
- package/dist/{chunk-SPSRGEDQ.js → chunk-MXCAJKMA.js} +9 -9
- package/dist/{chunk-BMYZTZ5D.js → chunk-MY7B3HYC.js} +2 -2
- package/dist/{chunk-NB3LRY4T.js → chunk-N4PYB235.js} +2 -2
- package/dist/{chunk-S3YM4SZQ.js → chunk-N7QV3CMX.js} +2 -2
- package/dist/{chunk-AQK54Q7O.js → chunk-NYKIUQL6.js} +2 -2
- package/dist/{chunk-WQZZDN5M.js → chunk-OCFXVDBZ.js} +322 -376
- package/dist/chunk-OCFXVDBZ.js.map +1 -0
- package/dist/{chunk-NE6PHPLG.js → chunk-OK52IY3W.js} +10 -10
- package/dist/{chunk-6TT6HRE4.js → chunk-P2ODTYOF.js} +2 -2
- package/dist/{chunk-W4AOR52H.js → chunk-PEBHPCX2.js} +2 -2
- package/dist/{chunk-VYCOJKSX.js → chunk-PO7NXNGC.js} +2 -2
- package/dist/{chunk-G6SVZWPB.js → chunk-PZX3AQLF.js} +44 -1
- package/dist/chunk-PZX3AQLF.js.map +1 -0
- package/dist/{chunk-XAITGGX5.js → chunk-Q4AUIFUA.js} +4 -4
- package/dist/{chunk-MQJL6CDZ.js → chunk-QAW55ZHJ.js} +4 -4
- package/dist/{chunk-FOH7AMMI.js → chunk-QB57WIS6.js} +73 -82
- package/dist/chunk-QB57WIS6.js.map +1 -0
- package/dist/{chunk-QVS22WTO.js → chunk-QI7K7BZ5.js} +2 -2
- package/dist/{chunk-NE7I2RCT.js → chunk-R3X2T2LP.js} +2 -2
- package/dist/{chunk-S7GCT7NU.js → chunk-RBITPCMK.js} +8 -8
- package/dist/{chunk-YTQGGO3K.js → chunk-S2GE2K36.js} +81 -78
- package/dist/{chunk-YTQGGO3K.js.map → chunk-S2GE2K36.js.map} +1 -1
- package/dist/{chunk-BJRAELDH.js → chunk-SCXURCVT.js} +3 -3
- package/dist/{chunk-FRCESVV6.js → chunk-STEOT4DI.js} +19 -19
- package/dist/chunk-STEOT4DI.js.map +1 -0
- package/dist/{chunk-BFO3IR56.js → chunk-SWBC3UER.js} +2 -2
- package/dist/chunk-TYT4TEQV.js +45 -0
- package/dist/chunk-TYT4TEQV.js.map +1 -0
- package/dist/{chunk-YRLHEEQT.js → chunk-U6VC6RP3.js} +2 -2
- package/dist/chunk-UCPGOUDZ.js +726 -0
- package/dist/chunk-UCPGOUDZ.js.map +1 -0
- package/dist/{chunk-MOQPEFLN.js → chunk-UP75KL3L.js} +2 -2
- package/dist/{chunk-D5F7TKMH.js → chunk-V4JZCTMK.js} +2 -2
- package/dist/{chunk-QEV26L2F.js → chunk-VALKBYBN.js} +2 -2
- package/dist/{chunk-7LCGFUMB.js → chunk-VDX2J7OX.js} +4 -4
- package/dist/{chunk-TDYAIXHK.js → chunk-VVX5PZFS.js} +5 -5
- package/dist/{chunk-6NQ4LSMS.js → chunk-W3XXFPRI.js} +52 -52
- package/dist/{chunk-4IIMMZZK.js → chunk-WKY7N3L7.js} +10 -10
- package/dist/{chunk-VVDWZRZG.js → chunk-X5SHE2O3.js} +3 -3
- package/dist/{chunk-NR2ASHBL.js → chunk-XOV7NRY5.js} +2 -2
- package/dist/{chunk-6L3PBSAR.js → chunk-YJ7MW627.js} +2 -2
- package/dist/{chunk-LQG5O5EP.js → chunk-Z3O53WZR.js} +2 -2
- package/dist/{chunk-FB64XE3R.js → chunk-Z5LP6ILP.js} +2 -2
- package/dist/{chunk-VKQI47DJ.js → chunk-Z6EZCN6Z.js} +8 -8
- package/dist/{chunk-V2ES4WCZ.js → chunk-ZZZ4I455.js} +2 -2
- package/dist/cli.js +106 -100
- package/dist/coding/export-okf-codegraph.js +16 -16
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +16 -16
- package/dist/config.js +27 -22
- package/dist/connectors/codex-materialize-runner.js +15 -15
- package/dist/connectors/index.js +15 -15
- package/dist/console/state.d.ts +30 -0
- package/dist/console/state.d.ts.map +1 -1
- package/dist/contradiction/index.js +5 -5
- package/dist/convergence-refresh.js +17 -17
- package/dist/corpus-watermark.js +17 -17
- package/dist/dashboard-runtime.js +2 -2
- package/dist/deep-recall-http-glue.js +15 -8
- package/dist/embedding-fallback.d.ts +41 -31
- package/dist/embedding-fallback.d.ts.map +1 -1
- package/dist/embedding-fallback.js +9 -1
- package/dist/embedding-generation-lock.d.ts +49 -0
- package/dist/embedding-generation-lock.d.ts.map +1 -0
- package/dist/embedding-generation-lock.js +19 -0
- package/dist/embedding-index-storage.d.ts +276 -0
- package/dist/embedding-index-storage.d.ts.map +1 -0
- package/dist/embedding-index-storage.js +28 -0
- package/dist/entity-retrieval.js +16 -16
- package/dist/event-order-recall.js +3 -3
- package/dist/explicit-capture.js +6 -6
- package/dist/external-wiki-access.js +62 -56
- package/dist/extraction.js +21 -21
- package/dist/{graph-edge-decay-KQGS3L3B.js → graph-edge-decay-MKIRZ762.js} +5 -5
- package/dist/graph-jsonl.js +3 -3
- package/dist/graph-snapshot.js +3 -3
- package/dist/graph.js +2 -2
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +207 -177
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +9 -9
- package/dist/local-llm.js +2 -2
- package/dist/location/index.js +18 -18
- package/dist/maintenance/memory-governance.js +15 -15
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -15
- package/dist/maintenance/rebuild-memory-projection.js +16 -16
- package/dist/memory-browse-operations.js +62 -56
- package/dist/memory-subject.js +3 -3
- package/dist/namespaces/migrate.js +16 -16
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.js +15 -15
- package/dist/offline-sync-embedding-generation.d.ts +207 -0
- package/dist/offline-sync-embedding-generation.d.ts.map +1 -0
- package/dist/offline-sync-embedding-generation.js +42 -0
- package/dist/offline-sync-exclude-globs.d.ts +15 -0
- package/dist/offline-sync-exclude-globs.d.ts.map +1 -1
- package/dist/offline-sync-exclude-globs.js +11 -3
- package/dist/offline-sync-file-io.d.ts +7 -0
- package/dist/offline-sync-file-io.d.ts.map +1 -1
- package/dist/offline-sync-file-io.js +14 -2
- package/dist/offline-sync-impression-drain.js +3 -3
- package/dist/offline-sync-runtime-state.d.ts +5 -0
- package/dist/offline-sync-runtime-state.d.ts.map +1 -0
- package/dist/offline-sync-runtime-state.js +14 -0
- package/dist/offline-sync-runtime-state.js.map +1 -0
- package/dist/offline-sync-snapshot-stream.d.ts +26 -0
- package/dist/offline-sync-snapshot-stream.d.ts.map +1 -0
- package/dist/offline-sync-snapshot-stream.js +28 -0
- package/dist/offline-sync-snapshot-stream.js.map +1 -0
- package/dist/offline-sync-upload-staging.d.ts +34 -0
- package/dist/offline-sync-upload-staging.d.ts.map +1 -0
- package/dist/offline-sync-upload-staging.js +26 -0
- package/dist/offline-sync-upload-staging.js.map +1 -0
- package/dist/offline-sync.d.ts +46 -27
- package/dist/offline-sync.d.ts.map +1 -1
- package/dist/offline-sync.js +21 -8
- package/dist/operator-doctor-corpus.js +18 -18
- package/dist/operator-toolkit.js +38 -33
- package/dist/orchestration/maintenance.js +26 -26
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +142 -136
- package/dist/recall-navigation-operations.js +62 -56
- package/dist/recall-navigation.js +61 -55
- package/dist/reconcile/manifest.js +4 -4
- package/dist/{resolution-DNX7FO7A.js → resolution-K4A3VPKZ.js} +6 -6
- package/dist/resolution-K4A3VPKZ.js.map +1 -0
- package/dist/resume-bundles.js +28 -23
- package/dist/salvage-envelope.js +5 -5
- package/dist/search/factory.js +3 -3
- package/dist/search/index.js +7 -7
- package/dist/secure-store/index.js +2 -2
- package/dist/secure-store/secure-fs.d.ts +14 -1
- package/dist/secure-store/secure-fs.d.ts.map +1 -1
- package/dist/semantic-consolidation.js +16 -16
- package/dist/semantic-rule-promotion.js +15 -15
- package/dist/semantic-rule-verifier.js +15 -15
- package/dist/source-agent-qualifier.js +18 -18
- package/dist/standup/build.js +15 -15
- package/dist/{state-ITXJKXRE.js → state-JB47KHYV.js} +2 -2
- package/dist/state-JB47KHYV.js.map +1 -0
- package/dist/storage.d.ts +9 -3
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +14 -14
- package/dist/subject-config.js +4 -4
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/index.js +64 -58
- package/dist/{trace-ZFVRH4GO.js → trace-LXXCMZJA.js} +3 -3
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/transfer/export-okf.js +16 -16
- package/dist/{tui-55UJMJX7.js → tui-2Z5WOV7L.js} +3 -3
- package/dist/tui-2Z5WOV7L.js.map +1 -0
- package/dist/verified-recall.js +15 -15
- package/dist/who-knows-cli.js +16 -16
- package/dist/who-knows.js +15 -15
- package/dist/write-envelope.js +4 -4
- package/package.json +2 -2
- package/src/access-http-offline-stream.ts +3 -0
- package/src/access-mcp-output-schemas.ts +1 -0
- package/src/access-service.ts +7 -18
- package/src/console/state.test.ts +56 -0
- package/src/console/state.ts +83 -0
- package/src/embedding-fallback.ts +325 -141
- package/src/embedding-generation-lock.ts +141 -0
- package/src/embedding-index-storage.test.ts +245 -0
- package/src/embedding-index-storage.ts +1068 -0
- package/src/index.ts +3 -3
- package/src/offline-sync-embedding-generation.test.ts +2173 -0
- package/src/offline-sync-embedding-generation.ts +885 -0
- package/src/offline-sync-exclude-globs.ts +112 -0
- package/src/offline-sync-file-io.ts +48 -0
- package/src/offline-sync-runtime-state.ts +48 -0
- package/src/offline-sync-snapshot-stream.ts +99 -0
- package/src/offline-sync-upload-staging.ts +291 -0
- package/src/offline-sync.test.ts +164 -1
- package/src/offline-sync.ts +458 -453
- package/src/orchestrator.ts +2 -1
- package/src/secure-store/secure-fs.ts +26 -6
- package/src/storage.ts +11 -6
- package/dist/chunk-3BTFBQLO.js.map +0 -1
- package/dist/chunk-AREGMX2J.js +0 -50
- package/dist/chunk-AREGMX2J.js.map +0 -1
- package/dist/chunk-FOH7AMMI.js.map +0 -1
- package/dist/chunk-FRCESVV6.js.map +0 -1
- package/dist/chunk-G6SVZWPB.js.map +0 -1
- package/dist/chunk-JN72EPOE.js.map +0 -1
- package/dist/chunk-RB776UH3.js.map +0 -1
- package/dist/chunk-WQZZDN5M.js.map +0 -1
- /package/dist/{auto-sync-6IPX2BAC.js.map → auto-sync-U5N3CB5Y.js.map} +0 -0
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
- /package/dist/{capsule-merge-3MNURAQ3.js.map → capsule-merge-ZCXUPLNJ.js.map} +0 -0
- /package/dist/{chunk-JE334SIA.js.map → chunk-22DYJLFN.js.map} +0 -0
- /package/dist/{chunk-JGSQ6P4D.js.map → chunk-2DLWLNO4.js.map} +0 -0
- /package/dist/{chunk-LS3XH3UE.js.map → chunk-2NAD4YCI.js.map} +0 -0
- /package/dist/{chunk-SY5KDWGU.js.map → chunk-2WLMWP2T.js.map} +0 -0
- /package/dist/{chunk-P6WFK4ZJ.js.map → chunk-3O5BB45T.js.map} +0 -0
- /package/dist/{chunk-SOZUJCME.js.map → chunk-3SGTV7RD.js.map} +0 -0
- /package/dist/{chunk-YITN5WXI.js.map → chunk-4KN2JLQV.js.map} +0 -0
- /package/dist/{chunk-JKMHZF3Q.js.map → chunk-4MP7XR5J.js.map} +0 -0
- /package/dist/{chunk-7DE2NOON.js.map → chunk-4O4XCBOS.js.map} +0 -0
- /package/dist/{chunk-SLEAJ67D.js.map → chunk-4OQET73E.js.map} +0 -0
- /package/dist/{chunk-GDYU6VF4.js.map → chunk-5AYUHSAU.js.map} +0 -0
- /package/dist/{chunk-QWW2JDDX.js.map → chunk-5E6RYRYW.js.map} +0 -0
- /package/dist/{chunk-ICZQG4XE.js.map → chunk-5FAGCOZD.js.map} +0 -0
- /package/dist/{chunk-4IEOGIPB.js.map → chunk-5MNUBT7T.js.map} +0 -0
- /package/dist/{chunk-52II7PB6.js.map → chunk-645WOU5I.js.map} +0 -0
- /package/dist/{chunk-DUJYRVUD.js.map → chunk-6O2VUWV6.js.map} +0 -0
- /package/dist/{chunk-CS52MFDV.js.map → chunk-6UQFJULG.js.map} +0 -0
- /package/dist/{chunk-HESDNYRB.js.map → chunk-76M5OK7V.js.map} +0 -0
- /package/dist/{chunk-4CDYONWO.js.map → chunk-A4VSOUCB.js.map} +0 -0
- /package/dist/{chunk-HBBHL4YF.js.map → chunk-AE5U5NMS.js.map} +0 -0
- /package/dist/{chunk-CUCWX3MO.js.map → chunk-AEHVZY3X.js.map} +0 -0
- /package/dist/{chunk-DZ525SNQ.js.map → chunk-AEU4EQGV.js.map} +0 -0
- /package/dist/{chunk-VLLD4TOB.js.map → chunk-AWG3TIAY.js.map} +0 -0
- /package/dist/{chunk-72JNNJJJ.js.map → chunk-AXC4WSF3.js.map} +0 -0
- /package/dist/{chunk-N4S4NUX5.js.map → chunk-B3O3IRZN.js.map} +0 -0
- /package/dist/{chunk-FAN73COB.js.map → chunk-BGI4NUAO.js.map} +0 -0
- /package/dist/{chunk-XTYX6V52.js.map → chunk-BJVO4H5C.js.map} +0 -0
- /package/dist/{chunk-EHS5MJEF.js.map → chunk-BLVR4WYU.js.map} +0 -0
- /package/dist/{chunk-KFCMMDHH.js.map → chunk-BOZJD5PK.js.map} +0 -0
- /package/dist/{chunk-TM3RXL4G.js.map → chunk-BUQKSQXL.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-EVBI7S4J.js.map → chunk-C6U57KG4.js.map} +0 -0
- /package/dist/{chunk-VX6GCKBS.js.map → chunk-C7ZDKWPS.js.map} +0 -0
- /package/dist/{chunk-ZI7QA4MK.js.map → chunk-CRXRZA3G.js.map} +0 -0
- /package/dist/{chunk-VQNONC4E.js.map → chunk-CZJA2EQE.js.map} +0 -0
- /package/dist/{chunk-M536WNTQ.js.map → chunk-D5BSQR63.js.map} +0 -0
- /package/dist/{chunk-37S3IMYB.js.map → chunk-DGS34QSY.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-C3QXITIP.js.map → chunk-EQUD7FP4.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-EQ6YOIKO.js.map → chunk-FJLQXMTU.js.map} +0 -0
- /package/dist/{chunk-SRZW7FNU.js.map → chunk-FKD656D2.js.map} +0 -0
- /package/dist/{chunk-CLXTDOKV.js.map → chunk-FUNBJQYR.js.map} +0 -0
- /package/dist/{chunk-CLSYE5OZ.js.map → chunk-G2STTRYB.js.map} +0 -0
- /package/dist/{chunk-KP7SANCF.js.map → chunk-GWM6T5EQ.js.map} +0 -0
- /package/dist/{chunk-UA5DX5C3.js.map → chunk-GZDTVYK2.js.map} +0 -0
- /package/dist/{chunk-3GQC3NMB.js.map → chunk-H56VY4BY.js.map} +0 -0
- /package/dist/{chunk-L6UZEAID.js.map → chunk-H5BJWPUI.js.map} +0 -0
- /package/dist/{chunk-YSJTCOHR.js.map → chunk-HLB7GKGG.js.map} +0 -0
- /package/dist/{chunk-PUKTBULQ.js.map → chunk-HWLIMQE2.js.map} +0 -0
- /package/dist/{chunk-CWHV574S.js.map → chunk-IOSZHHJC.js.map} +0 -0
- /package/dist/{chunk-P2WZHLUF.js.map → chunk-JVPWRNHA.js.map} +0 -0
- /package/dist/{chunk-6ET2VII7.js.map → chunk-JYG2334B.js.map} +0 -0
- /package/dist/{chunk-WIKO4SCR.js.map → chunk-LHRQBMJN.js.map} +0 -0
- /package/dist/{chunk-K4J4E7WV.js.map → chunk-LO4UKPKX.js.map} +0 -0
- /package/dist/{chunk-LF5IGOOD.js.map → chunk-LU7E2MXE.js.map} +0 -0
- /package/dist/{chunk-RJLV4BKR.js.map → chunk-LV5H6FTN.js.map} +0 -0
- /package/dist/{chunk-3C3J243X.js.map → chunk-LW3HKRL7.js.map} +0 -0
- /package/dist/{chunk-4BZP2B4U.js.map → chunk-M6C7SOR6.js.map} +0 -0
- /package/dist/{chunk-SPSRGEDQ.js.map → chunk-MXCAJKMA.js.map} +0 -0
- /package/dist/{chunk-BMYZTZ5D.js.map → chunk-MY7B3HYC.js.map} +0 -0
- /package/dist/{chunk-NB3LRY4T.js.map → chunk-N4PYB235.js.map} +0 -0
- /package/dist/{chunk-S3YM4SZQ.js.map → chunk-N7QV3CMX.js.map} +0 -0
- /package/dist/{chunk-AQK54Q7O.js.map → chunk-NYKIUQL6.js.map} +0 -0
- /package/dist/{chunk-NE6PHPLG.js.map → chunk-OK52IY3W.js.map} +0 -0
- /package/dist/{chunk-6TT6HRE4.js.map → chunk-P2ODTYOF.js.map} +0 -0
- /package/dist/{chunk-W4AOR52H.js.map → chunk-PEBHPCX2.js.map} +0 -0
- /package/dist/{chunk-VYCOJKSX.js.map → chunk-PO7NXNGC.js.map} +0 -0
- /package/dist/{chunk-XAITGGX5.js.map → chunk-Q4AUIFUA.js.map} +0 -0
- /package/dist/{chunk-MQJL6CDZ.js.map → chunk-QAW55ZHJ.js.map} +0 -0
- /package/dist/{chunk-QVS22WTO.js.map → chunk-QI7K7BZ5.js.map} +0 -0
- /package/dist/{chunk-NE7I2RCT.js.map → chunk-R3X2T2LP.js.map} +0 -0
- /package/dist/{chunk-S7GCT7NU.js.map → chunk-RBITPCMK.js.map} +0 -0
- /package/dist/{chunk-BJRAELDH.js.map → chunk-SCXURCVT.js.map} +0 -0
- /package/dist/{chunk-BFO3IR56.js.map → chunk-SWBC3UER.js.map} +0 -0
- /package/dist/{chunk-YRLHEEQT.js.map → chunk-U6VC6RP3.js.map} +0 -0
- /package/dist/{chunk-MOQPEFLN.js.map → chunk-UP75KL3L.js.map} +0 -0
- /package/dist/{chunk-D5F7TKMH.js.map → chunk-V4JZCTMK.js.map} +0 -0
- /package/dist/{chunk-QEV26L2F.js.map → chunk-VALKBYBN.js.map} +0 -0
- /package/dist/{chunk-7LCGFUMB.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-TDYAIXHK.js.map → chunk-VVX5PZFS.js.map} +0 -0
- /package/dist/{chunk-6NQ4LSMS.js.map → chunk-W3XXFPRI.js.map} +0 -0
- /package/dist/{chunk-4IIMMZZK.js.map → chunk-WKY7N3L7.js.map} +0 -0
- /package/dist/{chunk-VVDWZRZG.js.map → chunk-X5SHE2O3.js.map} +0 -0
- /package/dist/{chunk-NR2ASHBL.js.map → chunk-XOV7NRY5.js.map} +0 -0
- /package/dist/{chunk-6L3PBSAR.js.map → chunk-YJ7MW627.js.map} +0 -0
- /package/dist/{chunk-LQG5O5EP.js.map → chunk-Z3O53WZR.js.map} +0 -0
- /package/dist/{chunk-FB64XE3R.js.map → chunk-Z5LP6ILP.js.map} +0 -0
- /package/dist/{chunk-VKQI47DJ.js.map → chunk-Z6EZCN6Z.js.map} +0 -0
- /package/dist/{chunk-V2ES4WCZ.js.map → chunk-ZZZ4I455.js.map} +0 -0
- /package/dist/{graph-edge-decay-KQGS3L3B.js.map → embedding-generation-lock.js.map} +0 -0
- /package/dist/{resolution-DNX7FO7A.js.map → embedding-index-storage.js.map} +0 -0
- /package/dist/{state-ITXJKXRE.js.map → graph-edge-decay-MKIRZ762.js.map} +0 -0
- /package/dist/{tui-55UJMJX7.js.map → offline-sync-embedding-generation.js.map} +0 -0
- /package/dist/{trace-ZFVRH4GO.js.map → trace-LXXCMZJA.js.map} +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Cross-process embedding-generation mutation lock (issue #3148, Codex P1).
|
|
2
|
+
//
|
|
3
|
+
// Two processes legitimately mutate the SAME embedding generation: the
|
|
4
|
+
// daemon's EmbeddingFallback index mutations (load → modify → persist) and
|
|
5
|
+
// the offline-sync apply transaction (census → stage → swap) — plus the
|
|
6
|
+
// snapshot walkers that must capture one COMPLETE generation. All of them
|
|
7
|
+
// enumerate or replace `state/embeddings/` (or the legacy marker), so an
|
|
8
|
+
// unsynchronized writer's bytes are silently discarded by the other side's
|
|
9
|
+
// wholesale directory swap.
|
|
10
|
+
//
|
|
11
|
+
// Built directly on the existing utilities: `serializeMutations` (in-process
|
|
12
|
+
// keyed chain, rejection-recovering — gotcha #40) around `withHeldFileLock`
|
|
13
|
+
// (cross-process held advisory lock, replacement-safe stale breaking,
|
|
14
|
+
// ownership-checked release). A lock that cannot be acquired within the
|
|
15
|
+
// bounded wait FAILS CLOSED — proceeding unsynchronized would reintroduce
|
|
16
|
+
// the lost-write race this lock exists to prevent.
|
|
17
|
+
//
|
|
18
|
+
// The lock is keyed by the PHYSICAL state directory (dirname of the
|
|
19
|
+
// generation's shard dir / of the legacy marker), so every caller
|
|
20
|
+
// convention — memory-root-relative (`namespaces/<ns>/state`) or
|
|
21
|
+
// namespace-scoped roots (`<ns>/state`) — contends on the SAME lock file
|
|
22
|
+
// for the same generation. The lock file lives under that state dir's
|
|
23
|
+
// `.offline-sync/` (sync-internal, excluded from every offline-sync
|
|
24
|
+
// snapshot by the default excludes).
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import { type HeldFileLockController, serializeMutations, withHeldFileLock } from "./utils/serialize-mutations.js";
|
|
27
|
+
|
|
28
|
+
/** Stale-lock window for a crashed holder (mirrors the graph lock). */
|
|
29
|
+
const LOCK_STALE_MS = 30_000;
|
|
30
|
+
/** Bounded acquisition wait; a busy lock FAILS the operation (strict). */
|
|
31
|
+
const LOCK_MAX_WAIT_MS = 10_000;
|
|
32
|
+
|
|
33
|
+
/** Control surface handed to a held embedding-generation lock section. */
|
|
34
|
+
export type EmbeddingGenerationLockSection = HeldFileLockController;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A fence the store calls immediately before a destructive write: reasserts
|
|
38
|
+
* lock ownership so a peer that stale-broke and replaced the lock aborts
|
|
39
|
+
* this write instead of clobbering the peer's.
|
|
40
|
+
*/
|
|
41
|
+
export type EmbeddingGenerationFence = () => Promise<void>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A held embedding-generation lock section was lost mid-critical-section —
|
|
45
|
+
* a peer stale-broke and replaced it. Abort; never publish.
|
|
46
|
+
*/
|
|
47
|
+
export class EmbeddingGenerationLockLostError extends Error {
|
|
48
|
+
constructor(lockPath: string) {
|
|
49
|
+
super(
|
|
50
|
+
`embedding generation: the mutation lock for ${lockPath} was lost mid-section — a peer stale-broke and replaced it; aborting instead of clobbering the peer's write`
|
|
51
|
+
);
|
|
52
|
+
this.name = "EmbeddingGenerationLockLostError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The generation lock could not be acquired; callers fail closed and report it. */
|
|
57
|
+
export class EmbeddingGenerationLockUnavailableError extends Error {
|
|
58
|
+
constructor(lockPath: string, reason: "timeout" | "error") {
|
|
59
|
+
super(`embedding generation: could not acquire the mutation lock for ${lockPath} (${reason})`);
|
|
60
|
+
this.name = "EmbeddingGenerationLockUnavailableError";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The lock file path for the generation whose canonical state dir is
|
|
66
|
+
* `absoluteStateDir` (physical path; e.g. `<memoryRoot>/state` or
|
|
67
|
+
* `<memoryRoot>/namespaces/<ns>/state`). Keyed by the physical directory so
|
|
68
|
+
* every root convention contends on one lock file per generation.
|
|
69
|
+
*/
|
|
70
|
+
export function embeddingGenerationLockPath(absoluteStateDir: string): string {
|
|
71
|
+
const stateDir = path.resolve(absoluteStateDir);
|
|
72
|
+
return path.join(stateDir, ".offline-sync", "locks", "embedding-generation.lock");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Revalidate lock ownership immediately before a destructive publish.
|
|
77
|
+
* Throws {@link EmbeddingGenerationLockLostError} when the lock was
|
|
78
|
+
* broken/replaced, so the caller aborts and leaves the peer's write intact.
|
|
79
|
+
*/
|
|
80
|
+
export async function assertEmbeddingGenerationLockHeld(
|
|
81
|
+
lockPath: string,
|
|
82
|
+
section: EmbeddingGenerationLockSection
|
|
83
|
+
): Promise<void> {
|
|
84
|
+
if (!(await section.refresh())) throw new EmbeddingGenerationLockLostError(lockPath);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Run `fn` while holding the embedding-generation mutation lock for the
|
|
89
|
+
* generation whose state dir is `absoluteStateDir`, handing it the section
|
|
90
|
+
* controller. `serializeMutations` provides the in-process keyed chain
|
|
91
|
+
* INSIDE the cross-process advisory lock, so peers serialize against every
|
|
92
|
+
* section this process runs, one at a time. Times out fail-closed.
|
|
93
|
+
*/
|
|
94
|
+
export function withEmbeddingGenerationLock<T>(
|
|
95
|
+
absoluteStateDir: string,
|
|
96
|
+
fn: (lock: EmbeddingGenerationLockSection) => Promise<T>
|
|
97
|
+
): Promise<T> {
|
|
98
|
+
const lockPath = embeddingGenerationLockPath(absoluteStateDir);
|
|
99
|
+
return serializeMutations(lockPath, () =>
|
|
100
|
+
withHeldFileLock(lockPath, { staleMs: LOCK_STALE_MS, maxWaitMs: LOCK_MAX_WAIT_MS }, (acquired, lock) => {
|
|
101
|
+
if (!acquired) {
|
|
102
|
+
throw new EmbeddingGenerationLockUnavailableError(lockPath, lock.failure ?? "error");
|
|
103
|
+
}
|
|
104
|
+
return fn(lock);
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Capture a complete bounded digest census under lock, then stream outside
|
|
110
|
+
* the lock. If any member changes, throw rather than emit a complete subset. */
|
|
111
|
+
export async function* withEmbeddingGenerationLockIter<T extends { path: string; sha256: string; bytes: number }>(
|
|
112
|
+
absoluteStateDir: string,
|
|
113
|
+
make: (includeContent: boolean) => AsyncIterable<T>,
|
|
114
|
+
includeContent: boolean,
|
|
115
|
+
verify?: () => Promise<void>
|
|
116
|
+
): AsyncIterable<T> {
|
|
117
|
+
const lockPath = embeddingGenerationLockPath(absoluteStateDir);
|
|
118
|
+
// Capture only bounded metadata under the lock, never large content or
|
|
119
|
+
// network backpressure. Every emitted record must match this complete
|
|
120
|
+
// generation census; a concurrent swap/dirty write aborts the stream.
|
|
121
|
+
const baseline = await withEmbeddingGenerationLock(absoluteStateDir, async (section) => {
|
|
122
|
+
await verify?.();
|
|
123
|
+
const records: Array<{ path: string; sha256: string; bytes: number }> = [];
|
|
124
|
+
for await (const record of make(false)) {
|
|
125
|
+
await assertEmbeddingGenerationLockHeld(lockPath, section);
|
|
126
|
+
records.push({ path: record.path, sha256: record.sha256, bytes: record.bytes });
|
|
127
|
+
}
|
|
128
|
+
return records;
|
|
129
|
+
});
|
|
130
|
+
let count = 0;
|
|
131
|
+
for await (const record of make(includeContent)) {
|
|
132
|
+
const expected = baseline[count++];
|
|
133
|
+
if (!expected || expected.path !== record.path || expected.sha256 !== record.sha256 || expected.bytes !== record.bytes) {
|
|
134
|
+
throw new Error(`embedding generation changed while streaming ${absoluteStateDir}; retry the snapshot`);
|
|
135
|
+
}
|
|
136
|
+
yield record;
|
|
137
|
+
}
|
|
138
|
+
if (count !== baseline.length) {
|
|
139
|
+
throw new Error(`embedding generation changed while streaming ${absoluteStateDir}; retry the snapshot`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #3148 review round 1: detectLayout() must decide the layout on the
|
|
3
|
+
* FIRST call after a restart inside the replacement rename gap — rolling the
|
|
4
|
+
* transaction backup back into place and returning "sharded" immediately, so
|
|
5
|
+
* a stray legacy embeddings.json never wins. Higher-level search tests can
|
|
6
|
+
* hide this because a second detectLayout() call inside the same operation
|
|
7
|
+
* sees the already-rolled-back directory.
|
|
8
|
+
*/
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { lstat, mkdtemp, mkdir, rm, stat, symlink, writeFile } from "node:fs/promises";
|
|
14
|
+
import { EmbeddingIndexFileStore, EmbeddingIndexStorageError } from "./embedding-index-storage.js";
|
|
15
|
+
|
|
16
|
+
const SHARD_FILE = JSON.stringify({
|
|
17
|
+
version: 1,
|
|
18
|
+
provider: "openai",
|
|
19
|
+
model: "text-embedding-3-small",
|
|
20
|
+
entries: { "mem-old": { vector: [1, 0], path: "facts/old.md" } },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function newStore(memoryDir: string): EmbeddingIndexFileStore {
|
|
24
|
+
const stateDir = path.join(memoryDir, "state");
|
|
25
|
+
return new EmbeddingIndexFileStore(
|
|
26
|
+
path.join(stateDir, "embeddings.json"),
|
|
27
|
+
path.join(stateDir, "embeddings"),
|
|
28
|
+
path.join(stateDir, "embedding-fallback-status.json"),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test("detectLayout returns sharded on the first post-gap call and ignores a stray legacy file", async () => {
|
|
33
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-emb3146-gapfirst-"));
|
|
34
|
+
const store = newStore(memoryDir);
|
|
35
|
+
const stateDir = path.join(memoryDir, "state");
|
|
36
|
+
try {
|
|
37
|
+
const backupDir = path.join(stateDir, "embeddings.pre-replace.tmp");
|
|
38
|
+
await mkdir(backupDir, { recursive: true });
|
|
39
|
+
await writeFile(path.join(backupDir, "shard-0058.json"), SHARD_FILE, "utf-8");
|
|
40
|
+
await writeFile(
|
|
41
|
+
path.join(stateDir, "embeddings.json"),
|
|
42
|
+
JSON.stringify({
|
|
43
|
+
version: 1,
|
|
44
|
+
provider: "openai",
|
|
45
|
+
model: "text-embedding-3-small",
|
|
46
|
+
entries: { "mem-stale": { vector: [0, 1], path: "facts/stale.md" } },
|
|
47
|
+
}),
|
|
48
|
+
"utf-8",
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// First post-gap call reports SHARDED without writing: reads fail open
|
|
52
|
+
// to an empty view (the gap generation cannot be enumerated) and the
|
|
53
|
+
// stray legacy file never wins.
|
|
54
|
+
assert.equal(await store.detectLayout(), "sharded");
|
|
55
|
+
let probe: Record<string, { vector: number[]; path: string }> = {};
|
|
56
|
+
const identity = await store.readShardGenerationInto(probe);
|
|
57
|
+
assert.equal(identity, null);
|
|
58
|
+
assert.deepEqual(probe, {});
|
|
59
|
+
try {
|
|
60
|
+
await stat(path.join(stateDir, "embeddings"));
|
|
61
|
+
assert.fail("detectLayout must not write on a read path");
|
|
62
|
+
} catch (err) {
|
|
63
|
+
assert.equal((err as NodeJS.ErrnoException).code, "ENOENT");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The mutation wrapper performs the recovery (restored generation
|
|
67
|
+
// becomes the loaded view) and lands the write on it: old + new.
|
|
68
|
+
assert.equal(await store.recoverIfInterrupted(), true);
|
|
69
|
+
const loaded = await (async () => {
|
|
70
|
+
const merged: Record<string, { vector: number[]; path: string }> = {};
|
|
71
|
+
await store.readShardGenerationInto(merged);
|
|
72
|
+
return merged;
|
|
73
|
+
})();
|
|
74
|
+
loaded["mem-new"] = { vector: [1, 1], path: "facts/new.md" };
|
|
75
|
+
await store.persist(
|
|
76
|
+
{
|
|
77
|
+
version: 1,
|
|
78
|
+
provider: "openai",
|
|
79
|
+
model: "text-embedding-3-small",
|
|
80
|
+
entries: loaded,
|
|
81
|
+
},
|
|
82
|
+
{ touchedIds: ["mem-new"], memoryId: "mem-new" },
|
|
83
|
+
);
|
|
84
|
+
assert.equal(await store.detectLayout(), "sharded");
|
|
85
|
+
probe = {};
|
|
86
|
+
const identityAfter = await store.readShardGenerationInto(probe);
|
|
87
|
+
assert.deepEqual(identityAfter, { provider: "openai", model: "text-embedding-3-small" });
|
|
88
|
+
assert.deepEqual(Object.keys(probe).sort(), ["mem-new", "mem-old"]);
|
|
89
|
+
assert.equal(probe["mem-old"].path, "facts/old.md");
|
|
90
|
+
} finally {
|
|
91
|
+
await rm(memoryDir, { recursive: true, force: true }).catch(() => undefined);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("a corrupt replacement backup shape surfaces as a tagged storage error, never the legacy fallback", async () => {
|
|
96
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-emb3146-gapbackup-"));
|
|
97
|
+
const store = newStore(memoryDir);
|
|
98
|
+
try {
|
|
99
|
+
const stateDir = path.join(memoryDir, "state");
|
|
100
|
+
await mkdir(stateDir, { recursive: true });
|
|
101
|
+
// `embeddings.pre-replace.tmp` exists but is NOT a directory: the
|
|
102
|
+
// rollback moves that junk file into the published position, where the
|
|
103
|
+
// shard enumeration must fail loudly (tagged) instead of degrading to
|
|
104
|
+
// the legacy fallback.
|
|
105
|
+
await writeFile(path.join(stateDir, "embeddings.pre-replace.tmp"), "junk", "utf-8");
|
|
106
|
+
// The mutation wrapper's recovery moves the junk file into the published
|
|
107
|
+
// position; the subsequent persist must then fail loudly (tagged) when
|
|
108
|
+
// shard enumeration hits it, instead of degrading to the legacy
|
|
109
|
+
// fallback.
|
|
110
|
+
assert.equal(await store.recoverIfInterrupted(), true);
|
|
111
|
+
await assert.rejects(
|
|
112
|
+
store.persist({
|
|
113
|
+
version: 1,
|
|
114
|
+
provider: "openai",
|
|
115
|
+
model: "text-embedding-3-small",
|
|
116
|
+
entries: {},
|
|
117
|
+
}),
|
|
118
|
+
(err: NodeJS.ErrnoException) => err.name === "EmbeddingIndexStorageError",
|
|
119
|
+
);
|
|
120
|
+
} finally {
|
|
121
|
+
await rm(memoryDir, { recursive: true, force: true }).catch(() => undefined);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("removing an index on an empty store publishes an empty layout marker", async () => {
|
|
126
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-remove-empty-"));
|
|
127
|
+
try {
|
|
128
|
+
const store = newStore(root);
|
|
129
|
+
await store.removePublishedGeneration();
|
|
130
|
+
assert.equal(await newStore(root).detectLayout(), "sharded");
|
|
131
|
+
} finally {
|
|
132
|
+
await rm(root, { recursive: true, force: true });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("misplaced shard entries record read diagnostics before failing closed", async () => {
|
|
137
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-misplaced-status-"));
|
|
138
|
+
try {
|
|
139
|
+
await mkdir(path.join(root, "state/embeddings"), { recursive: true });
|
|
140
|
+
// mem-old hashes to shard-0058, not 0000.
|
|
141
|
+
await writeFile(path.join(root, "state/embeddings/shard-0000.json"), SHARD_FILE);
|
|
142
|
+
const store = newStore(root);
|
|
143
|
+
await assert.rejects(() => store.readShardGenerationInto({}), /shard/);
|
|
144
|
+
const { readFile } = await import("node:fs/promises");
|
|
145
|
+
const status = JSON.parse(await readFile(path.join(root, "state/embedding-fallback-status.json"), "utf-8"));
|
|
146
|
+
assert.match(status.lastReadRecovery.message, /shard-0000/);
|
|
147
|
+
assert.equal(await readFile(path.join(root, "state/embeddings/shard-0000.json"), "utf-8"), SHARD_FILE);
|
|
148
|
+
} finally {
|
|
149
|
+
await rm(root, { recursive: true, force: true });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Codex PRRT_kwDORJXyws6mZ72r — symlinked generation dir / members are
|
|
155
|
+
// rejected at read and mutation boundaries; planted links must never serve
|
|
156
|
+
// or write through to paths outside the state directory.
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
test("a symlinked embedding shard directory is rejected at the layout and read boundaries", async () => {
|
|
160
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-emb-symlink-dir-"));
|
|
161
|
+
const outside = await mkdtemp(path.join(os.tmpdir(), "remnic-emb-symlink-outside-"));
|
|
162
|
+
try {
|
|
163
|
+
// Use a shard whose entry id hashes to the same shard name so a
|
|
164
|
+
// successful parse would otherwise validate; the symlink boundary must
|
|
165
|
+
// throw BEFORE any shard membership check.
|
|
166
|
+
const shardNameForId = (id: string): string => {
|
|
167
|
+
let h = 0x811c9dc5;
|
|
168
|
+
for (let i = 0; i < id.length; i++) {
|
|
169
|
+
h ^= id.charCodeAt(i);
|
|
170
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
171
|
+
}
|
|
172
|
+
return `shard-${String(h % 64).padStart(4, "0")}.json`;
|
|
173
|
+
};
|
|
174
|
+
const fixtureId = "mem-old";
|
|
175
|
+
const fixtureShard = shardNameForId(fixtureId);
|
|
176
|
+
await mkdir(path.join(outside, "embeddings"), { recursive: true });
|
|
177
|
+
await writeFile(
|
|
178
|
+
path.join(outside, "embeddings", fixtureShard),
|
|
179
|
+
JSON.stringify({
|
|
180
|
+
version: 1,
|
|
181
|
+
provider: "openai",
|
|
182
|
+
model: "text-embedding-3-small",
|
|
183
|
+
entries: { [fixtureId]: { vector: [1, 0], path: "facts/old.md" } },
|
|
184
|
+
}),
|
|
185
|
+
"utf-8",
|
|
186
|
+
);
|
|
187
|
+
const stateDir = path.join(memoryDir, "state");
|
|
188
|
+
await mkdir(stateDir, { recursive: true });
|
|
189
|
+
await symlink(path.join(outside, "embeddings"), path.join(stateDir, "embeddings"), "dir");
|
|
190
|
+
assert.equal((await (await import("node:fs/promises")).lstat(path.join(stateDir, "embeddings"))).isSymbolicLink(), true, "fixture assumption: state/embeddings must be a symlink");
|
|
191
|
+
const store = newStore(memoryDir);
|
|
192
|
+
|
|
193
|
+
await assert.rejects(
|
|
194
|
+
() => store.detectLayout(),
|
|
195
|
+
(err: unknown) => err instanceof EmbeddingIndexStorageError && /symlink/.test(err.message),
|
|
196
|
+
);
|
|
197
|
+
await assert.rejects(
|
|
198
|
+
() => store.readShardGenerationInto({}),
|
|
199
|
+
(err: unknown) => err instanceof EmbeddingIndexStorageError && /symlink/.test(err.message),
|
|
200
|
+
);
|
|
201
|
+
} finally {
|
|
202
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
203
|
+
await rm(outside, { recursive: true, force: true });
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("a symlinked shard member pointing outside the state dir is rejected on read", async () => {
|
|
208
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-emb-symlink-mem-"));
|
|
209
|
+
const outside = await mkdtemp(path.join(os.tmpdir(), "remnic-emb-symlink-mem-out-"));
|
|
210
|
+
try {
|
|
211
|
+
const shardDir = path.join(memoryDir, "state", "embeddings");
|
|
212
|
+
await mkdir(shardDir, { recursive: true });
|
|
213
|
+
const fixtureId = "mem-old";
|
|
214
|
+
let h = 0x811c9dc5;
|
|
215
|
+
for (let i = 0; i < fixtureId.length; i++) {
|
|
216
|
+
h ^= fixtureId.charCodeAt(i);
|
|
217
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
218
|
+
}
|
|
219
|
+
const fixtureShard = `shard-${String(h % 64).padStart(4, "0")}.json`;
|
|
220
|
+
await writeFile(
|
|
221
|
+
path.join(outside, "planted.json"),
|
|
222
|
+
JSON.stringify({
|
|
223
|
+
version: 1,
|
|
224
|
+
provider: "openai",
|
|
225
|
+
model: "text-embedding-3-small",
|
|
226
|
+
entries: { [fixtureId]: { vector: [1, 0], path: "facts/old.md" } },
|
|
227
|
+
}),
|
|
228
|
+
"utf-8",
|
|
229
|
+
);
|
|
230
|
+
await symlink(
|
|
231
|
+
path.join(outside, "planted.json"),
|
|
232
|
+
path.join(shardDir, fixtureShard),
|
|
233
|
+
);
|
|
234
|
+
const store = newStore(memoryDir);
|
|
235
|
+
await assert.rejects(
|
|
236
|
+
() => store.readShardGenerationInto({}),
|
|
237
|
+
(err: unknown) =>
|
|
238
|
+
err instanceof EmbeddingIndexStorageError &&
|
|
239
|
+
(/symlink/.test(err.message) || /escapes the state directory/.test(err.message)),
|
|
240
|
+
);
|
|
241
|
+
} finally {
|
|
242
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
243
|
+
await rm(outside, { recursive: true, force: true });
|
|
244
|
+
}
|
|
245
|
+
});
|