@remnic/core 10.0.49 → 10.0.51
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 +49 -48
- package/dist/access-authorization-probe.js +50 -49
- package/dist/access-boundary.d.ts +6 -1
- package/dist/access-boundary.d.ts.map +1 -1
- package/dist/access-boundary.js +49 -48
- package/dist/access-cli.js +120 -119
- package/dist/access-cli.js.map +1 -1
- package/dist/access-errors.d.ts.map +1 -1
- package/dist/access-errors.js +1 -1
- package/dist/access-extraction-force-flush.js +49 -48
- 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 +50 -49
- package/dist/access-http-query.js +51 -50
- package/dist/access-http.d.ts.map +1 -1
- package/dist/access-http.js +70 -69
- package/dist/access-identity-continuity-surface.js +49 -48
- package/dist/access-lcm-surface.js +49 -48
- package/dist/access-mcp.js +60 -59
- package/dist/access-memory-search-fanout.js +2 -2
- package/dist/access-namespace-preflight.js +49 -48
- 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 +10 -10
- package/dist/access-observe-write-surface.d.ts.map +1 -1
- package/dist/access-observe-write-surface.js +49 -48
- package/dist/access-offline-manifest.js +6 -6
- package/dist/access-operations-batch.js +50 -49
- package/dist/access-operations.js +57 -56
- package/dist/access-recall-concurrency.js +49 -48
- package/dist/access-recall-diagnostics.js +2 -2
- package/dist/access-recall-response.js +49 -48
- package/dist/access-recall-surface.js +49 -48
- package/dist/access-schema.d.ts +245 -773
- package/dist/access-schema.d.ts.map +1 -1
- package/dist/access-schema.js +6 -6
- package/dist/access-service.d.ts +5 -0
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +49 -48
- package/dist/access-surface-catalog.js +51 -50
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/access-token-capabilities.js +2 -2
- package/dist/active-recall.js +16 -16
- 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 +12 -11
- package/dist/buffer.js +3 -3
- package/dist/calibration.js +2 -2
- package/dist/{capsule-merge-RNQUP2VL.js → capsule-merge-D4BTIJGK.js} +3 -3
- package/dist/causal-consolidation.js +11 -11
- package/dist/{chunk-ESDOQEJM.js → chunk-2AKIVSHP.js} +2 -2
- package/dist/{chunk-BWF3KNSQ.js → chunk-2EXF6IIB.js} +2 -2
- package/dist/{chunk-AGCEJDG2.js → chunk-2ONKXEC7.js} +3 -3
- package/dist/{chunk-3NYIXVCT.js → chunk-2S6JADBB.js} +2 -2
- package/dist/chunk-2S6JADBB.js.map +1 -0
- package/dist/{chunk-ZSEFGDCI.js → chunk-2TGIKMMZ.js} +2 -2
- package/dist/{chunk-MCS4LIXP.js → chunk-43TUWQNR.js} +2 -2
- package/dist/{chunk-Q6PYCPHO.js → chunk-44EUCSG4.js} +2 -2
- package/dist/{chunk-2J6BIOVN.js → chunk-4FM35YIA.js} +3 -3
- package/dist/{chunk-2NLCDNGA.js → chunk-564YP3DW.js} +2 -2
- package/dist/{chunk-YY6PWFUC.js → chunk-6ZAU7LAR.js} +3 -3
- package/dist/{chunk-HCPVL6SF.js → chunk-72CBJ5VP.js} +3 -3
- package/dist/{chunk-AF4UIHON.js → chunk-74AY3S7U.js} +9 -9
- package/dist/{chunk-N6EM7ESC.js → chunk-75BK4XGV.js} +27 -19
- package/dist/chunk-75BK4XGV.js.map +1 -0
- package/dist/{chunk-5IDQVE4U.js → chunk-A3UFV4K3.js} +6 -6
- package/dist/{chunk-LWRIGKFM.js → chunk-A5L5KGDI.js} +19 -1
- package/dist/chunk-A5L5KGDI.js.map +1 -0
- package/dist/{chunk-RZDDNH63.js → chunk-A6JDT6QF.js} +27 -15
- package/dist/chunk-A6JDT6QF.js.map +1 -0
- package/dist/{chunk-XFND63OB.js → chunk-AHGOOZUC.js} +2 -2
- package/dist/{chunk-A7AISUJI.js → chunk-AJG2TAMR.js} +2 -2
- package/dist/{chunk-2BY56HE6.js → chunk-AZLD4CKG.js} +36 -36
- package/dist/{chunk-E47UQBYW.js → chunk-B35EQQ7L.js} +3 -3
- package/dist/{chunk-E6UNM67F.js → chunk-B6SPPYGJ.js} +2 -2
- package/dist/{chunk-CMOYD5CA.js → chunk-CCL2OXFO.js} +2 -2
- package/dist/{chunk-SVJ5JVZO.js → chunk-CK6GCGMX.js} +4 -4
- package/dist/{chunk-QZK6HG4J.js → chunk-DF6ZF2BN.js} +3 -3
- package/dist/{chunk-CPBYKOTC.js → chunk-DJFULNXK.js} +4 -4
- package/dist/{chunk-VGRBKEBH.js → chunk-DQOGZZJA.js} +3 -3
- package/dist/{chunk-YB3WN6XA.js → chunk-EC2KVA27.js} +12 -12
- package/dist/{chunk-FRXS6SC7.js → chunk-EKUI7I2X.js} +20 -4
- package/dist/chunk-EKUI7I2X.js.map +1 -0
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-WIBWONL4.js → chunk-EZS6QJNC.js} +2 -2
- package/dist/{chunk-C7WDWD35.js → chunk-FNNGWN3O.js} +5 -5
- package/dist/{chunk-PBWJVRKE.js → chunk-FT75AQ74.js} +3 -3
- package/dist/{chunk-VY6BD5KB.js → chunk-G6A3SGCE.js} +14 -12
- package/dist/chunk-G6A3SGCE.js.map +1 -0
- package/dist/{chunk-O4CA53UY.js → chunk-GBGU5OZP.js} +111 -104
- package/dist/chunk-GBGU5OZP.js.map +1 -0
- package/dist/{chunk-WJCAAVZX.js → chunk-GE3ZX6YK.js} +2 -2
- package/dist/{chunk-3PVKV3XL.js → chunk-HCIRR3BA.js} +3 -3
- package/dist/{chunk-BJTFLP3J.js → chunk-HEF4BRMT.js} +3 -3
- package/dist/{chunk-BMG3SAXA.js → chunk-HQUYENKK.js} +9 -12
- package/dist/chunk-HQUYENKK.js.map +1 -0
- package/dist/{chunk-6UDLN4IA.js → chunk-I6ORDT54.js} +2 -2
- package/dist/{chunk-N5PISU26.js → chunk-IN6U2BS5.js} +2 -2
- package/dist/{chunk-3INLTKTW.js → chunk-IUQXRRJB.js} +4 -4
- package/dist/{chunk-6W3QH3QW.js → chunk-JIJAWTRK.js} +388 -292
- package/dist/chunk-JIJAWTRK.js.map +1 -0
- package/dist/{chunk-4KS4IHKH.js → chunk-JX3E4XCW.js} +2 -2
- package/dist/{chunk-SW3WHVKN.js → chunk-KAJYSO3U.js} +2 -2
- package/dist/{chunk-D76AHITY.js → chunk-KETS5A62.js} +2 -2
- package/dist/{chunk-LCHFINL4.js → chunk-KIWO4327.js} +2 -2
- package/dist/{chunk-FC3ZFEXF.js → chunk-KJ3MDX4V.js} +2 -2
- package/dist/{chunk-7LCGFUMB.js → chunk-KQAFEZQX.js} +4 -4
- package/dist/{chunk-6TIVVOVU.js → chunk-L3EFYRYK.js} +9 -9
- package/dist/{chunk-3MUKQQ7A.js → chunk-LGEH473S.js} +24 -29
- package/dist/chunk-LGEH473S.js.map +1 -0
- package/dist/{chunk-X52SXKVC.js → chunk-LMB47UM6.js} +2 -2
- package/dist/{chunk-A3WXV7YU.js → chunk-LWMLBVGB.js} +2 -2
- package/dist/{chunk-KMTMMBEB.js → chunk-MC7PXJDP.js} +7 -7
- package/dist/{chunk-NMOEOQV4.js → chunk-MUNYUSBU.js} +2 -2
- package/dist/{chunk-VKUMKVEU.js → chunk-OCRLJJOV.js} +5 -5
- package/dist/{chunk-XSJL62LE.js → chunk-ON77FQXH.js} +2 -2
- package/dist/{chunk-C3HRYVGD.js → chunk-POJRFSJM.js} +3 -3
- package/dist/{chunk-LKRXGOQW.js → chunk-QA3Y2O6H.js} +2 -2
- package/dist/{chunk-23ZLN5X6.js → chunk-QT5MGENX.js} +14 -6
- package/dist/chunk-QT5MGENX.js.map +1 -0
- package/dist/{chunk-QFTNWDJ6.js → chunk-RA7YRL6H.js} +2 -2
- package/dist/{chunk-WQFHHJWK.js → chunk-RJDYDF5K.js} +4 -4
- package/dist/{chunk-E3DL3CAL.js → chunk-ROAKLYOL.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-NWIT3U2N.js → chunk-S5CQI2KF.js} +2 -2
- package/dist/{chunk-P6P35FY4.js → chunk-SDESKFKT.js} +2 -2
- package/dist/{chunk-OD7ZXCUF.js → chunk-SFRBUBUZ.js} +10 -10
- package/dist/{chunk-7XFQSQYS.js → chunk-SHNM7RIT.js} +5 -4
- package/dist/chunk-SHNM7RIT.js.map +1 -0
- package/dist/{chunk-NH37SP6V.js → chunk-SJD334AG.js} +81 -78
- package/dist/chunk-SJD334AG.js.map +1 -0
- package/dist/{chunk-O7GNSHFF.js → chunk-SPTPBKZX.js} +2 -2
- package/dist/{chunk-HI53EK3Y.js → chunk-TC55M5PO.js} +2 -2
- package/dist/{chunk-SA6P2KS3.js → chunk-TEH5MDX5.js} +33 -17
- package/dist/chunk-TEH5MDX5.js.map +1 -0
- package/dist/{chunk-FOKTW6GY.js → chunk-UCLJTGP3.js} +2 -2
- package/dist/{chunk-FYZXZUGU.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-754XN6LX.js → chunk-V27FK6D6.js} +2 -2
- package/dist/{chunk-DM6SOROF.js → chunk-V6Z4NAJZ.js} +2 -2
- package/dist/{chunk-5FNZ3G6X.js → chunk-VKQI47DJ.js} +4 -4
- package/dist/{chunk-NUK5XU3P.js → chunk-VUMWVYMR.js} +28 -13
- package/dist/chunk-VUMWVYMR.js.map +1 -0
- package/dist/{chunk-MPJDQC4G.js → chunk-WJCXUE2J.js} +2 -2
- package/dist/{chunk-VCBS5GRQ.js → chunk-WY66J4PI.js} +2 -2
- package/dist/chunk-X2KO4FK5.js +40 -0
- package/dist/chunk-X2KO4FK5.js.map +1 -0
- package/dist/{chunk-OOOX5C4X.js → chunk-XL2EE5W3.js} +4 -4
- package/dist/{chunk-V4GPFCJ7.js → chunk-XWJUFER3.js} +3 -3
- package/dist/{chunk-CZVNOTLR.js → chunk-XXR4A33N.js} +2 -2
- package/dist/{chunk-CRJX4OMN.js → chunk-Y3T5KQCR.js} +2 -2
- package/dist/{chunk-KNCR7APE.js → chunk-Y77FXULH.js} +8 -8
- package/dist/{chunk-2AV2U2SZ.js → chunk-YDIOMDBW.js} +2 -2
- package/dist/{chunk-IG747LRN.js → chunk-YO2JEX2Z.js} +2 -2
- package/dist/{chunk-HDT3DMUF.js → chunk-ZKK3VMWU.js} +2 -2
- package/dist/cli.js +92 -91
- package/dist/coding/export-okf-codegraph.js +9 -9
- package/dist/compounding/engine.js +11 -11
- package/dist/config.js +16 -16
- package/dist/connectors/codex-materialize-runner.js +10 -10
- package/dist/connectors/index.js +14 -14
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.js +2 -2
- package/dist/convergence-refresh.js +12 -12
- package/dist/corpus-watermark.js +14 -14
- 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-config.js +2 -2
- package/dist/deep-recall-http-glue.js +9 -9
- package/dist/{entity-canonical-id-migration-2YOLMOFC.js → entity-canonical-id-migration-F3JVG6K6.js} +3 -3
- package/dist/entity-canonical-id-migration-F3JVG6K6.js.map +1 -0
- package/dist/entity-retrieval.js +11 -11
- package/dist/explicit-capture.js +2 -2
- package/dist/external-wiki-access.js +50 -49
- package/dist/extraction-span-wire.d.ts.map +1 -1
- package/dist/extraction-span-wire.js +1 -1
- package/dist/extraction.js +22 -22
- package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
- package/dist/harmonic-retrieval.js +10 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +143 -135
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +5 -5
- package/dist/local-llm.js +2 -2
- package/dist/location/index.js +16 -16
- package/dist/location/tagging.d.ts +7 -5
- package/dist/location/tagging.d.ts.map +1 -1
- package/dist/maintenance/memory-governance.js +10 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
- package/dist/maintenance/rebuild-memory-projection-runner.js +12 -12
- package/dist/maintenance/rebuild-memory-projection.js +11 -11
- package/dist/memory-browse-http-glue.js +2 -2
- package/dist/memory-browse-operations.js +50 -49
- package/dist/namespaces/migrate.js +11 -11
- package/dist/namespaces/storage.js +10 -10
- 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-impression-drain.js +2 -2
- package/dist/offline-sync-snapshot-stream.js +4 -4
- package/dist/offline-sync-upload-staging.js +2 -2
- package/dist/offline-sync.d.ts.map +1 -1
- package/dist/offline-sync.js +3 -3
- package/dist/operator-doctor-corpus.js +17 -17
- package/dist/operator-doctor-replica.js +4 -4
- package/dist/operator-toolkit.js +29 -29
- package/dist/orchestration/maintenance.js +25 -25
- package/dist/orchestrator.d.ts +1 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +122 -121
- package/dist/page-versioning.js +2 -2
- package/dist/recall-navigation-http-glue.js +2 -2
- package/dist/recall-navigation-operations.js +50 -49
- package/dist/recall-navigation.js +49 -48
- package/dist/relay/mission.d.ts +731 -3403
- package/dist/relay/mission.d.ts.map +1 -1
- package/dist/replica-divergence.js +3 -3
- package/dist/resume-bundles.js +17 -17
- package/dist/schemas.d.ts +346 -1771
- package/dist/schemas.d.ts.map +1 -1
- package/dist/semantic-consolidation.js +15 -15
- package/dist/semantic-rule-promotion.js +10 -10
- package/dist/semantic-rule-verifier.js +10 -10
- package/dist/shared-context/manager.d.ts +13 -33
- package/dist/shared-context/manager.d.ts.map +1 -1
- package/dist/source-agent-qualifier.js +18 -18
- package/dist/standup/build.js +11 -11
- 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 +9 -9
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/access-operations.d.ts +51 -51
- package/dist/support-passport/contracts.d.ts +85 -126
- package/dist/support-passport/contracts.d.ts.map +1 -1
- package/dist/support-passport/generated-batch.d.ts +3 -17
- package/dist/support-passport/generated-batch.d.ts.map +1 -1
- package/dist/support-passport/grant-contracts.d.ts +75 -381
- package/dist/support-passport/grant-contracts.d.ts.map +1 -1
- package/dist/support-passport/index.js +52 -51
- package/dist/support-passport/model-audit.d.ts +17 -82
- package/dist/support-passport/model-audit.d.ts.map +1 -1
- package/dist/support-passport/model-bridge.d.ts +12 -51
- package/dist/support-passport/model-bridge.d.ts.map +1 -1
- package/dist/support-passport/model-contracts.d.ts +53 -192
- package/dist/support-passport/model-contracts.d.ts.map +1 -1
- package/dist/tokens.js +3 -3
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +4 -4
- package/dist/transfer/export-okf.js +10 -10
- package/dist/transfer/types.d.ts +32 -737
- package/dist/transfer/types.d.ts.map +1 -1
- package/dist/verified-recall.js +10 -10
- package/dist/who-knows-cli.js +12 -12
- package/dist/who-knows.js +11 -11
- package/package.json +3 -4
- package/src/access-boundary.test.ts +14 -0
- package/src/access-boundary.ts +48 -42
- package/src/access-category-alias.test.ts +9 -7
- package/src/access-errors.test.ts +21 -9
- package/src/access-errors.ts +45 -31
- package/src/access-http-briefing.ts +4 -0
- package/src/access-http.test.ts +41 -12
- 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-schema.ts +13 -17
- package/src/access-service.ts +5 -0
- 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/correction/passive-correction-notifications-abort.test.ts +42 -0
- package/src/correction/passive-correction-notifications.ts +4 -0
- package/src/entity-lazy-compatibility.test.ts +229 -0
- package/src/entity-migration-completion-notification.test.ts +148 -0
- package/src/extraction-scope-source-agent.test.ts +6 -6
- package/src/extraction-span-wire.test.ts +28 -0
- package/src/extraction-span-wire.ts +34 -6
- package/src/index.ts +1 -1
- package/src/location/tagging.ts +14 -12
- package/src/offline-sync-file-io.ts +61 -2
- package/src/offline-sync.test.ts +313 -20
- package/src/offline-sync.ts +43 -21
- 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 +19 -10
- 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 +7 -2
- package/src/testing/subjects/entity-migration-completion-cache.test.ts +90 -0
- package/dist/chunk-23ZLN5X6.js.map +0 -1
- package/dist/chunk-3MUKQQ7A.js.map +0 -1
- package/dist/chunk-3NYIXVCT.js.map +0 -1
- package/dist/chunk-6W3QH3QW.js.map +0 -1
- package/dist/chunk-7XFQSQYS.js.map +0 -1
- package/dist/chunk-BMG3SAXA.js.map +0 -1
- package/dist/chunk-FRXS6SC7.js.map +0 -1
- package/dist/chunk-LWRIGKFM.js.map +0 -1
- package/dist/chunk-N6EM7ESC.js.map +0 -1
- package/dist/chunk-NH37SP6V.js.map +0 -1
- package/dist/chunk-NUK5XU3P.js.map +0 -1
- package/dist/chunk-O4CA53UY.js.map +0 -1
- package/dist/chunk-RZDDNH63.js.map +0 -1
- package/dist/chunk-SA6P2KS3.js.map +0 -1
- package/dist/chunk-SUKPM6DV.js.map +0 -1
- package/dist/chunk-VY6BD5KB.js.map +0 -1
- /package/dist/{auto-sync-BNCBMU6H.js.map → auto-sync-UQQ6YJSB.js.map} +0 -0
- /package/dist/{entity-canonical-id-migration-2YOLMOFC.js.map → briefing-deadline.js.map} +0 -0
- /package/dist/{capsule-merge-RNQUP2VL.js.map → capsule-merge-D4BTIJGK.js.map} +0 -0
- /package/dist/{chunk-ESDOQEJM.js.map → chunk-2AKIVSHP.js.map} +0 -0
- /package/dist/{chunk-BWF3KNSQ.js.map → chunk-2EXF6IIB.js.map} +0 -0
- /package/dist/{chunk-AGCEJDG2.js.map → chunk-2ONKXEC7.js.map} +0 -0
- /package/dist/{chunk-ZSEFGDCI.js.map → chunk-2TGIKMMZ.js.map} +0 -0
- /package/dist/{chunk-MCS4LIXP.js.map → chunk-43TUWQNR.js.map} +0 -0
- /package/dist/{chunk-Q6PYCPHO.js.map → chunk-44EUCSG4.js.map} +0 -0
- /package/dist/{chunk-2J6BIOVN.js.map → chunk-4FM35YIA.js.map} +0 -0
- /package/dist/{chunk-2NLCDNGA.js.map → chunk-564YP3DW.js.map} +0 -0
- /package/dist/{chunk-YY6PWFUC.js.map → chunk-6ZAU7LAR.js.map} +0 -0
- /package/dist/{chunk-HCPVL6SF.js.map → chunk-72CBJ5VP.js.map} +0 -0
- /package/dist/{chunk-AF4UIHON.js.map → chunk-74AY3S7U.js.map} +0 -0
- /package/dist/{chunk-5IDQVE4U.js.map → chunk-A3UFV4K3.js.map} +0 -0
- /package/dist/{chunk-XFND63OB.js.map → chunk-AHGOOZUC.js.map} +0 -0
- /package/dist/{chunk-A7AISUJI.js.map → chunk-AJG2TAMR.js.map} +0 -0
- /package/dist/{chunk-2BY56HE6.js.map → chunk-AZLD4CKG.js.map} +0 -0
- /package/dist/{chunk-E47UQBYW.js.map → chunk-B35EQQ7L.js.map} +0 -0
- /package/dist/{chunk-E6UNM67F.js.map → chunk-B6SPPYGJ.js.map} +0 -0
- /package/dist/{chunk-CMOYD5CA.js.map → chunk-CCL2OXFO.js.map} +0 -0
- /package/dist/{chunk-SVJ5JVZO.js.map → chunk-CK6GCGMX.js.map} +0 -0
- /package/dist/{chunk-QZK6HG4J.js.map → chunk-DF6ZF2BN.js.map} +0 -0
- /package/dist/{chunk-CPBYKOTC.js.map → chunk-DJFULNXK.js.map} +0 -0
- /package/dist/{chunk-VGRBKEBH.js.map → chunk-DQOGZZJA.js.map} +0 -0
- /package/dist/{chunk-YB3WN6XA.js.map → chunk-EC2KVA27.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-WIBWONL4.js.map → chunk-EZS6QJNC.js.map} +0 -0
- /package/dist/{chunk-C7WDWD35.js.map → chunk-FNNGWN3O.js.map} +0 -0
- /package/dist/{chunk-PBWJVRKE.js.map → chunk-FT75AQ74.js.map} +0 -0
- /package/dist/{chunk-WJCAAVZX.js.map → chunk-GE3ZX6YK.js.map} +0 -0
- /package/dist/{chunk-3PVKV3XL.js.map → chunk-HCIRR3BA.js.map} +0 -0
- /package/dist/{chunk-BJTFLP3J.js.map → chunk-HEF4BRMT.js.map} +0 -0
- /package/dist/{chunk-6UDLN4IA.js.map → chunk-I6ORDT54.js.map} +0 -0
- /package/dist/{chunk-N5PISU26.js.map → chunk-IN6U2BS5.js.map} +0 -0
- /package/dist/{chunk-3INLTKTW.js.map → chunk-IUQXRRJB.js.map} +0 -0
- /package/dist/{chunk-4KS4IHKH.js.map → chunk-JX3E4XCW.js.map} +0 -0
- /package/dist/{chunk-SW3WHVKN.js.map → chunk-KAJYSO3U.js.map} +0 -0
- /package/dist/{chunk-D76AHITY.js.map → chunk-KETS5A62.js.map} +0 -0
- /package/dist/{chunk-LCHFINL4.js.map → chunk-KIWO4327.js.map} +0 -0
- /package/dist/{chunk-FC3ZFEXF.js.map → chunk-KJ3MDX4V.js.map} +0 -0
- /package/dist/{chunk-7LCGFUMB.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-6TIVVOVU.js.map → chunk-L3EFYRYK.js.map} +0 -0
- /package/dist/{chunk-X52SXKVC.js.map → chunk-LMB47UM6.js.map} +0 -0
- /package/dist/{chunk-A3WXV7YU.js.map → chunk-LWMLBVGB.js.map} +0 -0
- /package/dist/{chunk-KMTMMBEB.js.map → chunk-MC7PXJDP.js.map} +0 -0
- /package/dist/{chunk-NMOEOQV4.js.map → chunk-MUNYUSBU.js.map} +0 -0
- /package/dist/{chunk-VKUMKVEU.js.map → chunk-OCRLJJOV.js.map} +0 -0
- /package/dist/{chunk-XSJL62LE.js.map → chunk-ON77FQXH.js.map} +0 -0
- /package/dist/{chunk-C3HRYVGD.js.map → chunk-POJRFSJM.js.map} +0 -0
- /package/dist/{chunk-LKRXGOQW.js.map → chunk-QA3Y2O6H.js.map} +0 -0
- /package/dist/{chunk-QFTNWDJ6.js.map → chunk-RA7YRL6H.js.map} +0 -0
- /package/dist/{chunk-WQFHHJWK.js.map → chunk-RJDYDF5K.js.map} +0 -0
- /package/dist/{chunk-E3DL3CAL.js.map → chunk-ROAKLYOL.js.map} +0 -0
- /package/dist/{chunk-NWIT3U2N.js.map → chunk-S5CQI2KF.js.map} +0 -0
- /package/dist/{chunk-P6P35FY4.js.map → chunk-SDESKFKT.js.map} +0 -0
- /package/dist/{chunk-OD7ZXCUF.js.map → chunk-SFRBUBUZ.js.map} +0 -0
- /package/dist/{chunk-O7GNSHFF.js.map → chunk-SPTPBKZX.js.map} +0 -0
- /package/dist/{chunk-HI53EK3Y.js.map → chunk-TC55M5PO.js.map} +0 -0
- /package/dist/{chunk-FOKTW6GY.js.map → chunk-UCLJTGP3.js.map} +0 -0
- /package/dist/{chunk-FYZXZUGU.js.map → chunk-UQBOPCDK.js.map} +0 -0
- /package/dist/{chunk-754XN6LX.js.map → chunk-V27FK6D6.js.map} +0 -0
- /package/dist/{chunk-DM6SOROF.js.map → chunk-V6Z4NAJZ.js.map} +0 -0
- /package/dist/{chunk-5FNZ3G6X.js.map → chunk-VKQI47DJ.js.map} +0 -0
- /package/dist/{chunk-MPJDQC4G.js.map → chunk-WJCXUE2J.js.map} +0 -0
- /package/dist/{chunk-VCBS5GRQ.js.map → chunk-WY66J4PI.js.map} +0 -0
- /package/dist/{chunk-OOOX5C4X.js.map → chunk-XL2EE5W3.js.map} +0 -0
- /package/dist/{chunk-V4GPFCJ7.js.map → chunk-XWJUFER3.js.map} +0 -0
- /package/dist/{chunk-CZVNOTLR.js.map → chunk-XXR4A33N.js.map} +0 -0
- /package/dist/{chunk-CRJX4OMN.js.map → chunk-Y3T5KQCR.js.map} +0 -0
- /package/dist/{chunk-KNCR7APE.js.map → chunk-Y77FXULH.js.map} +0 -0
- /package/dist/{chunk-2AV2U2SZ.js.map → chunk-YDIOMDBW.js.map} +0 -0
- /package/dist/{chunk-IG747LRN.js.map → chunk-YO2JEX2Z.js.map} +0 -0
- /package/dist/{chunk-HDT3DMUF.js.map → chunk-ZKK3VMWU.js.map} +0 -0
- /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
package/src/offline-sync.ts
CHANGED
|
@@ -70,6 +70,10 @@ import {
|
|
|
70
70
|
setSafeFileMtime,
|
|
71
71
|
assertNonNegativeFinite,
|
|
72
72
|
assertOfflineSyncMtimeMs,
|
|
73
|
+
offlineSyncRegularFilePresent,
|
|
74
|
+
OfflineSyncCensusRaceError,
|
|
75
|
+
censusRaceAsChangedWhileBuilding,
|
|
76
|
+
changedWhileBuildingChangesetError,
|
|
73
77
|
OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS,
|
|
74
78
|
} from "./offline-sync-file-io.js";
|
|
75
79
|
import { CENSUS_MAX_MTIME_MS, isSha256Hex } from "./census-validation.js";
|
|
@@ -793,19 +797,29 @@ async function canReuseFastBaseFileStateFromDisk(
|
|
|
793
797
|
|
|
794
798
|
async function readOfflineSyncFileRecord(
|
|
795
799
|
options: OfflineSyncFileRecordOptions,
|
|
796
|
-
): Promise<OfflineSyncFileRecord
|
|
800
|
+
): Promise<OfflineSyncFileRecord> {
|
|
797
801
|
try {
|
|
798
802
|
return await readPresentOfflineSyncFileRecord(options);
|
|
799
803
|
} catch (error) {
|
|
800
|
-
//
|
|
801
|
-
|
|
804
|
+
// Caller already observed the path (readdir/lstat). Digest ENOENT is an
|
|
805
|
+
// ambiguous race: exporting a silent omission assigns confirmed absence to
|
|
806
|
+
// every consumer (full snapshot apply + changeset apply). Fail closed.
|
|
807
|
+
if (isNotFoundError(error)) {
|
|
808
|
+
if (embeddingGenerationMembership(options.relPath) !== null) {
|
|
809
|
+
throw new Error(
|
|
810
|
+
`offline sync embedding generation member vanished during snapshot walk: ${options.relPath}`,
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
throw new OfflineSyncCensusRaceError(options.relPath, error);
|
|
814
|
+
}
|
|
802
815
|
throw error;
|
|
803
816
|
}
|
|
804
817
|
}
|
|
805
818
|
|
|
806
|
-
|
|
807
|
-
//
|
|
808
|
-
//
|
|
819
|
+
|
|
820
|
+
// Pre-stat vanish (readdir then lstat/stat ENOENT) may omit only when nothing
|
|
821
|
+
// can misread the gap: embedding generations travel as one unit (issue #3148),
|
|
822
|
+
// and a base-known file omitted here would fabricate a delete in
|
|
809
823
|
// buildOfflineSyncChangesetFromSnapshot.
|
|
810
824
|
function assertSnapshotVanishIsSafe(relPath: string, base: ReadonlyMap<string, unknown> | undefined): void {
|
|
811
825
|
if (embeddingGenerationMembership(relPath) !== null) {
|
|
@@ -816,6 +830,7 @@ function assertSnapshotVanishIsSafe(relPath: string, base: ReadonlyMap<string, u
|
|
|
816
830
|
}
|
|
817
831
|
}
|
|
818
832
|
|
|
833
|
+
|
|
819
834
|
async function readPresentOfflineSyncFileRecord(
|
|
820
835
|
options: OfflineSyncFileRecordOptions,
|
|
821
836
|
): Promise<OfflineSyncFileRecord> {
|
|
@@ -923,7 +938,7 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
|
|
|
923
938
|
path: relPosix,
|
|
924
939
|
filePath: abs,
|
|
925
940
|
})) continue;
|
|
926
|
-
|
|
941
|
+
yield await readOfflineSyncFileRecord({
|
|
927
942
|
root,
|
|
928
943
|
relPath: relPosix,
|
|
929
944
|
filePath: abs,
|
|
@@ -932,8 +947,7 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
|
|
|
932
947
|
readFileDigest: options.readFileDigest,
|
|
933
948
|
signal: options.signal,
|
|
934
949
|
});
|
|
935
|
-
|
|
936
|
-
else assertSnapshotVanishIsSafe(relPosix, undefined);
|
|
950
|
+
|
|
937
951
|
}
|
|
938
952
|
}
|
|
939
953
|
|
|
@@ -1094,8 +1108,7 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
|
|
|
1094
1108
|
try {
|
|
1095
1109
|
st = await stat(abs);
|
|
1096
1110
|
} catch (error) {
|
|
1097
|
-
//
|
|
1098
|
-
// path could fabricate a delete or downgrade a generation.
|
|
1111
|
+
// Pre-stat vanish after readdir: omit only when assertSnapshotVanishIsSafe allows.
|
|
1099
1112
|
if (isNotFoundError(error)) {
|
|
1100
1113
|
assertSnapshotVanishIsSafe(relPosix, base);
|
|
1101
1114
|
continue;
|
|
@@ -1112,7 +1125,7 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
|
|
|
1112
1125
|
files.push(baseEntry);
|
|
1113
1126
|
continue;
|
|
1114
1127
|
}
|
|
1115
|
-
|
|
1128
|
+
files.push(await readOfflineSyncFileRecord({
|
|
1116
1129
|
root,
|
|
1117
1130
|
relPath: relPosix,
|
|
1118
1131
|
filePath: abs,
|
|
@@ -1120,9 +1133,7 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
|
|
|
1120
1133
|
readFile: options.readFile,
|
|
1121
1134
|
readFileDigest: options.readFileDigest,
|
|
1122
1135
|
signal: options.signal,
|
|
1123
|
-
});
|
|
1124
|
-
if (record) files.push(record);
|
|
1125
|
-
else assertSnapshotVanishIsSafe(relPosix, base);
|
|
1136
|
+
}));
|
|
1126
1137
|
}
|
|
1127
1138
|
}
|
|
1128
1139
|
|
|
@@ -1208,7 +1219,7 @@ export async function buildOfflineSyncSnapshotForPaths(options: {
|
|
|
1208
1219
|
})) {
|
|
1209
1220
|
throw new Error(`offline sync snapshot path is excluded: ${relPath}`);
|
|
1210
1221
|
}
|
|
1211
|
-
|
|
1222
|
+
files.push(await readOfflineSyncFileRecord({
|
|
1212
1223
|
root,
|
|
1213
1224
|
relPath,
|
|
1214
1225
|
filePath,
|
|
@@ -1216,9 +1227,7 @@ export async function buildOfflineSyncSnapshotForPaths(options: {
|
|
|
1216
1227
|
readFile: options.readFile,
|
|
1217
1228
|
readFileDigest: options.readFileDigest,
|
|
1218
1229
|
signal: options.signal,
|
|
1219
|
-
});
|
|
1220
|
-
if (record) files.push(record);
|
|
1221
|
-
else assertSnapshotVanishIsSafe(relPath, undefined);
|
|
1230
|
+
}));
|
|
1222
1231
|
}
|
|
1223
1232
|
throwIfOfflineSyncAborted(options.signal);
|
|
1224
1233
|
|
|
@@ -1414,6 +1423,11 @@ export async function buildOfflineSyncChangesetFromSnapshot(options: {
|
|
|
1414
1423
|
excludeNodeLocalState?: boolean;
|
|
1415
1424
|
readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
|
|
1416
1425
|
}): Promise<OfflineSyncChangeset> {
|
|
1426
|
+
const root = await prepareSafeArchiveRoot(
|
|
1427
|
+
path.resolve(options.root),
|
|
1428
|
+
"buildOfflineSyncChangesetFromSnapshot",
|
|
1429
|
+
"root",
|
|
1430
|
+
);
|
|
1417
1431
|
const includeTranscripts = options.includeTranscripts !== false;
|
|
1418
1432
|
const excludedPaths = new Set(
|
|
1419
1433
|
(options.excludePaths ?? []).map((relPath) => normalizeRelativePath(relPath, "excludePaths[]")),
|
|
@@ -1453,10 +1467,10 @@ export async function buildOfflineSyncChangesetFromSnapshot(options: {
|
|
|
1453
1467
|
now: options.now,
|
|
1454
1468
|
userExcludeRegexps: options.userExcludeRegexps,
|
|
1455
1469
|
readFile: options.readFile,
|
|
1456
|
-
});
|
|
1470
|
+
}).catch(censusRaceAsChangedWhileBuilding(relPath));
|
|
1457
1471
|
const record = file.files[0];
|
|
1458
1472
|
if (!record || typeof record.contentBase64 !== "string" || record.sha256 !== currentEntry.sha256) {
|
|
1459
|
-
throw
|
|
1473
|
+
throw changedWhileBuildingChangesetError(relPath);
|
|
1460
1474
|
}
|
|
1461
1475
|
changes.push({
|
|
1462
1476
|
type: "upsert",
|
|
@@ -1467,6 +1481,8 @@ export async function buildOfflineSyncChangesetFromSnapshot(options: {
|
|
|
1467
1481
|
continue;
|
|
1468
1482
|
}
|
|
1469
1483
|
if (!currentEntry && baseEntry) {
|
|
1484
|
+
// Census miss is not confirmed delete — recheck disk (transient ENOENT / recreate).
|
|
1485
|
+
if (await offlineSyncRegularFilePresent(root, relPath)) continue;
|
|
1470
1486
|
const mtimeMs = deletionMtimeByPath?.get(relPath);
|
|
1471
1487
|
changes.push({
|
|
1472
1488
|
type: "delete",
|
|
@@ -1808,6 +1824,12 @@ export async function applyOfflineSyncSnapshot(options: {
|
|
|
1808
1824
|
continue;
|
|
1809
1825
|
}
|
|
1810
1826
|
if (!currentEntry && !base) {
|
|
1827
|
+
// Census miss with on-disk file: uncertain local presence, do not overwrite.
|
|
1828
|
+
if (await offlineSyncRegularFilePresent(root, relPath)) {
|
|
1829
|
+
pendingLocal += 1;
|
|
1830
|
+
skipped += 1;
|
|
1831
|
+
continue;
|
|
1832
|
+
}
|
|
1811
1833
|
await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile, incoming.mtimeMs);
|
|
1812
1834
|
nextBase.set(relPath, toFileState(incoming));
|
|
1813
1835
|
upserted += 1;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import test from "node:test";
|
|
@@ -1547,3 +1548,50 @@ test("startupSearchSync composes its caller abort into the QMD probe", async ()
|
|
|
1547
1548
|
await rm(memoryDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
|
|
1548
1549
|
}
|
|
1549
1550
|
});
|
|
1551
|
+
|
|
1552
|
+
// Both startup call sites must open read readiness without migration replay.
|
|
1553
|
+
for (const namespace of ["default", "shared"]) {
|
|
1554
|
+
test(`startup defers entity migration for ${namespace} storage`, async () => {
|
|
1555
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-init-migration-defer-"));
|
|
1556
|
+
const orchestrator = new Orchestrator(
|
|
1557
|
+
parseConfig({
|
|
1558
|
+
memoryDir,
|
|
1559
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1560
|
+
qmdEnabled: false,
|
|
1561
|
+
namespacesEnabled: namespace === "shared",
|
|
1562
|
+
namespaceCatalogEnabled: false,
|
|
1563
|
+
defaultNamespace: "default",
|
|
1564
|
+
sharedNamespace: "shared",
|
|
1565
|
+
namespacePolicies: [],
|
|
1566
|
+
embeddingFallbackEnabled: false,
|
|
1567
|
+
identityContinuityEnabled: false,
|
|
1568
|
+
transcriptEnabled: false,
|
|
1569
|
+
hourlySummariesEnabled: false,
|
|
1570
|
+
compoundingEnabled: false,
|
|
1571
|
+
knowledgeIndexEnabled: false,
|
|
1572
|
+
}),
|
|
1573
|
+
);
|
|
1574
|
+
const storage = await orchestrator.getStorage(namespace);
|
|
1575
|
+
const lockPath = path.join(storage.dir, "state", "entity-canonical-id-migration.lock");
|
|
1576
|
+
const journalPath = path.join(storage.dir, "state", "entity-canonical-id-migration-v1.json");
|
|
1577
|
+
try {
|
|
1578
|
+
await mkdir(path.dirname(lockPath), { recursive: true });
|
|
1579
|
+
await writeFile(lockPath, `${process.pid} init-migration-test ${new Date().toISOString()}\n`);
|
|
1580
|
+
const outcome = await withTestDeadline(
|
|
1581
|
+
orchestrator.initialize().then(() => "settled" as const, () => "rejected" as const),
|
|
1582
|
+
);
|
|
1583
|
+
assert.equal(outcome, "settled", "startup must succeed while the migration lock is held");
|
|
1584
|
+
assert.equal(existsSync(lockPath), true);
|
|
1585
|
+
assert.equal(existsSync(journalPath), false, "startup must not run the migration");
|
|
1586
|
+
await rm(lockPath);
|
|
1587
|
+
await storage.ensureDirectories();
|
|
1588
|
+
// SAFETY: the journal is written by the migration under test and carries an optional boolean `complete`.
|
|
1589
|
+
const state = JSON.parse(await readFile(journalPath, "utf8")) as { complete?: boolean };
|
|
1590
|
+
assert.equal(state.complete, true, "default setup must still run migration");
|
|
1591
|
+
} finally {
|
|
1592
|
+
await rm(lockPath, { force: true });
|
|
1593
|
+
await orchestrator.destroy();
|
|
1594
|
+
await rm(memoryDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
}
|
package/src/orchestrator.ts
CHANGED
|
@@ -746,6 +746,7 @@ export class Orchestrator {
|
|
|
746
746
|
private _dependencyPropagationDelivery: DependencyPropagationDelivery | undefined;
|
|
747
747
|
private _dependencyPropagationRecovery: Promise<void> | null = null;
|
|
748
748
|
private destroyed = false;
|
|
749
|
+
private acceptingEntityMigrationMaintenance = true;
|
|
749
750
|
|
|
750
751
|
private get dependencyPropagationDelivery(): DependencyPropagationDelivery {
|
|
751
752
|
if (this.destroyed) throw new Error("orchestrator has been destroyed");
|
|
@@ -967,6 +968,7 @@ export class Orchestrator {
|
|
|
967
968
|
* commands should call it before returning to let Node exit naturally.
|
|
968
969
|
*/
|
|
969
970
|
async destroy(): Promise<void> {
|
|
971
|
+
this.acceptingEntityMigrationMaintenance = false;
|
|
970
972
|
this._orchestratorInitCoordinator?.retireStartupEpoch();
|
|
971
973
|
this.abortDeferredInit();
|
|
972
974
|
this.extractionQueueCoordinator.stopAccepting();
|
|
@@ -1499,14 +1501,6 @@ export class Orchestrator {
|
|
|
1499
1501
|
(namespace) => this.namespaceSearchRouter.backendForNamespace(namespace),
|
|
1500
1502
|
(msg) => log.debug(msg),
|
|
1501
1503
|
);
|
|
1502
|
-
const defaultNs = this.config.defaultNamespace;
|
|
1503
|
-
this.storage.onMemoryWrite = (filePath) => this.prioritizedEmbedding?.enqueue(filePath, defaultNs);
|
|
1504
|
-
this.storageRouter.onStorageCreated = (storage, namespace) => {
|
|
1505
|
-
storage.onMemoryWrite = (filePath) => this.prioritizedEmbedding?.enqueue(filePath, namespace);
|
|
1506
|
-
};
|
|
1507
|
-
this.storageRouter.forEachCachedStorage((storage, namespace) => {
|
|
1508
|
-
storage.onMemoryWrite = (filePath) => this.prioritizedEmbedding?.enqueue(filePath, namespace);
|
|
1509
|
-
});
|
|
1510
1504
|
}
|
|
1511
1505
|
this.maintenanceScheduler = new MaintenanceScheduler({
|
|
1512
1506
|
config,
|
|
@@ -1516,6 +1510,13 @@ export class Orchestrator {
|
|
|
1516
1510
|
getStorage: () => this.storage, storageForNamespace: (namespace) => this.storageRouter.storageFor(namespace),
|
|
1517
1511
|
onActivitySynced: (summary) => { if (!this.config.meetings.enabled) return; void this.getMeetingsService().then((s) => s.requestBuildForActivitySync(summary.ranAt, this.config.activity.timezone, summary.touchedDates)).catch((err) => log.warn(`meetings: activity tail-step build failed to schedule: ${err instanceof Error ? err.message : String(err)}`)); },
|
|
1518
1512
|
});
|
|
1513
|
+
const bindStorage = (storage: StorageManager, namespace: string) => {
|
|
1514
|
+
storage.onEntityMigrationComplete = () => { if (this.acceptingEntityMigrationMaintenance) this.requestQmdMaintenance(); };
|
|
1515
|
+
if (this.prioritizedEmbedding) storage.onMemoryWrite = (filePath) => this.prioritizedEmbedding?.enqueue(filePath, namespace);
|
|
1516
|
+
};
|
|
1517
|
+
bindStorage(this.storage, this.config.defaultNamespace);
|
|
1518
|
+
this.storageRouter.onStorageCreated = bindStorage;
|
|
1519
|
+
this.storageRouter.forEachCachedStorage(bindStorage);
|
|
1519
1520
|
// Issue #1526: background extraction queue lives on its own coordinator.
|
|
1520
1521
|
this.extractionQueueCoordinator = new ExtractionQueueCoordinator();
|
|
1521
1522
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from "../logger.js";
|
|
1
2
|
import type { EntityFile, MemoryFile } from "../types.js";
|
|
2
3
|
import {
|
|
3
4
|
getFingerprint,
|
|
@@ -30,6 +31,7 @@ export interface EntityCanonicalIdMigrationHost {
|
|
|
30
31
|
invalidateColdMemoriesCache(): void;
|
|
31
32
|
bumpMemoryStatusVersion(): void;
|
|
32
33
|
getEntityMutationVersion(): number;
|
|
34
|
+
onEntityMigrationComplete?: () => void;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export async function runLegacyEntityCanonicalIdMigration(
|
|
@@ -40,6 +42,9 @@ export async function runLegacyEntityCanonicalIdMigration(
|
|
|
40
42
|
): Promise<string | undefined> {
|
|
41
43
|
const readMigrationFingerprint = () =>
|
|
42
44
|
getFingerprint(host.baseDir, host.entitiesDir, () => String(host.getEntityMutationVersion()));
|
|
45
|
+
// The migration bumps the status version only after a pass that rewrote
|
|
46
|
+
// references; a no-op replay must not ask search maintenance to run.
|
|
47
|
+
let rewroteReferences = false;
|
|
43
48
|
const completionFingerprint = await migrateLegacyEntityCanonicalIds({
|
|
44
49
|
stateDir: host.stateDir,
|
|
45
50
|
entitiesDir: host.entitiesDir,
|
|
@@ -65,9 +70,19 @@ export async function runLegacyEntityCanonicalIdMigration(
|
|
|
65
70
|
invalidateKnowledgeIndexCache: host.invalidateKnowledgeIndexCache.bind(host),
|
|
66
71
|
invalidateAllMemoriesCache: host.invalidateAllMemoriesCache.bind(host),
|
|
67
72
|
invalidateColdMemoriesCache: host.invalidateColdMemoriesCache.bind(host),
|
|
68
|
-
bumpMemoryStatusVersion:
|
|
73
|
+
bumpMemoryStatusVersion: () => {
|
|
74
|
+
rewroteReferences = true;
|
|
75
|
+
host.bumpMemoryStatusVersion();
|
|
76
|
+
},
|
|
69
77
|
readMigrationFingerprint,
|
|
70
78
|
} satisfies EntityCanonicalIdMigrationDependencies);
|
|
79
|
+
if (completionFingerprint !== undefined && rewroteReferences) {
|
|
80
|
+
try {
|
|
81
|
+
host.onEntityMigrationComplete?.();
|
|
82
|
+
} catch {
|
|
83
|
+
log.warn("entity migration: search maintenance scheduling failed");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
71
86
|
// An undefined completion fingerprint is a deliberate signal (issue #2213):
|
|
72
87
|
// a reconcile marker landed while the run scanned, and recomputing the
|
|
73
88
|
// fingerprint here would seal that pending request into the runner's
|
|
@@ -46,7 +46,8 @@ const ENTITY_MAPPING_RESCAN_MAX_PASSES = 3;
|
|
|
46
46
|
const EntityCanonicalIdMigrationStateSchema = z.object({
|
|
47
47
|
version: z.literal(ENTITY_CANONICAL_ID_MIGRATION_VERSION),
|
|
48
48
|
complete: z.boolean(),
|
|
49
|
-
mappings: z.record(z.string().min(1)),
|
|
49
|
+
mappings: z.record(z.string(), z.string().min(1)),
|
|
50
|
+
completionFingerprint: z.string().optional(),
|
|
50
51
|
/**
|
|
51
52
|
* Pairs the migration refused to resolve, PARKED rather than dropped.
|
|
52
53
|
* Deleting the record would strand the collision: once an operator removes
|
|
@@ -54,7 +55,7 @@ const EntityCanonicalIdMigrationStateSchema = z.object({
|
|
|
54
55
|
* still naming the legacy id would never be rewritten. Optional so a journal
|
|
55
56
|
* written by an older build still parses.
|
|
56
57
|
*/
|
|
57
|
-
blocked: z.record(z.string().min(1)).optional(),
|
|
58
|
+
blocked: z.record(z.string(), z.string().min(1)).optional(),
|
|
58
59
|
});
|
|
59
60
|
|
|
60
61
|
type EntityCanonicalIdMigrationState = z.infer<typeof EntityCanonicalIdMigrationStateSchema>;
|
|
@@ -370,6 +371,13 @@ async function assertEntityContentUnchanged(
|
|
|
370
371
|
}
|
|
371
372
|
}
|
|
372
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Fail-closed entity-entry validation for the startup path: refuses a
|
|
376
|
+
* symlinked or non-directory entities root and any symlinked entry, without
|
|
377
|
+
* taking the migration lock. Exported so `ensureDirectories()` can run it
|
|
378
|
+
* before deferring the migration (#3139) — the same check
|
|
379
|
+
* `migrateLegacyEntityCanonicalIds` performs under the lock.
|
|
380
|
+
*/
|
|
373
381
|
export async function readSafeEntityEntries(entitiesDir: string): Promise<string[]> {
|
|
374
382
|
const rootStat = await lstat(entitiesDir);
|
|
375
383
|
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
@@ -887,14 +895,11 @@ export async function migrateLegacyEntityCanonicalIds(
|
|
|
887
895
|
if (!isErrnoCode(error, "ENOENT")) throw error;
|
|
888
896
|
}
|
|
889
897
|
}
|
|
890
|
-
let state = await readState(deps)
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
mappings: {},
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
+
let state: EntityCanonicalIdMigrationState = await readState(deps) ?? {
|
|
899
|
+
version: ENTITY_CANONICAL_ID_MIGRATION_VERSION,
|
|
900
|
+
complete: false,
|
|
901
|
+
mappings: {},
|
|
902
|
+
};
|
|
898
903
|
const blocked: BlockedEntityPairs = new Map();
|
|
899
904
|
const scanNotes: EntityScanNotes = { junk: new Set(), malformed: new Set() };
|
|
900
905
|
// Parks for pairs whose files are GONE, tracked separately from
|
|
@@ -951,6 +956,10 @@ export async function migrateLegacyEntityCanonicalIds(
|
|
|
951
956
|
// fingerprint could SEAL a marker the check never saw.
|
|
952
957
|
const completionFingerprint = await deps.readMigrationFingerprint?.();
|
|
953
958
|
if (await fileExists(reconcileMarkerPath)) return undefined;
|
|
959
|
+
if (completionFingerprint !== undefined && state.completionFingerprint !== completionFingerprint) {
|
|
960
|
+
state = { ...state, complete: true, completionFingerprint };
|
|
961
|
+
await publishState(deps, state);
|
|
962
|
+
}
|
|
954
963
|
return completionFingerprint;
|
|
955
964
|
};
|
|
956
965
|
/**
|
|
@@ -16,6 +16,7 @@ import { statSync } from "node:fs";
|
|
|
16
16
|
* reconciliation pass instead ({@link requestEntityCanonicalIdReconcile}).
|
|
17
17
|
*/
|
|
18
18
|
import path from "node:path";
|
|
19
|
+
import { z } from "zod";
|
|
19
20
|
import {
|
|
20
21
|
closeSync,
|
|
21
22
|
constants,
|
|
@@ -112,6 +113,29 @@ function readJournalFileNoFollow(statePath: string): JournalRead {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
/** The journal fields completion attestation needs; extra journal fields are ignored. */
|
|
117
|
+
const CompletedJournalAttestationSchema = z.object({
|
|
118
|
+
version: z.literal(1),
|
|
119
|
+
complete: z.literal(true),
|
|
120
|
+
completionFingerprint: z.string(),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
/** Skip compatibility scans only when journal completion attests to the live inputs. */
|
|
124
|
+
export async function entityCanonicalIdMigrationCompleted(
|
|
125
|
+
stateDir: string,
|
|
126
|
+
readFingerprint: () => Promise<string>,
|
|
127
|
+
): Promise<boolean> {
|
|
128
|
+
const result = readJournalFileNoFollow(path.join(stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE));
|
|
129
|
+
if (result.kind !== "ok") return false;
|
|
130
|
+
try {
|
|
131
|
+
const parsed = CompletedJournalAttestationSchema.safeParse(JSON.parse(result.raw));
|
|
132
|
+
if (!parsed.success) return false;
|
|
133
|
+
return parsed.data.completionFingerprint === await readFingerprint();
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
115
139
|
export function loadHistoricalEntityCanonicalIds(stateDir: string): Readonly<Record<string, string>> {
|
|
116
140
|
const result = readJournalFileNoFollow(path.join(stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE));
|
|
117
141
|
if (result.kind !== "ok") return {};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression: amortized completion must refuse a symlinked entities root
|
|
3
|
+
* before getFingerprint → fingerprintEntityEntries readdir.
|
|
4
|
+
*
|
|
5
|
+
* inputSig already labels the root "L", but isCompleted still runs the full
|
|
6
|
+
* fingerprint (external tree walk) before any isTrustedEntitiesRoot-style
|
|
7
|
+
* guard. A planted complete journal makes that path reachable after a
|
|
8
|
+
* missing-id ENOENT.
|
|
9
|
+
*
|
|
10
|
+
* Instrumentation (deterministic): spawn a plain child (not nested --test —
|
|
11
|
+
* node skips recursive test runs) with --experimental-test-module-mocks that
|
|
12
|
+
* wraps node:fs/promises.readdir via mock.module, dynamic-imports the real
|
|
13
|
+
* EntityMigrationCompletionCache, and records every readdir whose realpath is
|
|
14
|
+
* the external canary (or whose path is the entities symlink). Assert
|
|
15
|
+
* externalCount === 0 — not merely completed === false. chmod/EACCES is not
|
|
16
|
+
* used (false-green on this host).
|
|
17
|
+
*
|
|
18
|
+
* Dynamic import in the probe is intentional: mock.module must install before
|
|
19
|
+
* the cache module loads fs.promises (module-loading boundary).
|
|
20
|
+
*
|
|
21
|
+
* Temp roots only. Expected today: RED (externalCount >= 1).
|
|
22
|
+
* After root nofollow guard: externalCount === 0 and completed === false.
|
|
23
|
+
*/
|
|
24
|
+
import assert from "node:assert/strict";
|
|
25
|
+
import { spawnSync } from "node:child_process";
|
|
26
|
+
import { existsSync } from "node:fs";
|
|
27
|
+
import {
|
|
28
|
+
lstat,
|
|
29
|
+
mkdir,
|
|
30
|
+
mkdtemp,
|
|
31
|
+
readFile,
|
|
32
|
+
realpath,
|
|
33
|
+
rm,
|
|
34
|
+
symlink,
|
|
35
|
+
writeFile,
|
|
36
|
+
} from "node:fs/promises";
|
|
37
|
+
import os from "node:os";
|
|
38
|
+
import path from "node:path";
|
|
39
|
+
import test from "node:test";
|
|
40
|
+
import { fileURLToPath } from "node:url";
|
|
41
|
+
|
|
42
|
+
import { ENTITY_CANONICAL_ID_MIGRATION_FILE } from "./entity-canonical-id-references.js";
|
|
43
|
+
|
|
44
|
+
function findRepoRoot(startDir: string): string {
|
|
45
|
+
let dir = startDir;
|
|
46
|
+
for (;;) {
|
|
47
|
+
if (existsSync(path.join(dir, "packages", "remnic-core", "package.json"))) return dir;
|
|
48
|
+
const parent = path.dirname(dir);
|
|
49
|
+
if (parent === dir) {
|
|
50
|
+
throw new Error(`remnic repo root not found from ${startDir}`);
|
|
51
|
+
}
|
|
52
|
+
dir = parent;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const REPO_ROOT = findRepoRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
57
|
+
const CACHE_MODULE = path.join(
|
|
58
|
+
REPO_ROOT,
|
|
59
|
+
"packages",
|
|
60
|
+
"remnic-core",
|
|
61
|
+
"src",
|
|
62
|
+
"storage",
|
|
63
|
+
"entity-migration-completion-cache.ts",
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
type ProbeResult = {
|
|
67
|
+
completed: boolean | null;
|
|
68
|
+
err: string | null;
|
|
69
|
+
externalCount: number;
|
|
70
|
+
seen: Array<{ raw: string; real: string }>;
|
|
71
|
+
canaryReal: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
test("isCompleted must not readdir through a symlinked entities root", async () => {
|
|
75
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-entities-root-nofollow-"));
|
|
76
|
+
const memoryDir = path.join(root, "memory");
|
|
77
|
+
const canaryDir = path.join(root, "canary-external");
|
|
78
|
+
const entitiesDir = path.join(memoryDir, "entities");
|
|
79
|
+
const stateDir = path.join(memoryDir, "state");
|
|
80
|
+
const probeFile = path.join(root, "completion-cache-nofollow.probe.mts");
|
|
81
|
+
const resultFile = path.join(root, "probe-result.json");
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await mkdir(stateDir, { recursive: true });
|
|
85
|
+
await mkdir(path.join(memoryDir, "config"), { recursive: true });
|
|
86
|
+
await mkdir(canaryDir, { recursive: true });
|
|
87
|
+
await writeFile(path.join(canaryDir, "outside-only.md"), "canary external entity body\n");
|
|
88
|
+
|
|
89
|
+
await symlink(canaryDir, entitiesDir);
|
|
90
|
+
assert.equal(
|
|
91
|
+
(await lstat(entitiesDir)).isSymbolicLink(),
|
|
92
|
+
true,
|
|
93
|
+
"fixture: entities/ must be a symlink to the external canary directory",
|
|
94
|
+
);
|
|
95
|
+
const canaryReal = await realpath(canaryDir);
|
|
96
|
+
|
|
97
|
+
// Regular complete journal — completionFingerprint compare invokes getFingerprint.
|
|
98
|
+
await writeFile(
|
|
99
|
+
path.join(stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE),
|
|
100
|
+
JSON.stringify({
|
|
101
|
+
version: 1,
|
|
102
|
+
complete: true,
|
|
103
|
+
mappings: {},
|
|
104
|
+
completionFingerprint: "planted-complete-journal-for-symlink-root-regression",
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Plain probe script (no nested --test: parent already runs under the test
|
|
109
|
+
// runner, and node skips recursive test-file execution).
|
|
110
|
+
// Dynamic import loads cache after mock.module wraps readdir.
|
|
111
|
+
await writeFile(
|
|
112
|
+
probeFile,
|
|
113
|
+
`import { mock } from "node:test";
|
|
114
|
+
import path from "node:path";
|
|
115
|
+
import { writeFile } from "node:fs/promises";
|
|
116
|
+
import { readdir as realReaddir, realpath } from "node:fs/promises";
|
|
117
|
+
|
|
118
|
+
const canaryReal = ${JSON.stringify(canaryReal)};
|
|
119
|
+
const memoryDir = ${JSON.stringify(memoryDir)};
|
|
120
|
+
const entitiesDir = ${JSON.stringify(entitiesDir)};
|
|
121
|
+
const cacheModule = ${JSON.stringify(CACHE_MODULE)};
|
|
122
|
+
const resultFile = ${JSON.stringify(resultFile)};
|
|
123
|
+
const seen = [];
|
|
124
|
+
|
|
125
|
+
const fsp = await import("node:fs/promises");
|
|
126
|
+
mock.module("node:fs/promises", {
|
|
127
|
+
namedExports: {
|
|
128
|
+
...fsp,
|
|
129
|
+
readdir: async (p, ...a) => {
|
|
130
|
+
const raw = path.resolve(String(p));
|
|
131
|
+
let real = raw;
|
|
132
|
+
try { real = await realpath(raw); } catch { /* keep raw */ }
|
|
133
|
+
seen.push({ raw, real });
|
|
134
|
+
return realReaddir(p, ...a);
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Dynamic import: mock.module must wrap fs.promises before the cache loads it.
|
|
140
|
+
const { EntityMigrationCompletionCache } = await import(cacheModule);
|
|
141
|
+
const cache = new EntityMigrationCompletionCache();
|
|
142
|
+
let completed = null;
|
|
143
|
+
let err = null;
|
|
144
|
+
try {
|
|
145
|
+
completed = await cache.isCompleted({
|
|
146
|
+
baseDir: memoryDir,
|
|
147
|
+
entitiesDir,
|
|
148
|
+
getEntityMutationVersion: () => 0,
|
|
149
|
+
});
|
|
150
|
+
} catch (e) {
|
|
151
|
+
err = e && typeof e === "object" && "code" in e ? String(e.code) : String(e);
|
|
152
|
+
}
|
|
153
|
+
const entitiesResolved = path.resolve(entitiesDir);
|
|
154
|
+
const external = seen.filter((s) => s.real === canaryReal || s.raw === entitiesResolved);
|
|
155
|
+
const payload = {
|
|
156
|
+
completed,
|
|
157
|
+
err,
|
|
158
|
+
seen,
|
|
159
|
+
externalCount: external.length,
|
|
160
|
+
canaryReal,
|
|
161
|
+
};
|
|
162
|
+
await writeFile(resultFile, JSON.stringify(payload), "utf8");
|
|
163
|
+
console.log(JSON.stringify(payload));
|
|
164
|
+
mock.reset();
|
|
165
|
+
`,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const probe = spawnSync(
|
|
169
|
+
process.execPath,
|
|
170
|
+
[
|
|
171
|
+
"--experimental-test-module-mocks",
|
|
172
|
+
"--import",
|
|
173
|
+
"tsx",
|
|
174
|
+
"--conditions=remnic-source",
|
|
175
|
+
probeFile,
|
|
176
|
+
],
|
|
177
|
+
{
|
|
178
|
+
encoding: "utf8",
|
|
179
|
+
cwd: REPO_ROOT,
|
|
180
|
+
env: {
|
|
181
|
+
...process.env,
|
|
182
|
+
NODE_PATH: path.join(REPO_ROOT, "node_modules"),
|
|
183
|
+
},
|
|
184
|
+
timeout: 60_000,
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (probe.error) {
|
|
189
|
+
assert.fail(`probe failed to start: ${probe.error.message}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let result: ProbeResult;
|
|
193
|
+
try {
|
|
194
|
+
// SAFETY: the child probe in this file writes `resultFile` as a ProbeResult before exiting.
|
|
195
|
+
result = JSON.parse(await readFile(resultFile, "utf8")) as ProbeResult;
|
|
196
|
+
} catch {
|
|
197
|
+
assert.fail(
|
|
198
|
+
[
|
|
199
|
+
"probe did not write result file",
|
|
200
|
+
`status=${String(probe.status)} signal=${String(probe.signal)}`,
|
|
201
|
+
`stdout:\n${probe.stdout ?? ""}`,
|
|
202
|
+
`stderr:\n${probe.stderr ?? ""}`,
|
|
203
|
+
].join("\n"),
|
|
204
|
+
);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
assert.equal(
|
|
209
|
+
result.externalCount,
|
|
210
|
+
0,
|
|
211
|
+
[
|
|
212
|
+
"isCompleted must not readdir a symlinked entities root (external canary).",
|
|
213
|
+
`externalCount=${result.externalCount} canaryReal=${result.canaryReal}`,
|
|
214
|
+
`seen=${JSON.stringify(result.seen)}`,
|
|
215
|
+
result.err ? `isCompleted err=${result.err}` : `completed=${String(result.completed)}`,
|
|
216
|
+
].join(" "),
|
|
217
|
+
);
|
|
218
|
+
assert.equal(result.err, null, "untrusted root must fail closed without throwing through external IO");
|
|
219
|
+
assert.equal(result.completed, false, "untrusted entities root must not report migration complete");
|
|
220
|
+
} finally {
|
|
221
|
+
await rm(root, { recursive: true, force: true });
|
|
222
|
+
}
|
|
223
|
+
});
|