@remnic/core 9.3.719 → 9.3.721
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-boundary.d.ts +10 -7
- package/dist/access-boundary.js +37 -34
- package/dist/access-cli.js +71 -66
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +11 -8
- package/dist/access-http.js +44 -41
- package/dist/access-mcp.d.ts +11 -8
- package/dist/access-mcp.js +40 -37
- package/dist/access-operations-batch.js +38 -35
- package/dist/access-operations.d.ts +11 -8
- package/dist/access-operations.js +39 -36
- package/dist/access-schema.d.ts +19 -19
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-CZbnt0t2.d.ts → access-service-Dj3I-VBS.d.ts} +3 -3
- package/dist/access-service.d.ts +11 -8
- package/dist/access-service.js +36 -33
- package/dist/access-surface-catalog.d.ts +10 -7
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +4 -2
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/{auto-sync-24NNS3GU.js → auto-sync-F3KCSQFJ.js} +5 -5
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +11 -8
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +12 -10
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/calibration.js +2 -2
- package/dist/capabilities.d.ts +30 -2
- package/dist/capabilities.js +3 -1
- package/dist/{catalog-CEYlMs9X.d.ts → catalog-F-OEVbRQ.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +15 -13
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.d.ts +4 -0
- package/dist/causal-trajectory-graph.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-TH7WMHVK.js → chunk-3NN73CM7.js} +2 -2
- package/dist/{chunk-A5TUPZ3N.js → chunk-4SF232E6.js} +60 -30
- package/dist/chunk-4SF232E6.js.map +1 -0
- package/dist/{chunk-DGTDRIUO.js → chunk-4T3BMHKD.js} +2 -2
- package/dist/{chunk-EH27LHML.js → chunk-5O42UKXI.js} +2 -2
- package/dist/{chunk-KRGC4EDX.js → chunk-6FAMMBQ3.js} +2 -2
- package/dist/{chunk-FEB7W5JG.js → chunk-6OIDDKAA.js} +27 -6
- package/dist/chunk-6OIDDKAA.js.map +1 -0
- package/dist/{chunk-67HG7KFE.js → chunk-6YKIYQU7.js} +2 -2
- package/dist/{chunk-LXSG4L55.js → chunk-7CTNUVHZ.js} +2 -2
- package/dist/{chunk-UCJ5CDTK.js → chunk-A5XNBK2D.js} +22 -14
- package/dist/chunk-A5XNBK2D.js.map +1 -0
- package/dist/{chunk-7EEVXW7X.js → chunk-A7TJZLPK.js} +2 -2
- package/dist/{chunk-P5IPFZVH.js → chunk-B7WISIAJ.js} +5 -5
- package/dist/chunk-BCDKBDSY.js +173 -0
- package/dist/chunk-BCDKBDSY.js.map +1 -0
- package/dist/{chunk-JAKYZGV4.js → chunk-BXEOHV7K.js} +3 -3
- package/dist/chunk-C4X4QGIO.js +60 -0
- package/dist/chunk-C4X4QGIO.js.map +1 -0
- package/dist/{chunk-S3OPGGZB.js → chunk-D7EBTQ24.js} +2 -2
- package/dist/{chunk-IO5NQEGZ.js → chunk-DKT3X5C3.js} +2 -2
- package/dist/{chunk-PBH4JUAB.js → chunk-DVRHLN2O.js} +18 -3
- package/dist/chunk-DVRHLN2O.js.map +1 -0
- package/dist/{chunk-S35FX6MU.js → chunk-EBDA5TYA.js} +2 -2
- package/dist/{chunk-2PTBLS2S.js → chunk-EDOQM4E4.js} +2 -2
- package/dist/{chunk-YTZLBBEB.js → chunk-EEJMD2L2.js} +3 -3
- package/dist/{chunk-YGKUAX2B.js → chunk-EHQ5HO5T.js} +11 -11
- package/dist/chunk-EHQ5HO5T.js.map +1 -0
- package/dist/{chunk-JIZGYQYH.js → chunk-EM2LDXJ2.js} +4 -4
- package/dist/{chunk-YYOK4GVF.js → chunk-EVPNWN7Z.js} +10 -10
- package/dist/{chunk-HLIR3RJ7.js → chunk-EXUCUPV2.js} +2 -2
- package/dist/{chunk-F3PVXKHP.js → chunk-GDUOCHCD.js} +27 -21
- package/dist/chunk-GDUOCHCD.js.map +1 -0
- package/dist/{chunk-L5ASZA2F.js → chunk-GJSL4WG2.js} +17 -2
- package/dist/chunk-GJSL4WG2.js.map +1 -0
- package/dist/{chunk-2SXMCQEQ.js → chunk-GZQXOQQ2.js} +3 -3
- package/dist/{chunk-2KAYTPPT.js → chunk-HCVWF657.js} +12 -12
- package/dist/{chunk-N3DUH6OO.js → chunk-ILFSEIRT.js} +1 -1
- package/dist/chunk-ILFSEIRT.js.map +1 -0
- package/dist/{chunk-4KZGLDJJ.js → chunk-J3JPB7ZI.js} +2 -2
- package/dist/{chunk-3JJWNZTT.js → chunk-L2B3EE7B.js} +102 -42
- package/dist/chunk-L2B3EE7B.js.map +1 -0
- package/dist/{chunk-NFRBADNR.js → chunk-LA3HZKES.js} +610 -369
- package/dist/chunk-LA3HZKES.js.map +1 -0
- package/dist/{chunk-LQ6JI4VH.js → chunk-LD53WPMU.js} +4 -4
- package/dist/{chunk-URM3HW2R.js → chunk-LLUGAZ3B.js} +2 -2
- package/dist/{chunk-RVLB4E6N.js → chunk-MDBZTH7N.js} +5 -32
- package/dist/chunk-MDBZTH7N.js.map +1 -0
- package/dist/{chunk-U5H7WHTS.js → chunk-N77ELJ2D.js} +4 -4
- package/dist/{chunk-2VHMVWPU.js → chunk-NXRUUDUF.js} +2 -2
- package/dist/{chunk-DIFU33GU.js → chunk-OHTOPFRF.js} +2 -2
- package/dist/{chunk-74MYJSKB.js → chunk-ORAK3LNZ.js} +2 -2
- package/dist/{chunk-REWQAFSA.js → chunk-PD42EYQN.js} +2 -2
- package/dist/chunk-PHK3HARR.js +32 -0
- package/dist/chunk-PHK3HARR.js.map +1 -0
- package/dist/{chunk-773DPI6T.js → chunk-PKMQK2WY.js} +4 -4
- package/dist/{chunk-72VDBALG.js → chunk-QD6QP5P6.js} +63 -54
- package/dist/chunk-QD6QP5P6.js.map +1 -0
- package/dist/{chunk-CTOQEZSN.js → chunk-QR67FU5G.js} +2 -2
- package/dist/{chunk-AU7Q3LSC.js → chunk-RKNJBZ55.js} +7 -7
- package/dist/{chunk-YI7RZO7Y.js → chunk-SQNED75R.js} +1 -1
- package/dist/{chunk-5CEJH5ZN.js → chunk-TAPSNIMA.js} +4 -4
- package/dist/{chunk-IF362TF6.js → chunk-TQT3USCG.js} +2 -2
- package/dist/chunk-TQT3USCG.js.map +1 -0
- package/dist/{chunk-5RHM43BJ.js → chunk-TXQCG6SE.js} +3 -3
- package/dist/{chunk-ANI5DUAE.js → chunk-UIXXE6NI.js} +7 -7
- package/dist/{chunk-2COL35PK.js → chunk-UO5ZHIIH.js} +2 -2
- package/dist/{chunk-OJU73BUX.js → chunk-VCLGUIFM.js} +2 -2
- package/dist/{chunk-5J4OL7TJ.js → chunk-VUA4S7P6.js} +4 -4
- package/dist/{chunk-A66YRSOS.js → chunk-WQYSDPMA.js} +2 -2
- package/dist/chunk-XGTBNVIU.js +79 -0
- package/dist/chunk-XGTBNVIU.js.map +1 -0
- package/dist/{chunk-OZ2KQ7HF.js → chunk-YEE5KJQQ.js} +2 -2
- package/dist/chunk-YN2UN7N3.js +217 -0
- package/dist/chunk-YN2UN7N3.js.map +1 -0
- package/dist/{chunk-WRFKZEO6.js → chunk-YQGHW5ML.js} +4 -4
- package/dist/{chunk-APUJIMDM.js → chunk-Z4RU5VPM.js} +2 -2
- package/dist/{chunk-DCWIQFNA.js → chunk-ZUDM75KG.js} +4 -4
- package/dist/{cli-1orcdXOY.d.ts → cli-P_GaYcCY.d.ts} +3 -3
- package/dist/cli.d.ts +9 -6
- package/dist/cli.js +67 -63
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +12 -10
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +12 -10
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +13 -11
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/backend.js +2 -2
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +12 -10
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +11 -8
- package/dist/extraction-faithfulness.d.ts +8 -2
- package/dist/extraction-faithfulness.js +4 -3
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-judge.js +3 -3
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +5 -4
- package/dist/faithfulness-config.d.ts +55 -0
- package/dist/faithfulness-config.js +11 -0
- package/dist/faithfulness-config.js.map +1 -0
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/fallback-llm.js +2 -2
- package/dist/{forget-U66KPDPY.js → forget-OEE5OELK.js} +2 -2
- package/dist/graph-dashboard-diff.d.ts +4 -0
- package/dist/graph-dashboard-key.d.ts +4 -0
- package/dist/graph-dashboard-parser.d.ts +4 -0
- package/dist/{graph-edge-decay-JLOF3YW4.js → graph-edge-decay-QPRJQ7DL.js} +3 -3
- package/dist/graph-edge-reinforcement.d.ts +4 -0
- package/dist/graph-snapshot.d.ts +7 -3
- package/dist/graph-snapshot.js +3 -3
- package/dist/graph.d.ts +7 -1
- package/dist/graph.js +2 -2
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +571 -570
- package/dist/index.js +104 -99
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +4 -4
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +96 -0
- package/dist/local-model-endpoint.js +12 -0
- package/dist/local-model-endpoint.js.map +1 -0
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +12 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -10
- package/dist/maintenance/rebuild-memory-projection.js +13 -11
- package/dist/mcp-memory-inspector-app.d.ts +11 -8
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +22 -20
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +12 -10
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -2
- package/dist/operator-toolkit.js +32 -28
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +14 -12
- package/dist/{orchestrator-kOxredSr.d.ts → orchestrator-f5xv19kp.d.ts} +178 -81
- package/dist/orchestrator.d.ts +8 -5
- package/dist/orchestrator.js +56 -52
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/provenance.js +2 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-planner-llm.js +2 -2
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +18 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +5 -3
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +8 -8
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +14 -14
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-B58v3S-a.d.ts → semantic-consolidation-BQm1e1Pn.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +13 -11
- package/dist/semantic-rule-promotion.js +12 -10
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +12 -10
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +45 -1
- package/dist/storage.js +11 -9
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +1 -1
- package/dist/trust-score-stage.d.ts +178 -0
- package/dist/trust-score-stage.js +18 -0
- package/dist/trust-score-stage.js.map +1 -0
- package/dist/trust-score.d.ts +164 -0
- package/dist/trust-score.js +22 -0
- package/dist/trust-score.js.map +1 -0
- package/dist/{types-ACmDOScJ.d.ts → types-DwO1Ot4n.d.ts} +56 -2
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +12 -10
- package/package.json +2 -2
- package/src/access-service.ts +8 -5
- package/src/capabilities-cluster-b.test.ts +78 -0
- package/src/capabilities.test.ts +11 -1
- package/src/capabilities.ts +58 -0
- package/src/cli/research-status-commands.ts +7 -3
- package/src/cli.ts +11 -6
- package/src/config.ts +60 -30
- package/src/extraction-faithfulness.test.ts +175 -0
- package/src/extraction-faithfulness.ts +53 -1
- package/src/faithfulness-config.test.ts +96 -0
- package/src/faithfulness-config.ts +138 -0
- package/src/graph-snapshot.ts +3 -3
- package/src/graph.ts +8 -7
- package/src/lifecycle/tombstones.test.ts +242 -1
- package/src/lifecycle/tombstones.ts +157 -70
- package/src/local-model-endpoint.test.ts +155 -0
- package/src/local-model-endpoint.ts +189 -0
- package/src/operator-toolkit.ts +24 -7
- package/src/orchestration/tier-migration-coordinator.ts +286 -0
- package/src/orchestrator.ts +331 -222
- package/src/recall-xray.test.ts +62 -0
- package/src/recall-xray.ts +34 -0
- package/src/trust-score-recall-paths.test.ts +157 -0
- package/src/trust-score-stage.test.ts +350 -0
- package/src/trust-score-stage.ts +398 -0
- package/src/trust-score.test.ts +282 -0
- package/src/trust-score.ts +449 -0
- package/src/types.ts +61 -1
- package/dist/chunk-3JJWNZTT.js.map +0 -1
- package/dist/chunk-72VDBALG.js.map +0 -1
- package/dist/chunk-A5TUPZ3N.js.map +0 -1
- package/dist/chunk-F3PVXKHP.js.map +0 -1
- package/dist/chunk-FEB7W5JG.js.map +0 -1
- package/dist/chunk-IF362TF6.js.map +0 -1
- package/dist/chunk-L5ASZA2F.js.map +0 -1
- package/dist/chunk-N3DUH6OO.js.map +0 -1
- package/dist/chunk-NFRBADNR.js.map +0 -1
- package/dist/chunk-PBH4JUAB.js.map +0 -1
- package/dist/chunk-RVLB4E6N.js.map +0 -1
- package/dist/chunk-UCJ5CDTK.js.map +0 -1
- package/dist/chunk-YGKUAX2B.js.map +0 -1
- /package/dist/{auto-sync-24NNS3GU.js.map → auto-sync-F3KCSQFJ.js.map} +0 -0
- /package/dist/{chunk-TH7WMHVK.js.map → chunk-3NN73CM7.js.map} +0 -0
- /package/dist/{chunk-DGTDRIUO.js.map → chunk-4T3BMHKD.js.map} +0 -0
- /package/dist/{chunk-EH27LHML.js.map → chunk-5O42UKXI.js.map} +0 -0
- /package/dist/{chunk-KRGC4EDX.js.map → chunk-6FAMMBQ3.js.map} +0 -0
- /package/dist/{chunk-67HG7KFE.js.map → chunk-6YKIYQU7.js.map} +0 -0
- /package/dist/{chunk-LXSG4L55.js.map → chunk-7CTNUVHZ.js.map} +0 -0
- /package/dist/{chunk-7EEVXW7X.js.map → chunk-A7TJZLPK.js.map} +0 -0
- /package/dist/{chunk-P5IPFZVH.js.map → chunk-B7WISIAJ.js.map} +0 -0
- /package/dist/{chunk-JAKYZGV4.js.map → chunk-BXEOHV7K.js.map} +0 -0
- /package/dist/{chunk-S3OPGGZB.js.map → chunk-D7EBTQ24.js.map} +0 -0
- /package/dist/{chunk-IO5NQEGZ.js.map → chunk-DKT3X5C3.js.map} +0 -0
- /package/dist/{chunk-S35FX6MU.js.map → chunk-EBDA5TYA.js.map} +0 -0
- /package/dist/{chunk-2PTBLS2S.js.map → chunk-EDOQM4E4.js.map} +0 -0
- /package/dist/{chunk-YTZLBBEB.js.map → chunk-EEJMD2L2.js.map} +0 -0
- /package/dist/{chunk-JIZGYQYH.js.map → chunk-EM2LDXJ2.js.map} +0 -0
- /package/dist/{chunk-YYOK4GVF.js.map → chunk-EVPNWN7Z.js.map} +0 -0
- /package/dist/{chunk-HLIR3RJ7.js.map → chunk-EXUCUPV2.js.map} +0 -0
- /package/dist/{chunk-2SXMCQEQ.js.map → chunk-GZQXOQQ2.js.map} +0 -0
- /package/dist/{chunk-2KAYTPPT.js.map → chunk-HCVWF657.js.map} +0 -0
- /package/dist/{chunk-4KZGLDJJ.js.map → chunk-J3JPB7ZI.js.map} +0 -0
- /package/dist/{chunk-LQ6JI4VH.js.map → chunk-LD53WPMU.js.map} +0 -0
- /package/dist/{chunk-URM3HW2R.js.map → chunk-LLUGAZ3B.js.map} +0 -0
- /package/dist/{chunk-U5H7WHTS.js.map → chunk-N77ELJ2D.js.map} +0 -0
- /package/dist/{chunk-2VHMVWPU.js.map → chunk-NXRUUDUF.js.map} +0 -0
- /package/dist/{chunk-DIFU33GU.js.map → chunk-OHTOPFRF.js.map} +0 -0
- /package/dist/{chunk-74MYJSKB.js.map → chunk-ORAK3LNZ.js.map} +0 -0
- /package/dist/{chunk-REWQAFSA.js.map → chunk-PD42EYQN.js.map} +0 -0
- /package/dist/{chunk-773DPI6T.js.map → chunk-PKMQK2WY.js.map} +0 -0
- /package/dist/{chunk-CTOQEZSN.js.map → chunk-QR67FU5G.js.map} +0 -0
- /package/dist/{chunk-AU7Q3LSC.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-YI7RZO7Y.js.map → chunk-SQNED75R.js.map} +0 -0
- /package/dist/{chunk-5CEJH5ZN.js.map → chunk-TAPSNIMA.js.map} +0 -0
- /package/dist/{chunk-5RHM43BJ.js.map → chunk-TXQCG6SE.js.map} +0 -0
- /package/dist/{chunk-ANI5DUAE.js.map → chunk-UIXXE6NI.js.map} +0 -0
- /package/dist/{chunk-2COL35PK.js.map → chunk-UO5ZHIIH.js.map} +0 -0
- /package/dist/{chunk-OJU73BUX.js.map → chunk-VCLGUIFM.js.map} +0 -0
- /package/dist/{chunk-5J4OL7TJ.js.map → chunk-VUA4S7P6.js.map} +0 -0
- /package/dist/{chunk-A66YRSOS.js.map → chunk-WQYSDPMA.js.map} +0 -0
- /package/dist/{chunk-OZ2KQ7HF.js.map → chunk-YEE5KJQQ.js.map} +0 -0
- /package/dist/{chunk-WRFKZEO6.js.map → chunk-YQGHW5ML.js.map} +0 -0
- /package/dist/{chunk-APUJIMDM.js.map → chunk-Z4RU5VPM.js.map} +0 -0
- /package/dist/{chunk-DCWIQFNA.js.map → chunk-ZUDM75KG.js.map} +0 -0
- /package/dist/{forget-U66KPDPY.js.map → forget-OEE5OELK.js.map} +0 -0
- /package/dist/{graph-edge-decay-JLOF3YW4.js.map → graph-edge-decay-QPRJQ7DL.js.map} +0 -0
|
@@ -34,6 +34,10 @@ import { log } from "./logger.js";
|
|
|
34
34
|
import type { PluginConfig, MemoryFrontmatter, FaithfulnessFrontmatter } from "./types.js";
|
|
35
35
|
import type { LocalLlmClient } from "./local-llm.js";
|
|
36
36
|
import { type FallbackLlmClient, gatewayTaskChainOptions } from "./fallback-llm.js";
|
|
37
|
+
import {
|
|
38
|
+
callOpenAiCompatibleChat,
|
|
39
|
+
resolveFaithfulnessGateEndpoint,
|
|
40
|
+
} from "./local-model-endpoint.js";
|
|
37
41
|
import { extractJsonCandidates } from "./json-extract.js";
|
|
38
42
|
|
|
39
43
|
// Re-export for callers importing from this module.
|
|
@@ -280,13 +284,54 @@ async function callFaithfulnessLlm(
|
|
|
280
284
|
fallbackLlm: FallbackLlmClient | null,
|
|
281
285
|
timeoutMs: number,
|
|
282
286
|
signal?: AbortSignal,
|
|
287
|
+
fetchImpl: typeof fetch = fetch,
|
|
283
288
|
): Promise<LlmCallResult> {
|
|
284
289
|
const messages: Array<{ role: "system" | "user"; content: string }> = [
|
|
285
290
|
{ role: "system", content: systemPrompt },
|
|
286
291
|
{ role: "user", content: userPrompt },
|
|
287
292
|
];
|
|
288
293
|
|
|
289
|
-
|
|
294
|
+
// Issue #1585 model-lab pointer: when an explicit local fine-tuned endpoint
|
|
295
|
+
// is configured (base URL + model name), try it FIRST — it is the cheapest,
|
|
296
|
+
// deterministic path the operator opted into by setting
|
|
297
|
+
// extractionFaithfulnessBaseUrl. Unset (the default) skips this entirely,
|
|
298
|
+
// preserving byte-identical pre-feature routing (rule 39). Any failure
|
|
299
|
+
// (network, non-2xx, malformed body, timeout) returns null and falls through
|
|
300
|
+
// to the configured chain (checklist §4 graceful degradation).
|
|
301
|
+
const localEndpoint = resolveFaithfulnessGateEndpoint(config);
|
|
302
|
+
if (localEndpoint) {
|
|
303
|
+
// Give the local probe a SMALLER budget than the outer batch timeout so a
|
|
304
|
+
// wedged local model returns null (and falls through to the configured
|
|
305
|
+
// chain) before the batch timer fires. The local endpoint is meant to be
|
|
306
|
+
// the fast path; if it cannot answer within half the budget (min 500ms),
|
|
307
|
+
// abandon it and let the fallback chain use the remainder (codex P2 — the
|
|
308
|
+
// advertised graceful fallback must actually reach the chain, not be starved
|
|
309
|
+
// by a hanging probe sharing the full batch budget).
|
|
310
|
+
const probeBudgetMs = Math.max(500, Math.floor(timeoutMs / 2));
|
|
311
|
+
const result = await callOpenAiCompatibleChat(
|
|
312
|
+
localEndpoint,
|
|
313
|
+
messages,
|
|
314
|
+
{ temperature: 0.1, maxTokens: 2048, responseFormatJson: true, timeoutMs: probeBudgetMs },
|
|
315
|
+
fetchImpl,
|
|
316
|
+
);
|
|
317
|
+
if (result?.content) {
|
|
318
|
+
return { content: result.content, modelUsed: result.modelUsed };
|
|
319
|
+
}
|
|
320
|
+
log.debug(
|
|
321
|
+
"extraction-faithfulness: local model-lab endpoint unavailable, trying configured chain",
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// extractionFaithfulnessModel is the LOCAL served model's name (e.g.
|
|
326
|
+
// remnic-faithfulness-gate-v1). When a local endpoint is configured it
|
|
327
|
+
// belongs to that endpoint ONLY — it must NOT leak into the fallback chain
|
|
328
|
+
// as a gateway/local-LLM override, or a local outage forces the configured
|
|
329
|
+
// chain onto an unavailable local-only model and turns graceful fallback
|
|
330
|
+
// into backend_unavailable (codex P2 PRRT_kwDORJXyws6Otp-L). Decouple: the
|
|
331
|
+
// override reaches the fallback chain only when NO local endpoint is
|
|
332
|
+
// configured (the pre-feature model-override contract, preserved
|
|
333
|
+
// byte-identical — rule 39).
|
|
334
|
+
const modelOverride = localEndpoint ? undefined : (config.extractionFaithfulnessModel || undefined);
|
|
290
335
|
|
|
291
336
|
// Skip the local backend when (a) modelSource is "gateway", or (b) a
|
|
292
337
|
// faithfulness model override is set. The local client always sends
|
|
@@ -415,6 +460,12 @@ export async function checkFaithfulnessBatch(
|
|
|
415
460
|
config: PluginConfig,
|
|
416
461
|
localLlm: LocalLlmClient | null,
|
|
417
462
|
fallbackLlm: FallbackLlmClient | null,
|
|
463
|
+
/**
|
|
464
|
+
* Optional fetch injection (issue #1585). The orchestrator does not pass it
|
|
465
|
+
* (uses global fetch); tests pass a stub to exercise the local model-lab
|
|
466
|
+
* endpoint path without a live server. See callFaithfulnessLlm.
|
|
467
|
+
*/
|
|
468
|
+
fetchImpl?: typeof fetch,
|
|
418
469
|
): Promise<FaithfulnessBatchResult> {
|
|
419
470
|
const timeoutMs =
|
|
420
471
|
typeof config.extractionFaithfulnessTimeoutMs === "number" &&
|
|
@@ -486,6 +537,7 @@ export async function checkFaithfulnessBatch(
|
|
|
486
537
|
fallbackLlm,
|
|
487
538
|
timeoutMs,
|
|
488
539
|
controller.signal,
|
|
540
|
+
fetchImpl,
|
|
489
541
|
);
|
|
490
542
|
const settled = await Promise.race([
|
|
491
543
|
callPromise.then(
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the extracted faithfulness + correction-intent config parser
|
|
3
|
+
* (issue #1576 / #1585). Pins the behavior-preserving extraction: defaults
|
|
4
|
+
* are byte-identical to the pre-extraction inline block, validation rejects
|
|
5
|
+
* the same inputs with the same messages, and the new model-lab pointer keys
|
|
6
|
+
* default empty (byte-identical pre-feature path, rule 39).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
|
|
12
|
+
import { parseFaithfulnessGateConfig, parseCorrectionIntentConfig } from "./faithfulness-config.js";
|
|
13
|
+
|
|
14
|
+
test("parseFaithfulnessGateConfig: empty input → byte-identical defaults (rule 39)", () => {
|
|
15
|
+
const out = parseFaithfulnessGateConfig({});
|
|
16
|
+
assert.equal(out.extractionFaithfulnessGate, "off");
|
|
17
|
+
assert.equal(out.extractionFaithfulnessModel, "");
|
|
18
|
+
assert.equal(out.extractionFaithfulnessBaseUrl, "");
|
|
19
|
+
assert.equal(out.extractionFaithfulnessContextChars, 400);
|
|
20
|
+
assert.equal(out.extractionFaithfulnessTimeoutMs, 8000);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("parseFaithfulnessGateConfig: null/undefined gate → off (not rejected)", () => {
|
|
24
|
+
assert.equal(parseFaithfulnessGateConfig({ extractionFaithfulnessGate: undefined }).extractionFaithfulnessGate, "off");
|
|
25
|
+
assert.equal(parseFaithfulnessGateConfig({ extractionFaithfulnessGate: null }).extractionFaithfulnessGate, "off");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("parseFaithfulnessGateConfig: valid gate modes accepted (case-insensitive, trimmed)", () => {
|
|
29
|
+
for (const v of ["off", "shadow", "enforce", "SHADOW", " Enforce "]) {
|
|
30
|
+
const mode = parseFaithfulnessGateConfig({ extractionFaithfulnessGate: v }).extractionFaithfulnessGate;
|
|
31
|
+
assert.ok(mode === "off" || mode === "shadow" || mode === "enforce", `${v} → ${mode}`);
|
|
32
|
+
}
|
|
33
|
+
assert.equal(parseFaithfulnessGateConfig({ extractionFaithfulnessGate: "SHADOW" }).extractionFaithfulnessGate, "shadow");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("parseFaithfulnessGateConfig: present-but-invalid gate rejects (Ob4RQ — never silently off)", () => {
|
|
37
|
+
for (const bad of [true, 1, {}, "maybe", "on"]) {
|
|
38
|
+
assert.throws(
|
|
39
|
+
() => parseFaithfulnessGateConfig({ extractionFaithfulnessGate: bad }),
|
|
40
|
+
/extractionFaithfulnessGate must be one of "off" \| "shadow" \| "enforce"/,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("parseFaithfulnessGateConfig: context chars clamp to [1, 4000]; invalid rejects (#1634)", () => {
|
|
46
|
+
assert.equal(parseFaithfulnessGateConfig({ extractionFaithfulnessContextChars: 100000 }).extractionFaithfulnessContextChars, 4000);
|
|
47
|
+
assert.equal(parseFaithfulnessGateConfig({ extractionFaithfulnessContextChars: "400" }).extractionFaithfulnessContextChars, 400);
|
|
48
|
+
for (const bad of [0, -1, 1.5, true, NaN, "abc"]) {
|
|
49
|
+
assert.throws(
|
|
50
|
+
() => parseFaithfulnessGateConfig({ extractionFaithfulnessContextChars: bad }),
|
|
51
|
+
/extractionFaithfulnessContextChars must be an integer/,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("parseFaithfulnessGateConfig: timeout clamps to [1, 60000]; invalid rejects", () => {
|
|
57
|
+
assert.equal(parseFaithfulnessGateConfig({ extractionFaithfulnessTimeoutMs: 999999 }).extractionFaithfulnessTimeoutMs, 60_000);
|
|
58
|
+
for (const bad of [0, -5, 2.5, false, "no"]) {
|
|
59
|
+
assert.throws(
|
|
60
|
+
() => parseFaithfulnessGateConfig({ extractionFaithfulnessTimeoutMs: bad }),
|
|
61
|
+
/extractionFaithfulnessTimeoutMs must be an integer/,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("parseFaithfulnessGateConfig: model-lab pointer keys default empty + accept strings (#1585)", () => {
|
|
67
|
+
// Defaults preserve the existing routing chain exactly.
|
|
68
|
+
assert.equal(parseFaithfulnessGateConfig({}).extractionFaithfulnessBaseUrl, "");
|
|
69
|
+
assert.equal(parseFaithfulnessGateConfig({}).extractionFaithfulnessModel, "");
|
|
70
|
+
// Non-string values are ignored → empty (not rejected; mirrors model key).
|
|
71
|
+
assert.equal(parseFaithfulnessGateConfig({ extractionFaithfulnessBaseUrl: 123 }).extractionFaithfulnessBaseUrl, "");
|
|
72
|
+
// Real pointer values round-trip.
|
|
73
|
+
const out = parseFaithfulnessGateConfig({
|
|
74
|
+
extractionFaithfulnessModel: "remnic-faithfulness-gate-v1",
|
|
75
|
+
extractionFaithfulnessBaseUrl: "http://localhost:11434/v1",
|
|
76
|
+
});
|
|
77
|
+
assert.equal(out.extractionFaithfulnessModel, "remnic-faithfulness-gate-v1");
|
|
78
|
+
assert.equal(out.extractionFaithfulnessBaseUrl, "http://localhost:11434/v1");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("parseCorrectionIntentConfig: empty input → empty pointers (rule-based detector stays default)", () => {
|
|
82
|
+
const out = parseCorrectionIntentConfig({});
|
|
83
|
+
assert.equal(out.correctionIntentModel, "");
|
|
84
|
+
assert.equal(out.correctionIntentBaseUrl, "");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("parseCorrectionIntentConfig: pointers round-trip; non-strings → empty", () => {
|
|
88
|
+
const out = parseCorrectionIntentConfig({
|
|
89
|
+
correctionIntentModel: "remnic-correction-intent-v1",
|
|
90
|
+
correctionIntentBaseUrl: "http://localhost:8000/v1",
|
|
91
|
+
});
|
|
92
|
+
assert.equal(out.correctionIntentModel, "remnic-correction-intent-v1");
|
|
93
|
+
assert.equal(out.correctionIntentBaseUrl, "http://localhost:8000/v1");
|
|
94
|
+
assert.equal(parseCorrectionIntentConfig({ correctionIntentModel: true }).correctionIntentModel, "");
|
|
95
|
+
assert.equal(parseCorrectionIntentConfig({ correctionIntentBaseUrl: 5 }).correctionIntentBaseUrl, "");
|
|
96
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Faithfulness-gate + correction-intent config parsing (issue #1576 / #1585).
|
|
3
|
+
*
|
|
4
|
+
* Extracted verbatim from `config.ts` (a watchlisted god file) so the gate
|
|
5
|
+
* config block can grow — the model-lab pointer keys
|
|
6
|
+
* `extractionFaithfulnessBaseUrl` (#1585) and the correction-intent pointers
|
|
7
|
+
* (`correctionIntentModel` / `correctionIntentBaseUrl`) — without growing the
|
|
8
|
+
* god file. This is a behavior-preserving extraction (#1526-style): the
|
|
9
|
+
* validation, defaults, and error messages are byte-identical to the inline
|
|
10
|
+
* block it replaces, which the existing extraction-faithfulness + config
|
|
11
|
+
* tests pin.
|
|
12
|
+
*
|
|
13
|
+
* Why a dedicated module (and not inline): the ratchet
|
|
14
|
+
* (`scripts/check-ratchets.mjs`) forbids `config.ts` from growing, and the
|
|
15
|
+
* model-lab integration needs three new optional keys. Centralizing the whole
|
|
16
|
+
* gate+intent parsing here keeps it unit-testable in isolation and lets
|
|
17
|
+
* `config.ts` spread the result in one line.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { coerceNumber } from "./connectors/coerce.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse a value as a strict integer >= `min`, throwing on anything else.
|
|
24
|
+
*
|
|
25
|
+
* Mirrors `parseIntegerAtLeast` in `config.ts` byte-for-byte (issue #1634:
|
|
26
|
+
* reject non-numeric, <=0, non-integer, NaN, Infinity, booleans, objects —
|
|
27
|
+
* gotcha #51). Reimplemented locally rather than exported from `config.ts`
|
|
28
|
+
* so this module has no dependency on the god file (and vice versa).
|
|
29
|
+
*/
|
|
30
|
+
function parseIntegerAtLeast(
|
|
31
|
+
value: unknown,
|
|
32
|
+
fallback: number,
|
|
33
|
+
min: number,
|
|
34
|
+
keyName: string,
|
|
35
|
+
): number {
|
|
36
|
+
if (value === undefined || value === null) return fallback;
|
|
37
|
+
const coerced = coerceNumber(value);
|
|
38
|
+
if (
|
|
39
|
+
coerced === undefined ||
|
|
40
|
+
!Number.isFinite(coerced) ||
|
|
41
|
+
!Number.isInteger(coerced) ||
|
|
42
|
+
coerced < min
|
|
43
|
+
) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`${keyName} must be an integer greater than or equal to ${min}; got ${JSON.stringify(value)}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return coerced;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parse the extraction faithfulness gate config block.
|
|
53
|
+
*
|
|
54
|
+
* Returns exactly the `PluginConfig` fields this block owns. Defaults preserve
|
|
55
|
+
* the byte-identical pre-feature pipeline (rule 39): gate "off", empty model
|
|
56
|
+
* + base URL, context 400 chars, timeout 8000 ms. Invalid `gate` values reject
|
|
57
|
+
* listing valid options (rule 51); invalid integers throw (issue #1634).
|
|
58
|
+
*
|
|
59
|
+
* `extractionFaithfulnessBaseUrl` (issue #1585) is the model-lab pointer: when
|
|
60
|
+
* set together with `extractionFaithfulnessModel`, the gate routes to that
|
|
61
|
+
* local openai-compatible endpoint before the configured chain; empty string
|
|
62
|
+
* (default) preserves the existing routing exactly.
|
|
63
|
+
*/
|
|
64
|
+
export function parseFaithfulnessGateConfig(cfg: Record<string, unknown>): {
|
|
65
|
+
extractionFaithfulnessGate: "off" | "shadow" | "enforce";
|
|
66
|
+
extractionFaithfulnessModel: string;
|
|
67
|
+
extractionFaithfulnessBaseUrl: string;
|
|
68
|
+
extractionFaithfulnessContextChars: number;
|
|
69
|
+
extractionFaithfulnessTimeoutMs: number;
|
|
70
|
+
} {
|
|
71
|
+
const gateValue = cfg.extractionFaithfulnessGate;
|
|
72
|
+
let extractionFaithfulnessGate: "off" | "shadow" | "enforce";
|
|
73
|
+
if (gateValue === undefined || gateValue === null) {
|
|
74
|
+
extractionFaithfulnessGate = "off";
|
|
75
|
+
} else {
|
|
76
|
+
// Present-but-invalid (true/1/{}) must reject, not silently disable the gate (Ob4RQ).
|
|
77
|
+
const raw = typeof gateValue === "string" ? gateValue.trim().toLowerCase() : gateValue;
|
|
78
|
+
if (raw === "off" || raw === "shadow" || raw === "enforce") {
|
|
79
|
+
extractionFaithfulnessGate = raw;
|
|
80
|
+
} else {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`extractionFaithfulnessGate must be one of "off" | "shadow" | "enforce" (got ${JSON.stringify(gateValue)})`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const extractionFaithfulnessModel =
|
|
88
|
+
typeof cfg.extractionFaithfulnessModel === "string" ? cfg.extractionFaithfulnessModel : "";
|
|
89
|
+
|
|
90
|
+
// Issue #1585: model-lab pointer. Empty default → gate uses its existing
|
|
91
|
+
// routing chain (byte-identical). Non-empty + a model name → gate prefers
|
|
92
|
+
// the local served endpoint, falling back to the chain on failure.
|
|
93
|
+
const extractionFaithfulnessBaseUrl =
|
|
94
|
+
typeof cfg.extractionFaithfulnessBaseUrl === "string"
|
|
95
|
+
? cfg.extractionFaithfulnessBaseUrl
|
|
96
|
+
: "";
|
|
97
|
+
|
|
98
|
+
const extractionFaithfulnessContextChars = Math.min(
|
|
99
|
+
parseIntegerAtLeast(cfg.extractionFaithfulnessContextChars, 400, 1, "extractionFaithfulnessContextChars"),
|
|
100
|
+
4000,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const extractionFaithfulnessTimeoutMs = Math.min(
|
|
104
|
+
parseIntegerAtLeast(cfg.extractionFaithfulnessTimeoutMs, 8000, 1, "extractionFaithfulnessTimeoutMs"),
|
|
105
|
+
60_000,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
extractionFaithfulnessGate,
|
|
110
|
+
extractionFaithfulnessModel,
|
|
111
|
+
extractionFaithfulnessBaseUrl,
|
|
112
|
+
extractionFaithfulnessContextChars,
|
|
113
|
+
extractionFaithfulnessTimeoutMs,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Parse the correction-intent config block (issue #1581 / #1585).
|
|
119
|
+
*
|
|
120
|
+
* The model-lab pointer for passive-correction detection: when both
|
|
121
|
+
* `correctionIntentModel` and `correctionIntentBaseUrl` are set, the detection
|
|
122
|
+
* path can route to that local fine-tuned classifier; empty defaults (the
|
|
123
|
+
* common case) keep the rule-based `detectPassiveCorrections` heuristic as the
|
|
124
|
+
* sole detector — byte-identical to the pre-feature path. Consumption by the
|
|
125
|
+
* detector is the consuming child's job (#1581's detector is a pure function;
|
|
126
|
+
* wiring a model call into it is out of scope for this config-pointer PR).
|
|
127
|
+
*/
|
|
128
|
+
export function parseCorrectionIntentConfig(cfg: Record<string, unknown>): {
|
|
129
|
+
correctionIntentModel: string;
|
|
130
|
+
correctionIntentBaseUrl: string;
|
|
131
|
+
} {
|
|
132
|
+
return {
|
|
133
|
+
correctionIntentModel:
|
|
134
|
+
typeof cfg.correctionIntentModel === "string" ? cfg.correctionIntentModel : "",
|
|
135
|
+
correctionIntentBaseUrl:
|
|
136
|
+
typeof cfg.correctionIntentBaseUrl === "string" ? cfg.correctionIntentBaseUrl : "",
|
|
137
|
+
};
|
|
138
|
+
}
|
package/src/graph-snapshot.ts
CHANGED
|
@@ -132,9 +132,9 @@ export function parseGraphSnapshotSince(raw: string | undefined): number | undef
|
|
|
132
132
|
export async function buildGraphSnapshot(opts: {
|
|
133
133
|
memoryDir: string;
|
|
134
134
|
graphConfig: {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
entityGraph: boolean;
|
|
136
|
+
timeGraph: boolean;
|
|
137
|
+
causalGraph: boolean;
|
|
138
138
|
};
|
|
139
139
|
request: GraphSnapshotRequest;
|
|
140
140
|
loadNode: GraphSnapshotNodeLoader;
|
package/src/graph.ts
CHANGED
|
@@ -15,6 +15,7 @@ import * as path from "node:path";
|
|
|
15
15
|
|
|
16
16
|
import { readEdgeConfidence } from "./graph-edge-reinforcement.js";
|
|
17
17
|
import { emitGraphEvent } from "./graph-events.js";
|
|
18
|
+
import type { GraphConstructionCapabilitySet } from "./capabilities.js";
|
|
18
19
|
|
|
19
20
|
export type GraphType = "entity" | "time" | "causal";
|
|
20
21
|
|
|
@@ -198,12 +199,12 @@ export async function readEdgesStrict(memoryDir: string, type: GraphType): Promi
|
|
|
198
199
|
*/
|
|
199
200
|
export async function readAllEdges(
|
|
200
201
|
memoryDir: string,
|
|
201
|
-
|
|
202
|
+
graphCaps: Pick<GraphConstructionCapabilitySet, "entityGraph" | "timeGraph" | "causalGraph">,
|
|
202
203
|
): Promise<GraphEdge[]> {
|
|
203
204
|
const parts: GraphEdge[][] = await Promise.all([
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
graphCaps.entityGraph ? readEdges(memoryDir, "entity") : Promise.resolve([]),
|
|
206
|
+
graphCaps.timeGraph ? readEdges(memoryDir, "time") : Promise.resolve([]),
|
|
207
|
+
graphCaps.causalGraph ? readEdges(memoryDir, "causal") : Promise.resolve([]),
|
|
207
208
|
]);
|
|
208
209
|
return parts.flat();
|
|
209
210
|
}
|
|
@@ -396,9 +397,9 @@ export class GraphIndex {
|
|
|
396
397
|
return this.edgeCache.allEdges;
|
|
397
398
|
}
|
|
398
399
|
const allEdges = await readAllEdges(this.memoryDir, {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
400
|
+
entityGraph: this.cfg.entityGraphEnabled,
|
|
401
|
+
timeGraph: this.cfg.timeGraphEnabled,
|
|
402
|
+
causalGraph: this.cfg.causalGraphEnabled,
|
|
402
403
|
});
|
|
403
404
|
this.edgeCache = { allEdges, loadedAt: Date.now() };
|
|
404
405
|
return allEdges;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { describe, it } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { mkdtemp, readFile, writeFile, rm, mkdir, appendFile } from "node:fs/promises";
|
|
3
|
+
import { mkdtemp, readFile, writeFile, rm, mkdir, appendFile, utimes } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { statSync } from "node:fs";
|
|
5
7
|
import path from "node:path";
|
|
6
8
|
import { createHash } from "node:crypto";
|
|
7
9
|
import {
|
|
@@ -345,6 +347,245 @@ describe("TombstoneStore — enabled gate (rule 30)", () => {
|
|
|
345
347
|
});
|
|
346
348
|
});
|
|
347
349
|
|
|
350
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
351
|
+
// Cross-process tombstone write lock (issue #1639)
|
|
352
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* A read-merge-write file io that simulates the secure-store append path
|
|
356
|
+
* (read encrypted → decrypt → concat → re-encrypt → atomic rename). Unlike a
|
|
357
|
+
* raw O_APPEND, this is NOT atomic across processes: two writers that each
|
|
358
|
+
* read the same contents and write back drop the other line — the exact
|
|
359
|
+
* lost-write race the #1639 cross-process lock closes. The setImmediate yield
|
|
360
|
+
* widens the race window so a missing lock would demonstrably lose entries.
|
|
361
|
+
*/
|
|
362
|
+
function makeReadMergeWriteIo() {
|
|
363
|
+
return {
|
|
364
|
+
read: (p: string) => readFile(p, "utf8"),
|
|
365
|
+
append: async (p: string, c: string) => {
|
|
366
|
+
await mkdir(path.dirname(p), { recursive: true });
|
|
367
|
+
let existing = "";
|
|
368
|
+
try {
|
|
369
|
+
existing = await readFile(p, "utf8");
|
|
370
|
+
} catch (err) {
|
|
371
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
|
|
372
|
+
}
|
|
373
|
+
// Yield so two concurrent read-merge-write appends observably overlap
|
|
374
|
+
// when the cross-process lock is absent.
|
|
375
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
376
|
+
await writeFile(p, existing + c, "utf8");
|
|
377
|
+
},
|
|
378
|
+
write: async (p: string, c: string) => {
|
|
379
|
+
await mkdir(path.dirname(p), { recursive: true });
|
|
380
|
+
await writeFile(p, c, "utf8");
|
|
381
|
+
},
|
|
382
|
+
stat: (p: string) => statSync(p),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
describe("TombstoneStore — cross-process write lock (issue #1639)", () => {
|
|
387
|
+
it("two processes appending concurrently produce no lost entries", async () => {
|
|
388
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-xproc-"));
|
|
389
|
+
const moduleUrl = new URL("./tombstones.ts", import.meta.url).href;
|
|
390
|
+
const entriesPerWorker = 15;
|
|
391
|
+
|
|
392
|
+
// Worker source: plain JS, no nested template literals. Each child imports
|
|
393
|
+
// the store, points it at the shared tombstones.jsonl with a read-merge-
|
|
394
|
+
// write io, and appends N entries. The cross-process lock serializes the
|
|
395
|
+
// appends across the two processes so no entry is dropped.
|
|
396
|
+
const workerSource = [
|
|
397
|
+
"(async () => {",
|
|
398
|
+
"const { TombstoneStore } = await import(process.argv[1]);",
|
|
399
|
+
"const { mkdir, readFile, writeFile } = await import(\"node:fs/promises\");",
|
|
400
|
+
"const { statSync } = await import(\"node:fs\");",
|
|
401
|
+
"const path = await import(\"node:path\");",
|
|
402
|
+
"const { createHash } = await import(\"node:crypto\");",
|
|
403
|
+
"const dir = process.argv[2];",
|
|
404
|
+
"const workerId = Number(process.argv[3]);",
|
|
405
|
+
"const count = Number(process.argv[4]);",
|
|
406
|
+
"const filePath = path.join(dir, \"tombstones.jsonl\");",
|
|
407
|
+
"function hash(c){return createHash(\"sha256\").update(c).digest(\"hex\");}",
|
|
408
|
+
"function normalize(c){return c;}",
|
|
409
|
+
"const io = {",
|
|
410
|
+
" read: (p) => readFile(p, \"utf8\"),",
|
|
411
|
+
" append: async (p, c) => {",
|
|
412
|
+
" await mkdir(path.dirname(p), { recursive: true });",
|
|
413
|
+
" let existing = \"\";",
|
|
414
|
+
" try { existing = await readFile(p, \"utf8\"); } catch (e) { if (e.code !== \"ENOENT\") throw e; }",
|
|
415
|
+
" await new Promise((r) => setImmediate(r));",
|
|
416
|
+
" await writeFile(p, existing + c, \"utf8\");",
|
|
417
|
+
" },",
|
|
418
|
+
" write: async (p, c) => { await mkdir(path.dirname(p), { recursive: true }); await writeFile(p, c, \"utf8\"); },",
|
|
419
|
+
" stat: (p) => statSync(p),",
|
|
420
|
+
"};",
|
|
421
|
+
"const store = new TombstoneStore(filePath, \"default\", { enabled: true, semanticMatch: false, semanticThreshold: 0.9, hashContent: hash, normalizeText: normalize }, io);",
|
|
422
|
+
"await store.load();",
|
|
423
|
+
"for (let i = 0; i < count; i += 1) {",
|
|
424
|
+
" await store.appendTombstone({ reason: \"correction\", createdBy: \"user_correction\", sourceMemoryId: \"w\" + workerId + \"-f\" + i, rawContent: \"worker \" + workerId + \" fact \" + i });",
|
|
425
|
+
"}",
|
|
426
|
+
"})();",
|
|
427
|
+
].join("\n");
|
|
428
|
+
|
|
429
|
+
function runWorker(workerId: number): Promise<void> {
|
|
430
|
+
return new Promise((resolve, reject) => {
|
|
431
|
+
const child = spawn(
|
|
432
|
+
process.execPath,
|
|
433
|
+
["--import", "tsx", "-e", workerSource, moduleUrl, dir, String(workerId), String(entriesPerWorker)],
|
|
434
|
+
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
435
|
+
);
|
|
436
|
+
let stderr = "";
|
|
437
|
+
child.stderr.setEncoding("utf8");
|
|
438
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
439
|
+
child.on("error", reject);
|
|
440
|
+
child.on("close", (code) => {
|
|
441
|
+
if (code === 0) resolve();
|
|
442
|
+
else reject(new Error(`worker ${workerId} exited ${code}: ${stderr}`));
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Two genuinely concurrent processes contending on the same lockfile.
|
|
448
|
+
await Promise.all([runWorker(0), runWorker(1)]);
|
|
449
|
+
|
|
450
|
+
const raw = await readFile(path.join(dir, "tombstones.jsonl"), "utf8");
|
|
451
|
+
const fileLines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
452
|
+
// No lost entries: every line parses and the total is exactly 2 × N.
|
|
453
|
+
assert.equal(fileLines.length, entriesPerWorker * 2);
|
|
454
|
+
for (const line of fileLines) {
|
|
455
|
+
assert.doesNotThrow(() => JSON.parse(line));
|
|
456
|
+
}
|
|
457
|
+
// Every worker entry is present (none dropped by a lost write).
|
|
458
|
+
const sourceIds = new Set(fileLines.map((l) => JSON.parse(l).sourceMemoryId));
|
|
459
|
+
for (let w = 0; w < 2; w += 1) {
|
|
460
|
+
for (let i = 0; i < entriesPerWorker; i += 1) {
|
|
461
|
+
assert.ok(sourceIds.has(`w${w}-f${i}`), `missing w${w}-f${i} — lost write`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// The advisory lockfile is released after the run (no lingering lock).
|
|
465
|
+
await rm(path.join(dir, "tombstones.lock"), { force: true });
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it("recovers from a stale lock left by a crashed holder", async () => {
|
|
469
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-stale-lock-"));
|
|
470
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
471
|
+
const lockPath = path.join(dir, "tombstones.lock");
|
|
472
|
+
// Seed a lockfile written by a (now-crashed) holder: <pid> <uuid> <iso>.
|
|
473
|
+
await mkdir(dir, { recursive: true });
|
|
474
|
+
await writeFile(
|
|
475
|
+
lockPath,
|
|
476
|
+
`${process.pid} 00000000-0000-0000-0000-000000000000 2000-01-01T00:00:00.000Z\n`,
|
|
477
|
+
"utf8",
|
|
478
|
+
);
|
|
479
|
+
// Make it older than staleMs so the next acquire breaks it.
|
|
480
|
+
const old = new Date(Date.now() - 120_000);
|
|
481
|
+
await utimes(lockPath, old, old);
|
|
482
|
+
|
|
483
|
+
const store = new TombstoneStore(
|
|
484
|
+
filePath,
|
|
485
|
+
"default",
|
|
486
|
+
{ enabled: true, semanticMatch: false, semanticThreshold: 0.9, hashContent: computeHash, normalizeText: normalizeContent, lockStaleMs: 1_000 },
|
|
487
|
+
makeReadMergeWriteIo(),
|
|
488
|
+
);
|
|
489
|
+
// The append must break the stale lock, acquire a fresh one, and succeed.
|
|
490
|
+
const id = await store.appendTombstone({
|
|
491
|
+
reason: "correction",
|
|
492
|
+
createdBy: "user_correction",
|
|
493
|
+
sourceMemoryId: "fact-stale",
|
|
494
|
+
rawContent: "Recovered from a stale lock.",
|
|
495
|
+
});
|
|
496
|
+
assert.match(id, /^tomb-/);
|
|
497
|
+
// The tombstone landed durably.
|
|
498
|
+
const raw = await readFile(filePath, "utf8");
|
|
499
|
+
const lines2 = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
500
|
+
assert.equal(lines2.length, 1);
|
|
501
|
+
// Our fresh lock was released on completion (the stale one was broken).
|
|
502
|
+
await rm(lockPath, { force: true });
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("rebuild preserves a peer's revocation appended before rebuild acquired the lock (cursor/codex #1639)", async () => {
|
|
506
|
+
// Regression for the rebuild race: rebuild must re-read the log under the
|
|
507
|
+
// lock (ensureFreshAgainstDisk) before computing its payload, otherwise a
|
|
508
|
+
// revocation a peer wrote while rebuild waited for the lock is dropped and
|
|
509
|
+
// the retired fact silently un-revokes (resurrection).
|
|
510
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-rebuild-race-"));
|
|
511
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
512
|
+
const opts = { enabled: true, semanticMatch: false, semanticThreshold: 0.9, hashContent: computeHash, normalizeText: normalizeContent };
|
|
513
|
+
const io = makeReadMergeWriteIo();
|
|
514
|
+
// Store A writes tombstone-1, then goes stale (never sees the peer write).
|
|
515
|
+
const storeA = new TombstoneStore(filePath, "default", opts, io);
|
|
516
|
+
const t1 = await storeA.appendTombstone({
|
|
517
|
+
reason: "correction",
|
|
518
|
+
createdBy: "user_correction",
|
|
519
|
+
sourceMemoryId: "fact-rebuild-1",
|
|
520
|
+
rawContent: "The DB is MySQL.",
|
|
521
|
+
});
|
|
522
|
+
// Peer (store B, same file) revokes t1 durably. Store A's in-memory index
|
|
523
|
+
// is now stale (it still thinks t1 is active).
|
|
524
|
+
const storeB = new TombstoneStore(filePath, "default", opts, io);
|
|
525
|
+
await storeB.revoke(t1, "user_correction");
|
|
526
|
+
// Store A rebuilds from a retired-memory corpus that reconstructs t1.
|
|
527
|
+
// Without the under-lock re-read, A computes existingRevocations from its
|
|
528
|
+
// stale index (empty), overwrites the file with [rebuilt-t1], and the
|
|
529
|
+
// revocation is LOST — t1 is active again (resurrection). With the fix,
|
|
530
|
+
// ensureFreshAgainstDisk reloads the revocation under the lock and rebuild
|
|
531
|
+
// preserves it.
|
|
532
|
+
await storeA.rebuild([
|
|
533
|
+
{
|
|
534
|
+
memoryId: "fact-rebuild-1",
|
|
535
|
+
rawContent: "The DB is MySQL.",
|
|
536
|
+
reason: "correction" as const,
|
|
537
|
+
createdBy: "user_correction" as const,
|
|
538
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
539
|
+
},
|
|
540
|
+
]);
|
|
541
|
+
// The rebuilt file must still contain the revocation (the retired fact
|
|
542
|
+
// stays revoked, not silently un-revoked by the rebuild).
|
|
543
|
+
const raw = await readFile(filePath, "utf8");
|
|
544
|
+
const rebuiltLines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
545
|
+
const hasRevocation = rebuiltLines.some((l) => {
|
|
546
|
+
try { return JSON.parse(l).kind === "revocation"; } catch { return false; }
|
|
547
|
+
});
|
|
548
|
+
assert.ok(hasRevocation, "rebuild dropped the peer's revocation — resurrection risk");
|
|
549
|
+
// And the rebuilt store A must report the fact as revoked (lookup returns null).
|
|
550
|
+
await storeA.load();
|
|
551
|
+
assert.equal(
|
|
552
|
+
storeA.lookup({ namespace: "default", contentHash: computeHash("The DB is MySQL.") }),
|
|
553
|
+
null,
|
|
554
|
+
"revoked fact resurrected after rebuild",
|
|
555
|
+
);
|
|
556
|
+
await rm(path.join(dir, "tombstones.lock"), { force: true });
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it("in-process concurrent appends still serialize under the lock", async () => {
|
|
560
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-inproc-lock-"));
|
|
561
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
562
|
+
const store = new TombstoneStore(
|
|
563
|
+
filePath,
|
|
564
|
+
"default",
|
|
565
|
+
{ enabled: true, semanticMatch: false, semanticThreshold: 0.9, hashContent: computeHash, normalizeText: normalizeContent },
|
|
566
|
+
makeReadMergeWriteIo(),
|
|
567
|
+
);
|
|
568
|
+
const contents = Array.from({ length: 25 }, (_, i) => `locked content ${i}`);
|
|
569
|
+
await Promise.all(
|
|
570
|
+
contents.map((c) =>
|
|
571
|
+
store.appendTombstone({
|
|
572
|
+
reason: "correction",
|
|
573
|
+
createdBy: "user_correction",
|
|
574
|
+
sourceMemoryId: `fact-${c}`,
|
|
575
|
+
rawContent: c,
|
|
576
|
+
}),
|
|
577
|
+
),
|
|
578
|
+
);
|
|
579
|
+
await store.load();
|
|
580
|
+
const raw = await readFile(filePath, "utf8");
|
|
581
|
+
const lines3 = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
582
|
+
// No lost writes despite the read-merge-write io + concurrent appends.
|
|
583
|
+
assert.equal(lines3.length, contents.length);
|
|
584
|
+
assert.equal(store.stats().count, contents.length);
|
|
585
|
+
await rm(path.join(dir, "tombstones.lock"), { force: true });
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
|
|
348
589
|
describe("parseTombstoneLine", () => {
|
|
349
590
|
it("rejects malformed input", () => {
|
|
350
591
|
assert.equal(parseTombstoneLine(""), null);
|