@remnic/core 10.0.48 → 10.0.50
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 +73 -70
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.js +74 -71
- package/dist/access-boundary.js +73 -70
- package/dist/access-cli.js +170 -167
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +73 -70
- package/dist/access-http-briefing.d.ts +2 -0
- package/dist/access-http-briefing.d.ts.map +1 -1
- package/dist/access-http-briefing.js +74 -71
- package/dist/access-http-query.js +75 -72
- package/dist/access-http.d.ts.map +1 -1
- package/dist/access-http.js +96 -93
- package/dist/access-identity-continuity-surface.js +73 -70
- package/dist/access-lcm-surface.js +73 -70
- package/dist/access-mcp.js +85 -82
- package/dist/access-namespace-preflight.js +73 -70
- package/dist/access-observe-write-category.d.ts +1 -1
- package/dist/access-observe-write-category.d.ts.map +1 -1
- package/dist/access-observe-write-category.js +15 -14
- package/dist/access-observe-write-surface.d.ts.map +1 -1
- package/dist/access-observe-write-surface.js +73 -70
- package/dist/access-offline-manifest.js +9 -8
- package/dist/access-operations-batch.js +76 -73
- package/dist/access-operations.js +83 -80
- package/dist/access-recall-concurrency.js +73 -70
- package/dist/access-recall-response.js +73 -70
- package/dist/access-recall-surface.js +73 -70
- package/dist/access-schema.js +11 -10
- package/dist/access-service.d.ts +5 -0
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +73 -70
- package/dist/access-surface-catalog.js +75 -72
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +32 -30
- 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-BNCBMU6H.js → auto-sync-UQQ6YJSB.js} +3 -3
- package/dist/briefing-deadline.d.ts +7 -0
- package/dist/briefing-deadline.d.ts.map +1 -0
- package/dist/briefing-deadline.js +13 -0
- package/dist/briefing-window.d.ts +12 -4
- package/dist/briefing-window.d.ts.map +1 -1
- package/dist/briefing-window.js +3 -1
- package/dist/briefing.d.ts +2 -0
- package/dist/briefing.d.ts.map +1 -1
- package/dist/briefing.js +22 -20
- package/dist/buffer.js +6 -6
- package/dist/calibration.js +2 -2
- package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/{capsule-merge-RNQUP2VL.js → capsule-merge-D4BTIJGK.js} +3 -3
- package/dist/capture-write-lock-scope.d.ts +7 -0
- package/dist/capture-write-lock-scope.d.ts.map +1 -0
- package/dist/capture-write-lock-scope.js +10 -0
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +23 -22
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +3 -3
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-FAKRTS3Y.js → chunk-2AKIVSHP.js} +2 -2
- package/dist/{chunk-EIYJK7OD.js → chunk-2M36PR7R.js} +2 -2
- package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-6P6MZDBL.js → chunk-3NL4ALUP.js} +2 -2
- package/dist/{chunk-6CC2YCKG.js → chunk-43TUWQNR.js} +3 -3
- package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
- package/dist/{chunk-2NLCDNGA.js → chunk-564YP3DW.js} +2 -2
- package/dist/{chunk-5KGYR5VN.js → chunk-5DVOBBTV.js} +2 -2
- package/dist/{chunk-RT2GN6KQ.js → chunk-5FUDSK6Z.js} +2 -2
- package/dist/{chunk-B4XSXFKI.js → chunk-5G53JSRO.js} +2 -2
- package/dist/{chunk-BGK3SFCC.js → chunk-5X2H3X4J.js} +2 -2
- package/dist/{chunk-OXSTKCYK.js → chunk-6AEKSOWV.js} +33 -5
- package/dist/chunk-6AEKSOWV.js.map +1 -0
- package/dist/{chunk-4J7BNL7B.js → chunk-6ZQX3R66.js} +2 -2
- package/dist/{chunk-JNLYHJG7.js → chunk-74AY3S7U.js} +4 -4
- package/dist/{chunk-RUAIO5UA.js → chunk-7J6A5MV2.js} +2 -2
- package/dist/{chunk-A45QXZMU.js → chunk-7SAE6T36.js} +2 -2
- package/dist/chunk-7VLBZKNB.js +26 -0
- package/dist/chunk-7VLBZKNB.js.map +1 -0
- package/dist/{chunk-QE2XQM3S.js → chunk-7YVER7ZO.js} +2 -2
- package/dist/{chunk-IJH6METJ.js → chunk-A3UFV4K3.js} +4 -4
- package/dist/{chunk-LWRIGKFM.js → chunk-A5L5KGDI.js} +19 -1
- package/dist/chunk-A5L5KGDI.js.map +1 -0
- package/dist/{chunk-LXN737FX.js → chunk-AAANTXCQ.js} +2 -2
- package/dist/{chunk-I3IPXGFQ.js → chunk-AJJPXUE6.js} +4 -4
- package/dist/{chunk-2DPJFJFH.js → chunk-BNJAVRNJ.js} +11 -11
- package/dist/{chunk-YL564QS2.js → chunk-CCL2OXFO.js} +3 -3
- package/dist/{chunk-CHSRA76M.js → chunk-CRN3EPE2.js} +4 -3
- package/dist/chunk-CRN3EPE2.js.map +1 -0
- package/dist/{chunk-ZX5N5D6M.js → chunk-DAOMWGO2.js} +4 -4
- package/dist/{chunk-YUE7J2HZ.js → chunk-DMKTOCC7.js} +2 -2
- package/dist/chunk-DMKTOCC7.js.map +1 -0
- package/dist/{chunk-FRXS6SC7.js → chunk-EKUI7I2X.js} +20 -4
- package/dist/chunk-EKUI7I2X.js.map +1 -0
- package/dist/{chunk-PKPOO6X5.js → chunk-EKYWZYZJ.js} +25 -17
- package/dist/chunk-EKYWZYZJ.js.map +1 -0
- package/dist/{chunk-23ZMQONL.js → chunk-ERMT5WQM.js} +2 -2
- package/dist/{chunk-VTCNVGM6.js → chunk-EWBXMGXR.js} +2 -2
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-XTN54NML.js → chunk-G3T6YMBO.js} +4 -4
- package/dist/{chunk-2JNBPOZ2.js → chunk-GDYLZ6FR.js} +3 -3
- package/dist/{chunk-DZPTR2XQ.js → chunk-GFAEKSFR.js} +2 -2
- package/dist/{chunk-L46MG2GE.js → chunk-GQIYL7OD.js} +400 -301
- package/dist/chunk-GQIYL7OD.js.map +1 -0
- package/dist/{chunk-M7LA4QBY.js → chunk-GXVXBYRA.js} +3 -3
- package/dist/{chunk-UXAF4RQM.js → chunk-HKOMHP6D.js} +27 -15
- package/dist/chunk-HKOMHP6D.js.map +1 -0
- package/dist/{chunk-JGMJZBFV.js → chunk-HPNXS6W6.js} +2 -2
- package/dist/{chunk-BFPF5N3H.js → chunk-HXFIILGF.js} +4 -4
- package/dist/{chunk-YBKQAVPZ.js → chunk-IMKOJVD4.js} +101 -81
- package/dist/chunk-IMKOJVD4.js.map +1 -0
- package/dist/{chunk-CMNL3QSL.js → chunk-IN6U2BS5.js} +2 -2
- package/dist/{chunk-BWRX322G.js → chunk-IUYXCTA7.js} +2 -2
- package/dist/{chunk-LU57EEJW.js → chunk-JBWD3J5U.js} +3 -3
- package/dist/chunk-JNA7LFMQ.js +41 -0
- package/dist/chunk-JNA7LFMQ.js.map +1 -0
- package/dist/{chunk-2V664H6O.js → chunk-JS44PBCM.js} +5 -5
- package/dist/{chunk-XKTHJHSQ.js → chunk-JZCBMKR5.js} +3 -3
- package/dist/{chunk-MXPVNDGO.js → chunk-K5QQXVG6.js} +12 -12
- package/dist/{chunk-BUKK5SWA.js → chunk-KQAFEZQX.js} +2 -2
- package/dist/{chunk-WUBGMV3G.js → chunk-KRAMCBIW.js} +9 -9
- package/dist/{chunk-NDMPRT3Z.js → chunk-KW5YZELC.js} +2 -2
- package/dist/{chunk-2RQR4TXV.js → chunk-LIBVANOF.js} +2 -2
- package/dist/{chunk-M7L5A2AT.js → chunk-LMMBV7QC.js} +4 -4
- package/dist/{chunk-PJBYVJKO.js → chunk-LXXIV7Y2.js} +2 -2
- package/dist/{chunk-MZCTS3ZG.js → chunk-M2DOJBH7.js} +2 -2
- package/dist/{chunk-WOPZ2P5Z.js → chunk-M4HRYLOA.js} +2 -2
- package/dist/{chunk-JCYEDMOY.js → chunk-M6TP6UYD.js} +3 -3
- package/dist/{chunk-KAUCVTAW.js → chunk-MLNXATFG.js} +2 -2
- package/dist/{chunk-2NX7JTST.js → chunk-MPWOAIIH.js} +2 -2
- package/dist/{chunk-RQLTCHUL.js → chunk-MWURUSRE.js} +13 -13
- package/dist/{chunk-3EGL7XZO.js → chunk-N74WJ7B2.js} +2 -2
- package/dist/{chunk-DRXTMDAG.js → chunk-N7FNCE7F.js} +2 -2
- package/dist/{chunk-6CWAY4R7.js → chunk-NPLM7AFU.js} +2 -2
- package/dist/{chunk-VECRSYQG.js → chunk-NRGQ5HJO.js} +1 -1
- package/dist/{chunk-PPDNG7WC.js → chunk-NSP5PIQH.js} +2 -2
- package/dist/{chunk-6HVLHIAB.js → chunk-P7OVFGMU.js} +2 -2
- package/dist/{chunk-EX27VKU6.js → chunk-PDTNQLPC.js} +2 -2
- package/dist/{chunk-ME5LVISI.js → chunk-PJCVI44H.js} +2 -2
- package/dist/{chunk-LJCHDFLP.js → chunk-Q3PNQYAC.js} +2 -2
- package/dist/{chunk-YFJHFEET.js → chunk-QS765SBL.js} +2 -2
- package/dist/{chunk-FJ264QBP.js → chunk-QTDZ2NYC.js} +11 -11
- package/dist/{chunk-Q6UZREJK.js → chunk-RKRX4C4H.js} +2 -2
- package/dist/{chunk-YFXRLRKP.js → chunk-S4OZKPBS.js} +7 -11
- package/dist/{chunk-YFXRLRKP.js.map → chunk-S4OZKPBS.js.map} +1 -1
- package/dist/{chunk-GLZWOIU3.js → chunk-SJTSWG2Z.js} +3 -3
- package/dist/{chunk-Y57GJDG5.js → chunk-SWRRZNHM.js} +14 -17
- package/dist/chunk-SWRRZNHM.js.map +1 -0
- package/dist/{chunk-CCTLRRV6.js → chunk-TEH5MDX5.js} +55 -34
- package/dist/chunk-TEH5MDX5.js.map +1 -0
- package/dist/{chunk-XM747YJF.js → chunk-TFWJXWL6.js} +2 -2
- package/dist/{chunk-QFQ4UG3I.js → chunk-TXKQBQYW.js} +4 -4
- package/dist/{chunk-BMOJFGVX.js → chunk-UALXWWGN.js} +2 -2
- package/dist/{chunk-B44NFHWI.js → chunk-UNOAVHV4.js} +2 -2
- package/dist/{chunk-TF5NZGPS.js → chunk-UQBOPCDK.js} +2 -2
- package/dist/{chunk-SUKPM6DV.js → chunk-UWMZT2TY.js} +47 -4
- package/dist/chunk-UWMZT2TY.js.map +1 -0
- package/dist/{chunk-5R2IF2KI.js → chunk-UXGXJUZ5.js} +7 -7
- package/dist/{chunk-FWV2SXSL.js → chunk-UZVQFGAY.js} +101 -98
- package/dist/chunk-UZVQFGAY.js.map +1 -0
- package/dist/{chunk-J6B6FK2X.js → chunk-VCOMACR6.js} +53 -53
- package/dist/{chunk-WCGJK4NQ.js → chunk-VKQI47DJ.js} +7 -7
- package/dist/{chunk-MPJDQC4G.js → chunk-WJCXUE2J.js} +2 -2
- package/dist/{chunk-BCDECPVO.js → chunk-WV4UYDP6.js} +2 -2
- package/dist/{chunk-VWJ56T63.js → chunk-WXJC2L66.js} +2 -2
- package/dist/chunk-X2KO4FK5.js +40 -0
- package/dist/chunk-X2KO4FK5.js.map +1 -0
- package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-FPANDKMQ.js → chunk-XC3H7KON.js} +2 -2
- package/dist/{chunk-OOOX5C4X.js → chunk-XL2EE5W3.js} +4 -4
- package/dist/{chunk-3DKKZVXS.js → chunk-XTXWR6KQ.js} +26 -11
- package/dist/chunk-XTXWR6KQ.js.map +1 -0
- package/dist/{chunk-3SL52JKD.js → chunk-XVZA6VXC.js} +4 -4
- package/dist/{chunk-CZVNOTLR.js → chunk-XXR4A33N.js} +2 -2
- package/dist/{chunk-2AV2U2SZ.js → chunk-YDIOMDBW.js} +2 -2
- package/dist/{chunk-XTVH2NAW.js → chunk-YECEEJUU.js} +3 -3
- package/dist/{chunk-QR6W3FSW.js → chunk-Z5277JWF.js} +3 -3
- package/dist/{chunk-CTHYR4QK.js → chunk-ZFNXJEUG.js} +2 -2
- package/dist/{chunk-6Q56WDHG.js → chunk-ZLCBLDJO.js} +2 -2
- package/dist/{chunk-VY6BD5KB.js → chunk-ZMO2S5RT.js} +12 -10
- package/dist/chunk-ZMO2S5RT.js.map +1 -0
- package/dist/{chunk-KRACAE73.js → chunk-ZNRTHYYW.js} +6 -6
- package/dist/{chunk-7AIPWNM7.js → chunk-ZSMA67FV.js} +2 -2
- package/dist/cli.js +123 -120
- package/dist/coding/export-okf-codegraph.js +19 -18
- package/dist/coding/export-okf-codegraph.js.map +1 -1
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +21 -20
- package/dist/config.js +32 -30
- package/dist/connectors/codex-materialize-runner.js +20 -19
- package/dist/connectors/index.js +20 -19
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.js +2 -2
- package/dist/convergence-refresh.js +22 -21
- package/dist/corpus-watermark.js +22 -21
- package/dist/correction/passive-correction-notifications.d.ts +1 -1
- package/dist/correction/passive-correction-notifications.d.ts.map +1 -1
- package/dist/dashboard-runtime.js +2 -2
- package/dist/deep-recall-http-glue.js +12 -11
- package/dist/deep-recall.js +2 -2
- package/dist/embedding-fallback.js +3 -2
- package/dist/embedding-generation-marker.d.ts +16 -0
- package/dist/embedding-generation-marker.d.ts.map +1 -0
- package/dist/embedding-generation-marker.js +19 -0
- package/dist/embedding-generation-marker.js.map +1 -0
- package/dist/embedding-index-storage.d.ts +2 -4
- package/dist/embedding-index-storage.d.ts.map +1 -1
- package/dist/embedding-index-storage.js +2 -1
- package/dist/{entity-canonical-id-migration-2YOLMOFC.js → entity-canonical-id-migration-ZBNRGUNV.js} +3 -3
- package/dist/entity-canonical-id-migration-ZBNRGUNV.js.map +1 -0
- package/dist/entity-retrieval.js +22 -21
- package/dist/event-order-recall.js +2 -2
- package/dist/explicit-capture.js +3 -3
- package/dist/external-wiki-access.js +74 -71
- package/dist/extraction-span-materialize.js +2 -2
- package/dist/extraction.js +30 -29
- package/dist/{graph-edge-decay-3HVLMT6U.js → graph-edge-decay-IU75NV42.js} +6 -6
- package/dist/graph-edge-decay-IU75NV42.js.map +1 -0
- package/dist/graph-jsonl.js +4 -4
- package/dist/graph-snapshot.js +4 -4
- package/dist/graph.js +5 -5
- package/dist/harmonic-retrieval.js +20 -19
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +230 -218
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +5 -5
- package/dist/local-llm.js +2 -2
- package/dist/location/index.js +25 -24
- package/dist/location/tagging.d.ts +7 -5
- package/dist/location/tagging.d.ts.map +1 -1
- package/dist/maintenance/memory-governance.js +20 -19
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -19
- package/dist/maintenance/rebuild-memory-projection-runner.js +22 -21
- package/dist/maintenance/rebuild-memory-projection.js +21 -20
- package/dist/memory-browse-operations.js +74 -71
- package/dist/memory-browse.js +5 -5
- package/dist/namespaces/migrate.js +21 -20
- package/dist/namespaces/search.js +6 -6
- package/dist/namespaces/storage.js +20 -19
- package/dist/offline-sync-changeset-generations.js +5 -4
- package/dist/offline-sync-embedding-generation.d.ts +12 -2
- package/dist/offline-sync-embedding-generation.d.ts.map +1 -1
- package/dist/offline-sync-embedding-generation.js +6 -3
- package/dist/offline-sync-file-io.d.ts +15 -0
- package/dist/offline-sync-file-io.d.ts.map +1 -1
- package/dist/offline-sync-file-io.js +9 -1
- package/dist/offline-sync-snapshot-stream.js +9 -8
- package/dist/offline-sync-upload-staging.js +2 -2
- package/dist/offline-sync.d.ts.map +1 -1
- package/dist/offline-sync.js +10 -9
- package/dist/operator-doctor-corpus.js +23 -22
- package/dist/operator-toolkit.js +45 -43
- package/dist/orchestration/maintenance.js +33 -32
- package/dist/orchestrator.d.ts +1 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +170 -167
- package/dist/page-versioning.js +2 -2
- package/dist/qmd.js +2 -2
- package/dist/recall-navigation-operations.js +74 -71
- package/dist/recall-navigation.js +73 -70
- package/dist/recall-state-view-admission.js +5 -5
- package/dist/recall-why-service.js +5 -5
- package/dist/resume-bundles.js +33 -31
- package/dist/search/factory.js +5 -5
- package/dist/search/index.js +9 -9
- package/dist/semantic-consolidation.js +25 -24
- package/dist/semantic-rule-promotion.js +20 -19
- package/dist/semantic-rule-verifier.js +20 -19
- package/dist/source-agent-qualifier.js +24 -23
- package/dist/standup/build.js +20 -19
- package/dist/storage/entity-canonical-id-migration-adapter.d.ts +1 -0
- package/dist/storage/entity-canonical-id-migration-adapter.d.ts.map +1 -1
- package/dist/storage/entity-canonical-id-migration.d.ts +7 -0
- package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
- package/dist/storage/entity-canonical-id-references.d.ts +2 -0
- package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
- package/dist/storage/entity-migration-completion-cache.d.ts +46 -0
- package/dist/storage/entity-migration-completion-cache.d.ts.map +1 -0
- package/dist/storage/entity-store.d.ts +1 -8
- package/dist/storage/entity-store.d.ts.map +1 -1
- package/dist/storage/memory-read-store.d.ts +3 -2
- package/dist/storage/memory-read-store.d.ts.map +1 -1
- package/dist/storage/windowed-memory-read.d.ts +6 -0
- package/dist/storage/windowed-memory-read.d.ts.map +1 -1
- package/dist/storage.d.ts +2 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +19 -18
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/index.js +76 -73
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +4 -4
- package/dist/transfer/export-okf.js +20 -19
- package/dist/verified-recall.js +20 -19
- package/dist/who-knows-cli.js +21 -20
- package/dist/who-knows.js +20 -19
- package/package.json +2 -2
- package/src/access-http-briefing.ts +4 -0
- package/src/access-http.test.ts +40 -11
- package/src/access-http.ts +6 -0
- package/src/access-observe-write-category.ts +1 -1
- package/src/access-observe-write-surface.ts +24 -19
- package/src/access-operations-batch.ts +18 -2
- package/src/access-service-peer-capture-backoff.test.ts +236 -0
- package/src/access-service.ts +11 -2
- package/src/briefing-abort.test.ts +173 -0
- package/src/briefing-deadline.test.ts +84 -0
- package/src/briefing-deadline.ts +33 -0
- package/src/briefing-window-abort.test.ts +47 -0
- package/src/briefing-window.ts +33 -4
- package/src/briefing.ts +23 -16
- package/src/capture-write-lock-scope.test.ts +32 -0
- package/src/capture-write-lock-scope.ts +30 -0
- package/src/correction/passive-correction-notifications-abort.test.ts +42 -0
- package/src/correction/passive-correction-notifications.ts +4 -0
- package/src/embedding-generation-marker.ts +51 -0
- package/src/embedding-index-storage.ts +13 -29
- package/src/entity-lazy-compatibility.test.ts +229 -0
- package/src/entity-migration-completion-notification.test.ts +148 -0
- package/src/index.ts +2 -2
- package/src/location/tagging.ts +14 -12
- package/src/offline-sync-embedding-delete-digest.test.ts +58 -0
- package/src/offline-sync-embedding-generation-marker.test.ts +245 -0
- package/src/offline-sync-embedding-generation.ts +47 -2
- package/src/offline-sync-embedding-lock-loss.test.ts +42 -0
- package/src/offline-sync-file-io.ts +61 -2
- package/src/offline-sync.test.ts +313 -20
- package/src/offline-sync.ts +47 -22
- package/src/orchestrator-init.test.ts +49 -1
- package/src/orchestrator.ts +9 -8
- package/src/storage/entity-canonical-id-migration-adapter.ts +16 -1
- package/src/storage/entity-canonical-id-migration.ts +17 -8
- package/src/storage/entity-canonical-id-references.ts +24 -0
- package/src/storage/entity-migration-completion-cache.nofollow.test.ts +223 -0
- package/src/storage/entity-migration-completion-cache.ts +153 -0
- package/src/storage/entity-store.ts +58 -86
- package/src/storage/memory-read-store.ts +9 -3
- package/src/storage/windowed-memory-read.ts +8 -0
- package/src/storage-startup-nonblocking-migration.test.ts +183 -0
- package/src/storage.ts +9 -3
- package/src/testing/subjects/entity-migration-completion-cache.test.ts +90 -0
- package/dist/chunk-3DKKZVXS.js.map +0 -1
- package/dist/chunk-CCTLRRV6.js.map +0 -1
- package/dist/chunk-CHSRA76M.js.map +0 -1
- package/dist/chunk-FRXS6SC7.js.map +0 -1
- package/dist/chunk-FWV2SXSL.js.map +0 -1
- package/dist/chunk-L46MG2GE.js.map +0 -1
- package/dist/chunk-LWRIGKFM.js.map +0 -1
- package/dist/chunk-OXSTKCYK.js.map +0 -1
- package/dist/chunk-PKPOO6X5.js.map +0 -1
- package/dist/chunk-SUKPM6DV.js.map +0 -1
- package/dist/chunk-UXAF4RQM.js.map +0 -1
- package/dist/chunk-VY6BD5KB.js.map +0 -1
- package/dist/chunk-Y57GJDG5.js.map +0 -1
- package/dist/chunk-YBKQAVPZ.js.map +0 -1
- package/dist/chunk-YUE7J2HZ.js.map +0 -1
- /package/dist/{auto-sync-BNCBMU6H.js.map → auto-sync-UQQ6YJSB.js.map} +0 -0
- /package/dist/{capsule-crypto-GWVG7LGC.js.map → briefing-deadline.js.map} +0 -0
- /package/dist/{entity-canonical-id-migration-2YOLMOFC.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
- /package/dist/{capsule-merge-RNQUP2VL.js.map → capsule-merge-D4BTIJGK.js.map} +0 -0
- /package/dist/{graph-edge-decay-3HVLMT6U.js.map → capture-write-lock-scope.js.map} +0 -0
- /package/dist/{chunk-FAKRTS3Y.js.map → chunk-2AKIVSHP.js.map} +0 -0
- /package/dist/{chunk-EIYJK7OD.js.map → chunk-2M36PR7R.js.map} +0 -0
- /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-6P6MZDBL.js.map → chunk-3NL4ALUP.js.map} +0 -0
- /package/dist/{chunk-6CC2YCKG.js.map → chunk-43TUWQNR.js.map} +0 -0
- /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
- /package/dist/{chunk-2NLCDNGA.js.map → chunk-564YP3DW.js.map} +0 -0
- /package/dist/{chunk-5KGYR5VN.js.map → chunk-5DVOBBTV.js.map} +0 -0
- /package/dist/{chunk-RT2GN6KQ.js.map → chunk-5FUDSK6Z.js.map} +0 -0
- /package/dist/{chunk-B4XSXFKI.js.map → chunk-5G53JSRO.js.map} +0 -0
- /package/dist/{chunk-BGK3SFCC.js.map → chunk-5X2H3X4J.js.map} +0 -0
- /package/dist/{chunk-4J7BNL7B.js.map → chunk-6ZQX3R66.js.map} +0 -0
- /package/dist/{chunk-JNLYHJG7.js.map → chunk-74AY3S7U.js.map} +0 -0
- /package/dist/{chunk-RUAIO5UA.js.map → chunk-7J6A5MV2.js.map} +0 -0
- /package/dist/{chunk-A45QXZMU.js.map → chunk-7SAE6T36.js.map} +0 -0
- /package/dist/{chunk-QE2XQM3S.js.map → chunk-7YVER7ZO.js.map} +0 -0
- /package/dist/{chunk-IJH6METJ.js.map → chunk-A3UFV4K3.js.map} +0 -0
- /package/dist/{chunk-LXN737FX.js.map → chunk-AAANTXCQ.js.map} +0 -0
- /package/dist/{chunk-I3IPXGFQ.js.map → chunk-AJJPXUE6.js.map} +0 -0
- /package/dist/{chunk-2DPJFJFH.js.map → chunk-BNJAVRNJ.js.map} +0 -0
- /package/dist/{chunk-YL564QS2.js.map → chunk-CCL2OXFO.js.map} +0 -0
- /package/dist/{chunk-ZX5N5D6M.js.map → chunk-DAOMWGO2.js.map} +0 -0
- /package/dist/{chunk-23ZMQONL.js.map → chunk-ERMT5WQM.js.map} +0 -0
- /package/dist/{chunk-VTCNVGM6.js.map → chunk-EWBXMGXR.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-XTN54NML.js.map → chunk-G3T6YMBO.js.map} +0 -0
- /package/dist/{chunk-2JNBPOZ2.js.map → chunk-GDYLZ6FR.js.map} +0 -0
- /package/dist/{chunk-DZPTR2XQ.js.map → chunk-GFAEKSFR.js.map} +0 -0
- /package/dist/{chunk-M7LA4QBY.js.map → chunk-GXVXBYRA.js.map} +0 -0
- /package/dist/{chunk-JGMJZBFV.js.map → chunk-HPNXS6W6.js.map} +0 -0
- /package/dist/{chunk-BFPF5N3H.js.map → chunk-HXFIILGF.js.map} +0 -0
- /package/dist/{chunk-CMNL3QSL.js.map → chunk-IN6U2BS5.js.map} +0 -0
- /package/dist/{chunk-BWRX322G.js.map → chunk-IUYXCTA7.js.map} +0 -0
- /package/dist/{chunk-LU57EEJW.js.map → chunk-JBWD3J5U.js.map} +0 -0
- /package/dist/{chunk-2V664H6O.js.map → chunk-JS44PBCM.js.map} +0 -0
- /package/dist/{chunk-XKTHJHSQ.js.map → chunk-JZCBMKR5.js.map} +0 -0
- /package/dist/{chunk-MXPVNDGO.js.map → chunk-K5QQXVG6.js.map} +0 -0
- /package/dist/{chunk-BUKK5SWA.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-WUBGMV3G.js.map → chunk-KRAMCBIW.js.map} +0 -0
- /package/dist/{chunk-NDMPRT3Z.js.map → chunk-KW5YZELC.js.map} +0 -0
- /package/dist/{chunk-2RQR4TXV.js.map → chunk-LIBVANOF.js.map} +0 -0
- /package/dist/{chunk-M7L5A2AT.js.map → chunk-LMMBV7QC.js.map} +0 -0
- /package/dist/{chunk-PJBYVJKO.js.map → chunk-LXXIV7Y2.js.map} +0 -0
- /package/dist/{chunk-MZCTS3ZG.js.map → chunk-M2DOJBH7.js.map} +0 -0
- /package/dist/{chunk-WOPZ2P5Z.js.map → chunk-M4HRYLOA.js.map} +0 -0
- /package/dist/{chunk-JCYEDMOY.js.map → chunk-M6TP6UYD.js.map} +0 -0
- /package/dist/{chunk-KAUCVTAW.js.map → chunk-MLNXATFG.js.map} +0 -0
- /package/dist/{chunk-2NX7JTST.js.map → chunk-MPWOAIIH.js.map} +0 -0
- /package/dist/{chunk-RQLTCHUL.js.map → chunk-MWURUSRE.js.map} +0 -0
- /package/dist/{chunk-3EGL7XZO.js.map → chunk-N74WJ7B2.js.map} +0 -0
- /package/dist/{chunk-DRXTMDAG.js.map → chunk-N7FNCE7F.js.map} +0 -0
- /package/dist/{chunk-6CWAY4R7.js.map → chunk-NPLM7AFU.js.map} +0 -0
- /package/dist/{chunk-VECRSYQG.js.map → chunk-NRGQ5HJO.js.map} +0 -0
- /package/dist/{chunk-PPDNG7WC.js.map → chunk-NSP5PIQH.js.map} +0 -0
- /package/dist/{chunk-6HVLHIAB.js.map → chunk-P7OVFGMU.js.map} +0 -0
- /package/dist/{chunk-EX27VKU6.js.map → chunk-PDTNQLPC.js.map} +0 -0
- /package/dist/{chunk-ME5LVISI.js.map → chunk-PJCVI44H.js.map} +0 -0
- /package/dist/{chunk-LJCHDFLP.js.map → chunk-Q3PNQYAC.js.map} +0 -0
- /package/dist/{chunk-YFJHFEET.js.map → chunk-QS765SBL.js.map} +0 -0
- /package/dist/{chunk-FJ264QBP.js.map → chunk-QTDZ2NYC.js.map} +0 -0
- /package/dist/{chunk-Q6UZREJK.js.map → chunk-RKRX4C4H.js.map} +0 -0
- /package/dist/{chunk-GLZWOIU3.js.map → chunk-SJTSWG2Z.js.map} +0 -0
- /package/dist/{chunk-XM747YJF.js.map → chunk-TFWJXWL6.js.map} +0 -0
- /package/dist/{chunk-QFQ4UG3I.js.map → chunk-TXKQBQYW.js.map} +0 -0
- /package/dist/{chunk-BMOJFGVX.js.map → chunk-UALXWWGN.js.map} +0 -0
- /package/dist/{chunk-B44NFHWI.js.map → chunk-UNOAVHV4.js.map} +0 -0
- /package/dist/{chunk-TF5NZGPS.js.map → chunk-UQBOPCDK.js.map} +0 -0
- /package/dist/{chunk-5R2IF2KI.js.map → chunk-UXGXJUZ5.js.map} +0 -0
- /package/dist/{chunk-J6B6FK2X.js.map → chunk-VCOMACR6.js.map} +0 -0
- /package/dist/{chunk-WCGJK4NQ.js.map → chunk-VKQI47DJ.js.map} +0 -0
- /package/dist/{chunk-MPJDQC4G.js.map → chunk-WJCXUE2J.js.map} +0 -0
- /package/dist/{chunk-BCDECPVO.js.map → chunk-WV4UYDP6.js.map} +0 -0
- /package/dist/{chunk-VWJ56T63.js.map → chunk-WXJC2L66.js.map} +0 -0
- /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-FPANDKMQ.js.map → chunk-XC3H7KON.js.map} +0 -0
- /package/dist/{chunk-OOOX5C4X.js.map → chunk-XL2EE5W3.js.map} +0 -0
- /package/dist/{chunk-3SL52JKD.js.map → chunk-XVZA6VXC.js.map} +0 -0
- /package/dist/{chunk-CZVNOTLR.js.map → chunk-XXR4A33N.js.map} +0 -0
- /package/dist/{chunk-2AV2U2SZ.js.map → chunk-YDIOMDBW.js.map} +0 -0
- /package/dist/{chunk-XTVH2NAW.js.map → chunk-YECEEJUU.js.map} +0 -0
- /package/dist/{chunk-QR6W3FSW.js.map → chunk-Z5277JWF.js.map} +0 -0
- /package/dist/{chunk-CTHYR4QK.js.map → chunk-ZFNXJEUG.js.map} +0 -0
- /package/dist/{chunk-6Q56WDHG.js.map → chunk-ZLCBLDJO.js.map} +0 -0
- /package/dist/{chunk-KRACAE73.js.map → chunk-ZNRTHYYW.js.map} +0 -0
- /package/dist/{chunk-7AIPWNM7.js.map → chunk-ZSMA67FV.js.map} +0 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// Issue #3182: the chunk-level pull path must advance the node-local
|
|
2
|
+
// generation marker around direct shard writes; partial chunks and no-ops
|
|
3
|
+
// must not disturb a valid completed stamp. Stat pinning emulates a
|
|
4
|
+
// coarse-tick filesystem so only a marker advance can move the stamp.
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { lstat, mkdir, mkdtemp, readFile, rm, utimes, writeFile } from "node:fs/promises";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
EmbeddingIndexFileStore,
|
|
14
|
+
type EmbeddingIndexEntry,
|
|
15
|
+
type EmbeddingIndexFile,
|
|
16
|
+
shardIndexOf,
|
|
17
|
+
} from "./embedding-index-storage.js";
|
|
18
|
+
import { applyOfflineSyncFileContentChunk } from "./offline-sync.js";
|
|
19
|
+
|
|
20
|
+
// Pin the shard dir to one whole-millisecond tick, emulating a coarse-tick
|
|
21
|
+
// filesystem where the pre- and post-write directory stats alias (same
|
|
22
|
+
// technique as embedding-fallback-cross-process.test.ts).
|
|
23
|
+
const TICK_SEC = 1_700_000_000.5;
|
|
24
|
+
const MODEL = "text-embedding-3-small";
|
|
25
|
+
|
|
26
|
+
function shardRelFor(id: string): string {
|
|
27
|
+
return `state/embeddings/shard-${String(shardIndexOf(id, 64)).padStart(4, "0")}.json`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function indexFile(entries: Record<string, EmbeddingIndexEntry>): EmbeddingIndexFile {
|
|
31
|
+
return { version: 1, provider: "openai", model: MODEL, entries };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function shardDirOf(memoryDir: string): string {
|
|
35
|
+
return path.join(memoryDir, "state", "embeddings");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function seedPublishedGeneration(memoryDir: string): Promise<EmbeddingIndexFileStore> {
|
|
39
|
+
const stateDir = path.join(memoryDir, "state");
|
|
40
|
+
const store = new EmbeddingIndexFileStore(
|
|
41
|
+
path.join(stateDir, "embeddings.json"),
|
|
42
|
+
path.join(stateDir, "embeddings"),
|
|
43
|
+
path.join(stateDir, "embedding-fallback-status.json"),
|
|
44
|
+
);
|
|
45
|
+
// Same fixture shape as embedding-index-storage.test.ts: a published
|
|
46
|
+
// sharded generation with one entry, completed through the store's
|
|
47
|
+
// two-phase marker.
|
|
48
|
+
await mkdir(path.join(stateDir, "embeddings"), { recursive: true });
|
|
49
|
+
await writeFile(
|
|
50
|
+
path.join(stateDir, "embeddings", shardRelFor("mem-old").split("/").pop()!),
|
|
51
|
+
JSON.stringify(indexFile({ "mem-old": { vector: [1, 0], path: "facts/old.md" } })),
|
|
52
|
+
);
|
|
53
|
+
await store.persist(indexFile({ "mem-old": { vector: [1, 0], path: "facts/old.md" } }), {
|
|
54
|
+
touchedIds: ["mem-old"],
|
|
55
|
+
memoryId: "mem-old",
|
|
56
|
+
});
|
|
57
|
+
return store;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function readMarker(memoryDir: string): Promise<string> {
|
|
61
|
+
return (await readFile(path.join(memoryDir, "state", "embeddings.generation"), "utf-8")).trim();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function pinShardDirStat(memoryDir: string): Promise<number> {
|
|
65
|
+
const shardDir = shardDirOf(memoryDir);
|
|
66
|
+
await utimes(shardDir, TICK_SEC, TICK_SEC);
|
|
67
|
+
return (await lstat(shardDir)).ino;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function assertInoUnchanged(memoryDir: string, inoBefore: number): Promise<void> {
|
|
71
|
+
assert.equal(
|
|
72
|
+
(await lstat(shardDirOf(memoryDir))).ino,
|
|
73
|
+
inoBefore,
|
|
74
|
+
"precondition: the pull rewrote the shard in place (no directory swap)",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
test("a completed offline-sync chunk write to a published shard moves the generation stamp", async () => {
|
|
79
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-3182-chunk-"));
|
|
80
|
+
try {
|
|
81
|
+
const store = await seedPublishedGeneration(memoryDir);
|
|
82
|
+
const shardRel = shardRelFor("mem-old");
|
|
83
|
+
const updated = Buffer.from(
|
|
84
|
+
JSON.stringify(indexFile({ "mem-old": { vector: [0, 1], path: "facts/old.md" } })),
|
|
85
|
+
"utf-8",
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const inoBefore = await pinShardDirStat(memoryDir);
|
|
89
|
+
const markerBefore = await readMarker(memoryDir);
|
|
90
|
+
const stampBefore = await store.identityStamp();
|
|
91
|
+
assert.equal(await store.identityStamp(), stampBefore, "precondition: the completed stamp is stable");
|
|
92
|
+
|
|
93
|
+
// The chunk-level pull path used by converge pulls and CLI hydrate:
|
|
94
|
+
// no IO hooks, plain fs final write (writeSafeFileFromUpload).
|
|
95
|
+
const result = await applyOfflineSyncFileContentChunk({
|
|
96
|
+
root: memoryDir,
|
|
97
|
+
sourceId: "remote",
|
|
98
|
+
path: shardRel,
|
|
99
|
+
sha256: createHash("sha256").update(updated).digest("hex"),
|
|
100
|
+
bytes: updated.length,
|
|
101
|
+
mtimeMs: 1000,
|
|
102
|
+
offset: 0,
|
|
103
|
+
content: updated,
|
|
104
|
+
});
|
|
105
|
+
assert.equal(result.applied, true, "precondition: the shard write applied");
|
|
106
|
+
|
|
107
|
+
// Force the exact stat the stamp reads so only a marker advance can move it.
|
|
108
|
+
await utimes(shardDirOf(memoryDir), TICK_SEC, TICK_SEC);
|
|
109
|
+
await assertInoUnchanged(memoryDir, inoBefore);
|
|
110
|
+
|
|
111
|
+
assert.notEqual(
|
|
112
|
+
await readMarker(memoryDir),
|
|
113
|
+
markerBefore,
|
|
114
|
+
"a peer pull that published new generation bytes must advance the generation marker",
|
|
115
|
+
);
|
|
116
|
+
const stampAfter = await store.identityStamp();
|
|
117
|
+
assert.notEqual(
|
|
118
|
+
stampAfter,
|
|
119
|
+
stampBefore,
|
|
120
|
+
"a peer pull that changed generation bytes must move the warm-cache stamp even when the directory stat aliases",
|
|
121
|
+
);
|
|
122
|
+
} finally {
|
|
123
|
+
await rm(memoryDir, { recursive: true, force: true }).catch(() => undefined);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("a partial chunk (staging only) must not invalidate a valid completed stamp", async () => {
|
|
128
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-3182-partial-"));
|
|
129
|
+
try {
|
|
130
|
+
const store = await seedPublishedGeneration(memoryDir);
|
|
131
|
+
const shardRel = shardRelFor("mem-old");
|
|
132
|
+
const onDiskBefore = await readFile(path.join(memoryDir, ...shardRel.split("/")));
|
|
133
|
+
|
|
134
|
+
const updated = Buffer.from(
|
|
135
|
+
JSON.stringify(indexFile({ "mem-old": { vector: [0, 1], path: "facts/old.md" } })),
|
|
136
|
+
"utf-8",
|
|
137
|
+
);
|
|
138
|
+
const inoBefore = await pinShardDirStat(memoryDir);
|
|
139
|
+
const markerBefore = await readMarker(memoryDir);
|
|
140
|
+
const stampBefore = await store.identityStamp();
|
|
141
|
+
|
|
142
|
+
const result = await applyOfflineSyncFileContentChunk({
|
|
143
|
+
root: memoryDir,
|
|
144
|
+
sourceId: "remote",
|
|
145
|
+
path: shardRel,
|
|
146
|
+
sha256: createHash("sha256").update(updated).digest("hex"),
|
|
147
|
+
bytes: updated.length,
|
|
148
|
+
mtimeMs: 1000,
|
|
149
|
+
offset: 0,
|
|
150
|
+
content: updated.subarray(0, Math.floor(updated.length / 2)),
|
|
151
|
+
});
|
|
152
|
+
assert.equal(result.done, false, "precondition: the chunk is partial");
|
|
153
|
+
assert.deepEqual(
|
|
154
|
+
await readFile(path.join(memoryDir, ...shardRel.split("/"))),
|
|
155
|
+
onDiskBefore,
|
|
156
|
+
"precondition: a partial chunk spools to staging, never to the generation",
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
await utimes(shardDirOf(memoryDir), TICK_SEC, TICK_SEC);
|
|
160
|
+
await assertInoUnchanged(memoryDir, inoBefore);
|
|
161
|
+
|
|
162
|
+
assert.equal(await readMarker(memoryDir), markerBefore, "a staging-only write must not touch the generation marker");
|
|
163
|
+
assert.equal(
|
|
164
|
+
await store.identityStamp(),
|
|
165
|
+
stampBefore,
|
|
166
|
+
"no generation bytes changed: the completed stamp must stay valid so warm caches keep working",
|
|
167
|
+
);
|
|
168
|
+
} finally {
|
|
169
|
+
await rm(memoryDir, { recursive: true, force: true }).catch(() => undefined);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("a skipped no-op chunk (content already identical) must not invalidate the stamp", async () => {
|
|
174
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-3182-noop-"));
|
|
175
|
+
try {
|
|
176
|
+
const store = await seedPublishedGeneration(memoryDir);
|
|
177
|
+
const shardRel = shardRelFor("mem-old");
|
|
178
|
+
const onDisk = await readFile(path.join(memoryDir, ...shardRel.split("/")));
|
|
179
|
+
|
|
180
|
+
const inoBefore = await pinShardDirStat(memoryDir);
|
|
181
|
+
const markerBefore = await readMarker(memoryDir);
|
|
182
|
+
const stampBefore = await store.identityStamp();
|
|
183
|
+
|
|
184
|
+
const result = await applyOfflineSyncFileContentChunk({
|
|
185
|
+
root: memoryDir,
|
|
186
|
+
sourceId: "remote",
|
|
187
|
+
path: shardRel,
|
|
188
|
+
sha256: createHash("sha256").update(onDisk).digest("hex"),
|
|
189
|
+
bytes: onDisk.length,
|
|
190
|
+
mtimeMs: 1000,
|
|
191
|
+
offset: 0,
|
|
192
|
+
content: onDisk,
|
|
193
|
+
});
|
|
194
|
+
assert.equal(result.skipped, true, "precondition: identical content is a no-op");
|
|
195
|
+
|
|
196
|
+
await utimes(shardDirOf(memoryDir), TICK_SEC, TICK_SEC);
|
|
197
|
+
await assertInoUnchanged(memoryDir, inoBefore);
|
|
198
|
+
|
|
199
|
+
assert.equal(await readMarker(memoryDir), markerBefore, "a no-op must not touch the generation marker");
|
|
200
|
+
assert.equal(await store.identityStamp(), stampBefore, "no generation bytes changed: the stamp stays valid");
|
|
201
|
+
} finally {
|
|
202
|
+
await rm(memoryDir, { recursive: true, force: true }).catch(() => undefined);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("a failed direct shard write leaves the marker in-flight: the stamp never repeats", async () => {
|
|
207
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-3182-failed-"));
|
|
208
|
+
try {
|
|
209
|
+
const store = await seedPublishedGeneration(memoryDir);
|
|
210
|
+
const shardRel = shardRelFor("mem-old");
|
|
211
|
+
const updated = Buffer.from(
|
|
212
|
+
JSON.stringify(indexFile({ "mem-old": { vector: [0, 1], path: "facts/old.md" } })),
|
|
213
|
+
"utf-8",
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const stampBefore = await store.identityStamp();
|
|
217
|
+
|
|
218
|
+
// Force the final destructive write to fail: the publish rename cannot
|
|
219
|
+
// replace the shard file with a directory in its place.
|
|
220
|
+
const shardAbs = path.join(memoryDir, ...shardRel.split("/"));
|
|
221
|
+
await rm(shardAbs);
|
|
222
|
+
await mkdir(shardAbs);
|
|
223
|
+
|
|
224
|
+
await assert.rejects(
|
|
225
|
+
applyOfflineSyncFileContentChunk({
|
|
226
|
+
root: memoryDir,
|
|
227
|
+
sourceId: "remote",
|
|
228
|
+
path: shardRel,
|
|
229
|
+
sha256: createHash("sha256").update(updated).digest("hex"),
|
|
230
|
+
bytes: updated.length,
|
|
231
|
+
mtimeMs: 1000,
|
|
232
|
+
offset: 0,
|
|
233
|
+
content: updated,
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
assert.equal(await readMarker(memoryDir), "in-flight", "a failed publication must leave the in-flight barrier");
|
|
238
|
+
const probeA = await store.identityStamp();
|
|
239
|
+
const probeB = await store.identityStamp();
|
|
240
|
+
assert.notEqual(probeA, probeB, "an in-flight stamp never repeats, so cached probes keep revalidating");
|
|
241
|
+
assert.notEqual(probeA, stampBefore);
|
|
242
|
+
} finally {
|
|
243
|
+
await rm(memoryDir, { recursive: true, force: true }).catch(() => undefined);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
@@ -45,7 +45,12 @@ import type {
|
|
|
45
45
|
OfflineSyncFileStagingWriteTarget,
|
|
46
46
|
OfflineSyncFileState,
|
|
47
47
|
} from "./offline-sync.js";
|
|
48
|
-
import { type SafeArchiveRoot, resolveSafeArchiveTarget } from "./transfer/fs-utils.js";
|
|
48
|
+
import { type SafeArchiveRoot, prepareSafeArchiveRoot, resolveSafeArchiveTarget } from "./transfer/fs-utils.js";
|
|
49
|
+
import {
|
|
50
|
+
GENERATION_MARKER_BASENAME,
|
|
51
|
+
beginGenerationMarker,
|
|
52
|
+
completeGenerationMarker,
|
|
53
|
+
} from "./embedding-generation-marker.js";
|
|
49
54
|
|
|
50
55
|
const EMBEDDING_MARKER_BASENAME = "embeddings.json";
|
|
51
56
|
export const EMBEDDING_SHARD_DIR_BASENAME = "embeddings";
|
|
@@ -95,7 +100,12 @@ export function embeddingMarkerDirOf(relPath: string): string | null {
|
|
|
95
100
|
* `.../embeddings` directory, the legacy marker belongs to the sibling
|
|
96
101
|
* `.../embeddings` directory of its state dir.
|
|
97
102
|
*/
|
|
98
|
-
export
|
|
103
|
+
export interface EmbeddingGenerationMembership {
|
|
104
|
+
kind: "shard" | "marker";
|
|
105
|
+
shardDir: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function embeddingGenerationMembership(relPath: string): EmbeddingGenerationMembership | null {
|
|
99
109
|
const shardDir = embeddingShardDirOf(relPath);
|
|
100
110
|
if (shardDir) return { kind: "shard", shardDir };
|
|
101
111
|
const markerDir = embeddingMarkerDirOf(relPath);
|
|
@@ -103,6 +113,41 @@ export function embeddingGenerationMembership(relPath: string): { kind: "shard"
|
|
|
103
113
|
return null;
|
|
104
114
|
}
|
|
105
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Advance the node-local generation marker around one out-of-band destructive
|
|
118
|
+
* write or delete of a generation member (issue #3182); the fence reasserts
|
|
119
|
+
* lock ownership so a stale-broken lock never publishes a stable marker over
|
|
120
|
+
* a peer's write. Callers must not already hold the generation lock — the
|
|
121
|
+
* snapshot removal deletes members under it, which is why converge hooks its
|
|
122
|
+
* own call sites, not the shared IO adapter.
|
|
123
|
+
*/
|
|
124
|
+
export async function advanceEmbeddingGenerationMarker<T>(
|
|
125
|
+
rootAbs: string,
|
|
126
|
+
membership: EmbeddingGenerationMembership | null,
|
|
127
|
+
write: () => Promise<T>,
|
|
128
|
+
validate?: () => Promise<void>,
|
|
129
|
+
): Promise<T> {
|
|
130
|
+
if (!membership) {
|
|
131
|
+
await validate?.();
|
|
132
|
+
return write();
|
|
133
|
+
}
|
|
134
|
+
const root = await prepareSafeArchiveRoot(rootAbs, "advanceEmbeddingGenerationMarker", "root");
|
|
135
|
+
const stateDirRel = membership.shardDir.slice(0, -`/${EMBEDDING_SHARD_DIR_BASENAME}`.length);
|
|
136
|
+
const stateDirAbs = await resolveSafeArchiveTarget(root, stateDirRel);
|
|
137
|
+
const lockPath = embeddingGenerationLockPath(stateDirAbs);
|
|
138
|
+
return await withEmbeddingGenerationLock(stateDirAbs, async (section) => {
|
|
139
|
+
const markerAbs = await resolveSafeArchiveTarget(root, `${stateDirRel}/${GENERATION_MARKER_BASENAME}`);
|
|
140
|
+
await validate?.();
|
|
141
|
+
await assertEmbeddingGenerationLockHeld(lockPath, section);
|
|
142
|
+
await beginGenerationMarker(markerAbs);
|
|
143
|
+
await assertEmbeddingGenerationLockHeld(lockPath, section);
|
|
144
|
+
const result = await write();
|
|
145
|
+
await assertEmbeddingGenerationLockHeld(lockPath, section);
|
|
146
|
+
await completeGenerationMarker(markerAbs);
|
|
147
|
+
return result;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
106
151
|
export interface IncomingEmbeddingGenerations {
|
|
107
152
|
/** Generation dirs with at least one incoming shard. */
|
|
108
153
|
shardDirs: Set<string>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { EmbeddingGenerationLockLostError, embeddingGenerationLockPath } from "./embedding-generation-lock.js";
|
|
8
|
+
import { advanceEmbeddingGenerationMarker, embeddingGenerationMembership } from "./offline-sync-embedding-generation.js";
|
|
9
|
+
|
|
10
|
+
// Lose ownership exactly after the in-flight rename, before any content write.
|
|
11
|
+
test("a direct embedding mutation rechecks ownership after its in-flight marker", async (t) => {
|
|
12
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "remnic-embedding-fence-"));
|
|
13
|
+
const stateDir = path.join(root, "state");
|
|
14
|
+
const relative = "state/embeddings/shard-0000.json";
|
|
15
|
+
const target = path.join(root, relative);
|
|
16
|
+
const marker = path.join(stateDir, "embeddings.generation");
|
|
17
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
18
|
+
await fs.writeFile(target, "winning generation");
|
|
19
|
+
await fs.writeFile(marker, "completed generation");
|
|
20
|
+
const rename = fs.rename;
|
|
21
|
+
t.mock.method(fs, "rename", async (source: Parameters<typeof rename>[0], destination: Parameters<typeof rename>[1]) => {
|
|
22
|
+
await rename(source, destination);
|
|
23
|
+
if (String(destination) === marker && await fs.readFile(marker, "utf8") === "in-flight") {
|
|
24
|
+
await fs.unlink(embeddingGenerationLockPath(stateDir));
|
|
25
|
+
await fs.writeFile(marker, "peer completed generation");
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
syncBuiltinESMExports();
|
|
29
|
+
t.after(() => { t.mock.restoreAll(); syncBuiltinESMExports(); });
|
|
30
|
+
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
31
|
+
let writes = 0;
|
|
32
|
+
await assert.rejects(
|
|
33
|
+
advanceEmbeddingGenerationMarker(root, embeddingGenerationMembership(relative), async () => {
|
|
34
|
+
writes++;
|
|
35
|
+
await fs.writeFile(target, "stale owner's generation");
|
|
36
|
+
}),
|
|
37
|
+
EmbeddingGenerationLockLostError,
|
|
38
|
+
);
|
|
39
|
+
assert.equal(writes, 0, "a lost owner must stop before touching generation bytes");
|
|
40
|
+
assert.equal(await fs.readFile(target, "utf8"), "winning generation");
|
|
41
|
+
assert.equal(await fs.readFile(marker, "utf8"), "peer completed generation");
|
|
42
|
+
});
|
|
@@ -77,9 +77,16 @@ export async function plainFileDigest(filePath: string): Promise<string> {
|
|
|
77
77
|
return hash.digest("hex");
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
import { lstat, utimes } from "node:fs/promises";
|
|
80
|
+
import { lstat, realpath, utimes } from "node:fs/promises";
|
|
81
81
|
import { isCensusMtimeMs } from "./census-validation.js";
|
|
82
|
-
import
|
|
82
|
+
import path from "node:path";
|
|
83
|
+
import {
|
|
84
|
+
fromPosixRelPath,
|
|
85
|
+
isPathInsideRoot,
|
|
86
|
+
resolveSafeArchiveTarget,
|
|
87
|
+
type SafeArchiveRoot,
|
|
88
|
+
validateArchiveRelativePath,
|
|
89
|
+
} from "./transfer/fs-utils.js";
|
|
83
90
|
|
|
84
91
|
/** Sync-internal directory spooling partial uploads and coordination state. */
|
|
85
92
|
export const SYNC_INTERNAL_DIR = ".offline-sync";
|
|
@@ -124,3 +131,55 @@ export async function setSafeFileMtime(
|
|
|
124
131
|
await utimes(target, mtime, mtime);
|
|
125
132
|
return true;
|
|
126
133
|
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* True when a regular (non-symlink) file exists at the archive-relative path.
|
|
137
|
+
* A symlink is not a regular local file and reads as absent, so callers treat
|
|
138
|
+
* it like any other missing path instead of failing the whole push.
|
|
139
|
+
*/
|
|
140
|
+
export async function offlineSyncRegularFilePresent(
|
|
141
|
+
root: SafeArchiveRoot,
|
|
142
|
+
relPath: string,
|
|
143
|
+
): Promise<boolean> {
|
|
144
|
+
const target = path.resolve(root.abs, fromPosixRelPath(validateArchiveRelativePath(relPath, root.errorPrefix)));
|
|
145
|
+
if (!isPathInsideRoot(root.abs, target)) {
|
|
146
|
+
throw new Error(`${root.errorPrefix}: record path escapes target root: ${relPath}`);
|
|
147
|
+
}
|
|
148
|
+
const st = await lstat(target).catch((error: NodeJS.ErrnoException) => {
|
|
149
|
+
// SAFETY: Node fs errors carry `code`; only ENOENT means absent, all other
|
|
150
|
+
// errno codes must propagate so census uncertainty fails closed.
|
|
151
|
+
if (error.code === "ENOENT") return null;
|
|
152
|
+
throw error;
|
|
153
|
+
});
|
|
154
|
+
if (!st || st.isSymbolicLink() || !st.isFile()) return false;
|
|
155
|
+
// A regular file still has to resolve inside the root (a symlinked parent directory escapes it).
|
|
156
|
+
await resolveSafeArchiveTarget(root, relPath);
|
|
157
|
+
// The census skips a symlinked directory, so a file reached only through one is absent here too.
|
|
158
|
+
return (await realpath(target)) === path.join(root.real, path.relative(root.abs, target));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** A path the census observed vanished before its digest was read: a retryable race, never a confirmed delete. */
|
|
162
|
+
export class OfflineSyncCensusRaceError extends Error {
|
|
163
|
+
constructor(readonly relPath: string, cause?: unknown) {
|
|
164
|
+
super(
|
|
165
|
+
`offline sync census race: ${relPath} vanished during digest; retry the snapshot`,
|
|
166
|
+
cause === undefined ? undefined : { cause },
|
|
167
|
+
);
|
|
168
|
+
this.name = "OfflineSyncCensusRaceError";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The message the CLI push retry loop matches: it defers the path and rebuilds the census. */
|
|
173
|
+
export function changedWhileBuildingChangesetError(relPath: string, cause?: unknown): Error {
|
|
174
|
+
return new Error(
|
|
175
|
+
`offline sync file changed while building changeset: ${relPath}`,
|
|
176
|
+
cause === undefined ? undefined : { cause },
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** A `.catch` handler: a census race while building a changeset upsert stays retryable. */
|
|
181
|
+
export function censusRaceAsChangedWhileBuilding(relPath: string): (error: Error) => never {
|
|
182
|
+
return (error) => {
|
|
183
|
+
throw error instanceof OfflineSyncCensusRaceError ? changedWhileBuildingChangesetError(relPath, error) : error;
|
|
184
|
+
};
|
|
185
|
+
}
|