@remnic/core 10.0.91 → 10.0.93
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 +334 -61
- package/dist/access-authorization-probe.js +2 -283
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.d.ts.map +1 -1
- package/dist/access-boundary.js +1 -282
- package/dist/access-cli.js +288 -304
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.js +3 -3
- package/dist/access-extraction-force-flush.js +334 -61
- package/dist/access-http-briefing.js +334 -62
- package/dist/access-http-query.js +334 -63
- package/dist/access-http.js +332 -114
- package/dist/access-identity-continuity-surface.js +334 -61
- package/dist/access-lcm-surface.js +334 -61
- package/dist/access-mcp-frames.d.ts +20 -6
- package/dist/access-mcp-frames.d.ts.map +1 -1
- package/dist/access-mcp-frames.js +1 -1
- package/dist/access-mcp.d.ts +5 -0
- package/dist/access-mcp.d.ts.map +1 -1
- package/dist/access-mcp.js +332 -89
- package/dist/access-namespace-preflight.js +2 -282
- package/dist/access-observe-transcript.js +3 -3
- package/dist/access-observe-write-category.js +6 -6
- package/dist/access-observe-write-surface.js +334 -61
- package/dist/access-offline-manifest.js +12 -11
- package/dist/access-operations-batch.js +332 -66
- package/dist/access-operations.js +332 -75
- package/dist/access-recall-concurrency.js +334 -61
- package/dist/access-recall-diagnostics.js +6 -5
- package/dist/access-recall-response.js +334 -61
- package/dist/access-recall-surface.js +334 -61
- package/dist/access-schema.js +2 -2
- package/dist/access-service.d.ts +1 -0
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +337 -64
- package/dist/access-surface-catalog.js +6 -283
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-context-transform.js +1 -1
- package/dist/active-memory-bridge.js +3 -2
- package/dist/active-recall.js +36 -37
- package/dist/active-recall.js.map +1 -1
- package/dist/activity/journal-extract.d.ts +13 -1
- package/dist/activity/journal-extract.d.ts.map +1 -1
- package/dist/{auto-sync-3AZW7MXI.js → auto-sync-BO5YZKM6.js} +13 -15
- package/dist/{auto-sync-3AZW7MXI.js.map → auto-sync-BO5YZKM6.js.map} +1 -1
- package/dist/briefing.js +20 -20
- package/dist/buffer.js +16 -16
- package/dist/calibration.js +10 -10
- package/dist/{capsule-merge-2IIXFXN5.js → capsule-merge-6IDAYTKG.js} +6 -6
- package/dist/causal-consolidation.js +23 -23
- package/dist/{chunk-QL7RHYK6.js → chunk-2LMA22UU.js} +5 -5
- package/dist/{chunk-RGUUBAT5.js → chunk-2WCLTWV2.js} +3 -3
- package/dist/{chunk-RSK2WIEM.js → chunk-2YODOG2O.js} +2 -2
- package/dist/{chunk-WYG5UNDY.js → chunk-3AUDELZL.js} +2 -2
- package/dist/{chunk-OFVCFKDH.js → chunk-3CYOCE6U.js} +3 -3
- package/dist/{chunk-6HM6HARE.js → chunk-3FBAEPCU.js} +2 -2
- package/dist/{chunk-PIITB2YZ.js → chunk-3ZS3NUEI.js} +4 -4
- package/dist/{chunk-V4XE263I.js → chunk-43AA5FVD.js} +1 -1
- package/dist/chunk-43AA5FVD.js.map +1 -0
- package/dist/{chunk-ANZZLRUX.js → chunk-4IVRQMZW.js} +2 -2
- package/dist/{chunk-63E37JCN.js → chunk-4RAJT5L2.js} +4 -4
- package/dist/{chunk-45VUHDV2.js → chunk-5CO5G4MN.js} +2 -2
- package/dist/{chunk-PUEO37QG.js → chunk-5YT2IIFX.js} +729 -17
- package/dist/chunk-5YT2IIFX.js.map +1 -0
- package/dist/{chunk-P5VZSSYL.js → chunk-622V2AZA.js} +233 -40
- package/dist/chunk-622V2AZA.js.map +1 -0
- package/dist/{chunk-3UMDRGFS.js → chunk-65HWAQ4F.js} +3 -3
- package/dist/{chunk-T67LUYCR.js → chunk-77NTFICT.js} +5 -5
- package/dist/chunk-A2WFUMUN.js +86 -0
- package/dist/chunk-A2WFUMUN.js.map +1 -0
- package/dist/{chunk-UE6RRXMF.js → chunk-A4F7OIC5.js} +2 -2
- package/dist/chunk-AHA5VQGP.js +117 -0
- package/dist/chunk-AHA5VQGP.js.map +1 -0
- package/dist/{chunk-AOFEMUHG.js → chunk-ATG2PQ4V.js} +56 -5
- package/dist/chunk-ATG2PQ4V.js.map +1 -0
- package/dist/{chunk-63O7LVMN.js → chunk-B33YWUJV.js} +3 -3
- package/dist/{chunk-QVMOCJRV.js → chunk-B377OKUP.js} +2 -2
- package/dist/{chunk-IUKYFTGW.js → chunk-B3DR2JBZ.js} +140 -125
- package/dist/chunk-B3DR2JBZ.js.map +1 -0
- package/dist/{chunk-ZT6WPSII.js → chunk-BGHCSLE5.js} +2 -2
- package/dist/{chunk-YYOMGOHB.js → chunk-BOK6BN5M.js} +2 -2
- package/dist/{chunk-OH7QLI6W.js → chunk-BPPUH5JM.js} +5 -5
- package/dist/{chunk-EV76IZ37.js → chunk-BTI4C4MA.js} +2 -2
- package/dist/{chunk-O6ZSWHVR.js → chunk-C2KE5DMD.js} +4 -4
- package/dist/{chunk-47F6WWLQ.js → chunk-C73N66PR.js} +2 -2
- package/dist/{chunk-3XIWW4TB.js → chunk-CGJYL633.js} +2 -2
- package/dist/{chunk-WRT7GJ2D.js → chunk-CIWMTNHW.js} +4 -4
- package/dist/{chunk-O55EKIEC.js → chunk-D3ZKHLN2.js} +2 -2
- package/dist/{chunk-CD4BCCNP.js → chunk-DJ7WVEWX.js} +2 -2
- package/dist/{chunk-RO7RUBLD.js → chunk-EFL24KOI.js} +2 -2
- package/dist/{chunk-4CJTQHEG.js → chunk-ESLOLCD4.js} +3 -3
- package/dist/{chunk-Z4Q6USKJ.js → chunk-ETBVCRVL.js} +4 -4
- package/dist/{chunk-VILPVT4S.js → chunk-EZ47HOJ6.js} +4 -4
- package/dist/{chunk-EFYN5I7Z.js → chunk-F4QFU5IZ.js} +2 -2
- package/dist/{chunk-T6OZW2WR.js → chunk-FA7SLQ5X.js} +7 -1
- package/dist/chunk-FA7SLQ5X.js.map +1 -0
- package/dist/{chunk-SHUBTAY2.js → chunk-FMKILENU.js} +14 -14
- package/dist/{chunk-YLXETSAH.js → chunk-GCZJ7XRI.js} +2 -2
- package/dist/{chunk-J4MMPQ7Q.js → chunk-GQXOBKPL.js} +7 -9
- package/dist/{chunk-J4MMPQ7Q.js.map → chunk-GQXOBKPL.js.map} +1 -1
- package/dist/{chunk-UDKWFDV2.js → chunk-HLUHEMN4.js} +27 -3
- package/dist/chunk-HLUHEMN4.js.map +1 -0
- package/dist/{chunk-QFEDITXM.js → chunk-JNDMIAJ4.js} +2 -2
- package/dist/chunk-JTSSSR4L.js +359 -0
- package/dist/chunk-JTSSSR4L.js.map +1 -0
- package/dist/{chunk-5KBIOI4G.js → chunk-JVBAPAWZ.js} +217 -13
- package/dist/chunk-JVBAPAWZ.js.map +1 -0
- package/dist/{chunk-E5PPE4VA.js → chunk-K2VRKCPA.js} +2 -2
- package/dist/{chunk-MKRYQROF.js → chunk-LGEHAQHK.js} +4 -4
- package/dist/{chunk-7HTUYFR3.js → chunk-LLPMMMWG.js} +27 -10
- package/dist/chunk-LLPMMMWG.js.map +1 -0
- package/dist/{chunk-XYTQJD7F.js → chunk-LMJVP5CM.js} +2 -2
- package/dist/{chunk-JC36FPQ5.js → chunk-LPV72KRQ.js} +2 -2
- package/dist/{chunk-2LCRG2YV.js → chunk-M2XR6B6V.js} +2 -2
- package/dist/{chunk-GGRRF4PZ.js → chunk-MDPT6QFK.js} +1 -1
- package/dist/{chunk-GGRRF4PZ.js.map → chunk-MDPT6QFK.js.map} +1 -1
- package/dist/{chunk-VPQFK63H.js → chunk-MH632KPO.js} +2 -2
- package/dist/{chunk-EQ4U73UH.js → chunk-MLAMXQAW.js} +14 -2
- package/dist/chunk-MLAMXQAW.js.map +1 -0
- package/dist/{chunk-ZU5JDLOH.js → chunk-MMRTPH3J.js} +2 -2
- package/dist/{chunk-ZZ67UTQX.js → chunk-MUX7IL77.js} +2 -2
- package/dist/{chunk-RYWROSL5.js → chunk-NAQ4A5IX.js} +7 -7
- package/dist/{chunk-TVIVY7KU.js → chunk-NED7IR7B.js} +4 -4
- package/dist/{chunk-ADYP7HXE.js → chunk-NHVAX7MH.js} +3 -3
- package/dist/{chunk-TG3R2SID.js → chunk-NMGHGTVR.js} +5 -5
- package/dist/{chunk-YCNIGRRF.js → chunk-NS7U4ZYU.js} +13 -117
- package/dist/chunk-NS7U4ZYU.js.map +1 -0
- package/dist/{chunk-NGHDW6UU.js → chunk-NUQEREIP.js} +2 -2
- package/dist/{chunk-5IIQU4JZ.js → chunk-OB72KNMF.js} +4 -4
- package/dist/{chunk-7L3TODI7.js → chunk-OJXJWNFY.js} +2 -2
- package/dist/{chunk-66L5ZRJC.js → chunk-P3WRNWWM.js} +157 -4
- package/dist/chunk-P3WRNWWM.js.map +1 -0
- package/dist/{chunk-E6M225BJ.js → chunk-PC5DRBGD.js} +18 -8
- package/dist/chunk-PC5DRBGD.js.map +1 -0
- package/dist/{chunk-EURPSF4Y.js → chunk-PH5ZQM35.js} +2 -2
- package/dist/{chunk-BMWMTAQF.js → chunk-PRAUN6PN.js} +3 -3
- package/dist/{chunk-RWGRSKN5.js → chunk-PT4DZDJA.js} +2 -2
- package/dist/{chunk-7EPY3IQL.js → chunk-PZJQ46DH.js} +2 -2
- package/dist/{chunk-AH637HZ5.js → chunk-QDESY76F.js} +3 -3
- package/dist/{chunk-I6QGI65T.js → chunk-QRMG4HK4.js} +3 -3
- package/dist/{chunk-ZU3NJNO7.js → chunk-QSF5SJAM.js} +4 -4
- package/dist/{chunk-5IYOKDM4.js → chunk-R23JWJD3.js} +5 -5
- package/dist/{chunk-EWJZZDRG.js → chunk-R4BBGPI2.js} +219 -219
- package/dist/chunk-R4BBGPI2.js.map +1 -0
- package/dist/{chunk-LJQFJF4J.js → chunk-RM2YUFH6.js} +6 -6
- package/dist/{chunk-IPT2OY4B.js → chunk-RQFXZYTF.js} +35 -33
- package/dist/chunk-RQFXZYTF.js.map +1 -0
- package/dist/{chunk-AP6NCP3V.js → chunk-S3S72HRM.js} +8 -6
- package/dist/chunk-S3S72HRM.js.map +1 -0
- package/dist/{chunk-6QMWECO5.js → chunk-SGQPR5SX.js} +7 -7
- package/dist/{chunk-ZGFLIK5H.js → chunk-SPAKBUJY.js} +2 -2
- package/dist/{chunk-ER7XANP3.js → chunk-SQ3GEIEL.js} +4 -4
- package/dist/{chunk-5UCMYPVF.js → chunk-SVT5IA2H.js} +2 -2
- package/dist/{chunk-HVKUCT3W.js → chunk-T4KY6UYF.js} +5 -3
- package/dist/{chunk-HVKUCT3W.js.map → chunk-T4KY6UYF.js.map} +1 -1
- package/dist/{chunk-FRSHF6FU.js → chunk-TJ4F6H4H.js} +3 -3
- package/dist/{chunk-R6HMAIKM.js → chunk-TOSPNM4T.js} +2 -2
- package/dist/{chunk-NJJMVS5Y.js → chunk-TRQE3RYR.js} +5 -5
- package/dist/{chunk-RMXS4I66.js → chunk-TVDJEUV5.js} +2 -2
- package/dist/{chunk-PLCYKAWH.js → chunk-TYRWSE4B.js} +2 -2
- package/dist/{chunk-YS2GHSWN.js → chunk-UBEKET2V.js} +389 -39
- package/dist/chunk-UBEKET2V.js.map +1 -0
- package/dist/{chunk-IQWUACVG.js → chunk-UHMHJX5G.js} +3 -3
- package/dist/{chunk-YODUDA6R.js → chunk-UN4RVUG2.js} +22 -22
- package/dist/{chunk-KZSGBWOJ.js → chunk-V2KGDESV.js} +2 -2
- package/dist/{chunk-E6Z4FBX6.js → chunk-V2QSNFCT.js} +2 -2
- package/dist/{chunk-BCCG7VWM.js → chunk-V65H7MI6.js} +396 -13
- package/dist/chunk-V65H7MI6.js.map +1 -0
- package/dist/chunk-W54Z2MOP.js +4500 -0
- package/dist/chunk-W54Z2MOP.js.map +1 -0
- package/dist/{chunk-N74SGESS.js → chunk-WKSQTMU7.js} +2 -2
- package/dist/{chunk-RIZ5NUNN.js → chunk-WQ4XKIOX.js} +4 -4
- package/dist/{chunk-WWK6J6SE.js → chunk-WQURALGV.js} +4996 -2748
- package/dist/chunk-WQURALGV.js.map +1 -0
- package/dist/{chunk-QEYTRFR3.js → chunk-X26YOFGS.js} +2 -2
- package/dist/{chunk-ITHZFHST.js → chunk-X2BVB74H.js} +2 -2
- package/dist/{chunk-GNXGJBCA.js → chunk-XIYLWEXC.js} +2 -2
- package/dist/{chunk-IBJ6U753.js → chunk-XKZBZOKV.js} +4 -4
- package/dist/{chunk-7O2MC42R.js → chunk-XNRLRNQF.js} +2 -2
- package/dist/{chunk-65AUYLU3.js → chunk-XPXHQI6N.js} +2 -2
- package/dist/{chunk-JBCCWVU2.js → chunk-XRDYRWZI.js} +2 -2
- package/dist/chunk-XRWWCW4H.js +102 -0
- package/dist/chunk-XRWWCW4H.js.map +1 -0
- package/dist/chunk-YKJBSMMP.js +67137 -0
- package/dist/chunk-YKJBSMMP.js.map +1 -0
- package/dist/{chunk-2JSDPUKD.js → chunk-YMFPFXEX.js} +2 -2
- package/dist/{chunk-645WOU5I.js → chunk-ZDZJNXL6.js} +4 -4
- package/dist/{chunk-6TCXIRP5.js → chunk-ZKE4CO6B.js} +4 -4
- package/dist/{chunk-KEPXC4KN.js → chunk-ZSE774RV.js} +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +305 -187
- package/dist/coding/export-okf-codegraph.js +29 -29
- package/dist/compounding/engine.js +23 -24
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +36 -37
- package/dist/connectors/codex-materialize-runner.js +20 -20
- package/dist/connectors/index.js +21 -21
- package/dist/connectors/live/framework.d.ts +7 -0
- package/dist/connectors/live/framework.d.ts.map +1 -1
- package/dist/connectors/live/google-drive.d.ts +17 -3
- package/dist/connectors/live/google-drive.d.ts.map +1 -1
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.js +5 -5
- package/dist/contradiction/index.js +7 -6
- package/dist/{contradiction-scan-P2MHYFVG.js → contradiction-scan-IBPYGQZF.js} +4 -3
- package/dist/convergence-refresh.js +22 -22
- package/dist/corpus-watermark.js +23 -23
- package/dist/deep-recall-http-glue.js +2 -2
- package/dist/deep-recall-policy-llm.js +4 -4
- package/dist/direct-answer-wiring.js +2 -2
- package/dist/embedding-fallback.js +1 -1
- package/dist/entity-retrieval.js +21 -21
- package/dist/eval-trajectory-store.js +2 -2
- package/dist/eval-trajectory.js +2 -2
- package/dist/event-order-recall.js +2 -2
- package/dist/explicit-capture.d.ts +3 -2
- package/dist/explicit-capture.d.ts.map +1 -1
- package/dist/explicit-capture.js +530 -6
- package/dist/explicit-cue-recall.js +2 -2
- package/dist/external-wiki-access.js +2 -283
- package/dist/extraction-faithfulness.js +4 -4
- package/dist/extraction-judge.js +5 -5
- package/dist/extraction-normalization.js +1 -1
- package/dist/extraction-quote-grounding.d.ts.map +1 -1
- package/dist/extraction-quote-grounding.js +1 -1
- package/dist/extraction-source-grounding-rules.js +2 -2
- package/dist/extraction-source-grounding.js +3 -3
- package/dist/extraction.js +43 -44
- package/dist/fallback-llm-codex-subscription.js +2 -2
- package/dist/fallback-llm.js +3 -3
- package/dist/{first-start-migration-QFI7B5SH.js → first-start-migration-FILJH3RO.js} +6 -5
- package/dist/{first-start-migration-QFI7B5SH.js.map → first-start-migration-FILJH3RO.js.map} +1 -1
- package/dist/focused-list-recall.js +2 -2
- package/dist/harmonic-retrieval.js +20 -20
- package/dist/index.js +776 -797
- package/dist/lcm/index.js +2 -2
- package/dist/live-connectors-runner.d.ts +2 -0
- package/dist/live-connectors-runner.d.ts.map +1 -1
- package/dist/live-connectors-runner.js +3 -4
- package/dist/local-llm-config.js +2 -2
- package/dist/local-llm-helpers.d.ts +4 -0
- package/dist/local-llm-helpers.d.ts.map +1 -1
- package/dist/local-llm-helpers.js +3 -1
- package/dist/local-llm.d.ts +4 -1
- package/dist/local-llm.d.ts.map +1 -1
- package/dist/local-llm.js +4 -4
- package/dist/location/index.js +27 -29
- package/dist/maintenance/memory-governance.js +20 -20
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -20
- package/dist/maintenance/rebuild-memory-projection-runner.js +22 -22
- package/dist/maintenance/rebuild-memory-projection.js +21 -21
- package/dist/memory-browse-operations.js +2 -283
- package/dist/memory-browse.js +8 -7
- package/dist/memory-controller.js +2 -2
- package/dist/memory-subject.js +2 -2
- package/dist/namespaces/migrate.js +21 -21
- package/dist/namespaces/search.js +5 -5
- package/dist/namespaces/storage.js +20 -20
- package/dist/offline-sync-changeset-generations.js +1 -1
- package/dist/offline-sync-embedding-generation.js +1 -1
- package/dist/offline-sync-impression-drain.js +6 -6
- package/dist/offline-sync-snapshot-stream.js +1 -1
- package/dist/offline-sync.js +1 -1
- package/dist/operator-doctor-corpus.js +24 -24
- package/dist/operator-toolkit.js +58 -61
- package/dist/orchestration/backlog-scheduler.js +279 -295
- package/dist/orchestration/backlog-scheduler.js.map +1 -1
- package/dist/orchestration/buffer-drain-runner.js +279 -295
- package/dist/orchestration/extraction-run.d.ts +4 -0
- package/dist/orchestration/extraction-run.d.ts.map +1 -1
- package/dist/orchestration/fact-dedup-window.d.ts +1 -1
- package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +39 -42
- package/dist/orchestrator.d.ts +11 -11
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +291 -308
- package/dist/page-versioning.js +3 -3
- package/dist/qmd-daemon-session.js +2 -2
- package/dist/qmd.js +3 -3
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-navigate-access.js +2 -2
- package/dist/recall-navigate-disclosure.js +2 -2
- package/dist/recall-navigate.js +2 -2
- package/dist/recall-navigation-operations.js +2 -283
- package/dist/recall-navigation.js +334 -61
- package/dist/recall-planner-llm.js +3 -3
- package/dist/recall-state-view-admission.js +6 -6
- package/dist/recall-why-service.js +10 -9
- package/dist/recall-why.js +4 -3
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/manifest.js +3 -3
- package/dist/{resolution-N7H3F2E4.js → resolution-OVVIF7ZY.js} +8 -7
- package/dist/response-guidance-recall.js +2 -2
- package/dist/resume-bundles.js +40 -41
- package/dist/salvage-envelope.js +4 -4
- package/dist/search/factory.js +4 -4
- package/dist/search/index.js +4 -4
- package/dist/semantic-consolidation.js +23 -23
- package/dist/semantic-rule-promotion.js +20 -20
- package/dist/semantic-rule-verifier.js +20 -20
- package/dist/shared-context/manager.js +2 -3
- package/dist/source-agent-qualifier.js +32 -33
- package/dist/standup/build.js +20 -20
- package/dist/{state-store-I5HLZTZT.js → state-store-EEAX2MBS.js} +2 -2
- package/dist/storage/content-hash-index.d.ts +10 -0
- package/dist/storage/content-hash-index.d.ts.map +1 -1
- package/dist/storage.js +19 -19
- package/dist/subject-config.js +3 -3
- package/dist/summarizer.d.ts +4 -1
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +6 -6
- package/dist/support-passport/index.js +53 -297
- package/dist/targeted-fact-recall.js +2 -2
- package/dist/tier-routing.js +3 -2
- package/dist/{tier-stats-FQGQSFET.js → tier-stats-FXEUJN73.js} +6 -5
- package/dist/{tier-stats-FQGQSFET.js.map → tier-stats-FXEUJN73.js.map} +1 -1
- package/dist/transcript.js +2 -2
- package/dist/transfer/capsule-import.js +4 -4
- package/dist/transfer/export-okf.js +20 -20
- package/dist/types.d.ts +10 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +20 -20
- package/dist/wearables/memory-gen.d.ts +5 -1
- package/dist/wearables/memory-gen.d.ts.map +1 -1
- package/dist/wearables/service.d.ts.map +1 -1
- package/dist/who-knows-cli.js +21 -21
- package/dist/who-knows.js +20 -20
- package/dist/write-envelope.js +3 -3
- package/package.json +2 -2
- package/src/access-boundary.ts +5 -1
- package/src/access-category-alias.test.ts +5 -0
- package/src/access-mcp-frames.test.ts +60 -5
- package/src/access-mcp-frames.ts +72 -18
- package/src/access-mcp.test.ts +66 -2
- package/src/access-mcp.ts +19 -13
- package/src/access-service-coding-write.test.ts +7 -0
- package/src/access-service.ts +3 -0
- package/src/activity/journal-extract-storage.test.ts +31 -0
- package/src/activity/journal-extract.test.ts +1 -0
- package/src/activity/journal-extract.ts +24 -3
- package/src/cli.ts +1 -2
- package/src/config.test.ts +17 -0
- package/src/config.ts +25 -7
- package/src/connectors/live/framework.ts +8 -0
- package/src/connectors/live/google-drive.test.ts +74 -0
- package/src/connectors/live/google-drive.ts +138 -11
- package/src/explicit-capture.test.ts +109 -17
- package/src/explicit-capture.ts +90 -37
- package/src/extraction-quote-grounding.test.ts +20 -0
- package/src/extraction-quote-grounding.ts +21 -11
- package/src/fact-dedup-window.test.ts +37 -0
- package/src/live-connectors-runner.ts +3 -0
- package/src/live-connectors-secretref.test.ts +126 -0
- package/src/local-llm-helpers.ts +12 -0
- package/src/local-llm-shutdown.test.ts +194 -0
- package/src/local-llm.ts +32 -30
- package/src/orchestration/extraction-run.ts +12 -3
- package/src/orchestration/fact-dedup-window.ts +5 -1
- package/src/orchestrator-connector-dedup.test.ts +9 -5
- package/src/orchestrator.ts +52 -55
- package/src/storage/content-hash-index.ts +27 -2
- package/src/storage-fact-hash-publish-failure.test.ts +87 -0
- package/src/summarizer-shutdown.test.ts +195 -0
- package/src/summarizer.ts +15 -2
- package/src/types.ts +10 -10
- package/src/wearables/memory-gen.test.ts +20 -0
- package/src/wearables/memory-gen.ts +14 -7
- package/src/wearables/service.ts +14 -1
- package/src/wearables/storage-io.test.ts +25 -0
- package/dist/chunk-27XYV2XL.js +0 -43
- package/dist/chunk-27XYV2XL.js.map +0 -1
- package/dist/chunk-5KBIOI4G.js.map +0 -1
- package/dist/chunk-66L5ZRJC.js.map +0 -1
- package/dist/chunk-7HTUYFR3.js.map +0 -1
- package/dist/chunk-AOFEMUHG.js.map +0 -1
- package/dist/chunk-AP6NCP3V.js.map +0 -1
- package/dist/chunk-APYY5CX3.js +0 -335
- package/dist/chunk-APYY5CX3.js.map +0 -1
- package/dist/chunk-ATPWMT2E.js +0 -223
- package/dist/chunk-ATPWMT2E.js.map +0 -1
- package/dist/chunk-BCCG7VWM.js.map +0 -1
- package/dist/chunk-E6M225BJ.js.map +0 -1
- package/dist/chunk-EQ4U73UH.js.map +0 -1
- package/dist/chunk-ETSJD5IV.js +0 -361
- package/dist/chunk-ETSJD5IV.js.map +0 -1
- package/dist/chunk-EWJZZDRG.js.map +0 -1
- package/dist/chunk-FHGWKODN.js +0 -2300
- package/dist/chunk-FHGWKODN.js.map +0 -1
- package/dist/chunk-GRTKLWYX.js +0 -621
- package/dist/chunk-GRTKLWYX.js.map +0 -1
- package/dist/chunk-IGLJNHOX.js +0 -62
- package/dist/chunk-IGLJNHOX.js.map +0 -1
- package/dist/chunk-IPT2OY4B.js.map +0 -1
- package/dist/chunk-IUKYFTGW.js.map +0 -1
- package/dist/chunk-KUIRDD74.js +0 -3991
- package/dist/chunk-KUIRDD74.js.map +0 -1
- package/dist/chunk-MVMRUGBH.js +0 -1919
- package/dist/chunk-MVMRUGBH.js.map +0 -1
- package/dist/chunk-ODL4ZTNI.js +0 -3644
- package/dist/chunk-ODL4ZTNI.js.map +0 -1
- package/dist/chunk-OGXXGHW6.js +0 -223
- package/dist/chunk-OGXXGHW6.js.map +0 -1
- package/dist/chunk-P5VZSSYL.js.map +0 -1
- package/dist/chunk-PPCLZ4JU.js +0 -28
- package/dist/chunk-PPCLZ4JU.js.map +0 -1
- package/dist/chunk-PUEO37QG.js.map +0 -1
- package/dist/chunk-RRTVGD3J.js +0 -792
- package/dist/chunk-RRTVGD3J.js.map +0 -1
- package/dist/chunk-STSLB4HT.js +0 -18793
- package/dist/chunk-STSLB4HT.js.map +0 -1
- package/dist/chunk-T6OZW2WR.js.map +0 -1
- package/dist/chunk-TRBGGJKZ.js +0 -32218
- package/dist/chunk-TRBGGJKZ.js.map +0 -1
- package/dist/chunk-UDKWFDV2.js.map +0 -1
- package/dist/chunk-V4XE263I.js.map +0 -1
- package/dist/chunk-V7IZKJI3.js +0 -22
- package/dist/chunk-V7IZKJI3.js.map +0 -1
- package/dist/chunk-VMVVSZTD.js +0 -61
- package/dist/chunk-VMVVSZTD.js.map +0 -1
- package/dist/chunk-VNUOAXWP.js +0 -203
- package/dist/chunk-VNUOAXWP.js.map +0 -1
- package/dist/chunk-WLBEZZK5.js +0 -729
- package/dist/chunk-WLBEZZK5.js.map +0 -1
- package/dist/chunk-WUUES6CU.js +0 -25
- package/dist/chunk-WUUES6CU.js.map +0 -1
- package/dist/chunk-WWK6J6SE.js.map +0 -1
- package/dist/chunk-XICOH5HT.js +0 -346
- package/dist/chunk-XICOH5HT.js.map +0 -1
- package/dist/chunk-YCNIGRRF.js.map +0 -1
- package/dist/chunk-YS2GHSWN.js.map +0 -1
- package/dist/chunk-ZCLEYJPB.js +0 -10143
- package/dist/chunk-ZCLEYJPB.js.map +0 -1
- /package/dist/{capsule-merge-2IIXFXN5.js.map → capsule-merge-6IDAYTKG.js.map} +0 -0
- /package/dist/{chunk-QL7RHYK6.js.map → chunk-2LMA22UU.js.map} +0 -0
- /package/dist/{chunk-RGUUBAT5.js.map → chunk-2WCLTWV2.js.map} +0 -0
- /package/dist/{chunk-RSK2WIEM.js.map → chunk-2YODOG2O.js.map} +0 -0
- /package/dist/{chunk-WYG5UNDY.js.map → chunk-3AUDELZL.js.map} +0 -0
- /package/dist/{chunk-OFVCFKDH.js.map → chunk-3CYOCE6U.js.map} +0 -0
- /package/dist/{chunk-6HM6HARE.js.map → chunk-3FBAEPCU.js.map} +0 -0
- /package/dist/{chunk-PIITB2YZ.js.map → chunk-3ZS3NUEI.js.map} +0 -0
- /package/dist/{chunk-ANZZLRUX.js.map → chunk-4IVRQMZW.js.map} +0 -0
- /package/dist/{chunk-63E37JCN.js.map → chunk-4RAJT5L2.js.map} +0 -0
- /package/dist/{chunk-45VUHDV2.js.map → chunk-5CO5G4MN.js.map} +0 -0
- /package/dist/{chunk-3UMDRGFS.js.map → chunk-65HWAQ4F.js.map} +0 -0
- /package/dist/{chunk-T67LUYCR.js.map → chunk-77NTFICT.js.map} +0 -0
- /package/dist/{chunk-UE6RRXMF.js.map → chunk-A4F7OIC5.js.map} +0 -0
- /package/dist/{chunk-63O7LVMN.js.map → chunk-B33YWUJV.js.map} +0 -0
- /package/dist/{chunk-QVMOCJRV.js.map → chunk-B377OKUP.js.map} +0 -0
- /package/dist/{chunk-ZT6WPSII.js.map → chunk-BGHCSLE5.js.map} +0 -0
- /package/dist/{chunk-YYOMGOHB.js.map → chunk-BOK6BN5M.js.map} +0 -0
- /package/dist/{chunk-OH7QLI6W.js.map → chunk-BPPUH5JM.js.map} +0 -0
- /package/dist/{chunk-EV76IZ37.js.map → chunk-BTI4C4MA.js.map} +0 -0
- /package/dist/{chunk-O6ZSWHVR.js.map → chunk-C2KE5DMD.js.map} +0 -0
- /package/dist/{chunk-47F6WWLQ.js.map → chunk-C73N66PR.js.map} +0 -0
- /package/dist/{chunk-3XIWW4TB.js.map → chunk-CGJYL633.js.map} +0 -0
- /package/dist/{chunk-WRT7GJ2D.js.map → chunk-CIWMTNHW.js.map} +0 -0
- /package/dist/{chunk-O55EKIEC.js.map → chunk-D3ZKHLN2.js.map} +0 -0
- /package/dist/{chunk-CD4BCCNP.js.map → chunk-DJ7WVEWX.js.map} +0 -0
- /package/dist/{chunk-RO7RUBLD.js.map → chunk-EFL24KOI.js.map} +0 -0
- /package/dist/{chunk-4CJTQHEG.js.map → chunk-ESLOLCD4.js.map} +0 -0
- /package/dist/{chunk-Z4Q6USKJ.js.map → chunk-ETBVCRVL.js.map} +0 -0
- /package/dist/{chunk-VILPVT4S.js.map → chunk-EZ47HOJ6.js.map} +0 -0
- /package/dist/{chunk-EFYN5I7Z.js.map → chunk-F4QFU5IZ.js.map} +0 -0
- /package/dist/{chunk-SHUBTAY2.js.map → chunk-FMKILENU.js.map} +0 -0
- /package/dist/{chunk-YLXETSAH.js.map → chunk-GCZJ7XRI.js.map} +0 -0
- /package/dist/{chunk-QFEDITXM.js.map → chunk-JNDMIAJ4.js.map} +0 -0
- /package/dist/{chunk-E5PPE4VA.js.map → chunk-K2VRKCPA.js.map} +0 -0
- /package/dist/{chunk-MKRYQROF.js.map → chunk-LGEHAQHK.js.map} +0 -0
- /package/dist/{chunk-XYTQJD7F.js.map → chunk-LMJVP5CM.js.map} +0 -0
- /package/dist/{chunk-JC36FPQ5.js.map → chunk-LPV72KRQ.js.map} +0 -0
- /package/dist/{chunk-2LCRG2YV.js.map → chunk-M2XR6B6V.js.map} +0 -0
- /package/dist/{chunk-VPQFK63H.js.map → chunk-MH632KPO.js.map} +0 -0
- /package/dist/{chunk-ZU5JDLOH.js.map → chunk-MMRTPH3J.js.map} +0 -0
- /package/dist/{chunk-ZZ67UTQX.js.map → chunk-MUX7IL77.js.map} +0 -0
- /package/dist/{chunk-RYWROSL5.js.map → chunk-NAQ4A5IX.js.map} +0 -0
- /package/dist/{chunk-TVIVY7KU.js.map → chunk-NED7IR7B.js.map} +0 -0
- /package/dist/{chunk-ADYP7HXE.js.map → chunk-NHVAX7MH.js.map} +0 -0
- /package/dist/{chunk-TG3R2SID.js.map → chunk-NMGHGTVR.js.map} +0 -0
- /package/dist/{chunk-NGHDW6UU.js.map → chunk-NUQEREIP.js.map} +0 -0
- /package/dist/{chunk-5IIQU4JZ.js.map → chunk-OB72KNMF.js.map} +0 -0
- /package/dist/{chunk-7L3TODI7.js.map → chunk-OJXJWNFY.js.map} +0 -0
- /package/dist/{chunk-EURPSF4Y.js.map → chunk-PH5ZQM35.js.map} +0 -0
- /package/dist/{chunk-BMWMTAQF.js.map → chunk-PRAUN6PN.js.map} +0 -0
- /package/dist/{chunk-RWGRSKN5.js.map → chunk-PT4DZDJA.js.map} +0 -0
- /package/dist/{chunk-7EPY3IQL.js.map → chunk-PZJQ46DH.js.map} +0 -0
- /package/dist/{chunk-AH637HZ5.js.map → chunk-QDESY76F.js.map} +0 -0
- /package/dist/{chunk-I6QGI65T.js.map → chunk-QRMG4HK4.js.map} +0 -0
- /package/dist/{chunk-ZU3NJNO7.js.map → chunk-QSF5SJAM.js.map} +0 -0
- /package/dist/{chunk-5IYOKDM4.js.map → chunk-R23JWJD3.js.map} +0 -0
- /package/dist/{chunk-LJQFJF4J.js.map → chunk-RM2YUFH6.js.map} +0 -0
- /package/dist/{chunk-6QMWECO5.js.map → chunk-SGQPR5SX.js.map} +0 -0
- /package/dist/{chunk-ZGFLIK5H.js.map → chunk-SPAKBUJY.js.map} +0 -0
- /package/dist/{chunk-ER7XANP3.js.map → chunk-SQ3GEIEL.js.map} +0 -0
- /package/dist/{chunk-5UCMYPVF.js.map → chunk-SVT5IA2H.js.map} +0 -0
- /package/dist/{chunk-FRSHF6FU.js.map → chunk-TJ4F6H4H.js.map} +0 -0
- /package/dist/{chunk-R6HMAIKM.js.map → chunk-TOSPNM4T.js.map} +0 -0
- /package/dist/{chunk-NJJMVS5Y.js.map → chunk-TRQE3RYR.js.map} +0 -0
- /package/dist/{chunk-RMXS4I66.js.map → chunk-TVDJEUV5.js.map} +0 -0
- /package/dist/{chunk-PLCYKAWH.js.map → chunk-TYRWSE4B.js.map} +0 -0
- /package/dist/{chunk-IQWUACVG.js.map → chunk-UHMHJX5G.js.map} +0 -0
- /package/dist/{chunk-YODUDA6R.js.map → chunk-UN4RVUG2.js.map} +0 -0
- /package/dist/{chunk-KZSGBWOJ.js.map → chunk-V2KGDESV.js.map} +0 -0
- /package/dist/{chunk-E6Z4FBX6.js.map → chunk-V2QSNFCT.js.map} +0 -0
- /package/dist/{chunk-N74SGESS.js.map → chunk-WKSQTMU7.js.map} +0 -0
- /package/dist/{chunk-RIZ5NUNN.js.map → chunk-WQ4XKIOX.js.map} +0 -0
- /package/dist/{chunk-QEYTRFR3.js.map → chunk-X26YOFGS.js.map} +0 -0
- /package/dist/{chunk-ITHZFHST.js.map → chunk-X2BVB74H.js.map} +0 -0
- /package/dist/{chunk-GNXGJBCA.js.map → chunk-XIYLWEXC.js.map} +0 -0
- /package/dist/{chunk-IBJ6U753.js.map → chunk-XKZBZOKV.js.map} +0 -0
- /package/dist/{chunk-7O2MC42R.js.map → chunk-XNRLRNQF.js.map} +0 -0
- /package/dist/{chunk-65AUYLU3.js.map → chunk-XPXHQI6N.js.map} +0 -0
- /package/dist/{chunk-JBCCWVU2.js.map → chunk-XRDYRWZI.js.map} +0 -0
- /package/dist/{chunk-2JSDPUKD.js.map → chunk-YMFPFXEX.js.map} +0 -0
- /package/dist/{chunk-645WOU5I.js.map → chunk-ZDZJNXL6.js.map} +0 -0
- /package/dist/{chunk-6TCXIRP5.js.map → chunk-ZKE4CO6B.js.map} +0 -0
- /package/dist/{chunk-KEPXC4KN.js.map → chunk-ZSE774RV.js.map} +0 -0
- /package/dist/{contradiction-scan-P2MHYFVG.js.map → contradiction-scan-IBPYGQZF.js.map} +0 -0
- /package/dist/{resolution-N7H3F2E4.js.map → resolution-OVVIF7ZY.js.map} +0 -0
- /package/dist/{state-store-I5HLZTZT.js.map → state-store-EEAX2MBS.js.map} +0 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import test from "node:test";
|
|
3
5
|
|
|
4
6
|
import type { Orchestrator } from "./orchestrator.js";
|
|
5
7
|
import type { StorageManager } from "./storage.js";
|
|
6
8
|
import type { PluginConfig } from "./types.js";
|
|
7
9
|
import type { SealedMemoryEnvelope } from "./write-envelope.js";
|
|
10
|
+
import { withHeldFileLock } from "./utils/serialize-mutations.js";
|
|
8
11
|
import {
|
|
9
12
|
InlineExplicitCaptureProcessor,
|
|
10
13
|
parseInlineExplicitCaptureNotes,
|
|
@@ -67,6 +70,7 @@ function createInlineCaptureProcessorProbe(
|
|
|
67
70
|
failPrimaryWrites?: boolean;
|
|
68
71
|
duplicateReadDelayMs?: number;
|
|
69
72
|
lockBusyAttempts?: number;
|
|
73
|
+
peerFactCommitAfterFirstReadAll?: boolean;
|
|
70
74
|
} = {}
|
|
71
75
|
) {
|
|
72
76
|
const envelopes: SealedMemoryEnvelope[] = [];
|
|
@@ -86,19 +90,38 @@ function createInlineCaptureProcessorProbe(
|
|
|
86
90
|
let nextId = 1;
|
|
87
91
|
let writeTail = Promise.resolve();
|
|
88
92
|
let lockBusyAttemptsRemaining = options.lockBusyAttempts ?? 0;
|
|
93
|
+
let peerCommitted = false;
|
|
94
|
+
const peerFactContent = "A concurrent writer committed this fact before the capture could.";
|
|
95
|
+
// #3354: the dedup window takes a real per-dir file lock, so the fake needs
|
|
96
|
+
// a writable per-process dir even though its reads/writes stay in memory.
|
|
97
|
+
const storageDir = path.join(os.tmpdir(), `remnic-explicit-capture-probe-${process.pid}`);
|
|
89
98
|
const storage = {
|
|
99
|
+
dir: storageDir,
|
|
90
100
|
readAllMemories: async () => {
|
|
91
101
|
readAllCalls += 1;
|
|
92
102
|
activeReadAllCalls += 1;
|
|
93
103
|
maxConcurrentReadAllCalls = Math.max(maxConcurrentReadAllCalls, activeReadAllCalls);
|
|
104
|
+
// A read reflects the corpus as it was when the read began, so a write
|
|
105
|
+
// that lands during the delay is invisible to this caller.
|
|
106
|
+
const snapshot = memories.map((memory) => ({
|
|
107
|
+
...memory,
|
|
108
|
+
frontmatter: { ...memory.frontmatter },
|
|
109
|
+
}));
|
|
94
110
|
if (options.duplicateReadDelayMs !== undefined) {
|
|
95
111
|
await new Promise((resolve) => setTimeout(resolve, options.duplicateReadDelayMs));
|
|
96
112
|
}
|
|
97
113
|
activeReadAllCalls -= 1;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
114
|
+
// Simulate a concurrent extraction writer committing between this read
|
|
115
|
+
// and a later one: the caller sees the snapshot above without the peer
|
|
116
|
+
// row, while subsequent reads include it.
|
|
117
|
+
if (options.peerFactCommitAfterFirstReadAll && !peerCommitted) {
|
|
118
|
+
peerCommitted = true;
|
|
119
|
+
memories.push({
|
|
120
|
+
frontmatter: { id: `peer-fact-${nextId++}`, category: "fact", tags: [] },
|
|
121
|
+
content: peerFactContent,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return snapshot;
|
|
102
125
|
},
|
|
103
126
|
readAllColdMemories: async () =>
|
|
104
127
|
coldMemories.map((memory) => ({
|
|
@@ -140,12 +163,9 @@ function createInlineCaptureProcessorProbe(
|
|
|
140
163
|
release();
|
|
141
164
|
}
|
|
142
165
|
},
|
|
143
|
-
|
|
144
|
-
? {
|
|
145
|
-
|
|
146
|
-
isFactContentHashAuthoritative: () => true,
|
|
147
|
-
}
|
|
148
|
-
: {}),
|
|
166
|
+
lookupContentHash: options.authoritativeFactHashMiss
|
|
167
|
+
? async () => ({ present: false, authoritative: true })
|
|
168
|
+
: undefined,
|
|
149
169
|
...(options.tombstoneBlockedCaptureIndexHit !== undefined
|
|
150
170
|
? {
|
|
151
171
|
checkTombstoneBlockedExplicitCapture: async (content: string, category: string, sourceConnector?: string) => {
|
|
@@ -279,6 +299,43 @@ test("persistExplicitCapture serializes duplicate checks across concurrent calls
|
|
|
279
299
|
assert.equal(probe.lifecycleEvents.length, 1);
|
|
280
300
|
});
|
|
281
301
|
|
|
302
|
+
test("persistExplicitCapture keeps the duplicate gate and the write atomic for non-fact categories (#3354)", async () => {
|
|
303
|
+
const probe = createInlineCaptureProcessorProbe({ duplicateReadDelayMs: 15 });
|
|
304
|
+
const candidate = {
|
|
305
|
+
content: "Concurrent preference captures must produce one durable capture.",
|
|
306
|
+
category: "preference" as const,
|
|
307
|
+
confidence: 0.9,
|
|
308
|
+
tags: [],
|
|
309
|
+
};
|
|
310
|
+
const [first, second] = await Promise.all([
|
|
311
|
+
persistExplicitCapture(probe.orchestrator, candidate, "memory_store"),
|
|
312
|
+
persistExplicitCapture(probe.orchestrator, candidate, "memory_store"),
|
|
313
|
+
]);
|
|
314
|
+
assert.equal(Number(first.duplicateOf !== undefined) + Number(second.duplicateOf !== undefined), 1);
|
|
315
|
+
assert.equal(probe.envelopes.length, 1);
|
|
316
|
+
assert.equal(probe.lifecycleEvents.length, 1);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("explicit capture suppresses a fact a concurrent writer commits between the gate and the write", async () => {
|
|
320
|
+
const probe = createInlineCaptureProcessorProbe({ peerFactCommitAfterFirstReadAll: true });
|
|
321
|
+
const candidate = validateExplicitCaptureInput({
|
|
322
|
+
content: "A concurrent writer committed this fact before the capture could.",
|
|
323
|
+
category: "fact",
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const result = await persistExplicitCapture(probe.orchestrator, candidate, "memory_store");
|
|
327
|
+
|
|
328
|
+
// The peer row committed between the persist-time gate and the dedup
|
|
329
|
+
// window, so the capture must report the raced copy instead of writing a
|
|
330
|
+
// second active fact (#3354).
|
|
331
|
+
assert.equal(result.id, "peer-fact-1");
|
|
332
|
+
assert.equal(result.duplicateOf, "peer-fact-1");
|
|
333
|
+
assert.equal(probe.envelopes.length, 0, "no second sealed write of the raced fact");
|
|
334
|
+
assert.equal(probe.lifecycleEvents.length, 0);
|
|
335
|
+
const active = probe.memories.filter((memory) => (memory.frontmatter.status ?? "active") === "active");
|
|
336
|
+
assert.equal(active.length, 1);
|
|
337
|
+
});
|
|
338
|
+
|
|
282
339
|
test("inline capture processor hashes the effective authorized input for replay dedupe", async () => {
|
|
283
340
|
const probe = createInlineCaptureProcessorProbe({ tombstoneBlocked: true });
|
|
284
341
|
const request = {
|
|
@@ -403,6 +460,40 @@ test("inline capture processor queues invalid complete markup and never leaves i
|
|
|
403
460
|
assert.equal(probe.envelopes.length, 1);
|
|
404
461
|
});
|
|
405
462
|
|
|
463
|
+
test("an inline fact capture that cannot take the dedup window fails closed and stays retryable (#3354)", async () => {
|
|
464
|
+
const probe = createInlineCaptureProcessorProbe();
|
|
465
|
+
const request = {
|
|
466
|
+
captureMode: "hybrid" as const,
|
|
467
|
+
content: [
|
|
468
|
+
"<memory_note>",
|
|
469
|
+
"content: A contended fact capture must stay retryable.",
|
|
470
|
+
"category: fact",
|
|
471
|
+
"</memory_note>",
|
|
472
|
+
].join("\n"),
|
|
473
|
+
dedupeKeys: ["window-contention-1"],
|
|
474
|
+
namespace: "principal-project",
|
|
475
|
+
namespacePreResolved: true,
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// A second process holding fact-dedup.lock: the window waits its bounded
|
|
479
|
+
// 2s, then throws instead of writing unlocked.
|
|
480
|
+
const contended = await withHeldFileLock(
|
|
481
|
+
path.join(probe.storage.dir, "fact-dedup.lock"),
|
|
482
|
+
{ staleMs: 60_000, maxWaitMs: 1_000 },
|
|
483
|
+
async (acquired) => {
|
|
484
|
+
assert.ok(acquired, "the test must hold the window lock");
|
|
485
|
+
return await probe.processor.process(request);
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
assert.equal(contended.failed, 1);
|
|
489
|
+
assert.equal(probe.envelopes.length, 0);
|
|
490
|
+
|
|
491
|
+
// The failed delivery left its dedupe key unremembered, so the replay commits.
|
|
492
|
+
const replay = await probe.processor.process(request);
|
|
493
|
+
assert.equal(replay.accepted, 1);
|
|
494
|
+
assert.equal(probe.envelopes.length, 1);
|
|
495
|
+
});
|
|
496
|
+
|
|
406
497
|
test("inline capture review fallback serializes duplicate checks across processor instances", async () => {
|
|
407
498
|
const probe = createInlineCaptureProcessorProbe({ duplicateReadDelayMs: 15 });
|
|
408
499
|
const secondProcessor = new InlineExplicitCaptureProcessor(probe.orchestrator, {
|
|
@@ -1117,13 +1208,14 @@ test("explicit capture keeps the authoritative fact-hash miss fast path", async
|
|
|
1117
1208
|
const result = await persistExplicitCapture(probe.orchestrator, candidate, "memory_store");
|
|
1118
1209
|
|
|
1119
1210
|
assert.equal(result.duplicateOf, undefined);
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1211
|
+
// The gate probes once outside the window and the in-window recheck
|
|
1212
|
+
// (#3354) probes again; both take the authoritative-miss fast path.
|
|
1213
|
+
const blockedProbe = {
|
|
1214
|
+
content: candidate.content,
|
|
1215
|
+
category: "fact",
|
|
1216
|
+
sourceConnector: undefined,
|
|
1217
|
+
};
|
|
1218
|
+
assert.deepEqual(probe.blockedIndexArguments(), [blockedProbe, blockedProbe]);
|
|
1127
1219
|
assert.equal(probe.envelopes.length, 1);
|
|
1128
1220
|
assert.equal(probe.readAllCalls(), 0);
|
|
1129
1221
|
});
|
package/src/explicit-capture.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { composeMemoryEnvelope, TAG_LIMITS } from "./write-envelope.js";
|
|
2
|
+
import type { SealedMemoryEnvelope } from "./write-envelope.js";
|
|
3
|
+
import { persistUnderFactDedupWindow } from "./orchestration/fact-dedup-window.js";
|
|
2
4
|
import { resolveNamespaceCapabilities } from "./capabilities.js";
|
|
3
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
6
|
import type { Orchestrator } from "./orchestrator.js";
|
|
@@ -419,19 +421,18 @@ async function findDuplicateExplicitCapture(
|
|
|
419
421
|
// authoritative miss can therefore skip the corpus scan only after the
|
|
420
422
|
// targeted blocked-row index reports a miss and remains authoritative.
|
|
421
423
|
let authoritativeFactHashMiss = false;
|
|
422
|
-
if (candidate.category === "fact"
|
|
424
|
+
if (candidate.category === "fact") {
|
|
423
425
|
try {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
authoritativeFactHashMiss = authoritative;
|
|
426
|
+
// One lookup returns the MISS together with the authority behind it, so
|
|
427
|
+
// a stale MISS is never paired with authority from another moment
|
|
428
|
+
// (#3312, #3354 — each separate call may rebuild the index).
|
|
429
|
+
const lookup = await storage.lookupContentHash(sanitizeMemoryContent(candidate.content).text);
|
|
430
|
+
if (!lookup.present && lookup.authoritative) {
|
|
431
|
+
authoritativeFactHashMiss = true;
|
|
431
432
|
}
|
|
432
433
|
} catch (err) {
|
|
433
434
|
// Fail open: hash indexes are only an optimization, so fall back to the
|
|
434
|
-
// full corpus scan when
|
|
435
|
+
// full corpus scan when the lookup cannot answer.
|
|
435
436
|
void err;
|
|
436
437
|
}
|
|
437
438
|
}
|
|
@@ -503,18 +504,55 @@ async function findDuplicateExplicitCapture(
|
|
|
503
504
|
}
|
|
504
505
|
: null;
|
|
505
506
|
}
|
|
507
|
+
type PersistedExplicitCapture = { id: string; duplicateOf?: string; tombstoneBlocked?: boolean };
|
|
508
|
+
type RacedDuplicate = { match: { id: string; tombstoneBlocked: boolean } | null };
|
|
509
|
+
|
|
510
|
+
function duplicatePersistResult(duplicate: { id: string; tombstoneBlocked: boolean }): PersistedExplicitCapture {
|
|
511
|
+
return duplicate.tombstoneBlocked
|
|
512
|
+
? { id: duplicate.id, duplicateOf: duplicate.id, tombstoneBlocked: true }
|
|
513
|
+
: { id: duplicate.id, duplicateOf: duplicate.id };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function persistSealedExplicitCapture(
|
|
517
|
+
storage: StorageManager,
|
|
518
|
+
captureEnvelope: SealedMemoryEnvelope,
|
|
519
|
+
candidate: ValidExplicitCapture,
|
|
520
|
+
source: ExplicitCaptureSource
|
|
521
|
+
): Promise<PersistedExplicitCapture> {
|
|
522
|
+
const { id, duplicateOf, tombstoneBlocked } = await storage.writeSealedMemory(captureEnvelope);
|
|
523
|
+
if (duplicateOf) return { id, duplicateOf, tombstoneBlocked };
|
|
524
|
+
// #1522: catalog touch handled at the storage chokepoint — the StorageManager's
|
|
525
|
+
// post-write hook records the namespace touch automatically.
|
|
526
|
+
|
|
527
|
+
const created = new Date().toISOString();
|
|
528
|
+
const event: MemoryLifecycleEvent = {
|
|
529
|
+
eventId: `mle-${randomUUID()}`,
|
|
530
|
+
memoryId: id,
|
|
531
|
+
eventType: tombstoneBlocked ? "explicit_capture_queued" : "explicit_capture_accepted",
|
|
532
|
+
timestamp: created,
|
|
533
|
+
actor: explicitCaptureActor(source),
|
|
534
|
+
reasonCode: candidate.sourceReason,
|
|
535
|
+
ruleVersion: "explicit-capture.v1",
|
|
536
|
+
};
|
|
537
|
+
await storage.appendMemoryLifecycleEvents([event]);
|
|
538
|
+
|
|
539
|
+
return { id, duplicateOf, tombstoneBlocked };
|
|
540
|
+
}
|
|
541
|
+
|
|
506
542
|
async function persistExplicitCaptureUnlocked(
|
|
507
543
|
orchestrator: Orchestrator,
|
|
508
544
|
candidate: ValidExplicitCapture,
|
|
509
545
|
source: ExplicitCaptureSource,
|
|
510
546
|
resolvedNamespace: string | undefined,
|
|
511
547
|
storage: StorageManager
|
|
512
|
-
): Promise<
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
548
|
+
): Promise<PersistedExplicitCapture> {
|
|
549
|
+
const isFact = candidate.category === "fact";
|
|
550
|
+
// Facts take a cheap early exit here and re-check inside the dedup window.
|
|
551
|
+
// Every other category checks only under the capture lock, so its gate and
|
|
552
|
+
// its write stay one atomic step per dedup key (#3354).
|
|
553
|
+
if (isFact) {
|
|
554
|
+
const duplicate = await findDuplicateExplicitCapture(orchestrator, resolvedNamespace, candidate, storage);
|
|
555
|
+
if (duplicate) return duplicatePersistResult(duplicate);
|
|
518
556
|
}
|
|
519
557
|
|
|
520
558
|
// #1645 (review thread yG-): surface the tombstone block so callers
|
|
@@ -534,39 +572,54 @@ async function persistExplicitCaptureUnlocked(
|
|
|
534
572
|
},
|
|
535
573
|
{ source: source === "inline" ? "explicit-inline" : "explicit" }
|
|
536
574
|
);
|
|
537
|
-
const { id, duplicateOf, tombstoneBlocked } = await storage.writeSealedMemory(captureEnvelope);
|
|
538
|
-
if (duplicateOf) return { id, duplicateOf, tombstoneBlocked };
|
|
539
|
-
// #1522: catalog touch handled at the storage chokepoint — the StorageManager's
|
|
540
|
-
// post-write hook records the namespace touch automatically.
|
|
541
575
|
|
|
542
|
-
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
576
|
+
// Test fakes may omit the lock; the real StorageManager always has it.
|
|
577
|
+
const withCaptureLock = <T>(run: () => Promise<T>): Promise<T> =>
|
|
578
|
+
storage.withTombstoneBlockedCaptureWriteLock?.(
|
|
579
|
+
run,
|
|
580
|
+
buildExplicitCaptureDedupKey(candidate.content, candidate.category, candidate.sourceConnector)
|
|
581
|
+
) ?? run();
|
|
582
|
+
const writeSealed = (): Promise<PersistedExplicitCapture> =>
|
|
583
|
+
persistSealedExplicitCapture(storage, captureEnvelope, candidate, source);
|
|
584
|
+
|
|
585
|
+
if (!isFact) {
|
|
586
|
+
return await withCaptureLock(async () => {
|
|
587
|
+
const duplicate = await findDuplicateExplicitCapture(orchestrator, resolvedNamespace, candidate, storage);
|
|
588
|
+
return duplicate ? duplicatePersistResult(duplicate) : await writeSealed();
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
// #3354: fact captures commit inside the per-dir fact-dedup window so a
|
|
592
|
+
// concurrent extraction write of the same fact is caught by the recheck
|
|
593
|
+
// instead of racing the persist-time gate. The tombstone capture write
|
|
594
|
+
// lock stays INSIDE the window: every other window writer (extraction,
|
|
595
|
+
// promotion) acquires the tombstone lock only inside writeMemory, so
|
|
596
|
+
// taking it here first would order tombstone→window against their
|
|
597
|
+
// window→tombstone and stall the capture on the 60s write-lock backoff.
|
|
598
|
+
const raced: RacedDuplicate = { match: null };
|
|
599
|
+
const outcome = await persistUnderFactDedupWindow(
|
|
600
|
+
storage.dir,
|
|
601
|
+
async () => {
|
|
602
|
+
raced.match = await findDuplicateExplicitCapture(orchestrator, resolvedNamespace, candidate, storage);
|
|
603
|
+
return raced.match !== null;
|
|
604
|
+
},
|
|
605
|
+
() => withCaptureLock(writeSealed)
|
|
606
|
+
);
|
|
607
|
+
if (!outcome.suppressed) return outcome.result;
|
|
608
|
+
if (!raced.match) throw new Error("explicit capture suppressed by the dedup window without a matched duplicate");
|
|
609
|
+
log.info(`dedup: raced explicit capture suppressed in fact-dedup window for ${storage.dir}`);
|
|
610
|
+
return duplicatePersistResult(raced.match);
|
|
555
611
|
}
|
|
556
612
|
|
|
557
613
|
export async function persistExplicitCapture(
|
|
558
614
|
orchestrator: Orchestrator,
|
|
559
615
|
candidate: ValidExplicitCapture,
|
|
560
616
|
source: ExplicitCaptureSource
|
|
561
|
-
): Promise<
|
|
617
|
+
): Promise<PersistedExplicitCapture> {
|
|
562
618
|
const resolvedNamespace = candidate.namespacePreResolved
|
|
563
619
|
? asTrimmed(candidate.namespace)
|
|
564
620
|
: resolveExplicitCaptureNamespace(orchestrator, candidate.namespace);
|
|
565
621
|
const storage = await orchestrator.getStorage(resolvedNamespace);
|
|
566
|
-
|
|
567
|
-
if (typeof storage.withTombstoneBlockedCaptureWriteLock !== "function") return await persist();
|
|
568
|
-
const lockIdentity = buildExplicitCaptureDedupKey(candidate.content, candidate.category, candidate.sourceConnector);
|
|
569
|
-
return await storage.withTombstoneBlockedCaptureWriteLock(persist, lockIdentity);
|
|
622
|
+
return await persistExplicitCaptureUnlocked(orchestrator, candidate, source, resolvedNamespace, storage);
|
|
570
623
|
}
|
|
571
624
|
|
|
572
625
|
function buildExplicitCaptureReviewContent(input: ExplicitCaptureInput, reason: string): string {
|
|
@@ -459,6 +459,26 @@ test("a negator in a claim clause that shares only the subject does not keep the
|
|
|
459
459
|
assert.equal(grounded.facts.length, 0);
|
|
460
460
|
});
|
|
461
461
|
|
|
462
|
+
test("a negated clause about another subject does not keep the quote's denial (#3356)", () => {
|
|
463
|
+
const source =
|
|
464
|
+
"Alice did not approve the budget. Bob did not approve the budget either. Carol asked about the plan.";
|
|
465
|
+
const quote = "Alice did not approve the budget.";
|
|
466
|
+
const kept = [
|
|
467
|
+
"Alice did not approve the budget.",
|
|
468
|
+
"The budget was not approved by Alice.",
|
|
469
|
+
"The budget was not approved.",
|
|
470
|
+
];
|
|
471
|
+
const grounded = filterExtractionResultBySource(
|
|
472
|
+
resultOf([
|
|
473
|
+
{ content: "Alice approved the budget, but Bob did not approve the budget.", quote },
|
|
474
|
+
...kept.map((content) => ({ content, quote })),
|
|
475
|
+
]),
|
|
476
|
+
source,
|
|
477
|
+
source,
|
|
478
|
+
);
|
|
479
|
+
assert.deepEqual(keptContents(grounded), kept);
|
|
480
|
+
});
|
|
481
|
+
|
|
462
482
|
test("a claim that inverts a denial verb in the quote is rejected", () => {
|
|
463
483
|
const source = "Notes: Alice denied approving the budget. Bob reviewed it.";
|
|
464
484
|
const grounded = filterExtractionResultBySource(
|
|
@@ -192,17 +192,19 @@ function negatedScope(clause: string): string {
|
|
|
192
192
|
return match ? clause.slice(match.index + match[0].length) : clause;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
/** The words before a clause's first negator, empty when it has none. */
|
|
196
|
+
function textBeforeNegator(clause: string): string {
|
|
197
|
+
const match = NEGATOR_PATTERN.exec(clause.replace(/[\u2018\u2019]/gu, "'"));
|
|
198
|
+
return match ? clause.slice(0, match.index) : "";
|
|
199
|
+
}
|
|
200
|
+
|
|
195
201
|
/**
|
|
196
|
-
* True when the claim asserts what a quote clause denies or does not assert
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* excluded", or "Alice reviewed it, but she did not approve it"). A negator in
|
|
203
|
-
* a clause about something else does not count, so a claim that keeps one
|
|
204
|
-
* quote negation can still misstate another quote clause, and a claim clause
|
|
205
|
-
* that shares only the subject does not satisfy the check.
|
|
202
|
+
* True when the claim asserts what a quote clause denies or does not assert.
|
|
203
|
+
* A quote denial is kept only by a claim clause that shares what the negator
|
|
204
|
+
* governs and does not name a different subject, so a negator about Bob does
|
|
205
|
+
* not keep a denial about Alice. A clause that names no subject of its own
|
|
206
|
+
* still counts, which keeps a pronoun clause and a subject-less passive. A
|
|
207
|
+
* quote clause with no subject of its own keeps the clause-level check.
|
|
206
208
|
*/
|
|
207
209
|
function misstatesQuote(content: string, quote: string): boolean {
|
|
208
210
|
const claimClauses = splitClaimClauses(content);
|
|
@@ -217,8 +219,16 @@ function misstatesQuote(content: string, quote: string): boolean {
|
|
|
217
219
|
claimClauses.filter((claimClause) => clauseOverlap(tokens, claimClause).shared > 0);
|
|
218
220
|
if (needsNegation) {
|
|
219
221
|
const scopeTokens = [...tokenize(negatedScope(quoteClause))];
|
|
222
|
+
const subjectTokens = [...tokenize(textBeforeNegator(quoteClause))];
|
|
223
|
+
const scopeHas = new Set(scopeTokens);
|
|
220
224
|
const related = clausesSharing(scopeTokens.length > 0 ? scopeTokens : clauseTokens);
|
|
221
|
-
|
|
225
|
+
const keepsDenial = related.some((text) => {
|
|
226
|
+
if (!hasNegation(text) && !EXCLUSION_PATTERN.test(text)) return false;
|
|
227
|
+
if (subjectTokens.length === 0 || clauseOverlap(subjectTokens, text).shared > 0) return true;
|
|
228
|
+
const ownSubject = [...tokenize(textBeforeNegator(text))].filter((token) => !scopeHas.has(token));
|
|
229
|
+
return ownSubject.length === 0;
|
|
230
|
+
});
|
|
231
|
+
if (!keepsDenial) return true;
|
|
222
232
|
}
|
|
223
233
|
return needsReporting
|
|
224
234
|
&& !clausesSharing(clauseTokens).some((text) => hasNonAssertiveReporting(text) || text.trim().endsWith("?"));
|
|
@@ -6,6 +6,8 @@ import path from "node:path";
|
|
|
6
6
|
import { StorageManager } from "./storage.js";
|
|
7
7
|
import { withHeldFileLock } from "./utils/serialize-mutations.js";
|
|
8
8
|
import { confirmActiveFactDuplicate, persistUnderFactDedupWindow } from "./orchestration/fact-dedup-window.js";
|
|
9
|
+
import { persistExplicitCapture } from "./explicit-capture.js";
|
|
10
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
9
11
|
|
|
10
12
|
const SKY_OPTS = {
|
|
11
13
|
category: "fact",
|
|
@@ -185,6 +187,41 @@ test("two managers on one directory commit the same fact through the window exac
|
|
|
185
187
|
}
|
|
186
188
|
});
|
|
187
189
|
|
|
190
|
+
test("explicit capture and an extraction write race the same new fact to exactly one active copy (#3354)", async () => {
|
|
191
|
+
const { storage, cleanup } = await makeStorage();
|
|
192
|
+
try {
|
|
193
|
+
const content = "An extraction run and an explicit capture hold this new fact.";
|
|
194
|
+
// SAFETY: persistExplicitCapture reads only getStorage here (namespacePreResolved skips the config-based namespace resolver); the prototype stub exists for the type only.
|
|
195
|
+
const orchestrator = Object.create(Orchestrator.prototype) as Orchestrator;
|
|
196
|
+
orchestrator.getStorage = async () => storage;
|
|
197
|
+
const candidate = { content, category: "fact" as const, confidence: 0.9, tags: [], namespacePreResolved: true };
|
|
198
|
+
const extractionCommit = persistUnderFactDedupWindow(
|
|
199
|
+
storage.dir,
|
|
200
|
+
() => confirmActiveFactDuplicate(storage, { ...SKY_OPTS, contentHashDedupKey: content }),
|
|
201
|
+
() => storage.writeMemory("fact", content),
|
|
202
|
+
);
|
|
203
|
+
const [capture, extraction] = await Promise.all([
|
|
204
|
+
persistExplicitCapture(orchestrator, candidate, "memory_store"),
|
|
205
|
+
extractionCommit,
|
|
206
|
+
]);
|
|
207
|
+
const stored = await storage.readAllMemories();
|
|
208
|
+
const active = stored.filter(
|
|
209
|
+
(memory) => memory.frontmatter.category === "fact" && (memory.frontmatter.status ?? "active") === "active",
|
|
210
|
+
);
|
|
211
|
+
assert.equal(active.length, 1, `exactly one active copy of the raced fact, got ${active.length}`);
|
|
212
|
+
const winner = active[0];
|
|
213
|
+
assert.ok(winner, "the single active copy must exist");
|
|
214
|
+
assert.ok(winner.content.includes(content));
|
|
215
|
+
if (capture.duplicateOf === undefined) {
|
|
216
|
+
assert.equal(extraction.suppressed, true, "the extraction write is the loser and must be suppressed");
|
|
217
|
+
} else {
|
|
218
|
+
assert.equal(winner.frontmatter.id, capture.duplicateOf, "the capture reports the winning copy");
|
|
219
|
+
}
|
|
220
|
+
} finally {
|
|
221
|
+
await cleanup();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
188
225
|
test("confirmActiveFactDuplicate keeps the authoritative MISS fast path", async () => {
|
|
189
226
|
const { storage, cleanup } = await makeStorage();
|
|
190
227
|
try {
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "./connectors/live/index.js";
|
|
32
32
|
import { abortError } from "./abort-error.js";
|
|
33
33
|
import type { LiveConnectorsConfig } from "./types.js";
|
|
34
|
+
import type { ResolveSecretRefFn } from "./resolve-auth-token.js";
|
|
34
35
|
|
|
35
36
|
export type LiveConnectorSkipReason =
|
|
36
37
|
| "disabled"
|
|
@@ -138,6 +139,7 @@ export async function runLiveConnectorsOnce(options: {
|
|
|
138
139
|
now?: LiveConnectorsNow;
|
|
139
140
|
abortSignal?: AbortSignal;
|
|
140
141
|
definitions?: LiveConnectorDefinition[];
|
|
142
|
+
resolveSecretRef?: ResolveSecretRefFn | null;
|
|
141
143
|
}): Promise<LiveConnectorsRunSummary> {
|
|
142
144
|
const ranAt = resolveNow(options.now);
|
|
143
145
|
const force = options.force === true;
|
|
@@ -228,6 +230,7 @@ export async function runLiveConnectorsOnce(options: {
|
|
|
228
230
|
cursor,
|
|
229
231
|
config: validatedConfig,
|
|
230
232
|
abortSignal: pollAbortSignal,
|
|
233
|
+
resolveSecretRef: options.resolveSecretRef,
|
|
231
234
|
}),
|
|
232
235
|
ingestFn: async (docs) => {
|
|
233
236
|
throwIfAborted(pollAbortSignal);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
import { parseConfig } from "./config.js";
|
|
8
|
+
import {
|
|
9
|
+
builtInLiveConnectorDefinitions,
|
|
10
|
+
runLiveConnectorsOnce,
|
|
11
|
+
} from "./live-connectors-runner.js";
|
|
12
|
+
import {
|
|
13
|
+
GOOGLE_DRIVE_CONNECTOR_ID,
|
|
14
|
+
NOTION_CONNECTOR_ID,
|
|
15
|
+
createGoogleDriveConnector,
|
|
16
|
+
type GoogleDriveClient,
|
|
17
|
+
} from "./connectors/live/index.js";
|
|
18
|
+
import type { LiveConnector } from "./connectors/live/framework.js";
|
|
19
|
+
|
|
20
|
+
const RESOLVED_SECRET = "resolved-drive-secret";
|
|
21
|
+
const SECRET_REF = { source: "keychain", id: "projects/drive-secret" };
|
|
22
|
+
|
|
23
|
+
function driveConfig() {
|
|
24
|
+
return {
|
|
25
|
+
openaiApiKey: "sk-test",
|
|
26
|
+
connectors: {
|
|
27
|
+
googleDrive: {
|
|
28
|
+
enabled: true,
|
|
29
|
+
clientId: "synthetic-client",
|
|
30
|
+
clientSecret: SECRET_REF,
|
|
31
|
+
refreshToken: "synthetic-token",
|
|
32
|
+
},
|
|
33
|
+
notion: { enabled: true, token: "secret_synthetic_notion_token" },
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function notionStub(): LiveConnector {
|
|
39
|
+
return {
|
|
40
|
+
id: NOTION_CONNECTOR_ID,
|
|
41
|
+
displayName: "Notion",
|
|
42
|
+
validateConfig: () => ({}),
|
|
43
|
+
async syncIncremental() {
|
|
44
|
+
return { newDocs: [], nextCursor: { kind: "notion", value: "cursor-1", updatedAt: "2026-11-11T00:00:00.000Z" } };
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
test("a Drive SecretRef resolves through parseConfig and the runner, and stays out of the state file", async () => {
|
|
50
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-drive-secretref-"));
|
|
51
|
+
try {
|
|
52
|
+
const parsed = parseConfig(driveConfig());
|
|
53
|
+
const definitions = builtInLiveConnectorDefinitions(parsed.connectors);
|
|
54
|
+
let seenSecret = "";
|
|
55
|
+
const client: GoogleDriveClient = {
|
|
56
|
+
async getStartPageToken() {
|
|
57
|
+
return { startPageToken: "seed-token" };
|
|
58
|
+
},
|
|
59
|
+
async listChanges() {
|
|
60
|
+
return { changes: [], newStartPageToken: "next-token" };
|
|
61
|
+
},
|
|
62
|
+
async exportFile() {
|
|
63
|
+
return "";
|
|
64
|
+
},
|
|
65
|
+
async getFileMedia() {
|
|
66
|
+
return "";
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
for (const definition of definitions) {
|
|
70
|
+
if (definition.id === GOOGLE_DRIVE_CONNECTOR_ID) {
|
|
71
|
+
definition.createConnector = () =>
|
|
72
|
+
createGoogleDriveConnector({
|
|
73
|
+
clientFactory: async (config) => {
|
|
74
|
+
seenSecret = config.clientSecret;
|
|
75
|
+
return client;
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (definition.id === NOTION_CONNECTOR_ID) {
|
|
80
|
+
definition.createConnector = notionStub;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const summary = await runLiveConnectorsOnce({
|
|
84
|
+
memoryDir,
|
|
85
|
+
connectors: parsed.connectors,
|
|
86
|
+
definitions,
|
|
87
|
+
force: true,
|
|
88
|
+
resolveSecretRef: async () => RESOLVED_SECRET,
|
|
89
|
+
ingestDocuments: async () => {},
|
|
90
|
+
});
|
|
91
|
+
assert.equal(seenSecret, RESOLVED_SECRET);
|
|
92
|
+
const drive = summary.results.find((item) => item.id === GOOGLE_DRIVE_CONNECTOR_ID);
|
|
93
|
+
assert.equal(drive?.ran, true);
|
|
94
|
+
const statePath = path.join(memoryDir, "state", "connectors", `${GOOGLE_DRIVE_CONNECTOR_ID}.json`);
|
|
95
|
+
const stateText = await readFile(statePath, "utf-8");
|
|
96
|
+
assert.equal(stateText.includes(RESOLVED_SECRET), false);
|
|
97
|
+
assert.equal(stateText.includes(SECRET_REF.id), false);
|
|
98
|
+
} finally {
|
|
99
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("a Drive SecretRef with no resolver fails that connector and the others still run", async () => {
|
|
104
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-drive-secretref-"));
|
|
105
|
+
try {
|
|
106
|
+
const parsed = parseConfig(driveConfig());
|
|
107
|
+
const definitions = builtInLiveConnectorDefinitions(parsed.connectors);
|
|
108
|
+
for (const definition of definitions) {
|
|
109
|
+
if (definition.id === NOTION_CONNECTOR_ID) definition.createConnector = notionStub;
|
|
110
|
+
}
|
|
111
|
+
const summary = await runLiveConnectorsOnce({
|
|
112
|
+
memoryDir,
|
|
113
|
+
connectors: parsed.connectors,
|
|
114
|
+
definitions,
|
|
115
|
+
force: true,
|
|
116
|
+
ingestDocuments: async () => {},
|
|
117
|
+
});
|
|
118
|
+
const drive = summary.results.find((item) => item.id === GOOGLE_DRIVE_CONNECTOR_ID);
|
|
119
|
+
const notion = summary.results.find((item) => item.id === NOTION_CONNECTOR_ID);
|
|
120
|
+
assert.match(drive?.error ?? "", /clientSecret is a secret reference but no resolver is configured/);
|
|
121
|
+
assert.equal((drive?.error ?? "").includes(SECRET_REF.id), false);
|
|
122
|
+
assert.equal(notion?.ran, true);
|
|
123
|
+
} finally {
|
|
124
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
});
|
package/src/local-llm-helpers.ts
CHANGED
|
@@ -40,6 +40,18 @@ export function isAbortError(err: unknown): boolean {
|
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/** Caller signal combined with a client-wide shutdown signal, so a daemon
|
|
44
|
+
* stop aborts in-flight requests and retry loops at once — even for
|
|
45
|
+
* callers that pass no signal of their own. */
|
|
46
|
+
export function combineClientAbortSignals(
|
|
47
|
+
caller: AbortSignal | undefined,
|
|
48
|
+
shutdown: AbortSignal | undefined,
|
|
49
|
+
): AbortSignal | undefined {
|
|
50
|
+
if (shutdown === undefined) return caller;
|
|
51
|
+
if (caller === undefined) return shutdown;
|
|
52
|
+
return AbortSignal.any([caller, shutdown]);
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
export function waitForRetryBackoff(backoffMs: number, signal?: AbortSignal): Promise<boolean> {
|
|
44
56
|
return new Promise((resolve) => {
|
|
45
57
|
let settled = false;
|