@remnic/core 9.69.39 → 9.69.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +16 -14
- package/dist/access-admin-ops-surface.js +37 -37
- package/dist/access-authorization-probe.d.ts +16 -14
- package/dist/access-authorization-probe.js +38 -38
- package/dist/access-boundary.d.ts +16 -14
- package/dist/access-boundary.js +37 -37
- package/dist/access-cli.js +98 -97
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.d.ts +1 -1
- package/dist/access-extraction-force-flush.d.ts +16 -14
- package/dist/access-extraction-force-flush.js +37 -37
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-briefing.d.ts +16 -14
- package/dist/access-http-briefing.js +38 -38
- package/dist/access-http-lcm-compaction.d.ts +16 -14
- package/dist/access-http-lifecycle-flush.d.ts +16 -14
- package/dist/access-http-offline-stream.d.ts +16 -14
- package/dist/access-http-query.d.ts +2 -2
- package/dist/access-http-query.js +39 -39
- package/dist/access-http.d.ts +17 -15
- package/dist/access-http.js +53 -53
- package/dist/access-identity-continuity-surface.d.ts +13 -12
- package/dist/access-identity-continuity-surface.js +37 -37
- package/dist/access-lcm-surface.d.ts +16 -14
- package/dist/access-lcm-surface.js +37 -37
- package/dist/access-mcp.d.ts +16 -14
- package/dist/access-mcp.js +45 -45
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-DY4z6L5X.d.ts → access-namespace-preflight-CKBumJ8W.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +37 -37
- package/dist/access-observe-transcript.d.ts +13 -12
- package/dist/access-observe-write-category.d.ts +1 -1
- package/dist/access-observe-write-category.js +2 -2
- package/dist/access-observe-write-surface.d.ts +16 -14
- package/dist/access-observe-write-surface.js +37 -37
- package/dist/access-offline-manifest.d.ts +37 -13
- package/dist/access-offline-manifest.js +7 -3
- package/dist/access-operations-batch.js +39 -39
- package/dist/access-operations.d.ts +16 -14
- package/dist/access-operations.js +44 -44
- package/dist/access-recall-concurrency.d.ts +16 -14
- package/dist/access-recall-concurrency.js +37 -37
- package/dist/access-recall-response.d.ts +16 -14
- package/dist/access-recall-response.js +37 -37
- package/dist/access-recall-surface.d.ts +16 -14
- package/dist/access-recall-surface.js +37 -37
- package/dist/access-schema.d.ts +5 -3
- package/dist/access-schema.js +1 -1
- package/dist/{access-service-MY4bsr_Q.d.ts → access-service-DJ3qogNt.d.ts} +8 -8
- package/dist/access-service-helpers.d.ts +16 -14
- package/dist/access-service.d.ts +16 -14
- package/dist/access-service.js +37 -37
- package/dist/access-surface-catalog.d.ts +16 -14
- package/dist/access-surface-catalog.js +39 -39
- package/dist/access-wearables-meetings-surface.d.ts +6 -5
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +6 -5
- package/dist/active-recall.js.map +1 -1
- package/dist/activity/digest.d.ts +1 -1
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/{auto-sync-RP4C6ZPF.js → auto-sync-D6BAPIJV.js} +5 -5
- package/dist/background-generation-config.d.ts +1 -1
- package/dist/background-generation-config.js +3 -1
- package/dist/background-generation.d.ts +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +13 -12
- package/dist/briefing-window.d.ts +2 -2
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +15 -15
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-turn-helpers.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/buffer.js +5 -5
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +3 -2
- package/dist/calibration.js +2 -2
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-CW87xGx8.d.ts → catalog-BHXkBVhg.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +19 -18
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-B36GUU6T.js → chunk-23HFXQ5J.js} +36 -3
- package/dist/chunk-23HFXQ5J.js.map +1 -0
- package/dist/{chunk-YFTNZMGR.js → chunk-27NO2KVB.js} +31 -8
- package/dist/chunk-27NO2KVB.js.map +1 -0
- package/dist/{chunk-JTQR6L2N.js → chunk-2SKMZUOW.js} +2 -2
- package/dist/{chunk-GG5FHZJJ.js → chunk-2SNCQHJZ.js} +2 -2
- package/dist/{chunk-O76F5QPP.js → chunk-3QEVOQF5.js} +2 -2
- package/dist/{chunk-3N3PBBEH.js → chunk-3XPN7AVY.js} +2 -2
- package/dist/{chunk-X4TD4O2F.js → chunk-46Z3VGWF.js} +2 -2
- package/dist/{chunk-2ZTDRNCS.js → chunk-4CMXI55L.js} +2 -2
- package/dist/{chunk-FWWWUPOE.js → chunk-5LYSEZOF.js} +18 -9
- package/dist/chunk-5LYSEZOF.js.map +1 -0
- package/dist/{chunk-IQEJIOEU.js → chunk-62HFZNCJ.js} +17 -10
- package/dist/chunk-62HFZNCJ.js.map +1 -0
- package/dist/{chunk-YKREX63Z.js → chunk-6L2II3KS.js} +5 -3
- package/dist/chunk-6L2II3KS.js.map +1 -0
- package/dist/{chunk-MUTVRKKY.js → chunk-7D6SER6A.js} +8 -8
- package/dist/{chunk-WBM2MVBK.js → chunk-7Z54NXVS.js} +2 -2
- package/dist/{chunk-ACY4ZPF7.js → chunk-AEOOGXMQ.js} +10 -5
- package/dist/chunk-AEOOGXMQ.js.map +1 -0
- package/dist/{chunk-UHRZ324M.js → chunk-BGRTZOJB.js} +3 -3
- package/dist/{chunk-ZEGL7YCC.js → chunk-BXIJSPCL.js} +2 -2
- package/dist/{chunk-JKKII6GX.js → chunk-BYQX4IOG.js} +2 -2
- package/dist/{chunk-WIWLX65T.js → chunk-C5R73BON.js} +2 -2
- package/dist/{chunk-BD7JTBYP.js → chunk-D7MAYFQP.js} +2 -2
- package/dist/{chunk-TNZNSMVJ.js → chunk-DAZANIYU.js} +249 -295
- package/dist/chunk-DAZANIYU.js.map +1 -0
- package/dist/{chunk-LMLGBG6H.js → chunk-DFVSJFHK.js} +2 -2
- package/dist/{chunk-3NPNBVFR.js → chunk-ES5BUPYB.js} +2 -2
- package/dist/{chunk-MVAQ2WM3.js → chunk-G2XZFGYN.js} +6 -2
- package/dist/chunk-G2XZFGYN.js.map +1 -0
- package/dist/{chunk-TNSCIPBQ.js → chunk-G6NQ3L4T.js} +2 -2
- package/dist/{chunk-5N4MZLEH.js → chunk-GO5LOHCD.js} +2 -2
- package/dist/{chunk-AECJY3GJ.js → chunk-GSSDHAYH.js} +2 -2
- package/dist/{chunk-TSP5WH72.js → chunk-HJUP3K6L.js} +2 -2
- package/dist/{chunk-AAZEKXUP.js → chunk-ICNJ6J46.js} +2 -2
- package/dist/{chunk-6EHM2AV7.js → chunk-IMBQFHRV.js} +2 -2
- package/dist/{chunk-4RZLTZVM.js → chunk-IOPIGX7Y.js} +1 -1
- package/dist/{chunk-EYKIDK33.js → chunk-IS2IDFJP.js} +13 -13
- package/dist/{chunk-VJIE4D2I.js → chunk-J4WGTN2N.js} +2 -2
- package/dist/{chunk-NXGSONWM.js → chunk-J7FVMLBR.js} +2 -2
- package/dist/{chunk-VSL63CFJ.js → chunk-JAJIMTFB.js} +6 -6
- package/dist/{chunk-4JVQRFZD.js → chunk-JOP7W24J.js} +9 -9
- package/dist/{chunk-YUW66UAH.js → chunk-JUDT33RX.js} +2 -2
- package/dist/{chunk-QHT7MOUF.js → chunk-K4A6UVPB.js} +22 -11
- package/dist/chunk-K4A6UVPB.js.map +1 -0
- package/dist/{chunk-D545Z7UH.js → chunk-K4JKAXMO.js} +2 -2
- package/dist/{chunk-BV4ZKIZH.js → chunk-K75A5XWU.js} +3 -2
- package/dist/{chunk-3CBI2ZSA.js → chunk-KG2OOAFF.js} +20 -12
- package/dist/chunk-KG2OOAFF.js.map +1 -0
- package/dist/{chunk-M6HBPLBA.js → chunk-KOTG3RXE.js} +2 -2
- package/dist/{chunk-CWTCMZGE.js → chunk-KTOXQMW3.js} +119 -3
- package/dist/chunk-KTOXQMW3.js.map +1 -0
- package/dist/{chunk-OB3XMWX5.js → chunk-KVO2BTA4.js} +2 -2
- package/dist/{chunk-GI4ZBWPQ.js → chunk-KY236SP5.js} +3 -3
- package/dist/{chunk-4BCHFSC7.js → chunk-LFVZD5EC.js} +3 -3
- package/dist/{chunk-O4M4BDIJ.js → chunk-LKFVVN7R.js} +2 -2
- package/dist/{chunk-IYKZJVL5.js → chunk-MDIEES23.js} +1 -1
- package/dist/chunk-MDIEES23.js.map +1 -0
- package/dist/{chunk-4RX5O34R.js → chunk-MJC7UKH7.js} +2 -2
- package/dist/{chunk-CZTPU65V.js → chunk-MVG3KXON.js} +63 -16
- package/dist/chunk-MVG3KXON.js.map +1 -0
- package/dist/{chunk-YDVAM7S4.js → chunk-NETQXOPY.js} +2 -2
- package/dist/chunk-NETQXOPY.js.map +1 -0
- package/dist/{chunk-UZZ7SQT3.js → chunk-NVVZPBKI.js} +2 -2
- package/dist/{chunk-BLDBT6HZ.js → chunk-PJRKLB4Z.js} +18 -2
- package/dist/chunk-PJRKLB4Z.js.map +1 -0
- package/dist/{chunk-VBSNUH4J.js → chunk-PKUJNICO.js} +2 -2
- package/dist/{chunk-OPBFP4TF.js → chunk-QE6YVNP5.js} +7 -7
- package/dist/{chunk-WKDFJXW5.js → chunk-QM2SPLKQ.js} +1 -1
- package/dist/chunk-QM2SPLKQ.js.map +1 -0
- package/dist/{chunk-ENBPQEVU.js → chunk-QPVNK2VE.js} +2 -2
- package/dist/{chunk-RLJHJQUY.js → chunk-QS5Q5RBV.js} +2 -2
- package/dist/{chunk-3EKVN2OP.js → chunk-RDLPFM7I.js} +2 -2
- package/dist/{chunk-F4EHXVP7.js → chunk-RJQAP333.js} +2 -2
- package/dist/{chunk-QTQ6XKGT.js → chunk-RLU2Y5GV.js} +4 -4
- package/dist/chunk-RLU2Y5GV.js.map +1 -0
- package/dist/{chunk-5SPCMAUC.js → chunk-ROEFLU37.js} +4 -4
- package/dist/{chunk-UZEHHXYC.js → chunk-SFHLQPBF.js} +22 -22
- package/dist/chunk-SK7D34OL.js +68 -0
- package/dist/chunk-SK7D34OL.js.map +1 -0
- package/dist/{chunk-ZBW7A667.js → chunk-SUEOG2S4.js} +2 -2
- package/dist/{chunk-Q7CLGFA7.js → chunk-TEMKBSV6.js} +2 -2
- package/dist/{chunk-LQNRVPTV.js → chunk-THTF2W7M.js} +151 -118
- package/dist/chunk-THTF2W7M.js.map +1 -0
- package/dist/{chunk-GMIQV6DD.js → chunk-TO36VPZI.js} +2 -2
- package/dist/{chunk-6YAIN5XJ.js → chunk-TZ44E732.js} +44 -6
- package/dist/chunk-TZ44E732.js.map +1 -0
- package/dist/{chunk-JXN77SGO.js → chunk-U2VDCFXW.js} +1 -1
- package/dist/chunk-U2VDCFXW.js.map +1 -0
- package/dist/{chunk-LPYYY4AP.js → chunk-UZ6JYPJ6.js} +500 -218
- package/dist/chunk-UZ6JYPJ6.js.map +1 -0
- package/dist/{chunk-FXHOZGSE.js → chunk-V2ZA4YWE.js} +2 -2
- package/dist/{chunk-XH3HGI2N.js → chunk-VHKXRYQN.js} +2 -2
- package/dist/{chunk-GAS7KYC7.js → chunk-VKWFNPBH.js} +2 -2
- package/dist/{chunk-6H2G7RFT.js → chunk-VNQRRUNS.js} +11 -10
- package/dist/chunk-VNQRRUNS.js.map +1 -0
- package/dist/{chunk-IL6ODG6M.js → chunk-VQWBUPSJ.js} +2 -2
- package/dist/{chunk-5YCNDC3C.js → chunk-VQYWKCDR.js} +2 -2
- package/dist/{chunk-54NX5EG4.js → chunk-W23JARAQ.js} +2 -2
- package/dist/{chunk-YQNNHLCG.js → chunk-WPGR3RW4.js} +2 -2
- package/dist/{chunk-FNHLI2RE.js → chunk-WZYHPAG5.js} +3 -3
- package/dist/{chunk-5HYUUFMQ.js → chunk-XCPNH2MG.js} +259 -10
- package/dist/chunk-XCPNH2MG.js.map +1 -0
- package/dist/{chunk-TA4C3XB7.js → chunk-XM46Y66S.js} +2 -2
- package/dist/{chunk-PCOVEDPK.js → chunk-YRIFXXMI.js} +3 -3
- package/dist/{chunk-TRLOFIQC.js → chunk-YTK5MVNF.js} +2 -2
- package/dist/{chunk-Q432REND.js → chunk-YTZN2UZO.js} +2 -2
- package/dist/{chunk-6AGWBKQU.js → chunk-Z4GUX2M6.js} +2 -2
- package/dist/{chunk-AENM6GKU.js → chunk-ZAEPD4C2.js} +2 -2
- package/dist/{chunk-KQW3YGEN.js → chunk-ZRMMXWYI.js} +2 -2
- package/dist/{chunk-P4ZAEYKG.js → chunk-ZX3AYNNV.js} +6 -3
- package/dist/chunk-ZX3AYNNV.js.map +1 -0
- package/dist/{chunk-MCRVQ5YQ.js → chunk-ZZCR2QYL.js} +2 -2
- package/dist/{cli-DGq0MNpO.d.ts → cli-BWri5-am.d.ts} +5 -5
- package/dist/cli.d.ts +18 -16
- package/dist/cli.js +75 -74
- package/dist/coding/export-okf-codegraph.d.ts +1 -1
- package/dist/coding/export-okf-codegraph.js +14 -14
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +15 -15
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +6 -5
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +15 -15
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +15 -15
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +6 -5
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +17 -17
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +1 -1
- package/dist/corpus-watermark.js +17 -17
- package/dist/cross-lingual-recall.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/deep-recall-http-glue.d.ts +16 -14
- package/dist/deep-recall-http-glue.js +2 -2
- package/dist/deep-recall-mcp-tools.d.ts +16 -14
- package/dist/deep-recall-policy-llm.d.ts +4 -3
- package/dist/deep-recall-policy-llm.js +3 -3
- package/dist/deep-recall-seeds.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/dependency-propagation-config.d.ts +1 -1
- package/dist/{dependency-propagation-delivery-Cq1ekOwV.d.ts → dependency-propagation-delivery-ueZlKTQB.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-origin-fields.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +15 -15
- package/dist/entity-schema.d.ts +1 -1
- package/dist/episodic-context.d.ts +1 -1
- package/dist/eval-trajectory-contracts.d.ts +1 -1
- package/dist/eval-trajectory-store.d.ts +1 -1
- package/dist/eval-trajectory.d.ts +1 -1
- package/dist/explicit-capture.d.ts +13 -12
- package/dist/external-wiki-access.d.ts +16 -14
- package/dist/external-wiki-access.js +38 -38
- package/dist/external-wiki-collection-registration.d.ts +1 -1
- package/dist/external-wiki-collection.d.ts +1 -1
- package/dist/external-wiki-mcp-tools.d.ts +16 -14
- package/dist/extraction-error-classification.d.ts +14 -3
- package/dist/extraction-error-classification.js +5 -3
- package/dist/extraction-faithfulness.d.ts +4 -3
- package/dist/extraction-faithfulness.js +3 -3
- package/dist/extraction-judge-telemetry.d.ts +4 -3
- package/dist/extraction-judge-training.d.ts +4 -3
- package/dist/extraction-judge.d.ts +4 -3
- package/dist/extraction-judge.js +3 -3
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-normalization.d.ts +1 -1
- package/dist/extraction-prompt.d.ts +1 -1
- package/dist/extraction-source-grounding-rules.d.ts +1 -1
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction-span-config.d.ts +1 -1
- package/dist/extraction-span-materialize.d.ts +1 -1
- package/dist/extraction.d.ts +4 -4
- package/dist/extraction.js +25 -23
- package/dist/fallback-llm-codex-subscription.d.ts +52 -7
- package/dist/fallback-llm-codex-subscription.js +17 -1
- package/dist/fallback-llm.d.ts +25 -28
- package/dist/fallback-llm.js +4 -3
- package/dist/{generic-recall-paths-BcySh_f9.d.ts → generic-recall-paths-CpyVmA88.d.ts} +1 -1
- package/dist/graph-append-error.d.ts +1 -1
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-jsonl.d.ts +1 -1
- package/dist/graph-path-reconstruction.d.ts +1 -1
- package/dist/graph-path-scoring.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/importers/test-utils.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/{index-DWOb4wz9.d.ts → index-BvUA4S7M.d.ts} +3 -3
- package/dist/index.d.ts +79 -39
- package/dist/index.js +119 -103
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +3 -3
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm-BsJGulur.d.ts +247 -0
- package/dist/local-llm-config.d.ts +4 -1
- package/dist/local-llm-config.js +4 -2
- package/dist/local-llm-helpers.d.ts +18 -69
- package/dist/local-llm-helpers.js +1 -1
- package/dist/local-llm.d.ts +3 -170
- package/dist/local-llm.js +2 -2
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/location/index.d.ts +3 -3
- package/dist/location/index.js +17 -17
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +15 -15
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -15
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +16 -16
- package/dist/{maintenance-CW9wCzMP.d.ts → maintenance-CYubtIRr.d.ts} +9 -3
- package/dist/mcp-memory-inspector-app.d.ts +16 -14
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-controller.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-snapshot.d.ts +1 -1
- package/dist/memory-subject-cli.d.ts +16 -14
- package/dist/memory-subject.d.ts +16 -14
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +16 -16
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +12 -1
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +15 -15
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +18 -18
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +29 -28
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +26 -26
- package/dist/{orchestrator-ComHLm49.d.ts → orchestrator-BWDRJ_0d.d.ts} +24 -11
- package/dist/orchestrator.d.ts +13 -12
- package/dist/orchestrator.js +101 -98
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-CiYcqqhl.d.ts → pipeline-7JMn2yMH.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +2 -2
- package/dist/provenance.d.ts +1 -1
- package/dist/{public-http-CuK4_Cb1.d.ts → public-http-CJWs3mo9.d.ts} +1 -1
- package/dist/{qmd-qXD4vatU.d.ts → qmd-CxCH9G9m.d.ts} +2 -2
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/qmd.js +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-navigate-access.d.ts +1 -1
- package/dist/recall-navigate-access.js +2 -2
- package/dist/recall-navigate-disclosure.d.ts +1 -1
- package/dist/recall-navigate-disclosure.js +1 -1
- package/dist/recall-navigate.d.ts +1 -1
- package/dist/recall-navigate.js +2 -2
- package/dist/recall-planner-llm.d.ts +3 -2
- package/dist/recall-planner-llm.js +4 -3
- package/dist/recall-planner-llm.js.map +1 -1
- package/dist/recall-state-view-admission.d.ts +13 -3
- package/dist/recall-state-view-admission.js +8 -8
- package/dist/recall-state-view-anchors.d.ts +1 -1
- package/dist/recall-state-view-anchors.js +2 -2
- package/dist/recall-state-view-inject.js +2 -2
- package/dist/recall-state-view-render.js +2 -2
- package/dist/recall-state-view-widen.js +2 -2
- package/dist/recall-state-view-wire.js +3 -3
- package/dist/recall-state-view.d.ts +1 -0
- package/dist/recall-state-view.js +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/cursor.d.ts +1 -1
- package/dist/reconcile/manifest.d.ts +1 -1
- package/dist/reconcile/plan.d.ts +1 -1
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +7 -6
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/{scope-profiles-BT9rqC3w.d.ts → scope-profiles-BGAUqhBR.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +8 -8
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +10 -10
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +2 -2
- package/dist/search/port.js +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/search/remote-backend.js +2 -2
- package/dist/{semantic-consolidation-CmZi7thY.d.ts → semantic-consolidation-CCeRD9pV.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +16 -16
- package/dist/semantic-rule-promotion.js +15 -15
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +15 -15
- package/dist/{service-CLkA7AQB.d.ts → service-BwpzRduk.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +18 -18
- package/dist/standup/build.js +15 -15
- package/dist/{storage-wvX01lht.d.ts → storage-BprAGBG0.d.ts} +2 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +14 -14
- package/dist/subject-config.d.ts +1 -1
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +5 -5
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/support-passport/index.d.ts +18 -17
- package/dist/support-passport/index.js +40 -40
- package/dist/task-llm-config.d.ts +16 -0
- package/dist/task-llm-config.js +12 -0
- package/dist/task-llm-config.js.map +1 -0
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/export-okf.d.ts +1 -1
- package/dist/transfer/export-okf.js +15 -15
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-Cyr32ABj.d.ts → types-nJV23pa3.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +15 -15
- package/dist/who-knows-cli.d.ts +16 -14
- package/dist/who-knows-cli.js +16 -16
- package/dist/who-knows.d.ts +16 -14
- package/dist/who-knows.js +15 -15
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/access-category-alias.test.ts +93 -0
- package/src/access-offline-manifest.ts +52 -2
- package/src/access-schema.ts +9 -4
- package/src/activity/config.test.ts +61 -0
- package/src/activity/config.ts +40 -1
- package/src/activity/timeline/analysis-provider.ts +8 -2
- package/src/activity/timeline/analysis-run.test.ts +82 -0
- package/src/activity/timeline/publish-cli.test.ts +30 -0
- package/src/activity/timeline/publish-cli.ts +7 -0
- package/src/activity/timeline/query.ts +8 -1
- package/src/activity/timeline/regenerate.test.ts +82 -2
- package/src/activity/timeline/regenerate.ts +7 -1
- package/src/activity/vault-frontmatter.test.ts +99 -1
- package/src/activity/vault-frontmatter.ts +131 -0
- package/src/activity/vault-publish.ts +6 -0
- package/src/activity/vault-publisher.test.ts +172 -0
- package/src/activity/vault-publisher.ts +69 -15
- package/src/activity/vault-region.test.ts +7 -2
- package/src/activity/vault-region.ts +7 -3
- package/src/background-generation-config.ts +32 -6
- package/src/background-generation.test.ts +10 -1
- package/src/cli/timeline-publish-commands.ts +9 -0
- package/src/config.test.ts +170 -43
- package/src/config.ts +17 -5
- package/src/extraction-error-classification.ts +36 -1
- package/src/extraction-timeout.test.ts +110 -1
- package/src/extraction.test.ts +19 -1
- package/src/extraction.ts +23 -21
- package/src/fallback-llm-codex-subscription.ts +193 -7
- package/src/fallback-llm-parse-failure.test.ts +156 -0
- package/src/fallback-llm.test.ts +58 -0
- package/src/fallback-llm.ts +187 -186
- package/src/index.ts +1 -0
- package/src/local-llm-config.ts +11 -10
- package/src/local-llm-headers-timeout.test.ts +30 -0
- package/src/local-llm-helpers.ts +28 -0
- package/src/local-llm.ts +8 -19
- package/src/namespaces/search.test.ts +51 -1
- package/src/namespaces/search.ts +48 -3
- package/src/orchestration/extraction-persist-promotion.test.ts +228 -2
- package/src/orchestration/extraction-persist-promotion.ts +17 -14
- package/src/orchestration/orchestrator-helpers.test.ts +104 -0
- package/src/orchestration/orchestrator-init.ts +366 -106
- package/src/orchestration/recall-internal.ts +5 -11
- package/src/orchestration/semantic-merge-promotion-payload.ts +62 -6
- package/src/orchestration/startup-collection-checks.ts +85 -0
- package/src/orchestrator-flush.test.ts +7 -0
- package/src/orchestrator-init.test.ts +1539 -0
- package/src/orchestrator.ts +7 -0
- package/src/qmd.ts +21 -10
- package/src/recall-state-view-admission.ts +11 -1
- package/src/recall-state-view-cron-intent.test.ts +268 -0
- package/src/recall-state-view-widen.test.ts +14 -5
- package/src/recall-state-view-widen.ts +8 -1
- package/src/recall-state-view.ts +5 -2
- package/src/reconcile-identity-cache.test.ts +82 -1
- package/src/search/port.ts +1 -1
- package/src/storage/cas-revision-store.test.ts +4 -0
- package/src/storage/cas-revision-store.ts +104 -12
- package/src/storage-frontmatter-cas.test.ts +19 -17
- package/src/summarizer.ts +5 -1
- package/src/task-llm-config.test.ts +124 -0
- package/src/task-llm-config.ts +88 -0
- package/src/testing/subjects/startup-discovery-lifecycle.test.ts +273 -0
- package/src/types.ts +1 -0
- package/dist/chunk-3CBI2ZSA.js.map +0 -1
- package/dist/chunk-5HYUUFMQ.js.map +0 -1
- package/dist/chunk-6H2G7RFT.js.map +0 -1
- package/dist/chunk-6YAIN5XJ.js.map +0 -1
- package/dist/chunk-ACY4ZPF7.js.map +0 -1
- package/dist/chunk-B36GUU6T.js.map +0 -1
- package/dist/chunk-BLDBT6HZ.js.map +0 -1
- package/dist/chunk-CWTCMZGE.js.map +0 -1
- package/dist/chunk-CZTPU65V.js.map +0 -1
- package/dist/chunk-FWWWUPOE.js.map +0 -1
- package/dist/chunk-IQEJIOEU.js.map +0 -1
- package/dist/chunk-IYKZJVL5.js.map +0 -1
- package/dist/chunk-JXN77SGO.js.map +0 -1
- package/dist/chunk-LPYYY4AP.js.map +0 -1
- package/dist/chunk-LQNRVPTV.js.map +0 -1
- package/dist/chunk-MVAQ2WM3.js.map +0 -1
- package/dist/chunk-P4ZAEYKG.js.map +0 -1
- package/dist/chunk-QHT7MOUF.js.map +0 -1
- package/dist/chunk-QTQ6XKGT.js.map +0 -1
- package/dist/chunk-TNZNSMVJ.js.map +0 -1
- package/dist/chunk-WKDFJXW5.js.map +0 -1
- package/dist/chunk-YDVAM7S4.js.map +0 -1
- package/dist/chunk-YFTNZMGR.js.map +0 -1
- package/dist/chunk-YKREX63Z.js.map +0 -1
- /package/dist/{auto-sync-RP4C6ZPF.js.map → auto-sync-D6BAPIJV.js.map} +0 -0
- /package/dist/{chunk-JTQR6L2N.js.map → chunk-2SKMZUOW.js.map} +0 -0
- /package/dist/{chunk-GG5FHZJJ.js.map → chunk-2SNCQHJZ.js.map} +0 -0
- /package/dist/{chunk-O76F5QPP.js.map → chunk-3QEVOQF5.js.map} +0 -0
- /package/dist/{chunk-3N3PBBEH.js.map → chunk-3XPN7AVY.js.map} +0 -0
- /package/dist/{chunk-X4TD4O2F.js.map → chunk-46Z3VGWF.js.map} +0 -0
- /package/dist/{chunk-2ZTDRNCS.js.map → chunk-4CMXI55L.js.map} +0 -0
- /package/dist/{chunk-MUTVRKKY.js.map → chunk-7D6SER6A.js.map} +0 -0
- /package/dist/{chunk-WBM2MVBK.js.map → chunk-7Z54NXVS.js.map} +0 -0
- /package/dist/{chunk-UHRZ324M.js.map → chunk-BGRTZOJB.js.map} +0 -0
- /package/dist/{chunk-ZEGL7YCC.js.map → chunk-BXIJSPCL.js.map} +0 -0
- /package/dist/{chunk-JKKII6GX.js.map → chunk-BYQX4IOG.js.map} +0 -0
- /package/dist/{chunk-WIWLX65T.js.map → chunk-C5R73BON.js.map} +0 -0
- /package/dist/{chunk-BD7JTBYP.js.map → chunk-D7MAYFQP.js.map} +0 -0
- /package/dist/{chunk-LMLGBG6H.js.map → chunk-DFVSJFHK.js.map} +0 -0
- /package/dist/{chunk-3NPNBVFR.js.map → chunk-ES5BUPYB.js.map} +0 -0
- /package/dist/{chunk-TNSCIPBQ.js.map → chunk-G6NQ3L4T.js.map} +0 -0
- /package/dist/{chunk-5N4MZLEH.js.map → chunk-GO5LOHCD.js.map} +0 -0
- /package/dist/{chunk-AECJY3GJ.js.map → chunk-GSSDHAYH.js.map} +0 -0
- /package/dist/{chunk-TSP5WH72.js.map → chunk-HJUP3K6L.js.map} +0 -0
- /package/dist/{chunk-AAZEKXUP.js.map → chunk-ICNJ6J46.js.map} +0 -0
- /package/dist/{chunk-6EHM2AV7.js.map → chunk-IMBQFHRV.js.map} +0 -0
- /package/dist/{chunk-4RZLTZVM.js.map → chunk-IOPIGX7Y.js.map} +0 -0
- /package/dist/{chunk-EYKIDK33.js.map → chunk-IS2IDFJP.js.map} +0 -0
- /package/dist/{chunk-VJIE4D2I.js.map → chunk-J4WGTN2N.js.map} +0 -0
- /package/dist/{chunk-NXGSONWM.js.map → chunk-J7FVMLBR.js.map} +0 -0
- /package/dist/{chunk-VSL63CFJ.js.map → chunk-JAJIMTFB.js.map} +0 -0
- /package/dist/{chunk-4JVQRFZD.js.map → chunk-JOP7W24J.js.map} +0 -0
- /package/dist/{chunk-YUW66UAH.js.map → chunk-JUDT33RX.js.map} +0 -0
- /package/dist/{chunk-D545Z7UH.js.map → chunk-K4JKAXMO.js.map} +0 -0
- /package/dist/{chunk-BV4ZKIZH.js.map → chunk-K75A5XWU.js.map} +0 -0
- /package/dist/{chunk-M6HBPLBA.js.map → chunk-KOTG3RXE.js.map} +0 -0
- /package/dist/{chunk-OB3XMWX5.js.map → chunk-KVO2BTA4.js.map} +0 -0
- /package/dist/{chunk-GI4ZBWPQ.js.map → chunk-KY236SP5.js.map} +0 -0
- /package/dist/{chunk-4BCHFSC7.js.map → chunk-LFVZD5EC.js.map} +0 -0
- /package/dist/{chunk-O4M4BDIJ.js.map → chunk-LKFVVN7R.js.map} +0 -0
- /package/dist/{chunk-4RX5O34R.js.map → chunk-MJC7UKH7.js.map} +0 -0
- /package/dist/{chunk-UZZ7SQT3.js.map → chunk-NVVZPBKI.js.map} +0 -0
- /package/dist/{chunk-VBSNUH4J.js.map → chunk-PKUJNICO.js.map} +0 -0
- /package/dist/{chunk-OPBFP4TF.js.map → chunk-QE6YVNP5.js.map} +0 -0
- /package/dist/{chunk-ENBPQEVU.js.map → chunk-QPVNK2VE.js.map} +0 -0
- /package/dist/{chunk-RLJHJQUY.js.map → chunk-QS5Q5RBV.js.map} +0 -0
- /package/dist/{chunk-3EKVN2OP.js.map → chunk-RDLPFM7I.js.map} +0 -0
- /package/dist/{chunk-F4EHXVP7.js.map → chunk-RJQAP333.js.map} +0 -0
- /package/dist/{chunk-5SPCMAUC.js.map → chunk-ROEFLU37.js.map} +0 -0
- /package/dist/{chunk-UZEHHXYC.js.map → chunk-SFHLQPBF.js.map} +0 -0
- /package/dist/{chunk-ZBW7A667.js.map → chunk-SUEOG2S4.js.map} +0 -0
- /package/dist/{chunk-Q7CLGFA7.js.map → chunk-TEMKBSV6.js.map} +0 -0
- /package/dist/{chunk-GMIQV6DD.js.map → chunk-TO36VPZI.js.map} +0 -0
- /package/dist/{chunk-FXHOZGSE.js.map → chunk-V2ZA4YWE.js.map} +0 -0
- /package/dist/{chunk-XH3HGI2N.js.map → chunk-VHKXRYQN.js.map} +0 -0
- /package/dist/{chunk-GAS7KYC7.js.map → chunk-VKWFNPBH.js.map} +0 -0
- /package/dist/{chunk-IL6ODG6M.js.map → chunk-VQWBUPSJ.js.map} +0 -0
- /package/dist/{chunk-5YCNDC3C.js.map → chunk-VQYWKCDR.js.map} +0 -0
- /package/dist/{chunk-54NX5EG4.js.map → chunk-W23JARAQ.js.map} +0 -0
- /package/dist/{chunk-YQNNHLCG.js.map → chunk-WPGR3RW4.js.map} +0 -0
- /package/dist/{chunk-FNHLI2RE.js.map → chunk-WZYHPAG5.js.map} +0 -0
- /package/dist/{chunk-TA4C3XB7.js.map → chunk-XM46Y66S.js.map} +0 -0
- /package/dist/{chunk-PCOVEDPK.js.map → chunk-YRIFXXMI.js.map} +0 -0
- /package/dist/{chunk-TRLOFIQC.js.map → chunk-YTK5MVNF.js.map} +0 -0
- /package/dist/{chunk-Q432REND.js.map → chunk-YTZN2UZO.js.map} +0 -0
- /package/dist/{chunk-6AGWBKQU.js.map → chunk-Z4GUX2M6.js.map} +0 -0
- /package/dist/{chunk-AENM6GKU.js.map → chunk-ZAEPD4C2.js.map} +0 -0
- /package/dist/{chunk-KQW3YGEN.js.map → chunk-ZRMMXWYI.js.map} +0 -0
- /package/dist/{chunk-MCRVQ5YQ.js.map → chunk-ZZCR2QYL.js.map} +0 -0
|
@@ -0,0 +1,1539 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { parseConfig } from "./config.js";
|
|
8
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
9
|
+
import { NoopSearchBackend } from "./search/noop-backend.js";
|
|
10
|
+
|
|
11
|
+
async function withTestDeadline<T>(
|
|
12
|
+
promise: Promise<T>,
|
|
13
|
+
timeoutMs = 1_250,
|
|
14
|
+
): Promise<T | "deadline"> {
|
|
15
|
+
let timer: NodeJS.Timeout | undefined;
|
|
16
|
+
const deadline = new Promise<"deadline">((resolve) => {
|
|
17
|
+
timer = setTimeout(() => resolve("deadline"), timeoutMs);
|
|
18
|
+
timer.unref?.();
|
|
19
|
+
});
|
|
20
|
+
try {
|
|
21
|
+
return await Promise.race([promise, deadline]);
|
|
22
|
+
} finally {
|
|
23
|
+
if (timer) clearTimeout(timer);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test("initialize and its init gate survive an enabled catalog planner timeout", async () => {
|
|
28
|
+
const originalTimeout = process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS;
|
|
29
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-init-deadline-"));
|
|
30
|
+
const orchestrator = new Orchestrator(
|
|
31
|
+
parseConfig({
|
|
32
|
+
memoryDir,
|
|
33
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
34
|
+
qmdEnabled: true,
|
|
35
|
+
namespacesEnabled: true,
|
|
36
|
+
namespaceCatalogEnabled: true,
|
|
37
|
+
defaultNamespace: "default",
|
|
38
|
+
sharedNamespace: "shared",
|
|
39
|
+
namespacePolicies: [],
|
|
40
|
+
embeddingFallbackEnabled: false,
|
|
41
|
+
identityContinuityEnabled: false,
|
|
42
|
+
transcriptEnabled: false,
|
|
43
|
+
hourlySummariesEnabled: false,
|
|
44
|
+
compoundingEnabled: false,
|
|
45
|
+
knowledgeIndexEnabled: false,
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
const enabledQmd = {
|
|
49
|
+
probe: async () => true,
|
|
50
|
+
debugStatus: () => "synthetic-qmd",
|
|
51
|
+
isAvailable: () => true,
|
|
52
|
+
search: async () => [],
|
|
53
|
+
};
|
|
54
|
+
let releasePlannerNamespaces: ((records: unknown[]) => void) | undefined;
|
|
55
|
+
const stalledPlannerNamespaces = new Promise<unknown[]>((resolve) => {
|
|
56
|
+
releasePlannerNamespaces = resolve;
|
|
57
|
+
});
|
|
58
|
+
const configuredNamespaces = (orchestrator as any).configuredNamespaceList();
|
|
59
|
+
const ensuredNamespaces: string[] = [];
|
|
60
|
+
const updatedNamespaces: string[][] = [];
|
|
61
|
+
const recoveredPlanNamespaces: string[][] = [];
|
|
62
|
+
let listNamespacesCalls = 0;
|
|
63
|
+
let secondPlannerCallStarted = false;
|
|
64
|
+
const dynamicRecord = {
|
|
65
|
+
namespace: "dynamic",
|
|
66
|
+
identityToken: "dynamic",
|
|
67
|
+
kind: "project" as const,
|
|
68
|
+
createdAt: new Date(0).toISOString(),
|
|
69
|
+
storageDir: path.join(memoryDir, "namespaces", "dynamic"),
|
|
70
|
+
discoveredBy: "write" as const,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS = "1000";
|
|
75
|
+
(orchestrator as any).qmd = enabledQmd;
|
|
76
|
+
await mkdir(path.join(dynamicRecord.storageDir, "state"), { recursive: true });
|
|
77
|
+
(orchestrator as any).passiveCorrectionService = () => ({
|
|
78
|
+
recoverStaleApplyingPlans: async (namespaces: string[]) => {
|
|
79
|
+
recoveredPlanNamespaces.push([...namespaces]);
|
|
80
|
+
return 0;
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
(orchestrator as any).namespaceCatalog.listNamespaces = () => {
|
|
84
|
+
listNamespacesCalls += 1;
|
|
85
|
+
if (listNamespacesCalls === 1) return Promise.resolve([dynamicRecord]);
|
|
86
|
+
if (listNamespacesCalls === 2) secondPlannerCallStarted = true;
|
|
87
|
+
return stalledPlannerNamespaces;
|
|
88
|
+
};
|
|
89
|
+
(orchestrator as any).namespaceSearchRouter.clearCache = () => undefined;
|
|
90
|
+
(orchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async (namespace: string) => {
|
|
91
|
+
ensuredNamespaces.push(namespace);
|
|
92
|
+
return "present";
|
|
93
|
+
};
|
|
94
|
+
(orchestrator as any).namespaceSearchRouter.updateNamespaces = async (namespaces: string[]) => {
|
|
95
|
+
updatedNamespaces.push([...namespaces]);
|
|
96
|
+
return namespaces.length;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const initPromise = (orchestrator as any).initPromise as Promise<void>;
|
|
100
|
+
const initializeResult = withTestDeadline(
|
|
101
|
+
orchestrator.initialize().then(() => "settled" as const),
|
|
102
|
+
);
|
|
103
|
+
const initGateResult = withTestDeadline(
|
|
104
|
+
initPromise.then(() => "settled" as const),
|
|
105
|
+
);
|
|
106
|
+
const [result, initGate] = await Promise.all([initializeResult, initGateResult]);
|
|
107
|
+
|
|
108
|
+
assert.equal(
|
|
109
|
+
result,
|
|
110
|
+
"settled",
|
|
111
|
+
"init must not remain pending behind namespace-maintenance discovery",
|
|
112
|
+
);
|
|
113
|
+
assert.equal(initGate, "settled", "the real initPromise gate must not remain pending");
|
|
114
|
+
assert.deepEqual(
|
|
115
|
+
recoveredPlanNamespaces,
|
|
116
|
+
[[...configuredNamespaces, "dynamic"]],
|
|
117
|
+
"the first catalog read must feed stale-plan recovery before QMD planning",
|
|
118
|
+
);
|
|
119
|
+
assert.equal(secondPlannerCallStarted, true, "the second catalog read must be the stalled QMD planner discovery");
|
|
120
|
+
assert.equal((orchestrator as any).qmd, enabledQmd, "unknown startup state must keep QMD enabled fail-open");
|
|
121
|
+
assert.deepEqual(
|
|
122
|
+
ensuredNamespaces,
|
|
123
|
+
configuredNamespaces,
|
|
124
|
+
"late namespace discovery must not launch collection checks after the startup deadline",
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const deferredResult = await withTestDeadline(
|
|
128
|
+
(orchestrator as any).deferredReady.then(() => "settled" as const),
|
|
129
|
+
);
|
|
130
|
+
assert.equal(deferredResult, "settled", "deferred initialization must settle after incomplete discovery");
|
|
131
|
+
assert.deepEqual(updatedNamespaces, [configuredNamespaces]);
|
|
132
|
+
assert.equal((orchestrator as any).deferredSyncSucceeded, false);
|
|
133
|
+
|
|
134
|
+
const retryResult = await withTestDeadline(orchestrator.startupSearchSync());
|
|
135
|
+
assert.equal(retryResult, false, "incomplete discovery must leave startup retry armed");
|
|
136
|
+
assert.deepEqual(updatedNamespaces, [configuredNamespaces, configuredNamespaces]);
|
|
137
|
+
const fallbackEnsureCalls = [...configuredNamespaces, ...configuredNamespaces];
|
|
138
|
+
assert.deepEqual(ensuredNamespaces, fallbackEnsureCalls);
|
|
139
|
+
|
|
140
|
+
releasePlannerNamespaces?.([dynamicRecord]);
|
|
141
|
+
await Promise.resolve();
|
|
142
|
+
assert.deepEqual(
|
|
143
|
+
ensuredNamespaces,
|
|
144
|
+
fallbackEnsureCalls,
|
|
145
|
+
"a late maintenance result must not launch a stale collection-check batch",
|
|
146
|
+
);
|
|
147
|
+
const recovered = await orchestrator.startupSearchSync();
|
|
148
|
+
assert.equal(recovered, true);
|
|
149
|
+
const completeNamespaces = [...configuredNamespaces, "dynamic"];
|
|
150
|
+
assert.deepEqual(
|
|
151
|
+
ensuredNamespaces,
|
|
152
|
+
[...fallbackEnsureCalls, ...completeNamespaces],
|
|
153
|
+
"a later complete retry must re-stage dynamic collection checks",
|
|
154
|
+
);
|
|
155
|
+
assert.deepEqual(updatedNamespaces, [
|
|
156
|
+
configuredNamespaces,
|
|
157
|
+
configuredNamespaces,
|
|
158
|
+
completeNamespaces,
|
|
159
|
+
]);
|
|
160
|
+
} finally {
|
|
161
|
+
releasePlannerNamespaces?.([dynamicRecord]);
|
|
162
|
+
if (originalTimeout === undefined) delete process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS;
|
|
163
|
+
else process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS = originalTimeout;
|
|
164
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
165
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("aborted deferred and retry discovery do not start QMD work after the source deadline", async () => {
|
|
170
|
+
const originalTimeout = process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS;
|
|
171
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-abort-discovery-"));
|
|
172
|
+
const orchestrator = new Orchestrator(
|
|
173
|
+
parseConfig({
|
|
174
|
+
memoryDir,
|
|
175
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
176
|
+
qmdEnabled: true,
|
|
177
|
+
qmdMaintenanceEnabled: true,
|
|
178
|
+
namespacesEnabled: true,
|
|
179
|
+
namespaceCatalogEnabled: true,
|
|
180
|
+
defaultNamespace: "default",
|
|
181
|
+
sharedNamespace: "shared",
|
|
182
|
+
namespacePolicies: [],
|
|
183
|
+
embeddingFallbackEnabled: false,
|
|
184
|
+
identityContinuityEnabled: false,
|
|
185
|
+
transcriptEnabled: false,
|
|
186
|
+
hourlySummariesEnabled: false,
|
|
187
|
+
compoundingEnabled: false,
|
|
188
|
+
knowledgeIndexEnabled: false,
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
let releaseDiscovery: (() => void) | undefined;
|
|
192
|
+
const stalledDiscovery = new Promise<string[]>((resolve) => {
|
|
193
|
+
releaseDiscovery = () => resolve([]);
|
|
194
|
+
});
|
|
195
|
+
let resolveBothDiscoveriesStarted: (() => void) | undefined;
|
|
196
|
+
const bothDiscoveriesStarted = new Promise<void>((resolve) => {
|
|
197
|
+
resolveBothDiscoveriesStarted = resolve;
|
|
198
|
+
});
|
|
199
|
+
let discoveryCalls = 0;
|
|
200
|
+
let ensureStarts = 0;
|
|
201
|
+
let updateStarts = 0;
|
|
202
|
+
const deferredAbort = new AbortController();
|
|
203
|
+
const retryAbort = new AbortController();
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS = "1000";
|
|
207
|
+
(orchestrator as any).qmd = {
|
|
208
|
+
probe: async () => true,
|
|
209
|
+
debugStatus: () => "synthetic-qmd",
|
|
210
|
+
isAvailable: () => true,
|
|
211
|
+
search: async () => [],
|
|
212
|
+
};
|
|
213
|
+
(orchestrator as any).maintenanceNamespaces = () => {
|
|
214
|
+
discoveryCalls += 1;
|
|
215
|
+
if (discoveryCalls === 1) resolveBothDiscoveriesStarted?.();
|
|
216
|
+
return stalledDiscovery;
|
|
217
|
+
};
|
|
218
|
+
(orchestrator as any).namespaceSearchRouter.clearCache = () => undefined;
|
|
219
|
+
(orchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async () => {
|
|
220
|
+
ensureStarts += 1;
|
|
221
|
+
return "present";
|
|
222
|
+
};
|
|
223
|
+
(orchestrator as any).namespaceSearchRouter.updateNamespaces = async () => {
|
|
224
|
+
updateStarts += 1;
|
|
225
|
+
return 1;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const deferred = (orchestrator as any).deferredInitialize(deferredAbort.signal);
|
|
229
|
+
const retry = orchestrator.startupSearchSync(retryAbort.signal);
|
|
230
|
+
const started = await withTestDeadline(bothDiscoveriesStarted);
|
|
231
|
+
assert.notEqual(started, "deadline", "both callers must be stalled in source discovery");
|
|
232
|
+
|
|
233
|
+
deferredAbort.abort();
|
|
234
|
+
retryAbort.abort();
|
|
235
|
+
|
|
236
|
+
const settled = await withTestDeadline(Promise.all([deferred, retry]));
|
|
237
|
+
if (settled === "deadline") {
|
|
238
|
+
assert.fail("both callers must return once the source discovery deadline resolves");
|
|
239
|
+
}
|
|
240
|
+
assert.equal(settled[1], false, "the aborted retry must report no successful sync");
|
|
241
|
+
assert.deepEqual(
|
|
242
|
+
{ ensureStarts, updateStarts },
|
|
243
|
+
{ ensureStarts: 0, updateStarts: 0 },
|
|
244
|
+
"aborted callers must not dispatch QMD ensure or update after stalled discovery resolves",
|
|
245
|
+
);
|
|
246
|
+
} finally {
|
|
247
|
+
releaseDiscovery?.();
|
|
248
|
+
if (originalTimeout === undefined) delete process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS;
|
|
249
|
+
else process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS = originalTimeout;
|
|
250
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
251
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("a late correction catalog result recovers a dynamic stale plan once without starting QMD work", async () => {
|
|
256
|
+
const originalTimeout = process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS;
|
|
257
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-correction-late-catalog-"));
|
|
258
|
+
const orchestrator = new Orchestrator(
|
|
259
|
+
parseConfig({
|
|
260
|
+
memoryDir,
|
|
261
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
262
|
+
qmdEnabled: true,
|
|
263
|
+
qmdMaintenanceEnabled: true,
|
|
264
|
+
namespacesEnabled: true,
|
|
265
|
+
namespaceCatalogEnabled: true,
|
|
266
|
+
defaultNamespace: "default",
|
|
267
|
+
sharedNamespace: "shared",
|
|
268
|
+
namespacePolicies: [],
|
|
269
|
+
embeddingFallbackEnabled: false,
|
|
270
|
+
identityContinuityEnabled: false,
|
|
271
|
+
transcriptEnabled: false,
|
|
272
|
+
hourlySummariesEnabled: false,
|
|
273
|
+
compoundingEnabled: false,
|
|
274
|
+
knowledgeIndexEnabled: false,
|
|
275
|
+
}),
|
|
276
|
+
);
|
|
277
|
+
const configuredNamespaces = (orchestrator as any).configuredNamespaceList() as string[];
|
|
278
|
+
const dynamicRecord = {
|
|
279
|
+
namespace: "dynamic",
|
|
280
|
+
identityToken: "dynamic",
|
|
281
|
+
kind: "project" as const,
|
|
282
|
+
createdAt: new Date(0).toISOString(),
|
|
283
|
+
storageDir: path.join(memoryDir, "namespaces", "dynamic"),
|
|
284
|
+
discoveredBy: "write" as const,
|
|
285
|
+
};
|
|
286
|
+
const recoveredPlanNamespaces: string[][] = [];
|
|
287
|
+
const ensuredNamespaces: string[] = [];
|
|
288
|
+
const updatedNamespaces: string[][] = [];
|
|
289
|
+
let warmupStarts = 0;
|
|
290
|
+
let releaseRawCatalog: ((records: unknown[]) => void) | undefined;
|
|
291
|
+
const rawCatalog = new Promise<unknown[]>((resolve) => {
|
|
292
|
+
releaseRawCatalog = resolve;
|
|
293
|
+
});
|
|
294
|
+
let catalogCalls = 0;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS = "1000";
|
|
298
|
+
await mkdir(path.join(dynamicRecord.storageDir, "state"), { recursive: true });
|
|
299
|
+
(orchestrator as any).qmd = {
|
|
300
|
+
probe: async () => true,
|
|
301
|
+
debugStatus: () => "synthetic-qmd",
|
|
302
|
+
isAvailable: () => true,
|
|
303
|
+
search: async () => {
|
|
304
|
+
warmupStarts += 1;
|
|
305
|
+
return [];
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
(orchestrator as any).passiveCorrectionService = () => ({
|
|
309
|
+
recoverStaleApplyingPlans: async (namespaces: string[]) => {
|
|
310
|
+
recoveredPlanNamespaces.push([...namespaces]);
|
|
311
|
+
return namespaces.includes(dynamicRecord.namespace) ? 1 : 0;
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
(orchestrator as any).namespaceCatalog.listNamespaces = () => {
|
|
315
|
+
catalogCalls += 1;
|
|
316
|
+
return catalogCalls === 1 ? rawCatalog : Promise.resolve([]);
|
|
317
|
+
};
|
|
318
|
+
(orchestrator as any).namespaceSearchRouter.clearCache = () => undefined;
|
|
319
|
+
(orchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async (namespace: string) => {
|
|
320
|
+
ensuredNamespaces.push(namespace);
|
|
321
|
+
return "present";
|
|
322
|
+
};
|
|
323
|
+
(orchestrator as any).namespaceSearchRouter.updateNamespaces = async (namespaces: string[]) => {
|
|
324
|
+
updatedNamespaces.push([...namespaces]);
|
|
325
|
+
return namespaces.length;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const initialized = await withTestDeadline(orchestrator.initialize().then(() => "settled" as const));
|
|
329
|
+
assert.equal(initialized, "settled", "the correction catalog timeout must keep initialization bounded");
|
|
330
|
+
const deferred = await withTestDeadline((orchestrator as any).deferredReady.then(() => "settled" as const));
|
|
331
|
+
assert.equal(deferred, "settled", "the fallback deferred pass must settle");
|
|
332
|
+
assert.deepEqual(
|
|
333
|
+
recoveredPlanNamespaces,
|
|
334
|
+
[configuredNamespaces],
|
|
335
|
+
"the timed-out correction sweep must immediately recover configured namespaces",
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const qmdStartsBeforeLateCatalog = {
|
|
339
|
+
ensuredNamespaces: [...ensuredNamespaces],
|
|
340
|
+
updatedNamespaces: updatedNamespaces.map((namespaces) => [...namespaces]),
|
|
341
|
+
warmupStarts,
|
|
342
|
+
};
|
|
343
|
+
releaseRawCatalog?.([dynamicRecord]);
|
|
344
|
+
await Promise.resolve();
|
|
345
|
+
await Promise.resolve();
|
|
346
|
+
await Promise.resolve();
|
|
347
|
+
|
|
348
|
+
assert.deepEqual(
|
|
349
|
+
recoveredPlanNamespaces,
|
|
350
|
+
[configuredNamespaces, [dynamicRecord.namespace]],
|
|
351
|
+
"the same late raw catalog result must recover the dynamic namespace exactly once",
|
|
352
|
+
);
|
|
353
|
+
assert.deepEqual(
|
|
354
|
+
{
|
|
355
|
+
ensuredNamespaces,
|
|
356
|
+
updatedNamespaces,
|
|
357
|
+
warmupStarts,
|
|
358
|
+
},
|
|
359
|
+
qmdStartsBeforeLateCatalog,
|
|
360
|
+
"late correction recovery must not dispatch collection, update, or warmup work",
|
|
361
|
+
);
|
|
362
|
+
} finally {
|
|
363
|
+
releaseRawCatalog?.([dynamicRecord]);
|
|
364
|
+
if (originalTimeout === undefined) delete process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS;
|
|
365
|
+
else process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS = originalTimeout;
|
|
366
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
367
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test("destroy retires a timed-out correction catalog epoch while a fresh epoch recovers and checks its dynamic namespace", async () => {
|
|
372
|
+
const originalTimeout = process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS;
|
|
373
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-destroyed-correction-catalog-"));
|
|
374
|
+
const firstOrchestrator = new Orchestrator(
|
|
375
|
+
parseConfig({
|
|
376
|
+
memoryDir,
|
|
377
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
378
|
+
qmdEnabled: true,
|
|
379
|
+
qmdMaintenanceEnabled: true,
|
|
380
|
+
namespacesEnabled: true,
|
|
381
|
+
namespaceCatalogEnabled: true,
|
|
382
|
+
defaultNamespace: "default",
|
|
383
|
+
sharedNamespace: "shared",
|
|
384
|
+
namespacePolicies: [],
|
|
385
|
+
embeddingFallbackEnabled: false,
|
|
386
|
+
identityContinuityEnabled: false,
|
|
387
|
+
transcriptEnabled: false,
|
|
388
|
+
hourlySummariesEnabled: false,
|
|
389
|
+
compoundingEnabled: false,
|
|
390
|
+
knowledgeIndexEnabled: false,
|
|
391
|
+
}),
|
|
392
|
+
);
|
|
393
|
+
let freshOrchestrator: Orchestrator | undefined;
|
|
394
|
+
const firstConfiguredNamespaces = (firstOrchestrator as any).configuredNamespaceList() as string[];
|
|
395
|
+
const dynamicRecord = {
|
|
396
|
+
namespace: "dynamic",
|
|
397
|
+
identityToken: "dynamic",
|
|
398
|
+
kind: "project" as const,
|
|
399
|
+
createdAt: new Date(0).toISOString(),
|
|
400
|
+
storageDir: path.join(memoryDir, "namespaces", "dynamic"),
|
|
401
|
+
discoveredBy: "write" as const,
|
|
402
|
+
};
|
|
403
|
+
const firstRecoveredPlanNamespaces: string[][] = [];
|
|
404
|
+
const firstEnsuredNamespaces: string[] = [];
|
|
405
|
+
const firstUpdatedNamespaces: string[][] = [];
|
|
406
|
+
let firstWarmupStarts = 0;
|
|
407
|
+
let firstCatalogCalls = 0;
|
|
408
|
+
let releaseRawCatalog: ((records: unknown[]) => void) | undefined;
|
|
409
|
+
const rawCatalog = new Promise<unknown[]>((resolve) => {
|
|
410
|
+
releaseRawCatalog = resolve;
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS = "1000";
|
|
415
|
+
await mkdir(path.join(dynamicRecord.storageDir, "state"), { recursive: true });
|
|
416
|
+
(firstOrchestrator as any).qmd = {
|
|
417
|
+
probe: async () => true,
|
|
418
|
+
debugStatus: () => "synthetic-first-qmd",
|
|
419
|
+
isAvailable: () => true,
|
|
420
|
+
search: async () => {
|
|
421
|
+
firstWarmupStarts += 1;
|
|
422
|
+
return [];
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
(firstOrchestrator as any).passiveCorrectionService = () => ({
|
|
426
|
+
recoverStaleApplyingPlans: async (namespaces: string[]) => {
|
|
427
|
+
firstRecoveredPlanNamespaces.push([...namespaces]);
|
|
428
|
+
return namespaces.includes(dynamicRecord.namespace) ? 1 : 0;
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
(firstOrchestrator as any).namespaceCatalog.listNamespaces = () => {
|
|
432
|
+
firstCatalogCalls += 1;
|
|
433
|
+
return rawCatalog;
|
|
434
|
+
};
|
|
435
|
+
(firstOrchestrator as any).maintenanceNamespaces = () => Promise.resolve(firstConfiguredNamespaces);
|
|
436
|
+
(firstOrchestrator as any).namespaceSearchRouter.clearCache = () => undefined;
|
|
437
|
+
(firstOrchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async (namespace: string) => {
|
|
438
|
+
firstEnsuredNamespaces.push(namespace);
|
|
439
|
+
return "present";
|
|
440
|
+
};
|
|
441
|
+
(firstOrchestrator as any).namespaceSearchRouter.updateNamespaces = async (namespaces: string[]) => {
|
|
442
|
+
firstUpdatedNamespaces.push([...namespaces]);
|
|
443
|
+
return namespaces.length;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const firstInitialized = await withTestDeadline(firstOrchestrator.initialize().then(() => "settled" as const));
|
|
447
|
+
assert.equal(firstInitialized, "settled", "a raw correction catalog timeout must keep the first startup bounded");
|
|
448
|
+
const firstDeferred = await withTestDeadline(
|
|
449
|
+
(firstOrchestrator as any).deferredReady.then(() => "settled" as const),
|
|
450
|
+
);
|
|
451
|
+
assert.equal(firstDeferred, "settled", "the first fallback startup must settle");
|
|
452
|
+
assert.equal(firstCatalogCalls, 1, "the first epoch must retain one raw correction catalog flight");
|
|
453
|
+
assert.deepEqual(
|
|
454
|
+
firstRecoveredPlanNamespaces,
|
|
455
|
+
[firstConfiguredNamespaces],
|
|
456
|
+
"the first timed-out correction sweep must recover only configured namespaces",
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
await firstOrchestrator.destroy();
|
|
460
|
+
const firstWorkBeforeOldCatalogResolution = {
|
|
461
|
+
ensuredNamespaces: [...firstEnsuredNamespaces],
|
|
462
|
+
updatedNamespaces: firstUpdatedNamespaces.map((namespaces) => [...namespaces]),
|
|
463
|
+
warmupStarts: firstWarmupStarts,
|
|
464
|
+
};
|
|
465
|
+
releaseRawCatalog?.([dynamicRecord]);
|
|
466
|
+
await rawCatalog;
|
|
467
|
+
await Promise.resolve();
|
|
468
|
+
await Promise.resolve();
|
|
469
|
+
await Promise.resolve();
|
|
470
|
+
|
|
471
|
+
assert.deepEqual(
|
|
472
|
+
firstRecoveredPlanNamespaces,
|
|
473
|
+
[firstConfiguredNamespaces],
|
|
474
|
+
"a raw catalog result from the destroyed epoch must not trigger late dynamic correction recovery",
|
|
475
|
+
);
|
|
476
|
+
assert.deepEqual(
|
|
477
|
+
{
|
|
478
|
+
ensuredNamespaces: firstEnsuredNamespaces,
|
|
479
|
+
updatedNamespaces: firstUpdatedNamespaces,
|
|
480
|
+
warmupStarts: firstWarmupStarts,
|
|
481
|
+
},
|
|
482
|
+
firstWorkBeforeOldCatalogResolution,
|
|
483
|
+
"a raw catalog result from the destroyed epoch must not start ensure, update, or warmup work",
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
freshOrchestrator = new Orchestrator(
|
|
487
|
+
parseConfig({
|
|
488
|
+
memoryDir,
|
|
489
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
490
|
+
qmdEnabled: true,
|
|
491
|
+
qmdMaintenanceEnabled: true,
|
|
492
|
+
namespacesEnabled: true,
|
|
493
|
+
namespaceCatalogEnabled: true,
|
|
494
|
+
defaultNamespace: "default",
|
|
495
|
+
sharedNamespace: "shared",
|
|
496
|
+
namespacePolicies: [],
|
|
497
|
+
embeddingFallbackEnabled: false,
|
|
498
|
+
identityContinuityEnabled: false,
|
|
499
|
+
transcriptEnabled: false,
|
|
500
|
+
hourlySummariesEnabled: false,
|
|
501
|
+
compoundingEnabled: false,
|
|
502
|
+
knowledgeIndexEnabled: false,
|
|
503
|
+
}),
|
|
504
|
+
);
|
|
505
|
+
const freshConfiguredNamespaces = (freshOrchestrator as any).configuredNamespaceList() as string[];
|
|
506
|
+
const freshRecoveredPlanNamespaces: string[][] = [];
|
|
507
|
+
const freshEnsuredNamespaces: string[] = [];
|
|
508
|
+
let freshCatalogCalls = 0;
|
|
509
|
+
(freshOrchestrator as any).qmd = {
|
|
510
|
+
probe: async () => true,
|
|
511
|
+
debugStatus: () => "synthetic-fresh-qmd",
|
|
512
|
+
isAvailable: () => true,
|
|
513
|
+
search: async () => [],
|
|
514
|
+
};
|
|
515
|
+
(freshOrchestrator as any).passiveCorrectionService = () => ({
|
|
516
|
+
recoverStaleApplyingPlans: async (namespaces: string[]) => {
|
|
517
|
+
freshRecoveredPlanNamespaces.push([...namespaces]);
|
|
518
|
+
return namespaces.includes(dynamicRecord.namespace) ? 1 : 0;
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
(freshOrchestrator as any).namespaceCatalog.listNamespaces = () => {
|
|
522
|
+
freshCatalogCalls += 1;
|
|
523
|
+
return Promise.resolve([dynamicRecord]);
|
|
524
|
+
};
|
|
525
|
+
(freshOrchestrator as any).maintenanceNamespaces = () => Promise.resolve([
|
|
526
|
+
...freshConfiguredNamespaces,
|
|
527
|
+
dynamicRecord.namespace,
|
|
528
|
+
]);
|
|
529
|
+
(freshOrchestrator as any).namespaceSearchRouter.clearCache = () => undefined;
|
|
530
|
+
(freshOrchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async (namespace: string) => {
|
|
531
|
+
freshEnsuredNamespaces.push(namespace);
|
|
532
|
+
return "present";
|
|
533
|
+
};
|
|
534
|
+
(freshOrchestrator as any).namespaceSearchRouter.updateNamespaces = async (namespaces: string[]) => namespaces.length;
|
|
535
|
+
|
|
536
|
+
const freshInitialized = await withTestDeadline(freshOrchestrator.initialize().then(() => "settled" as const));
|
|
537
|
+
assert.equal(freshInitialized, "settled", "a fresh orchestrator must start a new epoch");
|
|
538
|
+
const freshDeferred = await withTestDeadline(
|
|
539
|
+
(freshOrchestrator as any).deferredReady.then(() => "settled" as const),
|
|
540
|
+
);
|
|
541
|
+
assert.equal(freshDeferred, "settled", "the fresh epoch deferred startup must settle");
|
|
542
|
+
assert.equal(freshCatalogCalls, 1, "the fresh epoch must use its ready dynamic catalog result");
|
|
543
|
+
assert.deepEqual(
|
|
544
|
+
freshRecoveredPlanNamespaces,
|
|
545
|
+
[[...freshConfiguredNamespaces, dynamicRecord.namespace]],
|
|
546
|
+
"the fresh epoch must recover its dynamic namespace normally",
|
|
547
|
+
);
|
|
548
|
+
assert.deepEqual(
|
|
549
|
+
freshEnsuredNamespaces,
|
|
550
|
+
[...freshConfiguredNamespaces, dynamicRecord.namespace],
|
|
551
|
+
"the fresh epoch must check its dynamic namespace normally",
|
|
552
|
+
);
|
|
553
|
+
} finally {
|
|
554
|
+
await freshOrchestrator?.destroy().catch(() => undefined);
|
|
555
|
+
await firstOrchestrator.destroy().catch(() => undefined);
|
|
556
|
+
releaseRawCatalog?.([dynamicRecord]);
|
|
557
|
+
await rawCatalog;
|
|
558
|
+
if (originalTimeout === undefined) delete process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS;
|
|
559
|
+
else process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS = originalTimeout;
|
|
560
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
test("startup retries share one pending maintenance discovery and only a later retry consumes its snapshot", async () => {
|
|
565
|
+
const originalTimeout = process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS;
|
|
566
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-startup-single-flight-"));
|
|
567
|
+
const orchestrator = new Orchestrator(
|
|
568
|
+
parseConfig({
|
|
569
|
+
memoryDir,
|
|
570
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
571
|
+
qmdEnabled: true,
|
|
572
|
+
qmdMaintenanceEnabled: true,
|
|
573
|
+
namespacesEnabled: true,
|
|
574
|
+
namespaceCatalogEnabled: true,
|
|
575
|
+
defaultNamespace: "default",
|
|
576
|
+
sharedNamespace: "shared",
|
|
577
|
+
namespacePolicies: [],
|
|
578
|
+
embeddingFallbackEnabled: false,
|
|
579
|
+
identityContinuityEnabled: false,
|
|
580
|
+
transcriptEnabled: false,
|
|
581
|
+
hourlySummariesEnabled: false,
|
|
582
|
+
compoundingEnabled: false,
|
|
583
|
+
knowledgeIndexEnabled: false,
|
|
584
|
+
}),
|
|
585
|
+
);
|
|
586
|
+
const configuredNamespaces = (orchestrator as any).configuredNamespaceList() as string[];
|
|
587
|
+
const dynamicNamespace = "dynamic";
|
|
588
|
+
const ensuredNamespaces: string[] = [];
|
|
589
|
+
const updatedNamespaces: string[][] = [];
|
|
590
|
+
let warmupStarts = 0;
|
|
591
|
+
let maintenanceCalls = 0;
|
|
592
|
+
let releaseMaintenance: ((namespaces: string[]) => void) | undefined;
|
|
593
|
+
const pendingMaintenance = new Promise<string[]>((resolve) => {
|
|
594
|
+
releaseMaintenance = resolve;
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
try {
|
|
598
|
+
process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS = "1000";
|
|
599
|
+
(orchestrator as any).qmd = {
|
|
600
|
+
probe: async () => true,
|
|
601
|
+
debugStatus: () => "synthetic-qmd",
|
|
602
|
+
isAvailable: () => true,
|
|
603
|
+
search: async () => {
|
|
604
|
+
warmupStarts += 1;
|
|
605
|
+
return [];
|
|
606
|
+
},
|
|
607
|
+
};
|
|
608
|
+
(orchestrator as any).passiveCorrectionService = () => ({
|
|
609
|
+
recoverStaleApplyingPlans: async () => 0,
|
|
610
|
+
});
|
|
611
|
+
(orchestrator as any).namespaceCatalog.listNamespaces = () => Promise.resolve([]);
|
|
612
|
+
(orchestrator as any).maintenanceNamespaces = () => {
|
|
613
|
+
maintenanceCalls += 1;
|
|
614
|
+
return pendingMaintenance;
|
|
615
|
+
};
|
|
616
|
+
(orchestrator as any).namespaceSearchRouter.clearCache = () => undefined;
|
|
617
|
+
(orchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async (namespace: string) => {
|
|
618
|
+
ensuredNamespaces.push(namespace);
|
|
619
|
+
return "present";
|
|
620
|
+
};
|
|
621
|
+
(orchestrator as any).namespaceSearchRouter.updateNamespaces = async (namespaces: string[]) => {
|
|
622
|
+
updatedNamespaces.push([...namespaces]);
|
|
623
|
+
return namespaces.length;
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const initialized = await withTestDeadline(orchestrator.initialize().then(() => "settled" as const));
|
|
627
|
+
assert.equal(initialized, "settled", "a pending maintenance source must not block initialization");
|
|
628
|
+
const deferred = await withTestDeadline((orchestrator as any).deferredReady.then(() => "settled" as const));
|
|
629
|
+
assert.equal(deferred, "settled", "the deferred fallback pass must settle");
|
|
630
|
+
|
|
631
|
+
const retries = [
|
|
632
|
+
await withTestDeadline(orchestrator.startupSearchSync()),
|
|
633
|
+
await withTestDeadline(orchestrator.startupSearchSync()),
|
|
634
|
+
await withTestDeadline(orchestrator.startupSearchSync()),
|
|
635
|
+
];
|
|
636
|
+
assert.deepEqual(retries, [false, false, false]);
|
|
637
|
+
assert.equal(
|
|
638
|
+
maintenanceCalls,
|
|
639
|
+
1,
|
|
640
|
+
"initial, deferred, and sequential retry callers must share one pending maintenance source in an epoch",
|
|
641
|
+
);
|
|
642
|
+
assert.deepEqual(
|
|
643
|
+
ensuredNamespaces,
|
|
644
|
+
[...configuredNamespaces, ...configuredNamespaces, ...configuredNamespaces, ...configuredNamespaces],
|
|
645
|
+
"initial and retry fallback callers must retain configured namespace collection behavior",
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
const qmdStartsBeforeLateMaintenance = {
|
|
649
|
+
ensuredNamespaces: [...ensuredNamespaces],
|
|
650
|
+
updatedNamespaces: updatedNamespaces.map((namespaces) => [...namespaces]),
|
|
651
|
+
warmupStarts,
|
|
652
|
+
};
|
|
653
|
+
releaseMaintenance?.([...configuredNamespaces, dynamicNamespace]);
|
|
654
|
+
await Promise.resolve();
|
|
655
|
+
await Promise.resolve();
|
|
656
|
+
await Promise.resolve();
|
|
657
|
+
assert.deepEqual(
|
|
658
|
+
{
|
|
659
|
+
ensuredNamespaces,
|
|
660
|
+
updatedNamespaces,
|
|
661
|
+
warmupStarts,
|
|
662
|
+
},
|
|
663
|
+
qmdStartsBeforeLateMaintenance,
|
|
664
|
+
"a late maintenance result must not automatically dispatch QMD work",
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
const completeRetry = await withTestDeadline(orchestrator.startupSearchSync());
|
|
668
|
+
assert.equal(completeRetry, true, "a new explicit retry must consume the completed shared snapshot");
|
|
669
|
+
assert.deepEqual(
|
|
670
|
+
ensuredNamespaces.slice(-configuredNamespaces.length - 1),
|
|
671
|
+
[...configuredNamespaces, dynamicNamespace],
|
|
672
|
+
"the explicit complete retry must stage the dynamic namespace",
|
|
673
|
+
);
|
|
674
|
+
} finally {
|
|
675
|
+
releaseMaintenance?.([...configuredNamespaces, dynamicNamespace]);
|
|
676
|
+
if (originalTimeout === undefined) delete process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS;
|
|
677
|
+
else process.env.REMNIC_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS = originalTimeout;
|
|
678
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
679
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
test("destroying a paused initializer prevents its later raw catalog and maintenance discovery", async () => {
|
|
684
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-destroy-before-discovery-"));
|
|
685
|
+
const orchestrator = new Orchestrator(
|
|
686
|
+
parseConfig({
|
|
687
|
+
memoryDir,
|
|
688
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
689
|
+
qmdEnabled: true,
|
|
690
|
+
qmdMaintenanceEnabled: true,
|
|
691
|
+
namespacesEnabled: true,
|
|
692
|
+
namespaceCatalogEnabled: true,
|
|
693
|
+
defaultNamespace: "default",
|
|
694
|
+
sharedNamespace: "shared",
|
|
695
|
+
namespacePolicies: [],
|
|
696
|
+
embeddingFallbackEnabled: false,
|
|
697
|
+
identityContinuityEnabled: false,
|
|
698
|
+
transcriptEnabled: false,
|
|
699
|
+
hourlySummariesEnabled: false,
|
|
700
|
+
compoundingEnabled: false,
|
|
701
|
+
knowledgeIndexEnabled: false,
|
|
702
|
+
}),
|
|
703
|
+
);
|
|
704
|
+
const configuredNamespaces = (orchestrator as any).configuredNamespaceList() as string[];
|
|
705
|
+
const originalLoadAliases = (orchestrator as any).storage.loadAliases.bind((orchestrator as any).storage);
|
|
706
|
+
let storageLoadCalls = 0;
|
|
707
|
+
let releaseFirstStorageLoad: (() => void) | undefined;
|
|
708
|
+
const firstStorageLoadReleased = new Promise<void>((resolve) => {
|
|
709
|
+
releaseFirstStorageLoad = resolve;
|
|
710
|
+
});
|
|
711
|
+
let markFirstStorageLoad: (() => void) | undefined;
|
|
712
|
+
const firstStorageLoadReached = new Promise<void>((resolve) => {
|
|
713
|
+
markFirstStorageLoad = resolve;
|
|
714
|
+
});
|
|
715
|
+
let catalogCalls = 0;
|
|
716
|
+
let maintenanceCalls = 0;
|
|
717
|
+
|
|
718
|
+
try {
|
|
719
|
+
(orchestrator as any).storage.loadAliases = async () => {
|
|
720
|
+
storageLoadCalls += 1;
|
|
721
|
+
if (storageLoadCalls === 1) {
|
|
722
|
+
markFirstStorageLoad?.();
|
|
723
|
+
await firstStorageLoadReleased;
|
|
724
|
+
}
|
|
725
|
+
return originalLoadAliases();
|
|
726
|
+
};
|
|
727
|
+
(orchestrator as any).qmd = {
|
|
728
|
+
probe: async () => true,
|
|
729
|
+
debugStatus: () => "synthetic-qmd",
|
|
730
|
+
isAvailable: () => true,
|
|
731
|
+
search: async () => [],
|
|
732
|
+
};
|
|
733
|
+
(orchestrator as any).passiveCorrectionService = () => ({
|
|
734
|
+
recoverStaleApplyingPlans: async () => 0,
|
|
735
|
+
});
|
|
736
|
+
(orchestrator as any).namespaceCatalog.listNamespaces = () => {
|
|
737
|
+
catalogCalls += 1;
|
|
738
|
+
return Promise.resolve([]);
|
|
739
|
+
};
|
|
740
|
+
(orchestrator as any).maintenanceNamespaces = () => {
|
|
741
|
+
maintenanceCalls += 1;
|
|
742
|
+
return Promise.resolve(configuredNamespaces);
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
const firstInitGate = (orchestrator as any).initPromise as Promise<void>;
|
|
746
|
+
const firstInitialize = orchestrator.initialize();
|
|
747
|
+
const firstDeferredReady = (orchestrator as any).deferredReady as Promise<void>;
|
|
748
|
+
const paused = await withTestDeadline(firstStorageLoadReached);
|
|
749
|
+
assert.notEqual(paused, "deadline", "the first initializer must pause before discovery");
|
|
750
|
+
|
|
751
|
+
await orchestrator.destroy();
|
|
752
|
+
releaseFirstStorageLoad?.();
|
|
753
|
+
|
|
754
|
+
const firstResult = await withTestDeadline(firstInitialize.then(() => "settled" as const), 2_500);
|
|
755
|
+
assert.equal(firstResult, "settled", "the destroyed initializer must settle safely after release");
|
|
756
|
+
const initGateResult = await withTestDeadline(firstInitGate.then(() => "settled" as const), 2_500);
|
|
757
|
+
assert.equal(initGateResult, "settled", "the destroyed initializer must not orphan its init gate");
|
|
758
|
+
const deferredResult = await withTestDeadline(firstDeferredReady.then(() => "settled" as const), 2_500);
|
|
759
|
+
assert.equal(deferredResult, "settled", "the destroyed initializer must not orphan its deferred gate");
|
|
760
|
+
assert.equal(catalogCalls, 0, "the destroyed initializer must not start raw catalog discovery after retirement");
|
|
761
|
+
assert.equal(maintenanceCalls, 0, "the destroyed initializer must not start raw maintenance discovery after retirement");
|
|
762
|
+
} finally {
|
|
763
|
+
releaseFirstStorageLoad?.();
|
|
764
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
765
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
test("a superseded paused initializer cannot admit discovery or resolve the newer deferred gate", async () => {
|
|
770
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-superseded-before-discovery-"));
|
|
771
|
+
const orchestrator = new Orchestrator(
|
|
772
|
+
parseConfig({
|
|
773
|
+
memoryDir,
|
|
774
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
775
|
+
qmdEnabled: true,
|
|
776
|
+
qmdMaintenanceEnabled: true,
|
|
777
|
+
namespacesEnabled: true,
|
|
778
|
+
namespaceCatalogEnabled: true,
|
|
779
|
+
defaultNamespace: "default",
|
|
780
|
+
sharedNamespace: "shared",
|
|
781
|
+
namespacePolicies: [],
|
|
782
|
+
embeddingFallbackEnabled: false,
|
|
783
|
+
identityContinuityEnabled: false,
|
|
784
|
+
transcriptEnabled: false,
|
|
785
|
+
hourlySummariesEnabled: false,
|
|
786
|
+
compoundingEnabled: false,
|
|
787
|
+
knowledgeIndexEnabled: false,
|
|
788
|
+
}),
|
|
789
|
+
);
|
|
790
|
+
const configuredNamespaces = (orchestrator as any).configuredNamespaceList() as string[];
|
|
791
|
+
const originalLoadAliases = (orchestrator as any).storage.loadAliases.bind((orchestrator as any).storage);
|
|
792
|
+
let storageLoadCalls = 0;
|
|
793
|
+
let releaseFirstStorageLoad: (() => void) | undefined;
|
|
794
|
+
const firstStorageLoadReleased = new Promise<void>((resolve) => {
|
|
795
|
+
releaseFirstStorageLoad = resolve;
|
|
796
|
+
});
|
|
797
|
+
let markFirstStorageLoad: (() => void) | undefined;
|
|
798
|
+
const firstStorageLoadReached = new Promise<void>((resolve) => {
|
|
799
|
+
markFirstStorageLoad = resolve;
|
|
800
|
+
});
|
|
801
|
+
let releaseActiveProbe: (() => void) | undefined;
|
|
802
|
+
const activeProbeReleased = new Promise<void>((resolve) => {
|
|
803
|
+
releaseActiveProbe = resolve;
|
|
804
|
+
});
|
|
805
|
+
let markActiveProbe: (() => void) | undefined;
|
|
806
|
+
const activeProbeReached = new Promise<void>((resolve) => {
|
|
807
|
+
markActiveProbe = resolve;
|
|
808
|
+
});
|
|
809
|
+
let catalogCalls = 0;
|
|
810
|
+
let maintenanceCalls = 0;
|
|
811
|
+
let probeCalls = 0;
|
|
812
|
+
|
|
813
|
+
try {
|
|
814
|
+
(orchestrator as any).storage.loadAliases = async () => {
|
|
815
|
+
storageLoadCalls += 1;
|
|
816
|
+
if (storageLoadCalls === 1) {
|
|
817
|
+
markFirstStorageLoad?.();
|
|
818
|
+
await firstStorageLoadReleased;
|
|
819
|
+
}
|
|
820
|
+
return originalLoadAliases();
|
|
821
|
+
};
|
|
822
|
+
(orchestrator as any).qmd = {
|
|
823
|
+
probe: async () => {
|
|
824
|
+
probeCalls += 1;
|
|
825
|
+
if (probeCalls === 1) {
|
|
826
|
+
markActiveProbe?.();
|
|
827
|
+
await activeProbeReleased;
|
|
828
|
+
}
|
|
829
|
+
return true;
|
|
830
|
+
},
|
|
831
|
+
debugStatus: () => "synthetic-qmd",
|
|
832
|
+
isAvailable: () => true,
|
|
833
|
+
search: async () => [],
|
|
834
|
+
};
|
|
835
|
+
(orchestrator as any).passiveCorrectionService = () => ({
|
|
836
|
+
recoverStaleApplyingPlans: async () => 0,
|
|
837
|
+
});
|
|
838
|
+
(orchestrator as any).namespaceCatalog.listNamespaces = () => {
|
|
839
|
+
catalogCalls += 1;
|
|
840
|
+
return Promise.resolve([]);
|
|
841
|
+
};
|
|
842
|
+
(orchestrator as any).maintenanceNamespaces = () => {
|
|
843
|
+
maintenanceCalls += 1;
|
|
844
|
+
return Promise.resolve(configuredNamespaces);
|
|
845
|
+
};
|
|
846
|
+
(orchestrator as any).namespaceSearchRouter.clearCache = () => undefined;
|
|
847
|
+
(orchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async () => "present";
|
|
848
|
+
(orchestrator as any).namespaceSearchRouter.updateNamespaces = async (namespaces: string[]) => namespaces.length;
|
|
849
|
+
|
|
850
|
+
const initGate = (orchestrator as any).initPromise as Promise<void>;
|
|
851
|
+
let initGateSettled = false;
|
|
852
|
+
void initGate.then(() => {
|
|
853
|
+
initGateSettled = true;
|
|
854
|
+
});
|
|
855
|
+
const firstInitialize = orchestrator.initialize();
|
|
856
|
+
const firstDeferredReady = (orchestrator as any).deferredReady as Promise<void>;
|
|
857
|
+
const paused = await withTestDeadline(firstStorageLoadReached);
|
|
858
|
+
assert.notEqual(paused, "deadline", "the first initializer must pause before discovery");
|
|
859
|
+
|
|
860
|
+
const secondInitialize = orchestrator.initialize();
|
|
861
|
+
const secondDeferredReady = (orchestrator as any).deferredReady as Promise<void>;
|
|
862
|
+
let secondDeferredSettled = false;
|
|
863
|
+
void secondDeferredReady.then(() => {
|
|
864
|
+
secondDeferredSettled = true;
|
|
865
|
+
});
|
|
866
|
+
const activeProbe = await withTestDeadline(activeProbeReached, 2_500);
|
|
867
|
+
assert.notEqual(activeProbe, "deadline", "the replacement initializer must reach its probe");
|
|
868
|
+
assert.equal(catalogCalls, 1, "only the replacement initializer may start the catalog flight");
|
|
869
|
+
|
|
870
|
+
releaseFirstStorageLoad?.();
|
|
871
|
+
const firstResult = await withTestDeadline(firstInitialize.then(() => "settled" as const), 2_500);
|
|
872
|
+
assert.equal(firstResult, "settled", "the superseded initializer must settle safely after release");
|
|
873
|
+
const firstDeferredResult = await withTestDeadline(firstDeferredReady.then(() => "settled" as const), 2_500);
|
|
874
|
+
assert.equal(firstDeferredResult, "settled", "the superseded initializer must resolve its own deferred gate");
|
|
875
|
+
await Promise.resolve();
|
|
876
|
+
assert.equal(catalogCalls, 1, "the superseded initializer must not admit a second raw catalog flight");
|
|
877
|
+
assert.equal(maintenanceCalls, 0, "the superseded initializer must not admit maintenance before the replacement reaches it");
|
|
878
|
+
assert.equal(initGateSettled, false, "the superseded initializer must not resolve the replacement init gate");
|
|
879
|
+
assert.equal(secondDeferredSettled, false, "the superseded initializer must not resolve the replacement deferred gate");
|
|
880
|
+
|
|
881
|
+
releaseActiveProbe?.();
|
|
882
|
+
const secondResult = await withTestDeadline(secondInitialize.then(() => "settled" as const), 2_500);
|
|
883
|
+
assert.equal(secondResult, "settled", "the replacement initializer must complete normally");
|
|
884
|
+
const initGateResult = await withTestDeadline(initGate.then(() => "settled" as const), 2_500);
|
|
885
|
+
assert.equal(initGateResult, "settled", "the replacement initializer must resolve the shared init gate");
|
|
886
|
+
const secondDeferredResult = await withTestDeadline(secondDeferredReady.then(() => "settled" as const), 2_500);
|
|
887
|
+
assert.equal(secondDeferredResult, "settled", "the replacement deferred gate must settle with its own lifecycle");
|
|
888
|
+
assert.equal(catalogCalls, 1, "the active lifecycle must retain exactly one catalog flight");
|
|
889
|
+
assert.equal(maintenanceCalls, 1, "the active lifecycle must retain exactly one maintenance flight");
|
|
890
|
+
} finally {
|
|
891
|
+
releaseFirstStorageLoad?.();
|
|
892
|
+
releaseActiveProbe?.();
|
|
893
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
894
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
test("retiring an epoch before queued discovery admission starts no raw sources", async () => {
|
|
899
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-retired-queued-discovery-"));
|
|
900
|
+
const orchestrator = new Orchestrator(
|
|
901
|
+
parseConfig({
|
|
902
|
+
memoryDir,
|
|
903
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
904
|
+
qmdEnabled: true,
|
|
905
|
+
namespacesEnabled: true,
|
|
906
|
+
namespaceCatalogEnabled: true,
|
|
907
|
+
defaultNamespace: "default",
|
|
908
|
+
sharedNamespace: "shared",
|
|
909
|
+
namespacePolicies: [],
|
|
910
|
+
embeddingFallbackEnabled: false,
|
|
911
|
+
identityContinuityEnabled: false,
|
|
912
|
+
transcriptEnabled: false,
|
|
913
|
+
hourlySummariesEnabled: false,
|
|
914
|
+
compoundingEnabled: false,
|
|
915
|
+
knowledgeIndexEnabled: false,
|
|
916
|
+
}),
|
|
917
|
+
);
|
|
918
|
+
let catalogCalls = 0;
|
|
919
|
+
let maintenanceCalls = 0;
|
|
920
|
+
|
|
921
|
+
try {
|
|
922
|
+
(orchestrator as any).namespaceCatalog.listNamespaces = () => {
|
|
923
|
+
catalogCalls += 1;
|
|
924
|
+
return Promise.resolve([]);
|
|
925
|
+
};
|
|
926
|
+
(orchestrator as any).maintenanceNamespaces = () => {
|
|
927
|
+
maintenanceCalls += 1;
|
|
928
|
+
return Promise.resolve([]);
|
|
929
|
+
};
|
|
930
|
+
const coordinator = (orchestrator as any).orchestratorInitCoordinator;
|
|
931
|
+
const epoch = coordinator.beginStartupEpoch();
|
|
932
|
+
const catalogFlight = coordinator.correctionCatalogFlightFor(epoch);
|
|
933
|
+
const maintenanceFlight = coordinator.maintenanceNamespacesFlightFor(epoch);
|
|
934
|
+
coordinator.retireStartupEpoch();
|
|
935
|
+
|
|
936
|
+
await Promise.all([catalogFlight, maintenanceFlight]);
|
|
937
|
+
assert.deepEqual(
|
|
938
|
+
{ catalogCalls, maintenanceCalls },
|
|
939
|
+
{ catalogCalls: 0, maintenanceCalls: 0 },
|
|
940
|
+
"retirement between raw-flight queueing and callback execution must suppress both non-cancellable source admissions",
|
|
941
|
+
);
|
|
942
|
+
} finally {
|
|
943
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
944
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
test("a stale collection-check batch cannot replace the newer QMD backend", async () => {
|
|
949
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-stale-collection-qmd-"));
|
|
950
|
+
const orchestrator = new Orchestrator(
|
|
951
|
+
parseConfig({
|
|
952
|
+
memoryDir,
|
|
953
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
954
|
+
qmdEnabled: true,
|
|
955
|
+
qmdMaintenanceEnabled: false,
|
|
956
|
+
namespacesEnabled: true,
|
|
957
|
+
namespaceCatalogEnabled: false,
|
|
958
|
+
defaultNamespace: "default",
|
|
959
|
+
sharedNamespace: "shared",
|
|
960
|
+
namespacePolicies: [],
|
|
961
|
+
embeddingFallbackEnabled: false,
|
|
962
|
+
identityContinuityEnabled: false,
|
|
963
|
+
transcriptEnabled: false,
|
|
964
|
+
hourlySummariesEnabled: false,
|
|
965
|
+
compoundingEnabled: false,
|
|
966
|
+
knowledgeIndexEnabled: false,
|
|
967
|
+
}),
|
|
968
|
+
);
|
|
969
|
+
let releaseOldDefaultCheck: (() => void) | undefined;
|
|
970
|
+
const oldDefaultCheckReleased = new Promise<void>((resolve) => {
|
|
971
|
+
releaseOldDefaultCheck = resolve;
|
|
972
|
+
});
|
|
973
|
+
let markOldDefaultCheck: (() => void) | undefined;
|
|
974
|
+
const oldDefaultCheckReached = new Promise<void>((resolve) => {
|
|
975
|
+
markOldDefaultCheck = resolve;
|
|
976
|
+
});
|
|
977
|
+
let markNewDefaultCheck: (() => void) | undefined;
|
|
978
|
+
const newDefaultCheckReached = new Promise<void>((resolve) => {
|
|
979
|
+
markNewDefaultCheck = resolve;
|
|
980
|
+
});
|
|
981
|
+
let defaultCheckCalls = 0;
|
|
982
|
+
let disposeCalls = 0;
|
|
983
|
+
const newerQmd = {
|
|
984
|
+
probe: async () => true,
|
|
985
|
+
debugStatus: () => "newer-qmd",
|
|
986
|
+
isAvailable: () => true,
|
|
987
|
+
search: async () => [],
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
try {
|
|
991
|
+
(orchestrator as any).qmd = {
|
|
992
|
+
probe: async () => true,
|
|
993
|
+
debugStatus: () => "old-qmd",
|
|
994
|
+
isAvailable: () => true,
|
|
995
|
+
search: async () => [],
|
|
996
|
+
};
|
|
997
|
+
(orchestrator as any).passiveCorrectionService = () => ({
|
|
998
|
+
recoverStaleApplyingPlans: async () => 0,
|
|
999
|
+
});
|
|
1000
|
+
(orchestrator as any).maintenanceNamespaces = () =>
|
|
1001
|
+
Promise.resolve((orchestrator as any).configuredNamespaceList());
|
|
1002
|
+
(orchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async (namespace: string) => {
|
|
1003
|
+
if (namespace !== "default") return "present";
|
|
1004
|
+
defaultCheckCalls += 1;
|
|
1005
|
+
if (defaultCheckCalls === 1) {
|
|
1006
|
+
markOldDefaultCheck?.();
|
|
1007
|
+
await oldDefaultCheckReleased;
|
|
1008
|
+
return "missing";
|
|
1009
|
+
}
|
|
1010
|
+
markNewDefaultCheck?.();
|
|
1011
|
+
return "present";
|
|
1012
|
+
};
|
|
1013
|
+
(orchestrator as any).disposeSearchBackendIfNeeded = async () => {
|
|
1014
|
+
disposeCalls += 1;
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
const oldInitialize = orchestrator.initialize();
|
|
1018
|
+
const oldCheck = await withTestDeadline(oldDefaultCheckReached);
|
|
1019
|
+
assert.notEqual(oldCheck, "deadline", "the first epoch must pause in its collection-check batch");
|
|
1020
|
+
|
|
1021
|
+
const newerInitialize = orchestrator.initialize();
|
|
1022
|
+
(orchestrator as any).qmd = newerQmd;
|
|
1023
|
+
const newCheck = await withTestDeadline(newDefaultCheckReached);
|
|
1024
|
+
assert.notEqual(newCheck, "deadline", "the replacement epoch must reach its collection check");
|
|
1025
|
+
const newerResult = await withTestDeadline(newerInitialize.then(() => "settled" as const), 2_500);
|
|
1026
|
+
assert.equal(newerResult, "settled", "the replacement epoch must finalize its QMD state");
|
|
1027
|
+
|
|
1028
|
+
releaseOldDefaultCheck?.();
|
|
1029
|
+
const oldResult = await withTestDeadline(oldInitialize.then(() => "settled" as const), 2_500);
|
|
1030
|
+
assert.equal(oldResult, "settled", "the stale initializer must settle after its collection check releases");
|
|
1031
|
+
assert.equal(disposeCalls, 0, "a retired collection-check batch must not dispose the active backend");
|
|
1032
|
+
assert.equal(
|
|
1033
|
+
(orchestrator as any).qmd,
|
|
1034
|
+
newerQmd,
|
|
1035
|
+
"a retired collection-check batch must not replace the newer QMD backend with NoopSearchBackend",
|
|
1036
|
+
);
|
|
1037
|
+
} finally {
|
|
1038
|
+
releaseOldDefaultCheck?.();
|
|
1039
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
1040
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
test("a disposal that becomes stale cannot replace the newer QMD backend", async () => {
|
|
1045
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-stale-disposal-qmd-"));
|
|
1046
|
+
const orchestrator = new Orchestrator(
|
|
1047
|
+
parseConfig({
|
|
1048
|
+
memoryDir,
|
|
1049
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1050
|
+
qmdEnabled: true,
|
|
1051
|
+
qmdMaintenanceEnabled: false,
|
|
1052
|
+
namespacesEnabled: true,
|
|
1053
|
+
namespaceCatalogEnabled: false,
|
|
1054
|
+
defaultNamespace: "default",
|
|
1055
|
+
sharedNamespace: "shared",
|
|
1056
|
+
namespacePolicies: [],
|
|
1057
|
+
embeddingFallbackEnabled: false,
|
|
1058
|
+
identityContinuityEnabled: false,
|
|
1059
|
+
transcriptEnabled: false,
|
|
1060
|
+
hourlySummariesEnabled: false,
|
|
1061
|
+
compoundingEnabled: false,
|
|
1062
|
+
knowledgeIndexEnabled: false,
|
|
1063
|
+
}),
|
|
1064
|
+
);
|
|
1065
|
+
let releaseOldDisposal: (() => void) | undefined;
|
|
1066
|
+
const oldDisposalReleased = new Promise<void>((resolve) => {
|
|
1067
|
+
releaseOldDisposal = resolve;
|
|
1068
|
+
});
|
|
1069
|
+
let markOldDisposal: (() => void) | undefined;
|
|
1070
|
+
const oldDisposalReached = new Promise<void>((resolve) => {
|
|
1071
|
+
markOldDisposal = resolve;
|
|
1072
|
+
});
|
|
1073
|
+
let defaultCheckCalls = 0;
|
|
1074
|
+
const newerQmd = {
|
|
1075
|
+
probe: async () => true,
|
|
1076
|
+
debugStatus: () => "newer-qmd",
|
|
1077
|
+
isAvailable: () => true,
|
|
1078
|
+
search: async () => [],
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
try {
|
|
1082
|
+
(orchestrator as any).qmd = {
|
|
1083
|
+
probe: async () => true,
|
|
1084
|
+
debugStatus: () => "old-qmd",
|
|
1085
|
+
isAvailable: () => true,
|
|
1086
|
+
search: async () => [],
|
|
1087
|
+
};
|
|
1088
|
+
(orchestrator as any).passiveCorrectionService = () => ({
|
|
1089
|
+
recoverStaleApplyingPlans: async () => 0,
|
|
1090
|
+
});
|
|
1091
|
+
(orchestrator as any).maintenanceNamespaces = () =>
|
|
1092
|
+
Promise.resolve((orchestrator as any).configuredNamespaceList());
|
|
1093
|
+
(orchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async (namespace: string) => {
|
|
1094
|
+
if (namespace !== "default") return "present";
|
|
1095
|
+
defaultCheckCalls += 1;
|
|
1096
|
+
return defaultCheckCalls === 1 ? "missing" : "present";
|
|
1097
|
+
};
|
|
1098
|
+
(orchestrator as any).disposeSearchBackendIfNeeded = async () => {
|
|
1099
|
+
markOldDisposal?.();
|
|
1100
|
+
await oldDisposalReleased;
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
const oldInitialize = orchestrator.initialize();
|
|
1104
|
+
const disposal = await withTestDeadline(oldDisposalReached);
|
|
1105
|
+
assert.notEqual(disposal, "deadline", "the first epoch must pause while disposing its missing backend");
|
|
1106
|
+
|
|
1107
|
+
const newerInitialize = orchestrator.initialize();
|
|
1108
|
+
(orchestrator as any).qmd = newerQmd;
|
|
1109
|
+
const newerResult = await withTestDeadline(newerInitialize.then(() => "settled" as const), 2_500);
|
|
1110
|
+
assert.equal(newerResult, "settled", "the replacement epoch must finalize before stale disposal releases");
|
|
1111
|
+
|
|
1112
|
+
releaseOldDisposal?.();
|
|
1113
|
+
const oldResult = await withTestDeadline(oldInitialize.then(() => "settled" as const), 2_500);
|
|
1114
|
+
assert.equal(oldResult, "settled", "the stale initializer must settle after disposal releases");
|
|
1115
|
+
assert.equal(
|
|
1116
|
+
(orchestrator as any).qmd,
|
|
1117
|
+
newerQmd,
|
|
1118
|
+
"a disposal completed by a retired epoch must not replace the newer QMD backend with NoopSearchBackend",
|
|
1119
|
+
);
|
|
1120
|
+
} finally {
|
|
1121
|
+
releaseOldDisposal?.();
|
|
1122
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
1123
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
test("a stale startup-search-sync disposal cannot replace the newer QMD backend", async () => {
|
|
1128
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-stale-retry-disposal-qmd-"));
|
|
1129
|
+
const orchestrator = new Orchestrator(
|
|
1130
|
+
parseConfig({
|
|
1131
|
+
memoryDir,
|
|
1132
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1133
|
+
qmdEnabled: true,
|
|
1134
|
+
qmdMaintenanceEnabled: false,
|
|
1135
|
+
namespacesEnabled: true,
|
|
1136
|
+
namespaceCatalogEnabled: false,
|
|
1137
|
+
defaultNamespace: "default",
|
|
1138
|
+
sharedNamespace: "shared",
|
|
1139
|
+
namespacePolicies: [],
|
|
1140
|
+
embeddingFallbackEnabled: false,
|
|
1141
|
+
identityContinuityEnabled: false,
|
|
1142
|
+
transcriptEnabled: false,
|
|
1143
|
+
hourlySummariesEnabled: false,
|
|
1144
|
+
compoundingEnabled: false,
|
|
1145
|
+
knowledgeIndexEnabled: false,
|
|
1146
|
+
}),
|
|
1147
|
+
);
|
|
1148
|
+
let releaseOldDisposal: (() => void) | undefined;
|
|
1149
|
+
const oldDisposalReleased = new Promise<void>((resolve) => {
|
|
1150
|
+
releaseOldDisposal = resolve;
|
|
1151
|
+
});
|
|
1152
|
+
let markOldDisposal: (() => void) | undefined;
|
|
1153
|
+
const oldDisposalReached = new Promise<void>((resolve) => {
|
|
1154
|
+
markOldDisposal = resolve;
|
|
1155
|
+
});
|
|
1156
|
+
let defaultCheckCalls = 0;
|
|
1157
|
+
const newerQmd = {
|
|
1158
|
+
available: true,
|
|
1159
|
+
probe: async () => true,
|
|
1160
|
+
debugStatus: () => "newer-qmd",
|
|
1161
|
+
isAvailable: () => true,
|
|
1162
|
+
search: async () => [],
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
try {
|
|
1166
|
+
(orchestrator as any).qmd = {
|
|
1167
|
+
available: true,
|
|
1168
|
+
probe: async () => true,
|
|
1169
|
+
debugStatus: () => "old-qmd",
|
|
1170
|
+
isAvailable: () => true,
|
|
1171
|
+
search: async () => [],
|
|
1172
|
+
};
|
|
1173
|
+
(orchestrator as any).passiveCorrectionService = () => ({
|
|
1174
|
+
recoverStaleApplyingPlans: async () => 0,
|
|
1175
|
+
});
|
|
1176
|
+
(orchestrator as any).maintenanceNamespaces = () =>
|
|
1177
|
+
Promise.resolve((orchestrator as any).configuredNamespaceList());
|
|
1178
|
+
(orchestrator as any).namespaceSearchRouter.clearCache = () => undefined;
|
|
1179
|
+
(orchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async (namespace: string) => {
|
|
1180
|
+
if (namespace !== "default") return "present";
|
|
1181
|
+
defaultCheckCalls += 1;
|
|
1182
|
+
return defaultCheckCalls === 1 ? "missing" : "present";
|
|
1183
|
+
};
|
|
1184
|
+
(orchestrator as any).disposeSearchBackendIfNeeded = async () => {
|
|
1185
|
+
markOldDisposal?.();
|
|
1186
|
+
await oldDisposalReleased;
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
const oldSync = orchestrator.startupSearchSync();
|
|
1190
|
+
const disposal = await withTestDeadline(oldDisposalReached);
|
|
1191
|
+
assert.notEqual(disposal, "deadline", "the first retry must pause while disposing its missing backend");
|
|
1192
|
+
|
|
1193
|
+
const newerInitialize = orchestrator.initialize();
|
|
1194
|
+
(orchestrator as any).qmd = newerQmd;
|
|
1195
|
+
const newerResult = await withTestDeadline(newerInitialize.then(() => "settled" as const), 2_500);
|
|
1196
|
+
assert.equal(newerResult, "settled", "the replacement epoch must finalize before stale retry disposal releases");
|
|
1197
|
+
|
|
1198
|
+
releaseOldDisposal?.();
|
|
1199
|
+
const oldResult = await withTestDeadline(oldSync, 2_500);
|
|
1200
|
+
assert.equal(oldResult, false, "a stale retry must report no successful sync");
|
|
1201
|
+
assert.equal(
|
|
1202
|
+
(orchestrator as any).qmd,
|
|
1203
|
+
newerQmd,
|
|
1204
|
+
"a disposal completed by a retired retry must not replace the newer QMD backend with NoopSearchBackend",
|
|
1205
|
+
);
|
|
1206
|
+
assert.equal(newerQmd.available, true, "a stale retry must not disable the newer backend availability flag");
|
|
1207
|
+
} finally {
|
|
1208
|
+
releaseOldDisposal?.();
|
|
1209
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
1210
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
test("a newer epoch aborts a paused deferred warmup before cache cron or timer work", async () => {
|
|
1215
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-stale-deferred-warmup-"));
|
|
1216
|
+
const orchestrator = new Orchestrator(
|
|
1217
|
+
parseConfig({
|
|
1218
|
+
memoryDir,
|
|
1219
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1220
|
+
qmdEnabled: true,
|
|
1221
|
+
qmdMaintenanceEnabled: false,
|
|
1222
|
+
namespacesEnabled: true,
|
|
1223
|
+
namespaceCatalogEnabled: false,
|
|
1224
|
+
defaultNamespace: "default",
|
|
1225
|
+
sharedNamespace: "shared",
|
|
1226
|
+
namespacePolicies: [],
|
|
1227
|
+
embeddingFallbackEnabled: false,
|
|
1228
|
+
identityContinuityEnabled: false,
|
|
1229
|
+
transcriptEnabled: false,
|
|
1230
|
+
hourlySummariesEnabled: false,
|
|
1231
|
+
compoundingEnabled: false,
|
|
1232
|
+
knowledgeIndexEnabled: false,
|
|
1233
|
+
wearables: {
|
|
1234
|
+
enabled: true,
|
|
1235
|
+
sources: { synthetic: { enabled: true } },
|
|
1236
|
+
},
|
|
1237
|
+
}),
|
|
1238
|
+
);
|
|
1239
|
+
let releaseOldWarmup: (() => void) | undefined;
|
|
1240
|
+
const oldWarmupReleased = new Promise<void>((resolve) => {
|
|
1241
|
+
releaseOldWarmup = resolve;
|
|
1242
|
+
});
|
|
1243
|
+
let markOldWarmup: (() => void) | undefined;
|
|
1244
|
+
const oldWarmupReached = new Promise<void>((resolve) => {
|
|
1245
|
+
markOldWarmup = resolve;
|
|
1246
|
+
});
|
|
1247
|
+
let releaseNewWarmup: (() => void) | undefined;
|
|
1248
|
+
const newWarmupReleased = new Promise<void>((resolve) => {
|
|
1249
|
+
releaseNewWarmup = resolve;
|
|
1250
|
+
});
|
|
1251
|
+
let markNewPrimary: (() => void) | undefined;
|
|
1252
|
+
const newPrimaryReached = new Promise<void>((resolve) => {
|
|
1253
|
+
markNewPrimary = resolve;
|
|
1254
|
+
});
|
|
1255
|
+
let releaseNewPrimary: (() => void) | undefined;
|
|
1256
|
+
const newPrimaryReleased = new Promise<void>((resolve) => {
|
|
1257
|
+
releaseNewPrimary = resolve;
|
|
1258
|
+
});
|
|
1259
|
+
let searchCalls = 0;
|
|
1260
|
+
let oldWarmupSignal: AbortSignal | undefined;
|
|
1261
|
+
let cacheCalls = 0;
|
|
1262
|
+
let cronCalls = 0;
|
|
1263
|
+
const originalLoadAliases = (orchestrator as any).storage.loadAliases.bind((orchestrator as any).storage);
|
|
1264
|
+
|
|
1265
|
+
try {
|
|
1266
|
+
(orchestrator as any).qmd = {
|
|
1267
|
+
probe: async () => true,
|
|
1268
|
+
debugStatus: () => "synthetic-qmd",
|
|
1269
|
+
isAvailable: () => true,
|
|
1270
|
+
search: async (...args: any[]) => {
|
|
1271
|
+
searchCalls += 1;
|
|
1272
|
+
if (searchCalls === 1) {
|
|
1273
|
+
oldWarmupSignal = args[4]?.signal as AbortSignal | undefined;
|
|
1274
|
+
markOldWarmup?.();
|
|
1275
|
+
await oldWarmupReleased;
|
|
1276
|
+
return [];
|
|
1277
|
+
}
|
|
1278
|
+
await newWarmupReleased;
|
|
1279
|
+
return [];
|
|
1280
|
+
},
|
|
1281
|
+
};
|
|
1282
|
+
(orchestrator as any).passiveCorrectionService = () => ({
|
|
1283
|
+
recoverStaleApplyingPlans: async () => 0,
|
|
1284
|
+
});
|
|
1285
|
+
(orchestrator as any).maintenanceNamespaces = () =>
|
|
1286
|
+
Promise.resolve((orchestrator as any).configuredNamespaceList());
|
|
1287
|
+
(orchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async () => "present";
|
|
1288
|
+
(orchestrator as any).storage.readAllMemories = async () => {
|
|
1289
|
+
cacheCalls += 1;
|
|
1290
|
+
return [];
|
|
1291
|
+
};
|
|
1292
|
+
(orchestrator as any).storage.readAllEntityFiles = async () => {
|
|
1293
|
+
cacheCalls += 1;
|
|
1294
|
+
return [];
|
|
1295
|
+
};
|
|
1296
|
+
(orchestrator as any).maintenanceScheduler.autoRegisterCrons = async () => {
|
|
1297
|
+
cronCalls += 1;
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
const oldInitialize = orchestrator.initialize();
|
|
1301
|
+
const oldDeferredReady = (orchestrator as any).deferredReady as Promise<void>;
|
|
1302
|
+
const oldWarmup = await withTestDeadline(oldWarmupReached);
|
|
1303
|
+
assert.notEqual(oldWarmup, "deadline", "the first deferred epoch must pause in warmup");
|
|
1304
|
+
assert.equal((orchestrator as any).deferredSyncSucceeded, true, "the old epoch must have recorded its successful sync before warmup");
|
|
1305
|
+
const cacheCallsBeforeReplacement = cacheCalls;
|
|
1306
|
+
|
|
1307
|
+
(orchestrator as any).storage.loadAliases = async () => {
|
|
1308
|
+
markNewPrimary?.();
|
|
1309
|
+
await newPrimaryReleased;
|
|
1310
|
+
return originalLoadAliases();
|
|
1311
|
+
};
|
|
1312
|
+
const newerInitialize = orchestrator.initialize();
|
|
1313
|
+
const newerDeferredReady = (orchestrator as any).deferredReady as Promise<void>;
|
|
1314
|
+
const newPrimary = await withTestDeadline(newPrimaryReached);
|
|
1315
|
+
assert.notEqual(newPrimary, "deadline", "the replacement initializer must start a newer epoch");
|
|
1316
|
+
assert.equal(oldWarmupSignal?.aborted, true, "starting a newer epoch must abort the old deferred controller");
|
|
1317
|
+
assert.equal((orchestrator as any).deferredSyncSucceeded, false, "a newer epoch must not retain the old sync-success state");
|
|
1318
|
+
|
|
1319
|
+
releaseOldWarmup?.();
|
|
1320
|
+
const oldDeferredResult = await withTestDeadline(oldDeferredReady.then(() => "settled" as const), 2_500);
|
|
1321
|
+
assert.equal(oldDeferredResult, "settled", "the retired deferred resolver must settle after its warmup releases");
|
|
1322
|
+
assert.deepEqual(
|
|
1323
|
+
{
|
|
1324
|
+
cacheCalls,
|
|
1325
|
+
cronCalls,
|
|
1326
|
+
wearablesAutoSyncHandle: (orchestrator as any).wearablesAutoSyncHandle,
|
|
1327
|
+
deferredSyncSucceeded: (orchestrator as any).deferredSyncSucceeded,
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
cacheCalls: cacheCallsBeforeReplacement,
|
|
1331
|
+
cronCalls: 0,
|
|
1332
|
+
wearablesAutoSyncHandle: null,
|
|
1333
|
+
deferredSyncSucceeded: false,
|
|
1334
|
+
},
|
|
1335
|
+
"a retired warmup must not dispatch cache, cron, timer, or stale sync-status work",
|
|
1336
|
+
);
|
|
1337
|
+
|
|
1338
|
+
releaseNewPrimary?.();
|
|
1339
|
+
const newerResult = await withTestDeadline(newerInitialize.then(() => "settled" as const), 2_500);
|
|
1340
|
+
assert.equal(newerResult, "settled", "the replacement initializer must settle after its primary pause releases");
|
|
1341
|
+
releaseNewWarmup?.();
|
|
1342
|
+
const newerDeferredResult = await withTestDeadline(newerDeferredReady.then(() => "settled" as const), 2_500);
|
|
1343
|
+
assert.equal(newerDeferredResult, "settled", "the replacement deferred lifecycle must finish before cleanup");
|
|
1344
|
+
} finally {
|
|
1345
|
+
releaseOldWarmup?.();
|
|
1346
|
+
releaseNewPrimary?.();
|
|
1347
|
+
releaseNewWarmup?.();
|
|
1348
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
1349
|
+
await rm(memoryDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
test("real orchestrator missing collection disposes the backend and returns false on retry", async () => {
|
|
1354
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-real-noop-disposition-"));
|
|
1355
|
+
const orchestrator = new Orchestrator(
|
|
1356
|
+
parseConfig({
|
|
1357
|
+
memoryDir,
|
|
1358
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1359
|
+
qmdEnabled: true,
|
|
1360
|
+
qmdMaintenanceEnabled: false,
|
|
1361
|
+
namespacesEnabled: true,
|
|
1362
|
+
namespaceCatalogEnabled: false,
|
|
1363
|
+
defaultNamespace: "default",
|
|
1364
|
+
sharedNamespace: "shared",
|
|
1365
|
+
namespacePolicies: [],
|
|
1366
|
+
embeddingFallbackEnabled: false,
|
|
1367
|
+
identityContinuityEnabled: false,
|
|
1368
|
+
transcriptEnabled: false,
|
|
1369
|
+
hourlySummariesEnabled: false,
|
|
1370
|
+
compoundingEnabled: false,
|
|
1371
|
+
knowledgeIndexEnabled: false,
|
|
1372
|
+
}),
|
|
1373
|
+
);
|
|
1374
|
+
let disposeCalls = 0;
|
|
1375
|
+
const makeBackend = () => ({
|
|
1376
|
+
probe: async () => true,
|
|
1377
|
+
debugStatus: () => "synthetic-qmd",
|
|
1378
|
+
isAvailable: () => true,
|
|
1379
|
+
search: async () => [],
|
|
1380
|
+
dispose: async () => { disposeCalls += 1; },
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
try {
|
|
1384
|
+
(orchestrator as any).qmd = makeBackend();
|
|
1385
|
+
(orchestrator as any).passiveCorrectionService = () => ({ recoverStaleApplyingPlans: async () => 0 });
|
|
1386
|
+
(orchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async () => "missing";
|
|
1387
|
+
|
|
1388
|
+
const initialized = await withTestDeadline(orchestrator.initialize().then(() => "settled" as const));
|
|
1389
|
+
assert.equal(initialized, "settled", "missing collection must remain a non-fatal initialization outcome");
|
|
1390
|
+
await withTestDeadline((orchestrator as any).deferredReady.then(() => "settled" as const));
|
|
1391
|
+
assert.equal(disposeCalls, 1, "the real Orchestrator disposer must dispose the missing backend");
|
|
1392
|
+
assert.ok((orchestrator as any).qmd instanceof NoopSearchBackend);
|
|
1393
|
+
assert.equal((orchestrator as any).qmd.isAvailable(), false);
|
|
1394
|
+
|
|
1395
|
+
(orchestrator as any).qmd = makeBackend();
|
|
1396
|
+
const retry = await withTestDeadline(orchestrator.startupSearchSync());
|
|
1397
|
+
assert.equal(retry, false, "missing collection retry must return false rather than reject");
|
|
1398
|
+
assert.equal(disposeCalls, 2, "retry must use the same real disposer");
|
|
1399
|
+
assert.ok((orchestrator as any).qmd instanceof NoopSearchBackend);
|
|
1400
|
+
} finally {
|
|
1401
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
1402
|
+
await rm(memoryDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
test("retiring an epoch aborts its in-flight probe and primary collection check", async () => {
|
|
1407
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-epoch-qmd-abort-"));
|
|
1408
|
+
const orchestrator = new Orchestrator(
|
|
1409
|
+
parseConfig({
|
|
1410
|
+
memoryDir,
|
|
1411
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1412
|
+
qmdEnabled: true,
|
|
1413
|
+
namespacesEnabled: true,
|
|
1414
|
+
namespaceCatalogEnabled: false,
|
|
1415
|
+
defaultNamespace: "default",
|
|
1416
|
+
sharedNamespace: "shared",
|
|
1417
|
+
namespacePolicies: [],
|
|
1418
|
+
embeddingFallbackEnabled: false,
|
|
1419
|
+
identityContinuityEnabled: false,
|
|
1420
|
+
transcriptEnabled: false,
|
|
1421
|
+
hourlySummariesEnabled: false,
|
|
1422
|
+
compoundingEnabled: false,
|
|
1423
|
+
knowledgeIndexEnabled: false,
|
|
1424
|
+
}),
|
|
1425
|
+
);
|
|
1426
|
+
let releaseProbe: (() => void) | undefined;
|
|
1427
|
+
const probeReleased = new Promise<void>((resolve) => { releaseProbe = resolve; });
|
|
1428
|
+
let markProbe: (() => void) | undefined;
|
|
1429
|
+
const probeReached = new Promise<void>((resolve) => { markProbe = resolve; });
|
|
1430
|
+
let probeSignal: AbortSignal | undefined;
|
|
1431
|
+
let releaseCollection: (() => void) | undefined;
|
|
1432
|
+
const collectionReleased = new Promise<void>((resolve) => { releaseCollection = resolve; });
|
|
1433
|
+
let markCollection: (() => void) | undefined;
|
|
1434
|
+
const collectionReached = new Promise<void>((resolve) => { markCollection = resolve; });
|
|
1435
|
+
let collectionSignal: AbortSignal | undefined;
|
|
1436
|
+
let probeCalls = 0;
|
|
1437
|
+
|
|
1438
|
+
try {
|
|
1439
|
+
(orchestrator as any).qmd = {
|
|
1440
|
+
probe: async (execution?: { signal?: AbortSignal }) => {
|
|
1441
|
+
probeCalls += 1;
|
|
1442
|
+
if (probeCalls === 1) {
|
|
1443
|
+
probeSignal = execution?.signal;
|
|
1444
|
+
markProbe?.();
|
|
1445
|
+
await probeReleased;
|
|
1446
|
+
}
|
|
1447
|
+
return true;
|
|
1448
|
+
},
|
|
1449
|
+
debugStatus: () => "synthetic-qmd",
|
|
1450
|
+
isAvailable: () => true,
|
|
1451
|
+
search: async () => [],
|
|
1452
|
+
};
|
|
1453
|
+
(orchestrator as any).passiveCorrectionService = () => ({ recoverStaleApplyingPlans: async () => 0 });
|
|
1454
|
+
(orchestrator as any).namespaceSearchRouter.ensureNamespaceCollection = async (
|
|
1455
|
+
_namespace: string,
|
|
1456
|
+
execution?: { signal?: AbortSignal },
|
|
1457
|
+
) => {
|
|
1458
|
+
collectionSignal = execution?.signal;
|
|
1459
|
+
markCollection?.();
|
|
1460
|
+
await collectionReleased;
|
|
1461
|
+
return "present";
|
|
1462
|
+
};
|
|
1463
|
+
|
|
1464
|
+
const initialization = orchestrator.initialize();
|
|
1465
|
+
assert.notEqual(await withTestDeadline(probeReached), "deadline");
|
|
1466
|
+
const destroy = orchestrator.destroy();
|
|
1467
|
+
assert.equal(probeSignal?.aborted, true, "destroy must abort an in-flight primary QMD probe");
|
|
1468
|
+
releaseProbe?.();
|
|
1469
|
+
assert.equal(await withTestDeadline(initialization.then(() => "settled" as const)), "settled");
|
|
1470
|
+
assert.equal(await withTestDeadline(destroy.then(() => "settled" as const)), "settled");
|
|
1471
|
+
|
|
1472
|
+
// Fresh lifecycle uses a new epoch controller; retirement during collection
|
|
1473
|
+
// must abort the controller forwarded to the real namespace-check seam.
|
|
1474
|
+
const freshInitialization = orchestrator.initialize();
|
|
1475
|
+
assert.notEqual(await withTestDeadline(collectionReached), "deadline");
|
|
1476
|
+
const freshDestroy = orchestrator.destroy();
|
|
1477
|
+
assert.equal(collectionSignal?.aborted, true, "destroy must abort an in-flight primary collection check");
|
|
1478
|
+
releaseCollection?.();
|
|
1479
|
+
assert.equal(await withTestDeadline(freshInitialization.then(() => "settled" as const)), "settled");
|
|
1480
|
+
assert.equal(await withTestDeadline(freshDestroy.then(() => "settled" as const)), "settled");
|
|
1481
|
+
} finally {
|
|
1482
|
+
releaseProbe?.();
|
|
1483
|
+
releaseCollection?.();
|
|
1484
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
1485
|
+
await rm(memoryDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
test("startupSearchSync composes its caller abort into the QMD probe", async () => {
|
|
1490
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-retry-probe-abort-"));
|
|
1491
|
+
const orchestrator = new Orchestrator(
|
|
1492
|
+
parseConfig({
|
|
1493
|
+
memoryDir,
|
|
1494
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1495
|
+
qmdEnabled: true,
|
|
1496
|
+
namespacesEnabled: true,
|
|
1497
|
+
namespaceCatalogEnabled: false,
|
|
1498
|
+
defaultNamespace: "default",
|
|
1499
|
+
sharedNamespace: "shared",
|
|
1500
|
+
namespacePolicies: [],
|
|
1501
|
+
embeddingFallbackEnabled: false,
|
|
1502
|
+
identityContinuityEnabled: false,
|
|
1503
|
+
transcriptEnabled: false,
|
|
1504
|
+
hourlySummariesEnabled: false,
|
|
1505
|
+
compoundingEnabled: false,
|
|
1506
|
+
knowledgeIndexEnabled: false,
|
|
1507
|
+
}),
|
|
1508
|
+
);
|
|
1509
|
+
const callerAbort = new AbortController();
|
|
1510
|
+
let probeSignal: AbortSignal | undefined;
|
|
1511
|
+
let releaseProbe: (() => void) | undefined;
|
|
1512
|
+
const probeReleased = new Promise<void>((resolve) => { releaseProbe = resolve; });
|
|
1513
|
+
let markProbe: (() => void) | undefined;
|
|
1514
|
+
const probeReached = new Promise<void>((resolve) => { markProbe = resolve; });
|
|
1515
|
+
|
|
1516
|
+
try {
|
|
1517
|
+
(orchestrator as any).qmd = {
|
|
1518
|
+
probe: async (execution?: { signal?: AbortSignal }) => {
|
|
1519
|
+
probeSignal = execution?.signal;
|
|
1520
|
+
markProbe?.();
|
|
1521
|
+
await probeReleased;
|
|
1522
|
+
return true;
|
|
1523
|
+
},
|
|
1524
|
+
debugStatus: () => "synthetic-qmd",
|
|
1525
|
+
isAvailable: () => true,
|
|
1526
|
+
search: async () => [],
|
|
1527
|
+
};
|
|
1528
|
+
const retry = orchestrator.startupSearchSync(callerAbort.signal);
|
|
1529
|
+
assert.notEqual(await withTestDeadline(probeReached), "deadline");
|
|
1530
|
+
callerAbort.abort();
|
|
1531
|
+
assert.equal(probeSignal?.aborted, true, "caller abort must reach the in-flight retry QMD probe");
|
|
1532
|
+
releaseProbe?.();
|
|
1533
|
+
assert.equal(await withTestDeadline(retry), false, "aborted retry must return false after the probe releases");
|
|
1534
|
+
} finally {
|
|
1535
|
+
releaseProbe?.();
|
|
1536
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
1537
|
+
await rm(memoryDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
|
|
1538
|
+
}
|
|
1539
|
+
});
|