@remnic/core 10.0.11 → 10.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.js +31 -31
- package/dist/access-authorization-probe.js +32 -32
- package/dist/access-boundary.js +31 -31
- package/dist/access-cli.js +84 -84
- package/dist/access-extraction-force-flush.js +31 -31
- package/dist/access-http-briefing.js +32 -32
- package/dist/access-http-query.js +33 -33
- package/dist/access-http.js +49 -49
- package/dist/access-identity-continuity-surface.js +31 -31
- package/dist/access-lcm-surface.js +31 -31
- package/dist/access-mcp.js +42 -42
- package/dist/access-memory-search-fanout.js +3 -3
- package/dist/access-namespace-preflight.js +31 -31
- package/dist/access-observe-write-category.js +1 -1
- package/dist/access-observe-write-surface.js +31 -31
- package/dist/access-offline-manifest.js +7 -7
- package/dist/access-operations-batch.js +33 -33
- package/dist/access-operations.js +40 -40
- package/dist/access-recall-concurrency.js +31 -31
- package/dist/access-recall-diagnostics.js +4 -4
- package/dist/access-recall-response.js +31 -31
- package/dist/access-recall-surface.js +31 -31
- package/dist/access-service.js +31 -31
- package/dist/access-surface-catalog.js +33 -33
- package/dist/active-memory-bridge.js +3 -3
- package/dist/active-recall.js +8 -8
- package/dist/{auto-sync-WSJAOLOP.js → auto-sync-DGJOZYGT.js} +6 -6
- package/dist/briefing.js +7 -7
- package/dist/buffer.js +6 -6
- package/dist/calibration.d.ts +20 -0
- package/dist/calibration.d.ts.map +1 -1
- package/dist/calibration.js +60 -13
- package/dist/calibration.js.map +1 -1
- package/dist/capabilities.d.ts +3 -2
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +1 -1
- package/dist/causal-consolidation.js +8 -8
- package/dist/{chunk-TCYLCPC6.js → chunk-2LUSQV4U.js} +2 -2
- package/dist/{chunk-EKKAHJ4B.js → chunk-2ZQJLBV6.js} +2 -2
- package/dist/{chunk-BX4WDBTE.js → chunk-3J5JKBYD.js} +26 -26
- package/dist/{chunk-XYOT5GQU.js → chunk-3NF4QUNZ.js} +3 -3
- package/dist/{chunk-EEDI3OXB.js → chunk-4Q26RNSR.js} +2 -2
- package/dist/{chunk-O52WWLKU.js → chunk-4THZGKQ2.js} +5 -5
- package/dist/{chunk-L3G5CGL5.js → chunk-4TTGRETU.js} +66 -22
- package/dist/chunk-4TTGRETU.js.map +1 -0
- package/dist/{chunk-MK552I5J.js → chunk-5AOAQR37.js} +3 -3
- package/dist/{chunk-JXWTPOMJ.js → chunk-5JCLELOP.js} +2 -2
- package/dist/{chunk-VBJD4RSJ.js → chunk-5MDM7UE4.js} +2 -2
- package/dist/{chunk-VAOC6GHN.js → chunk-5PHOU5QI.js} +2 -2
- package/dist/{chunk-WL7N5VGG.js → chunk-5TJEOU6Y.js} +2 -2
- package/dist/{chunk-I2M4ZVDD.js → chunk-5ZJYHOJT.js} +3 -3
- package/dist/{chunk-W4RLOZOG.js → chunk-6M56RFFJ.js} +2 -2
- package/dist/{chunk-YS7SA4HD.js → chunk-6OMUHXHP.js} +3 -3
- package/dist/{chunk-MO25AN7J.js → chunk-6OQ6N5RP.js} +14 -14
- package/dist/{chunk-N75Q5ZSP.js → chunk-6Z2AZ4AJ.js} +2 -2
- package/dist/{chunk-4G2YCKLT.js → chunk-7G5NTS7F.js} +2 -2
- package/dist/{chunk-E6KXJSMA.js → chunk-7KYJNHGM.js} +2 -2
- package/dist/{chunk-7UX44C4N.js → chunk-A2E4WNW4.js} +3 -3
- package/dist/{chunk-WH4QXVKU.js → chunk-A6TVQ3QP.js} +2 -2
- package/dist/{chunk-KJOGIRMF.js → chunk-AKREOU5F.js} +2 -2
- package/dist/{chunk-YYNHJF3N.js → chunk-B43NMCGQ.js} +2 -2
- package/dist/{chunk-PDVWGGX4.js → chunk-B7CANWCA.js} +2 -2
- package/dist/{chunk-AEGNDL24.js → chunk-BVRZSI7Q.js} +2 -2
- package/dist/{chunk-OM3RWZQK.js → chunk-BZTRPCCH.js} +2 -2
- package/dist/{chunk-2EMROIXS.js → chunk-C5LWPYS3.js} +2 -1
- package/dist/{chunk-2EMROIXS.js.map → chunk-C5LWPYS3.js.map} +1 -1
- package/dist/{chunk-A2L3P2IY.js → chunk-CKUUP64O.js} +2 -2
- package/dist/{chunk-O6YDGTH4.js → chunk-CNG2XVZG.js} +3 -3
- package/dist/{chunk-WXFKXCOQ.js → chunk-CS2JEHTB.js} +6 -6
- package/dist/{chunk-VDXTXVFZ.js → chunk-DA2INEB5.js} +4 -4
- package/dist/{chunk-JXSYWTFK.js → chunk-DYJHGJZ4.js} +2 -2
- package/dist/{chunk-LOFVW6JA.js → chunk-EGDYVYU7.js} +4 -4
- package/dist/{chunk-NQMG2W5L.js → chunk-EMFBC7L3.js} +2 -2
- package/dist/{chunk-OEIA4IHU.js → chunk-ESG6ITGP.js} +2 -2
- package/dist/{chunk-7XI2UJHL.js → chunk-ETZUIRCI.js} +4 -4
- package/dist/{chunk-VUQDJ3JB.js → chunk-FZB4TS2W.js} +2 -2
- package/dist/{chunk-XLIK2VDE.js → chunk-GD7BWGIB.js} +2 -2
- package/dist/{chunk-N6UMXWBA.js → chunk-GKIXBH7Y.js} +2 -2
- package/dist/{chunk-GKOML66M.js → chunk-HYNEDMYJ.js} +4 -4
- package/dist/{chunk-J7MDPTX7.js → chunk-IWHEJXR2.js} +189 -43
- package/dist/chunk-IWHEJXR2.js.map +1 -0
- package/dist/{chunk-VJGDQA3D.js → chunk-IZT3BVDW.js} +3 -3
- package/dist/{chunk-4TP3MJ3D.js → chunk-JFVCDEIC.js} +5 -5
- package/dist/{chunk-56GJLXBA.js → chunk-JUWJYXI4.js} +3 -3
- package/dist/{chunk-PPIASWWR.js → chunk-K2FSASGZ.js} +2 -2
- package/dist/{chunk-5X4ZJESN.js → chunk-K4QY55XV.js} +2 -2
- package/dist/{chunk-MB5EJ2CT.js → chunk-KC46VZR5.js} +3 -3
- package/dist/{chunk-CAUNCRLQ.js → chunk-KZO43HPY.js} +39 -39
- package/dist/{chunk-E57LYSHI.js → chunk-LZACDIM2.js} +2 -2
- package/dist/{chunk-QAIKLICT.js → chunk-M7KGIW5F.js} +2 -2
- package/dist/{chunk-UOA2VQ7C.js → chunk-M7V65M6H.js} +2 -2
- package/dist/{chunk-2AFHZTPI.js → chunk-NSFOERZQ.js} +2 -2
- package/dist/{chunk-BFIZXFUG.js → chunk-NWIT3U2N.js} +2 -2
- package/dist/{chunk-7SUWY37K.js → chunk-O4KYRDRT.js} +2 -2
- package/dist/{chunk-6OAXWYE3.js → chunk-ON4ILX3G.js} +2 -2
- package/dist/{chunk-55ZVZBX4.js → chunk-OYKQ6T4V.js} +4 -4
- package/dist/{chunk-JVNEXW6X.js → chunk-P34FDDB5.js} +2 -2
- package/dist/{chunk-CDWFWOSZ.js → chunk-P4JED3ZL.js} +2 -2
- package/dist/{chunk-OGWEWJRH.js → chunk-PCWXLAJY.js} +2 -2
- package/dist/{chunk-SSAXGPXY.js → chunk-QUA7RLK6.js} +2 -2
- package/dist/{chunk-XQIHFXYP.js → chunk-RP5SJ3XR.js} +2 -2
- package/dist/{chunk-J6FFBORM.js → chunk-RT6D3WN7.js} +2 -2
- package/dist/{chunk-SQA2MM2F.js → chunk-RWRQ67OR.js} +2 -2
- package/dist/{chunk-A5ZYP4K5.js → chunk-TNRCDMQT.js} +2 -2
- package/dist/{chunk-VA2HNBGQ.js → chunk-U7VPOXVM.js} +23 -23
- package/dist/{chunk-BH44JOLA.js → chunk-UOKPJFO3.js} +2 -2
- package/dist/{chunk-4N2MFNTG.js → chunk-UZSBCQEC.js} +2 -2
- package/dist/{chunk-5BUCKFRE.js → chunk-VOKHZRHW.js} +2 -2
- package/dist/{chunk-O5MW6CQL.js → chunk-VYPFEVOM.js} +2 -2
- package/dist/{chunk-E5H4TM4Y.js → chunk-WC3V7HHP.js} +3 -3
- package/dist/{chunk-WCTL3PVM.js → chunk-WETFW255.js} +2 -2
- package/dist/{chunk-OFDIGTC2.js → chunk-WWSRLS4M.js} +10 -10
- package/dist/{chunk-D5UPBXZM.js → chunk-X3VID2TT.js} +3 -3
- package/dist/{chunk-KX36OCGK.js → chunk-XERVJYH2.js} +2 -2
- package/dist/{chunk-KWVI52ER.js → chunk-XHWJRUVP.js} +4 -4
- package/dist/{chunk-SQ2O52SU.js → chunk-XRBHXUTQ.js} +7 -7
- package/dist/{chunk-3ZCRHW7U.js → chunk-XZLWYZ7C.js} +2 -2
- package/dist/{chunk-BD5QEP3T.js → chunk-Y3VDGK2O.js} +3 -3
- package/dist/{chunk-SOJ3IQD3.js → chunk-YEM7K3KD.js} +4 -4
- package/dist/{chunk-JLIGUFPF.js → chunk-YN2DQ2D6.js} +2 -2
- package/dist/{chunk-UCJINCZT.js → chunk-YUZRI3QU.js} +2 -2
- package/dist/{chunk-2U4BM3TW.js → chunk-ZIZ2G47N.js} +2 -2
- package/dist/{chunk-RNPJ4GQA.js → chunk-ZUMXY4KA.js} +5 -5
- package/dist/cli.js +62 -62
- package/dist/coding/export-okf-codegraph.js +6 -6
- package/dist/compounding/engine.d.ts +15 -11
- package/dist/compounding/engine.d.ts.map +1 -1
- package/dist/compounding/engine.js +9 -9
- package/dist/compounding/iso-week.d.ts +2 -0
- package/dist/compounding/iso-week.d.ts.map +1 -1
- package/dist/compounding/weekly-scheduler.d.ts +81 -0
- package/dist/compounding/weekly-scheduler.d.ts.map +1 -0
- package/dist/compounding/weekly-synthesis-queue.d.ts +9 -0
- package/dist/compounding/weekly-synthesis-queue.d.ts.map +1 -0
- package/dist/config.js +8 -8
- package/dist/connectors/codex-materialize-runner.js +7 -7
- package/dist/connectors/index.js +8 -8
- package/dist/contradiction/index.js +3 -3
- package/dist/{contradiction-scan-GGRTVB7C.js → contradiction-scan-P7KXB5R4.js} +3 -3
- package/dist/convergence-refresh.js +9 -9
- package/dist/corpus-watermark.js +9 -9
- package/dist/day-summary.js +2 -2
- package/dist/embedding-fallback.js +2 -2
- package/dist/entity-retrieval.js +7 -7
- package/dist/explicit-capture.js +2 -2
- package/dist/external-wiki-access.js +32 -32
- package/dist/extraction-judge.js +3 -3
- package/dist/extraction-normalization.js +3 -3
- package/dist/extraction-prompt.js +2 -2
- package/dist/extraction.js +18 -18
- package/dist/{first-start-migration-OM3PHMZO.js → first-start-migration-HYAMKJ6V.js} +2 -2
- package/dist/harmonic-retrieval.js +7 -7
- package/dist/index.js +84 -84
- package/dist/local-llm.js +2 -2
- package/dist/location/index.js +9 -9
- package/dist/maintenance/memory-governance.js +7 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection-runner.js +9 -9
- package/dist/maintenance/rebuild-memory-projection.js +8 -8
- package/dist/memory-browse-operations.js +32 -32
- package/dist/namespaces/migrate.js +8 -8
- package/dist/namespaces/principal.js +2 -2
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.js +7 -7
- package/dist/offline-sync-impression-drain.js +5 -5
- package/dist/operator-doctor-corpus.js +10 -10
- package/dist/operator-toolkit.js +20 -20
- package/dist/orchestration/compression-guideline-coordinator.js +2 -2
- package/dist/orchestration/maintenance.d.ts +2 -0
- package/dist/orchestration/maintenance.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +24 -20
- package/dist/orchestrator.js +84 -84
- package/dist/recall-navigation-operations.js +32 -32
- package/dist/recall-navigation.js +31 -31
- package/dist/recall-why-service.js +5 -5
- package/dist/recall-why.js +3 -3
- package/dist/rerank-outcome.js +2 -2
- package/dist/{resolution-E4GLHLY6.js → resolution-EKJHREVJ.js} +4 -4
- package/dist/resume-bundles.js +9 -9
- package/dist/search/embed-helper.js +2 -2
- package/dist/search/factory.js +3 -3
- package/dist/search/index.js +3 -3
- package/dist/semantic-consolidation.js +8 -8
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.js +7 -7
- package/dist/shared-context/manager.js +2 -2
- package/dist/source-agent-qualifier.js +13 -13
- package/dist/standup/build.js +7 -7
- package/dist/storage.js +6 -6
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/index.js +34 -34
- package/dist/{tier-stats-M2FMMD2N.js → tier-stats-RQM76XYY.js} +2 -2
- package/dist/transfer/export-okf.js +7 -7
- package/dist/verified-recall.js +7 -7
- package/dist/who-knows-cli.js +8 -8
- package/dist/who-knows.js +7 -7
- package/package.json +2 -2
- package/src/calibration.test.ts +80 -1
- package/src/calibration.ts +77 -8
- package/src/capabilities.test.ts +1 -0
- package/src/capabilities.ts +4 -4
- package/src/compounding/engine.ts +32 -31
- package/src/compounding/iso-week.ts +16 -0
- package/src/compounding/weekly-scheduler.test.ts +426 -0
- package/src/compounding/weekly-scheduler.ts +231 -0
- package/src/compounding/weekly-synthesis-queue.ts +23 -0
- package/src/orchestration/maintenance.ts +6 -2
- package/dist/chunk-J7MDPTX7.js.map +0 -1
- package/dist/chunk-L3G5CGL5.js.map +0 -1
- /package/dist/{auto-sync-WSJAOLOP.js.map → auto-sync-DGJOZYGT.js.map} +0 -0
- /package/dist/{chunk-TCYLCPC6.js.map → chunk-2LUSQV4U.js.map} +0 -0
- /package/dist/{chunk-EKKAHJ4B.js.map → chunk-2ZQJLBV6.js.map} +0 -0
- /package/dist/{chunk-BX4WDBTE.js.map → chunk-3J5JKBYD.js.map} +0 -0
- /package/dist/{chunk-XYOT5GQU.js.map → chunk-3NF4QUNZ.js.map} +0 -0
- /package/dist/{chunk-EEDI3OXB.js.map → chunk-4Q26RNSR.js.map} +0 -0
- /package/dist/{chunk-O52WWLKU.js.map → chunk-4THZGKQ2.js.map} +0 -0
- /package/dist/{chunk-MK552I5J.js.map → chunk-5AOAQR37.js.map} +0 -0
- /package/dist/{chunk-JXWTPOMJ.js.map → chunk-5JCLELOP.js.map} +0 -0
- /package/dist/{chunk-VBJD4RSJ.js.map → chunk-5MDM7UE4.js.map} +0 -0
- /package/dist/{chunk-VAOC6GHN.js.map → chunk-5PHOU5QI.js.map} +0 -0
- /package/dist/{chunk-WL7N5VGG.js.map → chunk-5TJEOU6Y.js.map} +0 -0
- /package/dist/{chunk-I2M4ZVDD.js.map → chunk-5ZJYHOJT.js.map} +0 -0
- /package/dist/{chunk-W4RLOZOG.js.map → chunk-6M56RFFJ.js.map} +0 -0
- /package/dist/{chunk-YS7SA4HD.js.map → chunk-6OMUHXHP.js.map} +0 -0
- /package/dist/{chunk-MO25AN7J.js.map → chunk-6OQ6N5RP.js.map} +0 -0
- /package/dist/{chunk-N75Q5ZSP.js.map → chunk-6Z2AZ4AJ.js.map} +0 -0
- /package/dist/{chunk-4G2YCKLT.js.map → chunk-7G5NTS7F.js.map} +0 -0
- /package/dist/{chunk-E6KXJSMA.js.map → chunk-7KYJNHGM.js.map} +0 -0
- /package/dist/{chunk-7UX44C4N.js.map → chunk-A2E4WNW4.js.map} +0 -0
- /package/dist/{chunk-WH4QXVKU.js.map → chunk-A6TVQ3QP.js.map} +0 -0
- /package/dist/{chunk-KJOGIRMF.js.map → chunk-AKREOU5F.js.map} +0 -0
- /package/dist/{chunk-YYNHJF3N.js.map → chunk-B43NMCGQ.js.map} +0 -0
- /package/dist/{chunk-PDVWGGX4.js.map → chunk-B7CANWCA.js.map} +0 -0
- /package/dist/{chunk-AEGNDL24.js.map → chunk-BVRZSI7Q.js.map} +0 -0
- /package/dist/{chunk-OM3RWZQK.js.map → chunk-BZTRPCCH.js.map} +0 -0
- /package/dist/{chunk-A2L3P2IY.js.map → chunk-CKUUP64O.js.map} +0 -0
- /package/dist/{chunk-O6YDGTH4.js.map → chunk-CNG2XVZG.js.map} +0 -0
- /package/dist/{chunk-WXFKXCOQ.js.map → chunk-CS2JEHTB.js.map} +0 -0
- /package/dist/{chunk-VDXTXVFZ.js.map → chunk-DA2INEB5.js.map} +0 -0
- /package/dist/{chunk-JXSYWTFK.js.map → chunk-DYJHGJZ4.js.map} +0 -0
- /package/dist/{chunk-LOFVW6JA.js.map → chunk-EGDYVYU7.js.map} +0 -0
- /package/dist/{chunk-NQMG2W5L.js.map → chunk-EMFBC7L3.js.map} +0 -0
- /package/dist/{chunk-OEIA4IHU.js.map → chunk-ESG6ITGP.js.map} +0 -0
- /package/dist/{chunk-7XI2UJHL.js.map → chunk-ETZUIRCI.js.map} +0 -0
- /package/dist/{chunk-VUQDJ3JB.js.map → chunk-FZB4TS2W.js.map} +0 -0
- /package/dist/{chunk-XLIK2VDE.js.map → chunk-GD7BWGIB.js.map} +0 -0
- /package/dist/{chunk-N6UMXWBA.js.map → chunk-GKIXBH7Y.js.map} +0 -0
- /package/dist/{chunk-GKOML66M.js.map → chunk-HYNEDMYJ.js.map} +0 -0
- /package/dist/{chunk-VJGDQA3D.js.map → chunk-IZT3BVDW.js.map} +0 -0
- /package/dist/{chunk-4TP3MJ3D.js.map → chunk-JFVCDEIC.js.map} +0 -0
- /package/dist/{chunk-56GJLXBA.js.map → chunk-JUWJYXI4.js.map} +0 -0
- /package/dist/{chunk-PPIASWWR.js.map → chunk-K2FSASGZ.js.map} +0 -0
- /package/dist/{chunk-5X4ZJESN.js.map → chunk-K4QY55XV.js.map} +0 -0
- /package/dist/{chunk-MB5EJ2CT.js.map → chunk-KC46VZR5.js.map} +0 -0
- /package/dist/{chunk-CAUNCRLQ.js.map → chunk-KZO43HPY.js.map} +0 -0
- /package/dist/{chunk-E57LYSHI.js.map → chunk-LZACDIM2.js.map} +0 -0
- /package/dist/{chunk-QAIKLICT.js.map → chunk-M7KGIW5F.js.map} +0 -0
- /package/dist/{chunk-UOA2VQ7C.js.map → chunk-M7V65M6H.js.map} +0 -0
- /package/dist/{chunk-2AFHZTPI.js.map → chunk-NSFOERZQ.js.map} +0 -0
- /package/dist/{chunk-BFIZXFUG.js.map → chunk-NWIT3U2N.js.map} +0 -0
- /package/dist/{chunk-7SUWY37K.js.map → chunk-O4KYRDRT.js.map} +0 -0
- /package/dist/{chunk-6OAXWYE3.js.map → chunk-ON4ILX3G.js.map} +0 -0
- /package/dist/{chunk-55ZVZBX4.js.map → chunk-OYKQ6T4V.js.map} +0 -0
- /package/dist/{chunk-JVNEXW6X.js.map → chunk-P34FDDB5.js.map} +0 -0
- /package/dist/{chunk-CDWFWOSZ.js.map → chunk-P4JED3ZL.js.map} +0 -0
- /package/dist/{chunk-OGWEWJRH.js.map → chunk-PCWXLAJY.js.map} +0 -0
- /package/dist/{chunk-SSAXGPXY.js.map → chunk-QUA7RLK6.js.map} +0 -0
- /package/dist/{chunk-XQIHFXYP.js.map → chunk-RP5SJ3XR.js.map} +0 -0
- /package/dist/{chunk-J6FFBORM.js.map → chunk-RT6D3WN7.js.map} +0 -0
- /package/dist/{chunk-SQA2MM2F.js.map → chunk-RWRQ67OR.js.map} +0 -0
- /package/dist/{chunk-A5ZYP4K5.js.map → chunk-TNRCDMQT.js.map} +0 -0
- /package/dist/{chunk-VA2HNBGQ.js.map → chunk-U7VPOXVM.js.map} +0 -0
- /package/dist/{chunk-BH44JOLA.js.map → chunk-UOKPJFO3.js.map} +0 -0
- /package/dist/{chunk-4N2MFNTG.js.map → chunk-UZSBCQEC.js.map} +0 -0
- /package/dist/{chunk-5BUCKFRE.js.map → chunk-VOKHZRHW.js.map} +0 -0
- /package/dist/{chunk-O5MW6CQL.js.map → chunk-VYPFEVOM.js.map} +0 -0
- /package/dist/{chunk-E5H4TM4Y.js.map → chunk-WC3V7HHP.js.map} +0 -0
- /package/dist/{chunk-WCTL3PVM.js.map → chunk-WETFW255.js.map} +0 -0
- /package/dist/{chunk-OFDIGTC2.js.map → chunk-WWSRLS4M.js.map} +0 -0
- /package/dist/{chunk-D5UPBXZM.js.map → chunk-X3VID2TT.js.map} +0 -0
- /package/dist/{chunk-KX36OCGK.js.map → chunk-XERVJYH2.js.map} +0 -0
- /package/dist/{chunk-KWVI52ER.js.map → chunk-XHWJRUVP.js.map} +0 -0
- /package/dist/{chunk-SQ2O52SU.js.map → chunk-XRBHXUTQ.js.map} +0 -0
- /package/dist/{chunk-3ZCRHW7U.js.map → chunk-XZLWYZ7C.js.map} +0 -0
- /package/dist/{chunk-BD5QEP3T.js.map → chunk-Y3VDGK2O.js.map} +0 -0
- /package/dist/{chunk-SOJ3IQD3.js.map → chunk-YEM7K3KD.js.map} +0 -0
- /package/dist/{chunk-JLIGUFPF.js.map → chunk-YN2DQ2D6.js.map} +0 -0
- /package/dist/{chunk-UCJINCZT.js.map → chunk-YUZRI3QU.js.map} +0 -0
- /package/dist/{chunk-2U4BM3TW.js.map → chunk-ZIZ2G47N.js.map} +0 -0
- /package/dist/{chunk-RNPJ4GQA.js.map → chunk-ZUMXY4KA.js.map} +0 -0
- /package/dist/{contradiction-scan-GGRTVB7C.js.map → contradiction-scan-P7KXB5R4.js.map} +0 -0
- /package/dist/{first-start-migration-OM3PHMZO.js.map → first-start-migration-HYAMKJ6V.js.map} +0 -0
- /package/dist/{resolution-E4GLHLY6.js.map → resolution-EKJHREVJ.js.map} +0 -0
- /package/dist/{tier-stats-M2FMMD2N.js.map → tier-stats-RQM76XYY.js.map} +0 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon weekly compounding run: once per completed ISO week, skip only when
|
|
3
|
+
* a scheduled run already completed that week, and catch up the missed week
|
|
4
|
+
* after a restart.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import test from "node:test";
|
|
12
|
+
|
|
13
|
+
import { parseConfig } from "../config.js";
|
|
14
|
+
import { initLogger, resetLogger } from "../logger.js";
|
|
15
|
+
import type { NamespaceCatalog } from "../namespaces/catalog.js";
|
|
16
|
+
import type { NamespaceSearchRouter } from "../namespaces/search.js";
|
|
17
|
+
import { MaintenanceScheduler } from "../orchestration/maintenance.js";
|
|
18
|
+
import type { SearchBackend } from "../search/port.js";
|
|
19
|
+
import { CompoundingEngine } from "./engine.js";
|
|
20
|
+
import { queueWeeklySynthesis } from "./weekly-synthesis-queue.js";
|
|
21
|
+
import { isIsoWeekId, isoWeekId, msUntilNextIsoWeek, previousIsoWeekId } from "./iso-week.js";
|
|
22
|
+
import {
|
|
23
|
+
type CompoundingWeeklySchedulerOptions,
|
|
24
|
+
compoundingWeeklyDoneMarkerPath,
|
|
25
|
+
compoundingWeeklyReportPath,
|
|
26
|
+
startCompoundingWeeklyScheduler,
|
|
27
|
+
} from "./weekly-scheduler.js";
|
|
28
|
+
|
|
29
|
+
const QUIET = { info() {}, warn() {}, debug() {} };
|
|
30
|
+
|
|
31
|
+
function fixedClock(iso: string): () => Date {
|
|
32
|
+
const value = new Date(iso);
|
|
33
|
+
return () => new Date(value.getTime());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function fakeTimers() {
|
|
37
|
+
const armed: Array<{ fn: () => void; ms: number; unrefd: boolean }> = [];
|
|
38
|
+
const cleared: unknown[] = [];
|
|
39
|
+
return {
|
|
40
|
+
armed,
|
|
41
|
+
cleared,
|
|
42
|
+
setTimer: (fn: () => void, ms: number) => {
|
|
43
|
+
const handle = {
|
|
44
|
+
fn,
|
|
45
|
+
ms,
|
|
46
|
+
unrefd: false,
|
|
47
|
+
unref() {
|
|
48
|
+
this.unrefd = true;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
armed.push(handle);
|
|
52
|
+
return handle;
|
|
53
|
+
},
|
|
54
|
+
clearTimer: (handle: unknown) => {
|
|
55
|
+
cleared.push(handle);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function startFor(memoryDir: string, overrides: Partial<CompoundingWeeklySchedulerOptions> & { now: () => Date }) {
|
|
61
|
+
const timers = fakeTimers();
|
|
62
|
+
const handle = startCompoundingWeeklyScheduler({
|
|
63
|
+
memoryDir,
|
|
64
|
+
synthesizeWeekly: async () => undefined,
|
|
65
|
+
log: QUIET,
|
|
66
|
+
setTimer: timers.setTimer,
|
|
67
|
+
clearTimer: timers.clearTimer,
|
|
68
|
+
...overrides,
|
|
69
|
+
});
|
|
70
|
+
return { handle, timers };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
test("iso week ids follow the Thursday rule across the year boundary", () => {
|
|
74
|
+
assert.equal(isoWeekId(new Date("2026-01-01T00:00:00.000Z")), "2026-W01");
|
|
75
|
+
assert.equal(previousIsoWeekId(new Date("2026-01-01T00:00:00.000Z")), "2025-W52");
|
|
76
|
+
assert.equal(isoWeekId(new Date("2026-01-05T00:00:00.000Z")), "2026-W02");
|
|
77
|
+
assert.equal(previousIsoWeekId(new Date("2026-01-05T12:00:00.000Z")), "2026-W01");
|
|
78
|
+
assert.equal(isIsoWeekId("2026-W01"), true);
|
|
79
|
+
assert.equal(isIsoWeekId("2026-W00"), false);
|
|
80
|
+
assert.equal(isIsoWeekId("2026-W54"), false);
|
|
81
|
+
assert.equal(isIsoWeekId("../W01"), false);
|
|
82
|
+
assert.equal(isIsoWeekId("2026-W1"), false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("next Monday delay is one week when already on the Monday boundary", () => {
|
|
86
|
+
const monday = new Date("2026-03-16T00:00:00.000Z");
|
|
87
|
+
assert.equal(monday.getUTCDay(), 1);
|
|
88
|
+
assert.equal(msUntilNextIsoWeek(monday), 7 * 86_400_000);
|
|
89
|
+
const sundayEvening = new Date("2026-03-15T23:00:00.000Z");
|
|
90
|
+
assert.equal(msUntilNextIsoWeek(sundayEvening), 60 * 60 * 1000);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("startup synthesizes the completed ISO week and arms an unref'd boundary timer", async () => {
|
|
94
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-cron-"));
|
|
95
|
+
const now = fixedClock("2026-03-18T12:00:00.000Z");
|
|
96
|
+
const calls: string[] = [];
|
|
97
|
+
try {
|
|
98
|
+
const { handle, timers } = startFor(memoryDir, {
|
|
99
|
+
now,
|
|
100
|
+
synthesizeWeekly: async (weekId) => {
|
|
101
|
+
calls.push(weekId);
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
await handle.whenIdle();
|
|
105
|
+
assert.deepEqual(calls, [previousIsoWeekId(now())]);
|
|
106
|
+
assert.notEqual(calls[0], isoWeekId(now()));
|
|
107
|
+
assert.equal(timers.armed.length, 1);
|
|
108
|
+
assert.equal(timers.armed[0]?.unrefd, true);
|
|
109
|
+
assert.equal(timers.armed[0]?.ms, msUntilNextIsoWeek(now()));
|
|
110
|
+
await handle.stop();
|
|
111
|
+
assert.equal(timers.cleared.length, 1);
|
|
112
|
+
} finally {
|
|
113
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("a week a scheduled run completed is skipped, including after a restart", async () => {
|
|
118
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-cron-"));
|
|
119
|
+
const now = fixedClock("2026-03-18T12:00:00.000Z");
|
|
120
|
+
const weekId = previousIsoWeekId(now());
|
|
121
|
+
const calls: string[] = [];
|
|
122
|
+
const synthesizeWeekly = async (id: string) => {
|
|
123
|
+
calls.push(id);
|
|
124
|
+
const report = compoundingWeeklyReportPath(memoryDir, id);
|
|
125
|
+
await mkdir(path.dirname(report), { recursive: true });
|
|
126
|
+
await writeFile(report, `# ${id}\n`, "utf8");
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
const first = startFor(memoryDir, { now, synthesizeWeekly });
|
|
130
|
+
await first.handle.whenIdle();
|
|
131
|
+
await first.handle.stop();
|
|
132
|
+
assert.deepEqual(calls, [weekId]);
|
|
133
|
+
|
|
134
|
+
const restarted = startFor(memoryDir, { now, synthesizeWeekly });
|
|
135
|
+
await restarted.handle.whenIdle();
|
|
136
|
+
await restarted.handle.stop();
|
|
137
|
+
assert.deepEqual(calls, [weekId], "restart must not re-run a week the scheduler completed");
|
|
138
|
+
assert.equal(await readFile(compoundingWeeklyReportPath(memoryDir, weekId), "utf8"), `# ${weekId}\n`);
|
|
139
|
+
await stat(compoundingWeeklyDoneMarkerPath(memoryDir, weekId));
|
|
140
|
+
} finally {
|
|
141
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("a manual access-surface report does not suppress the scheduled run", async () => {
|
|
146
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-cron-"));
|
|
147
|
+
const now = fixedClock("2026-03-18T12:00:00.000Z");
|
|
148
|
+
const weekId = previousIsoWeekId(now());
|
|
149
|
+
const calls: string[] = [];
|
|
150
|
+
try {
|
|
151
|
+
// A manual compounding_weekly_synthesize call wrote the week's report
|
|
152
|
+
// while that week was still in progress; no scheduler marker exists.
|
|
153
|
+
const report = compoundingWeeklyReportPath(memoryDir, weekId);
|
|
154
|
+
await mkdir(path.dirname(report), { recursive: true });
|
|
155
|
+
await writeFile(report, `# ${weekId}\n`, "utf8");
|
|
156
|
+
|
|
157
|
+
const { handle } = startFor(memoryDir, {
|
|
158
|
+
now,
|
|
159
|
+
synthesizeWeekly: async (id) => {
|
|
160
|
+
calls.push(id);
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
await handle.whenIdle();
|
|
164
|
+
await handle.stop();
|
|
165
|
+
assert.deepEqual(calls, [weekId], "the report file alone must not satisfy the due check");
|
|
166
|
+
await stat(compoundingWeeklyDoneMarkerPath(memoryDir, weekId));
|
|
167
|
+
} finally {
|
|
168
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("a missed week is caught up once and older gaps are not backfilled", async () => {
|
|
173
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-cron-"));
|
|
174
|
+
let now = new Date("2026-04-08T12:00:00.000Z");
|
|
175
|
+
const calls: string[] = [];
|
|
176
|
+
const synthesizeWeekly = async (id: string) => {
|
|
177
|
+
calls.push(id);
|
|
178
|
+
const report = compoundingWeeklyReportPath(memoryDir, id);
|
|
179
|
+
await mkdir(path.dirname(report), { recursive: true });
|
|
180
|
+
await writeFile(report, `# ${id}\n`, "utf8");
|
|
181
|
+
};
|
|
182
|
+
try {
|
|
183
|
+
const first = startFor(memoryDir, { now: () => now, synthesizeWeekly });
|
|
184
|
+
await first.handle.whenIdle();
|
|
185
|
+
await first.handle.stop();
|
|
186
|
+
|
|
187
|
+
const completed = previousIsoWeekId(now);
|
|
188
|
+
now = new Date(now.getTime() + 14 * 86_400_000);
|
|
189
|
+
const missed = previousIsoWeekId(new Date(now.getTime() - 7 * 86_400_000));
|
|
190
|
+
const due = previousIsoWeekId(now);
|
|
191
|
+
assert.notEqual(completed, missed);
|
|
192
|
+
assert.notEqual(missed, due);
|
|
193
|
+
|
|
194
|
+
const restarted = startFor(memoryDir, { now: () => now, synthesizeWeekly });
|
|
195
|
+
await restarted.handle.whenIdle();
|
|
196
|
+
await restarted.handle.stop();
|
|
197
|
+
assert.deepEqual(calls, [completed, due]);
|
|
198
|
+
assert.equal(calls.includes(missed), false);
|
|
199
|
+
} finally {
|
|
200
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("a failed tick retries on the next boundary and a stopped timer does not run", async () => {
|
|
205
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-cron-"));
|
|
206
|
+
const now = fixedClock("2026-03-18T12:00:00.000Z");
|
|
207
|
+
let fail = true;
|
|
208
|
+
const calls: string[] = [];
|
|
209
|
+
try {
|
|
210
|
+
const { handle, timers } = startFor(memoryDir, {
|
|
211
|
+
now,
|
|
212
|
+
synthesizeWeekly: async (weekId) => {
|
|
213
|
+
calls.push(weekId);
|
|
214
|
+
if (fail) throw new Error("synthesis unavailable");
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
await handle.whenIdle();
|
|
218
|
+
assert.equal(calls.length, 1);
|
|
219
|
+
fail = false;
|
|
220
|
+
timers.armed[0]?.fn();
|
|
221
|
+
await handle.whenIdle();
|
|
222
|
+
assert.equal(calls.length, 2);
|
|
223
|
+
await handle.stop();
|
|
224
|
+
const afterStop = calls.length;
|
|
225
|
+
timers.armed[0]?.fn();
|
|
226
|
+
await handle.whenIdle();
|
|
227
|
+
assert.equal(calls.length, afterStop);
|
|
228
|
+
} finally {
|
|
229
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("a failed week is retried after the ISO boundary, before the newly due week", async () => {
|
|
234
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-cron-"));
|
|
235
|
+
let now = new Date("2026-03-18T12:00:00.000Z");
|
|
236
|
+
let fail = true;
|
|
237
|
+
const calls: string[] = [];
|
|
238
|
+
try {
|
|
239
|
+
const { handle, timers } = startFor(memoryDir, {
|
|
240
|
+
now: () => now,
|
|
241
|
+
synthesizeWeekly: async (weekId) => {
|
|
242
|
+
calls.push(weekId);
|
|
243
|
+
if (fail) throw new Error("synthesis unavailable");
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
await handle.whenIdle();
|
|
247
|
+
const failed = previousIsoWeekId(now);
|
|
248
|
+
now = new Date(now.getTime() + 7 * 86_400_000);
|
|
249
|
+
const due = previousIsoWeekId(now);
|
|
250
|
+
assert.notEqual(failed, due);
|
|
251
|
+
fail = false;
|
|
252
|
+
timers.armed.at(-1)?.fn();
|
|
253
|
+
await handle.whenIdle();
|
|
254
|
+
assert.deepEqual(calls, [failed, failed, due]);
|
|
255
|
+
await handle.stop();
|
|
256
|
+
} finally {
|
|
257
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("a later artifact failure does not leave the weekly report marker", async () => {
|
|
262
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-cron-"));
|
|
263
|
+
const weekId = previousIsoWeekId(new Date("2026-03-18T12:00:00.000Z"));
|
|
264
|
+
try {
|
|
265
|
+
await mkdir(path.join(memoryDir, "compounding", "mistakes.json"), { recursive: true });
|
|
266
|
+
const engine = new CompoundingEngine(parseConfig({ memoryDir, qmdEnabled: false, compoundingEnabled: true }));
|
|
267
|
+
await assert.rejects(() => engine.synthesizeWeekly({ weekId }));
|
|
268
|
+
await assert.rejects(
|
|
269
|
+
() => stat(compoundingWeeklyReportPath(memoryDir, weekId)),
|
|
270
|
+
(error: unknown) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT"
|
|
271
|
+
);
|
|
272
|
+
} finally {
|
|
273
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("an overlapping tick coalesces into one follow-up run", async () => {
|
|
278
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-cron-"));
|
|
279
|
+
let release: (() => void) | undefined;
|
|
280
|
+
const gate = new Promise<void>((resolve) => {
|
|
281
|
+
release = resolve;
|
|
282
|
+
});
|
|
283
|
+
let calls = 0;
|
|
284
|
+
try {
|
|
285
|
+
const { handle } = startFor(memoryDir, {
|
|
286
|
+
now: fixedClock("2026-03-18T12:00:00.000Z"),
|
|
287
|
+
// Every tick reports the week as not yet done, so the follow-up run
|
|
288
|
+
// the coalescing produces is observable as a second synthesis.
|
|
289
|
+
weekDone: async () => false,
|
|
290
|
+
synthesizeWeekly: async () => {
|
|
291
|
+
calls += 1;
|
|
292
|
+
if (calls === 1) await gate;
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
const extra = handle.tick();
|
|
296
|
+
release?.();
|
|
297
|
+
await extra;
|
|
298
|
+
await handle.whenIdle();
|
|
299
|
+
assert.equal(calls, 2);
|
|
300
|
+
await handle.stop();
|
|
301
|
+
} finally {
|
|
302
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
function maintenanceScheduler(
|
|
307
|
+
memoryDir: string,
|
|
308
|
+
flags: { compounding: boolean; weekly: boolean }
|
|
309
|
+
): MaintenanceScheduler {
|
|
310
|
+
const config = parseConfig({
|
|
311
|
+
memoryDir,
|
|
312
|
+
qmdEnabled: false,
|
|
313
|
+
compoundingEnabled: flags.compounding,
|
|
314
|
+
compoundingWeeklyCronEnabled: flags.weekly,
|
|
315
|
+
calibrationEnabled: true,
|
|
316
|
+
});
|
|
317
|
+
return new MaintenanceScheduler({
|
|
318
|
+
config,
|
|
319
|
+
getQmd: () => ({ isAvailable: () => false }) as unknown as SearchBackend,
|
|
320
|
+
namespaceSearchRouter: {} as unknown as NamespaceSearchRouter,
|
|
321
|
+
namespaceCatalog: {} as unknown as NamespaceCatalog,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
test("compoundingWeeklyCronEnabled defaults off and the daemon does not arm the run", async () => {
|
|
326
|
+
assert.equal(parseConfig({}).compoundingWeeklyCronEnabled, false);
|
|
327
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-cron-"));
|
|
328
|
+
try {
|
|
329
|
+
for (const flags of [
|
|
330
|
+
{ compounding: false, weekly: false },
|
|
331
|
+
{ compounding: true, weekly: false },
|
|
332
|
+
{ compounding: false, weekly: true },
|
|
333
|
+
]) {
|
|
334
|
+
const scheduler = maintenanceScheduler(memoryDir, flags);
|
|
335
|
+
try {
|
|
336
|
+
assert.equal(scheduler.armCompoundingWeeklyCron(), false);
|
|
337
|
+
} finally {
|
|
338
|
+
await scheduler.dispose();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} finally {
|
|
342
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("the daemon arms synthesizeWeekly, including its calibration pass, for the completed week", async () => {
|
|
347
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-cron-"));
|
|
348
|
+
const weekId = previousIsoWeekId(new Date());
|
|
349
|
+
const original = CompoundingEngine.prototype.synthesizeWeekly;
|
|
350
|
+
const calls: Array<string | undefined> = [];
|
|
351
|
+
const logs: string[] = [];
|
|
352
|
+
CompoundingEngine.prototype.synthesizeWeekly = async function (opts) {
|
|
353
|
+
calls.push(opts?.weekId);
|
|
354
|
+
return original.call(this, opts);
|
|
355
|
+
};
|
|
356
|
+
initLogger(
|
|
357
|
+
{
|
|
358
|
+
info(msg) {
|
|
359
|
+
logs.push(String(msg));
|
|
360
|
+
},
|
|
361
|
+
warn(msg) {
|
|
362
|
+
logs.push(String(msg));
|
|
363
|
+
},
|
|
364
|
+
error(msg) {
|
|
365
|
+
logs.push(String(msg));
|
|
366
|
+
},
|
|
367
|
+
debug(msg) {
|
|
368
|
+
logs.push(String(msg));
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
true,
|
|
372
|
+
{ timestamps: false }
|
|
373
|
+
);
|
|
374
|
+
const scheduler = maintenanceScheduler(memoryDir, { compounding: true, weekly: true });
|
|
375
|
+
try {
|
|
376
|
+
assert.equal(scheduler.armCompoundingWeeklyCron(), true);
|
|
377
|
+
assert.equal(scheduler.armCompoundingWeeklyCron(), true, "arming twice must not start a second runner");
|
|
378
|
+
await scheduler.dispose();
|
|
379
|
+
assert.deepEqual(calls, [weekId]);
|
|
380
|
+
const reportPath = compoundingWeeklyReportPath(memoryDir, weekId);
|
|
381
|
+
const report = await readFile(reportPath, "utf8");
|
|
382
|
+
assert.equal(report.includes(weekId), true);
|
|
383
|
+
const weeklyNames = await readdir(path.dirname(reportPath));
|
|
384
|
+
assert.equal(weeklyNames.includes(`${weekId}.md`), true);
|
|
385
|
+
assert.equal(
|
|
386
|
+
weeklyNames.some((name) => name.endsWith(".tmp")),
|
|
387
|
+
false,
|
|
388
|
+
"the completion marker is renamed into place, not left as a partial temp file"
|
|
389
|
+
);
|
|
390
|
+
assert.equal(
|
|
391
|
+
logs.some((line) => line.includes("weekly synthesis produced")),
|
|
392
|
+
true,
|
|
393
|
+
"synthesizeWeekly must run the calibration pass"
|
|
394
|
+
);
|
|
395
|
+
} finally {
|
|
396
|
+
CompoundingEngine.prototype.synthesizeWeekly = original;
|
|
397
|
+
resetLogger();
|
|
398
|
+
await scheduler.dispose();
|
|
399
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("weekly synthesis serializes per memory directory across engines", async () => {
|
|
404
|
+
const order: string[] = [];
|
|
405
|
+
let releaseFirst: (() => void) | undefined;
|
|
406
|
+
const firstGate = new Promise<void>((resolve) => {
|
|
407
|
+
releaseFirst = resolve;
|
|
408
|
+
});
|
|
409
|
+
const first = queueWeeklySynthesis("dir-a", async () => {
|
|
410
|
+
order.push("first-start");
|
|
411
|
+
await firstGate;
|
|
412
|
+
order.push("first-end");
|
|
413
|
+
});
|
|
414
|
+
const second = queueWeeklySynthesis("dir-a", async () => {
|
|
415
|
+
order.push("second-start");
|
|
416
|
+
});
|
|
417
|
+
const otherDir = queueWeeklySynthesis("dir-b", async () => {
|
|
418
|
+
order.push("other-dir-start");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
422
|
+
assert.deepEqual(order, ["first-start", "other-dir-start"], "same-dir run waits; other dir does not");
|
|
423
|
+
releaseFirst?.();
|
|
424
|
+
await Promise.all([first, second, otherDir]);
|
|
425
|
+
assert.deepEqual(order, ["first-start", "other-dir-start", "first-end", "second-start"]);
|
|
426
|
+
});
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process weekly compounding run for the standalone daemon.
|
|
3
|
+
*
|
|
4
|
+
* OpenClaw delegate mode registers only memory_search and memory_get, so a
|
|
5
|
+
* gateway cron can no longer call `compounding_weekly_synthesize`. When both
|
|
6
|
+
* compounding flags are on, the standalone daemon arms this runner after
|
|
7
|
+
* its HTTP listener is up. Embedded OpenClaw init does not.
|
|
8
|
+
*
|
|
9
|
+
* Each tick synthesizes the ISO week that most recently ended (previous week,
|
|
10
|
+
* not the in-progress one) by calling `synthesizeWeekly`, which includes
|
|
11
|
+
* calibration when that gate is on. A scheduler-owned completion marker is
|
|
12
|
+
* the durable skip signal: only a scheduled run writes it, so a manual
|
|
13
|
+
* access-surface synthesis of the in-progress week cannot suppress the week's
|
|
14
|
+
* scheduled run, and a restart does not write a completed week twice. A
|
|
15
|
+
* daemon that was down at the Monday boundary still writes that missed week
|
|
16
|
+
* on the next start. Older gaps are left alone — one missed week, not a
|
|
17
|
+
* history backfill.
|
|
18
|
+
*
|
|
19
|
+
* The timer is unref'd. Startup fires one tick immediately; later ticks land
|
|
20
|
+
* on the next Monday 00:00 UTC. A failed tick remembers that week and retries
|
|
21
|
+
* it before the newly due week, including after the boundary. That memory is
|
|
22
|
+
* process-local: a restart still keys off the completion marker.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { lstat } from "node:fs/promises";
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
|
|
28
|
+
import { resolveConsolidationCapabilities } from "../capabilities.js";
|
|
29
|
+
import { writeFileAtomically } from "../maintenance/atomic-file.js";
|
|
30
|
+
import { log as defaultLog } from "../logger.js";
|
|
31
|
+
import type { StorageManager } from "../storage.js";
|
|
32
|
+
import type { PluginConfig } from "../types.js";
|
|
33
|
+
import { CompoundingEngine } from "./engine.js";
|
|
34
|
+
import { isIsoWeekId, msUntilNextIsoWeek, previousIsoWeekId } from "./iso-week.js";
|
|
35
|
+
|
|
36
|
+
export interface CompoundingWeeklyLog {
|
|
37
|
+
info(message: string): void;
|
|
38
|
+
warn(message: string): void;
|
|
39
|
+
debug(message: string): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type TimerHandle = { unref?: () => void };
|
|
43
|
+
|
|
44
|
+
export interface CompoundingWeeklySchedulerOptions {
|
|
45
|
+
memoryDir: string;
|
|
46
|
+
/** Full `synthesizeWeekly` path, including its calibration pass. */
|
|
47
|
+
synthesizeWeekly: (weekId: string) => Promise<unknown>;
|
|
48
|
+
now?: () => Date;
|
|
49
|
+
/** Scheduler-owned completion check: has a scheduled run finished this week? */
|
|
50
|
+
weekDone?: (weekId: string) => Promise<boolean>;
|
|
51
|
+
/** Record a scheduled run's completion. Manual access-surface runs never do. */
|
|
52
|
+
markWeekDone?: (weekId: string) => Promise<void>;
|
|
53
|
+
setTimer?: (fn: () => void, ms: number) => TimerHandle;
|
|
54
|
+
clearTimer?: (handle: TimerHandle) => void;
|
|
55
|
+
log?: CompoundingWeeklyLog;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type CompoundingWeeklyTick =
|
|
59
|
+
| { status: "synthesized" | "skipped" | "failed"; weekId: string }
|
|
60
|
+
| { status: "stopped" | "coalesced"; weekId: null };
|
|
61
|
+
|
|
62
|
+
export interface CompoundingWeeklySchedulerHandle {
|
|
63
|
+
/** Run one check now (startup catch-up and tests). */
|
|
64
|
+
tick(): Promise<CompoundingWeeklyTick>;
|
|
65
|
+
/** Resolve when no tick is in flight, including a coalesced follow-up. */
|
|
66
|
+
whenIdle(): Promise<void>;
|
|
67
|
+
/** Prevent later ticks and wait for the one already running. */
|
|
68
|
+
stop(): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function compoundingWeeklyReportPath(memoryDir: string, weekId: string): string {
|
|
72
|
+
if (!isIsoWeekId(weekId)) {
|
|
73
|
+
throw new Error("compounding weekly cron: week id must be YYYY-Www");
|
|
74
|
+
}
|
|
75
|
+
return path.join(memoryDir, "compounding", "weekly", `${weekId}.md`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Completion marker for scheduled runs only. A manual access-surface run can
|
|
80
|
+
* synthesize the in-progress current week early; its report file must not
|
|
81
|
+
* satisfy the scheduler's due check, or the evidence added after the manual
|
|
82
|
+
* run would never reach that week's scheduled synthesis.
|
|
83
|
+
*/
|
|
84
|
+
export function compoundingWeeklyDoneMarkerPath(memoryDir: string, weekId: string): string {
|
|
85
|
+
if (!isIsoWeekId(weekId)) {
|
|
86
|
+
throw new Error("compounding weekly cron: week id must be YYYY-Www");
|
|
87
|
+
}
|
|
88
|
+
return path.join(memoryDir, "compounding", "weekly", ".scheduler", `${weekId}.done`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function defaultWeekDone(memoryDir: string, weekId: string): Promise<boolean> {
|
|
92
|
+
try {
|
|
93
|
+
await lstat(compoundingWeeklyDoneMarkerPath(memoryDir, weekId));
|
|
94
|
+
return true;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (isNotFound(error)) return false;
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function defaultMarkWeekDone(memoryDir: string, weekId: string): Promise<void> {
|
|
102
|
+
// Atomic: a partially written marker must never satisfy the skip check.
|
|
103
|
+
await writeFileAtomically(compoundingWeeklyDoneMarkerPath(memoryDir, weekId), `${new Date().toISOString()}\n`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isNotFound(error: unknown): boolean {
|
|
107
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function startCompoundingWeeklyScheduler(
|
|
111
|
+
options: CompoundingWeeklySchedulerOptions
|
|
112
|
+
): CompoundingWeeklySchedulerHandle {
|
|
113
|
+
const now = options.now ?? (() => new Date());
|
|
114
|
+
const logger = options.log ?? defaultLog;
|
|
115
|
+
const weekDone = options.weekDone ?? ((weekId) => defaultWeekDone(options.memoryDir, weekId));
|
|
116
|
+
const markWeekDone = options.markWeekDone ?? ((weekId) => defaultMarkWeekDone(options.memoryDir, weekId));
|
|
117
|
+
const setTimer =
|
|
118
|
+
options.setTimer ??
|
|
119
|
+
((fn, ms) => {
|
|
120
|
+
const timer = setTimeout(fn, ms);
|
|
121
|
+
timer.unref?.();
|
|
122
|
+
return timer;
|
|
123
|
+
});
|
|
124
|
+
const clearTimer = options.clearTimer ?? ((handle) => clearTimeout(handle as NodeJS.Timeout));
|
|
125
|
+
|
|
126
|
+
let stopped = false;
|
|
127
|
+
let pending = false;
|
|
128
|
+
let inFlight: Promise<CompoundingWeeklyTick> | null = null;
|
|
129
|
+
let timer: TimerHandle | null = null;
|
|
130
|
+
let retryWeekId: string | null = null;
|
|
131
|
+
|
|
132
|
+
const runWeek = async (weekId: string): Promise<CompoundingWeeklyTick> => {
|
|
133
|
+
try {
|
|
134
|
+
if (await weekDone(weekId)) {
|
|
135
|
+
if (retryWeekId === weekId) retryWeekId = null;
|
|
136
|
+
logger.debug(`compounding weekly cron: ${weekId} already synthesized by the scheduler, skipping`);
|
|
137
|
+
return { status: "skipped", weekId };
|
|
138
|
+
}
|
|
139
|
+
await options.synthesizeWeekly(weekId);
|
|
140
|
+
await markWeekDone(weekId);
|
|
141
|
+
logger.info(`compounding weekly cron: synthesized ${weekId}`);
|
|
142
|
+
return { status: "synthesized", weekId };
|
|
143
|
+
} catch (error) {
|
|
144
|
+
retryWeekId = weekId;
|
|
145
|
+
const name = error instanceof Error ? error.name : "Error";
|
|
146
|
+
logger.warn(`compounding weekly cron: ${weekId} failed (${name}) — retrying on the next tick`);
|
|
147
|
+
return { status: "failed", weekId };
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const arm = (): void => {
|
|
152
|
+
if (stopped) return;
|
|
153
|
+
if (timer) clearTimer(timer);
|
|
154
|
+
const delay = msUntilNextIsoWeek(now());
|
|
155
|
+
timer = setTimer(() => {
|
|
156
|
+
timer = null;
|
|
157
|
+
if (stopped) return;
|
|
158
|
+
arm();
|
|
159
|
+
void request();
|
|
160
|
+
}, delay);
|
|
161
|
+
timer.unref?.();
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const doTick = async (): Promise<CompoundingWeeklyTick> => {
|
|
165
|
+
const due = previousIsoWeekId(now());
|
|
166
|
+
const weekId = retryWeekId ?? due;
|
|
167
|
+
const first = await runWeek(weekId);
|
|
168
|
+
if (first.status === "failed" || weekId === due) return first;
|
|
169
|
+
return runWeek(due);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const request = (): Promise<CompoundingWeeklyTick> => {
|
|
173
|
+
if (stopped) return Promise.resolve({ status: "stopped", weekId: null });
|
|
174
|
+
if (inFlight) {
|
|
175
|
+
pending = true;
|
|
176
|
+
return inFlight.then(() => ({ status: "coalesced" as const, weekId: null }));
|
|
177
|
+
}
|
|
178
|
+
const run = doTick().finally(() => {
|
|
179
|
+
inFlight = null;
|
|
180
|
+
if (!stopped && pending) {
|
|
181
|
+
pending = false;
|
|
182
|
+
void request();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
inFlight = run;
|
|
186
|
+
return run;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
arm();
|
|
190
|
+
void request();
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
tick: () => request(),
|
|
194
|
+
async whenIdle() {
|
|
195
|
+
while (inFlight) await inFlight;
|
|
196
|
+
},
|
|
197
|
+
async stop() {
|
|
198
|
+
stopped = true;
|
|
199
|
+
pending = false;
|
|
200
|
+
if (timer) clearTimer(timer);
|
|
201
|
+
timer = null;
|
|
202
|
+
while (inFlight) await inFlight;
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Arm the daemon weekly run when both compounding flags are on.
|
|
209
|
+
* Returns null when the run stays off or arming fails. The returned handle's
|
|
210
|
+
* first tick is already in flight and includes calibration via synthesizeWeekly.
|
|
211
|
+
*/
|
|
212
|
+
export function createCompoundingWeeklyCron(deps: {
|
|
213
|
+
config: PluginConfig;
|
|
214
|
+
getStorage?: () => StorageManager | undefined;
|
|
215
|
+
}): CompoundingWeeklySchedulerHandle | null {
|
|
216
|
+
const caps = resolveConsolidationCapabilities(deps.config);
|
|
217
|
+
if (!caps.compounding || !caps.compoundingWeeklyCron) return null;
|
|
218
|
+
try {
|
|
219
|
+
const engine = new CompoundingEngine(deps.config, deps.getStorage?.());
|
|
220
|
+
const handle = startCompoundingWeeklyScheduler({
|
|
221
|
+
memoryDir: deps.config.memoryDir,
|
|
222
|
+
synthesizeWeekly: (weekId) => engine.synthesizeWeekly({ weekId }),
|
|
223
|
+
});
|
|
224
|
+
defaultLog.info("compounding weekly cron armed");
|
|
225
|
+
return handle;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const name = error instanceof Error ? error.name : "Error";
|
|
228
|
+
defaultLog.warn(`compounding weekly cron failed to arm (${name}) — non-fatal`);
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-flight boundary for weekly synthesis, keyed by memory directory.
|
|
3
|
+
* The daemon scheduler and the access surface each drive their own engine
|
|
4
|
+
* instance against the same files; without serialization, concurrent
|
|
5
|
+
* read-modify-write of mistakes.json, rubrics, and the calibration index
|
|
6
|
+
* would let the last finisher discard the other run's updates.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const weeklySynthesisTails = new Map<string, Promise<void>>();
|
|
10
|
+
|
|
11
|
+
export function queueWeeklySynthesis<T>(memoryDir: string, run: () => Promise<T>): Promise<T> {
|
|
12
|
+
const prior = weeklySynthesisTails.get(memoryDir);
|
|
13
|
+
const next = prior === undefined ? run() : prior.then(run, run);
|
|
14
|
+
const tail = next.then(
|
|
15
|
+
() => undefined,
|
|
16
|
+
() => undefined
|
|
17
|
+
);
|
|
18
|
+
weeklySynthesisTails.set(memoryDir, tail);
|
|
19
|
+
void tail.then(() => {
|
|
20
|
+
if (weeklySynthesisTails.get(memoryDir) === tail) weeklySynthesisTails.delete(memoryDir);
|
|
21
|
+
});
|
|
22
|
+
return next;
|
|
23
|
+
}
|