@remnic/core 9.3.720 → 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 +5 -5
- package/dist/access-boundary.js +35 -33
- package/dist/access-cli.js +69 -66
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +39 -37
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +38 -36
- package/dist/access-operations-batch.js +36 -34
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +37 -35
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-Dw-tK1pa.d.ts → access-service-Dj3I-VBS.d.ts} +3 -3
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +34 -32
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +4 -2
- package/dist/active-recall.js.map +1 -1
- 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 +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +11 -9
- 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 +1 -1
- package/dist/{catalog-B9SPfcsE.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 +14 -12
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.d.ts +1 -1
- 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-ZKUW2MJG.js → chunk-4SF232E6.js} +18 -30
- package/dist/chunk-4SF232E6.js.map +1 -0
- package/dist/{chunk-DGTDRIUO.js → chunk-4T3BMHKD.js} +2 -2
- package/dist/{chunk-RX7EBFFJ.js → chunk-5O42UKXI.js} +2 -2
- package/dist/{chunk-5YQN2XYC.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-V7UUIIR7.js → chunk-7CTNUVHZ.js} +2 -2
- package/dist/{chunk-HU4EBIXO.js → chunk-A5XNBK2D.js} +8 -8
- package/dist/{chunk-2RBMGTK4.js → chunk-A7TJZLPK.js} +2 -2
- package/dist/{chunk-BGQ7WKMT.js → chunk-B7WISIAJ.js} +5 -5
- package/dist/{chunk-CYJV5AYC.js → chunk-BXEOHV7K.js} +3 -3
- package/dist/chunk-C4X4QGIO.js +60 -0
- package/dist/chunk-C4X4QGIO.js.map +1 -0
- package/dist/{chunk-RIW3UDDY.js → chunk-D7EBTQ24.js} +2 -2
- package/dist/{chunk-IO5NQEGZ.js → chunk-DKT3X5C3.js} +2 -2
- package/dist/{chunk-H2AGJ5SV.js → chunk-EBDA5TYA.js} +2 -2
- package/dist/{chunk-NBYXOEO7.js → chunk-EDOQM4E4.js} +2 -2
- package/dist/{chunk-YMPEGBLL.js → chunk-EEJMD2L2.js} +3 -3
- package/dist/{chunk-XROP4ZHB.js → chunk-EHQ5HO5T.js} +4 -4
- package/dist/{chunk-ATGRQ2NE.js → chunk-EM2LDXJ2.js} +4 -4
- package/dist/{chunk-422TIKIH.js → chunk-EVPNWN7Z.js} +9 -9
- package/dist/{chunk-WBHQ5LP5.js → chunk-EXUCUPV2.js} +2 -2
- package/dist/{chunk-M3GBOKUX.js → chunk-GDUOCHCD.js} +16 -16
- package/dist/{chunk-3XEGHEQ2.js → chunk-GJSL4WG2.js} +2 -2
- package/dist/{chunk-2SXMCQEQ.js → chunk-GZQXOQQ2.js} +3 -3
- package/dist/{chunk-UUMYB3FR.js → chunk-HCVWF657.js} +11 -11
- package/dist/{chunk-ZW5I3UNP.js → chunk-ILFSEIRT.js} +1 -1
- package/dist/chunk-ILFSEIRT.js.map +1 -0
- package/dist/{chunk-HHSO6VCQ.js → chunk-J3JPB7ZI.js} +2 -2
- package/dist/{chunk-Y4I7SOJN.js → chunk-LA3HZKES.js} +78 -78
- package/dist/{chunk-LQ6JI4VH.js → chunk-LD53WPMU.js} +4 -4
- package/dist/{chunk-NE5WFIY6.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-KNN6DAZQ.js → chunk-N77ELJ2D.js} +4 -4
- package/dist/{chunk-HTIL3EJK.js → chunk-NXRUUDUF.js} +2 -2
- package/dist/{chunk-JV7Q226R.js → chunk-OHTOPFRF.js} +2 -2
- package/dist/{chunk-ZG2WP425.js → chunk-ORAK3LNZ.js} +2 -2
- package/dist/{chunk-NERBFQNY.js → chunk-PD42EYQN.js} +2 -2
- package/dist/chunk-PHK3HARR.js +32 -0
- package/dist/chunk-PHK3HARR.js.map +1 -0
- package/dist/{chunk-NO4DWSQE.js → chunk-PKMQK2WY.js} +7 -7
- package/dist/{chunk-V5EGLYXE.js → chunk-QD6QP5P6.js} +42 -42
- package/dist/{chunk-IPJD7WJH.js → chunk-QR67FU5G.js} +2 -2
- package/dist/{chunk-ZE7GJ76H.js → chunk-SQNED75R.js} +1 -1
- package/dist/{chunk-5CEJH5ZN.js → chunk-TAPSNIMA.js} +4 -4
- package/dist/{chunk-PMBWVQ7D.js → chunk-TQT3USCG.js} +2 -2
- package/dist/{chunk-LLLG5PTE.js → chunk-TXQCG6SE.js} +3 -3
- package/dist/{chunk-6S5ZI7HB.js → chunk-UIXXE6NI.js} +6 -6
- package/dist/{chunk-T3XSXAWK.js → chunk-UO5ZHIIH.js} +2 -2
- package/dist/{chunk-PPVCZPYA.js → chunk-VCLGUIFM.js} +2 -2
- package/dist/{chunk-5J4OL7TJ.js → chunk-VUA4S7P6.js} +4 -4
- package/dist/{chunk-HAPVZ4CO.js → chunk-WQYSDPMA.js} +2 -2
- package/dist/chunk-XGTBNVIU.js +79 -0
- package/dist/chunk-XGTBNVIU.js.map +1 -0
- package/dist/{chunk-O6SEGJNV.js → chunk-YEE5KJQQ.js} +2 -2
- package/dist/{chunk-WRFKZEO6.js → chunk-YQGHW5ML.js} +4 -4
- package/dist/{chunk-DG3ORVCU.js → chunk-Z4RU5VPM.js} +2 -2
- package/dist/{chunk-DCWIQFNA.js → chunk-ZUDM75KG.js} +4 -4
- package/dist/{cli-qcLCJlOD.d.ts → cli-P_GaYcCY.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +63 -60
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +11 -9
- 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 +11 -9
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +12 -10
- 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 +11 -9
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- 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/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/{graph-edge-decay-QDDC6AVV.js → graph-edge-decay-QPRJQ7DL.js} +3 -3
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph-snapshot.js +3 -3
- package/dist/graph.d.ts +1 -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 +9 -9
- package/dist/index.js +101 -98
- 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 +3 -3
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +5 -5
- 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 +11 -9
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +11 -9
- package/dist/maintenance/rebuild-memory-projection.js +12 -10
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- 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 +21 -19
- 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 +11 -9
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +30 -27
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +13 -11
- package/dist/{orchestrator-Bg5Fnza6.d.ts → orchestrator-f5xv19kp.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +54 -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 +1 -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 +13 -13
- 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-ClOiS6KP.d.ts → semantic-consolidation-BQm1e1Pn.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +12 -10
- package/dist/semantic-rule-promotion.js +11 -9
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +11 -9
- 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 +1 -1
- package/dist/storage.js +10 -8
- 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 +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-BSdUWePh.d.ts → types-DwO1Ot4n.d.ts} +19 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +11 -9
- package/package.json +2 -2
- package/src/config.ts +9 -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/local-model-endpoint.test.ts +155 -0
- package/src/local-model-endpoint.ts +189 -0
- package/src/types.ts +19 -1
- package/dist/chunk-FEB7W5JG.js.map +0 -1
- package/dist/chunk-RVLB4E6N.js.map +0 -1
- package/dist/chunk-ZKUW2MJG.js.map +0 -1
- package/dist/chunk-ZW5I3UNP.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-RX7EBFFJ.js.map → chunk-5O42UKXI.js.map} +0 -0
- /package/dist/{chunk-5YQN2XYC.js.map → chunk-6FAMMBQ3.js.map} +0 -0
- /package/dist/{chunk-67HG7KFE.js.map → chunk-6YKIYQU7.js.map} +0 -0
- /package/dist/{chunk-V7UUIIR7.js.map → chunk-7CTNUVHZ.js.map} +0 -0
- /package/dist/{chunk-HU4EBIXO.js.map → chunk-A5XNBK2D.js.map} +0 -0
- /package/dist/{chunk-2RBMGTK4.js.map → chunk-A7TJZLPK.js.map} +0 -0
- /package/dist/{chunk-BGQ7WKMT.js.map → chunk-B7WISIAJ.js.map} +0 -0
- /package/dist/{chunk-CYJV5AYC.js.map → chunk-BXEOHV7K.js.map} +0 -0
- /package/dist/{chunk-RIW3UDDY.js.map → chunk-D7EBTQ24.js.map} +0 -0
- /package/dist/{chunk-IO5NQEGZ.js.map → chunk-DKT3X5C3.js.map} +0 -0
- /package/dist/{chunk-H2AGJ5SV.js.map → chunk-EBDA5TYA.js.map} +0 -0
- /package/dist/{chunk-NBYXOEO7.js.map → chunk-EDOQM4E4.js.map} +0 -0
- /package/dist/{chunk-YMPEGBLL.js.map → chunk-EEJMD2L2.js.map} +0 -0
- /package/dist/{chunk-XROP4ZHB.js.map → chunk-EHQ5HO5T.js.map} +0 -0
- /package/dist/{chunk-ATGRQ2NE.js.map → chunk-EM2LDXJ2.js.map} +0 -0
- /package/dist/{chunk-422TIKIH.js.map → chunk-EVPNWN7Z.js.map} +0 -0
- /package/dist/{chunk-WBHQ5LP5.js.map → chunk-EXUCUPV2.js.map} +0 -0
- /package/dist/{chunk-M3GBOKUX.js.map → chunk-GDUOCHCD.js.map} +0 -0
- /package/dist/{chunk-3XEGHEQ2.js.map → chunk-GJSL4WG2.js.map} +0 -0
- /package/dist/{chunk-2SXMCQEQ.js.map → chunk-GZQXOQQ2.js.map} +0 -0
- /package/dist/{chunk-UUMYB3FR.js.map → chunk-HCVWF657.js.map} +0 -0
- /package/dist/{chunk-HHSO6VCQ.js.map → chunk-J3JPB7ZI.js.map} +0 -0
- /package/dist/{chunk-Y4I7SOJN.js.map → chunk-LA3HZKES.js.map} +0 -0
- /package/dist/{chunk-LQ6JI4VH.js.map → chunk-LD53WPMU.js.map} +0 -0
- /package/dist/{chunk-NE5WFIY6.js.map → chunk-LLUGAZ3B.js.map} +0 -0
- /package/dist/{chunk-KNN6DAZQ.js.map → chunk-N77ELJ2D.js.map} +0 -0
- /package/dist/{chunk-HTIL3EJK.js.map → chunk-NXRUUDUF.js.map} +0 -0
- /package/dist/{chunk-JV7Q226R.js.map → chunk-OHTOPFRF.js.map} +0 -0
- /package/dist/{chunk-ZG2WP425.js.map → chunk-ORAK3LNZ.js.map} +0 -0
- /package/dist/{chunk-NERBFQNY.js.map → chunk-PD42EYQN.js.map} +0 -0
- /package/dist/{chunk-NO4DWSQE.js.map → chunk-PKMQK2WY.js.map} +0 -0
- /package/dist/{chunk-V5EGLYXE.js.map → chunk-QD6QP5P6.js.map} +0 -0
- /package/dist/{chunk-IPJD7WJH.js.map → chunk-QR67FU5G.js.map} +0 -0
- /package/dist/{chunk-ZE7GJ76H.js.map → chunk-SQNED75R.js.map} +0 -0
- /package/dist/{chunk-5CEJH5ZN.js.map → chunk-TAPSNIMA.js.map} +0 -0
- /package/dist/{chunk-PMBWVQ7D.js.map → chunk-TQT3USCG.js.map} +0 -0
- /package/dist/{chunk-LLLG5PTE.js.map → chunk-TXQCG6SE.js.map} +0 -0
- /package/dist/{chunk-6S5ZI7HB.js.map → chunk-UIXXE6NI.js.map} +0 -0
- /package/dist/{chunk-T3XSXAWK.js.map → chunk-UO5ZHIIH.js.map} +0 -0
- /package/dist/{chunk-PPVCZPYA.js.map → chunk-VCLGUIFM.js.map} +0 -0
- /package/dist/{chunk-5J4OL7TJ.js.map → chunk-VUA4S7P6.js.map} +0 -0
- /package/dist/{chunk-HAPVZ4CO.js.map → chunk-WQYSDPMA.js.map} +0 -0
- /package/dist/{chunk-O6SEGJNV.js.map → chunk-YEE5KJQQ.js.map} +0 -0
- /package/dist/{chunk-WRFKZEO6.js.map → chunk-YQGHW5ML.js.map} +0 -0
- /package/dist/{chunk-DG3ORVCU.js.map → chunk-Z4RU5VPM.js.map} +0 -0
- /package/dist/{chunk-DCWIQFNA.js.map → chunk-ZUDM75KG.js.map} +0 -0
- /package/dist/{graph-edge-decay-QDDC6AVV.js.map → graph-edge-decay-QPRJQ7DL.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/extraction-faithfulness.ts"],"sourcesContent":["/**\n * Extraction faithfulness gate — entailment verification of extracted\n * facts against their verified source spans (issue #1576).\n *\n * Part of #1572 (glass-box memory). Depends on #1575 (provenance spans).\n * Improved later by #1585 (fine-tuned local gate model).\n *\n * Unlike the extraction-judge (#376), which gates on fact-worthiness\n * (\"is this worth remembering?\"), this module gates on faithfulness\n * (\"is this fact actually supported by what was said?\"). Hallucinated\n * or mis-paraphrased extractions become durable memories, get recalled\n * with confidence, and poison downstream answers — the highest-severity\n * accuracy failure a memory system can have.\n *\n * Design constraints:\n * - Tagged results only — a backend failure is distinguishable from a\n * genuine verdict (rule 34 / checklist §22: never conflate \"empty\"\n * with \"failed\").\n * - The gate consumes the QUOTE, not the whole conversation — passing\n * full turns re-introduces the hallucination surface you're guarding\n * and blows the latency budget.\n * - No module-level state — batch queues hang off the orchestrator\n * instance (rule 11).\n * - Byte-identical pre-feature pipeline when mode is \"off\" (rule 39).\n * - Graceful degradation — a backend failure never blocks writes\n * (checklist §4); the fact proceeds with verdict \"unchecked\".\n * - Never mutate mw_*\\/trust fields — faithfulness is its own\n * frontmatter island consumed by #1577.\n */\n\nimport { createHash } from \"node:crypto\";\n\nimport { log } from \"./logger.js\";\nimport type { PluginConfig, MemoryFrontmatter, FaithfulnessFrontmatter } from \"./types.js\";\nimport type { LocalLlmClient } from \"./local-llm.js\";\nimport { type FallbackLlmClient, gatewayTaskChainOptions } from \"./fallback-llm.js\";\nimport { extractJsonCandidates } from \"./json-extract.js\";\n\n// Re-export for callers importing from this module.\nexport type { FaithfulnessFrontmatter } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * The three entailment verdicts. \"unchecked\" is NOT a member of this union —\n * it lives only in the frontmatter to mark facts that bypassed the gate\n * (backend failure, no span, mode=off). A real verdict is always one of\n * these three.\n */\nexport type FaithfulnessVerdict = \"entailed\" | \"contradicted\" | \"unsupported\";\n\n/**\n * Failure codes for tagged-error results. Each is distinguishable so callers\n * and telemetry can tell \"no span available\" from \"the backend was down\".\n */\nexport type FaithfulnessFailureCode =\n | \"no_span\"\n | \"backend_unavailable\"\n | \"malformed_output\"\n | \"timeout\";\n\n/**\n * Tagged result — success carries the verdict + model + optional rationale;\n * failure carries only the error code. Never conflate the two (rule 34).\n */\nexport type FaithfulnessResult =\n | { ok: true; verdict: FaithfulnessVerdict; model: string; rationale?: string }\n | { ok: false; error: { code: FaithfulnessFailureCode } };\n\n/**\n * Input for a single faithfulness check. The quote is the verified span from\n * #1575; context is optional surrounding turn text (bounded by\n * faithfulnessContextChars).\n */\nexport interface FaithfulnessCheckInput {\n factText: string;\n quote: string;\n context?: string;\n}\n\n/**\n * Shape persisted to frontmatter (issue #1576). Serialized as a single JSON\n * line so it round-trips through the existing YAML parser without special\n * handling.\n *\n * `verdict: \"unchecked\"` marks a fact that entered the gate but could not be\n * evaluated (backend failure, timeout). It is distinct from the field being\n * absent (which means the gate was off entirely or the fact predates #1576).\n */\n\n// ---------------------------------------------------------------------------\n// Sealed prompt (hash lets #1573-style caching key on it)\n// ---------------------------------------------------------------------------\n\nconst FAITHFULNESS_SYSTEM_PROMPT = `You are a faithfulness verifier for a memory system. Given a QUOTE from the source conversation and an extracted FACT, determine whether the FACT is supported by the QUOTE.\n\nRules:\n- \"entailed\" — the FACT directly follows from the QUOTE (paraphrase is OK).\n- \"contradicted\" — the FACT asserts something the QUOTE directly negates.\n- \"unsupported\" — the FACT introduces information not present in the QUOTE.\n\nAnswer with a JSON array. Each element: {\"index\": <int>, \"verdict\": \"entailed\"|\"contradicted\"|\"unsupported\", \"rationale\": \"<one sentence>\"}.\n\nExamples:\nQUOTE: \"I've been using Vim for about ten years now.\"\nFACT: \"The user has used Vim for approximately a decade.\"\n{\"index\": 0, \"verdict\": \"entailed\", \"rationale\": \"Paraphrase of the same duration.\"}\n\nQUOTE: \"I've been using Vim for about ten years now.\"\nFACT: \"The user prefers Emacs.\"\n{\"index\": 0, \"verdict\": \"unsupported\", \"rationale\": \"QUOTE mentions Vim, not Emacs.\"}\n\nQUOTE: \"I stopped drinking coffee last month.\"\nFACT: \"The user drinks coffee regularly.\"\n{\"index\": 0, \"verdict\": \"contradicted\", \"rationale\": \"QUOTE says they stopped; FACT claims the opposite.\"}`;\n\n/**\n * SHA-256 hash of the sealed system prompt. Cache keys and prompt-version\n * telemetry include this so a prompt change is detectable (#1573-style\n * caching can invalidate stale entries).\n */\nexport const FAITHFULNESS_PROMPT_HASH = createHash(\"sha256\")\n .update(FAITHFULNESS_SYSTEM_PROMPT)\n .digest(\"hex\");\n\n// ---------------------------------------------------------------------------\n// Prompt construction\n// ---------------------------------------------------------------------------\n\n/**\n * Build the user-prompt payload for a batch of (fact, quote) pairs.\n * Each pair is numbered so the LLM's response can be correlated back.\n *\n * Context (when provided) is included after the quote, clearly delimited,\n * so the model knows it is supplementary — not the primary evidence.\n */\nfunction buildBatchPrompt(inputs: FaithfulnessCheckInput[], contextChars: number): string {\n const parts: string[] = [];\n for (let i = 0; i < inputs.length; i++) {\n const inp = inputs[i];\n if (!inp) continue;\n // The QUOTE is the primary entailment evidence and is already bounded\n // upstream (provenance.maxQuoteChars / locateFactQuote). Send it whole\n // so the verifier never judges entailment on a truncated span. Only the\n // supplementary CONTEXT is bounded by contextChars (the config's \"window\n // around the quote\" semantics — cursor review).\n parts.push(`--- Item ${i} ---\nQUOTE: \"${inp.quote}\"`);\n if (inp.context && inp.context.trim().length > 0) {\n parts.push(`CONTEXT: \"${inp.context.slice(0, contextChars)}\"`);\n }\n parts.push(`FACT: \"${inp.factText}\"\n\nRespond with the JSON array entry for index ${i}.`);\n }\n parts.push(`\\nRespond now with a single JSON array covering indexes 0–${inputs.length - 1}.`);\n return parts.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Response parsing\n// ---------------------------------------------------------------------------\n\nconst VALID_VERDICTS = new Set<string>([\"entailed\", \"contradicted\", \"unsupported\"]);\n\n/**\n * Parsed LLM response entry — validated shape.\n */\ninterface ParsedFaithfulnessEntry {\n index: number;\n verdict: FaithfulnessVerdict;\n rationale?: string;\n}\n\n/**\n * Parse the LLM response into a map of index → entry. Returns null when the\n * response is unparseable (rules 13/18: malformed output → tagged error,\n * never a crash).\n *\n * Accepts both a bare JSON array and a JSON array embedded in surrounding\n * text (the `extractJsonCandidates` helper handles fenced blocks and\n * code-block-wrapped JSON).\n */\nexport function parseFaithfulnessResponse(\n raw: string,\n expectedCount: number,\n): Map<number, ParsedFaithfulnessEntry> | null {\n if (!raw || typeof raw !== \"string\" || raw.trim().length === 0) {\n return null;\n }\n\n const candidates = extractJsonCandidates(raw);\n for (const candidate of candidates) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(candidate);\n } catch {\n continue;\n }\n const map = parseEntries(parsed, expectedCount);\n if (map) return map;\n }\n\n // Last resort: the model may have returned bare objects without array\n // wrapping — try parsing the whole thing as a single entry.\n try {\n const single = JSON.parse(raw.trim());\n const map = parseEntries([single], expectedCount);\n if (map) return map;\n } catch {\n // fall through\n }\n\n return null;\n}\n\nfunction parseEntries(\n data: unknown,\n expectedCount: number,\n): Map<number, ParsedFaithfulnessEntry> | null {\n if (!Array.isArray(data)) {\n // Accept object-wrapped arrays — {\"results\":[...]}, {\"verdicts\":[...]},\n // {\"entries\":[...]}, {\"facts\":[...]} — a common JSON-object prompt shape the\n // extraction-judge parser already accepts. Rejecting them marked the whole\n // batch malformed_output; in enforce mode those facts then wrote as ACTIVE\n // (gate bypass — codex Ob4RO).\n if (data && typeof data === \"object\") {\n for (const key of [\"results\", \"verdicts\", \"entries\", \"facts\"]) {\n const inner = (data as Record<string, unknown>)[key];\n if (Array.isArray(inner)) return parseEntries(inner, expectedCount);\n }\n }\n return null;\n }\n const map = new Map<number, ParsedFaithfulnessEntry>();\n for (const entry of data) {\n if (!entry || typeof entry !== \"object\") continue;\n const obj = entry as Record<string, unknown>;\n const idx = typeof obj.index === \"number\" ? obj.index : undefined;\n if (idx === undefined || idx < 0 || idx >= expectedCount) continue;\n const verdictRaw = typeof obj.verdict === \"string\" ? obj.verdict : undefined;\n if (!verdictRaw || !VALID_VERDICTS.has(verdictRaw)) continue;\n const rationale =\n typeof obj.rationale === \"string\" && obj.rationale.trim().length > 0\n ? obj.rationale.trim().slice(0, 500)\n : undefined;\n map.set(idx, {\n index: idx,\n verdict: verdictRaw as FaithfulnessVerdict,\n rationale,\n });\n }\n return map.size > 0 ? map : null;\n}\n\n// ---------------------------------------------------------------------------\n// LLM call helper\n// ---------------------------------------------------------------------------\n\ninterface LlmCallResult {\n content: string | null;\n modelUsed: string | null;\n}\n\n/**\n * Call the model routing chain (local → fallback) with the faithfulness\n * classification prompt. Mirrors `callJudgeLlm` in extraction-judge.ts so\n * the same routing, model-override, and gateway-chain logic applies.\n *\n * Returns the raw content string and the model that produced it, or\n * `{ content: null, modelUsed: null }` when every backend is unavailable.\n */\nasync function callFaithfulnessLlm(\n systemPrompt: string,\n userPrompt: string,\n config: PluginConfig,\n localLlm: LocalLlmClient | null,\n fallbackLlm: FallbackLlmClient | null,\n timeoutMs: number,\n signal?: AbortSignal,\n): Promise<LlmCallResult> {\n const messages: Array<{ role: \"system\" | \"user\"; content: string }> = [\n { role: \"system\", content: systemPrompt },\n { role: \"user\", content: userPrompt },\n ];\n\n const modelOverride = config.extractionFaithfulnessModel || undefined;\n\n // Skip the local backend when (a) modelSource is \"gateway\", or (b) a\n // faithfulness model override is set. The local client always sends\n // config.localLlmModel and silently ignores options.model, so a local\n // success would run the wrong model and prevent the override from ever\n // reaching the gateway. Routing straight to the gateway honors the\n // override (codex review PRRT_kwDORJXyws6ObYQ8).\n const skipLocal = config.modelSource === \"gateway\" || Boolean(modelOverride);\n const gatewayChain = gatewayTaskChainOptions(config);\n\n let modelUsed: string | null = null;\n\n // Try local LLM first (only when no override routes the call to gateway).\n // The local client uses its OWN per-attempt AbortController keyed on\n // `timeoutMs` (it does not read options.signal — see LocalLlmChatCompletionOptions),\n // so the batch `signal` is not forwarded here; `timeoutMs` bounds each local\n // attempt instead. The batch AbortController still governs the fallback path,\n // which DOES honor the signal. (Matches extraction-judge.ts, which also passes\n // no signal to the local client.)\n if (localLlm && !skipLocal) {\n try {\n const result = await callLocalLlm(localLlm, messages, {\n temperature: 0.1,\n maxTokens: 2048,\n responseFormat: { type: \"json_object\" },\n timeoutMs,\n operation: \"extraction-faithfulness\",\n });\n if (result.content) {\n return { content: result.content, modelUsed: result.modelUsed ?? \"local\" };\n }\n } catch (err) {\n log.debug(\n `extraction-faithfulness: local LLM failed, trying fallback: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n // Try fallback LLM\n if (fallbackLlm) {\n try {\n const result = await fallbackLlm.chatCompletion(\n messages as Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n {\n temperature: 0.1,\n maxTokens: 2048,\n timeoutMs,\n ...(modelOverride ? { model: modelOverride } : {}),\n ...gatewayChain,\n ...(signal ? { signal } : {}),\n },\n );\n if (result?.content) {\n return { content: result.content, modelUsed: result.modelUsed ?? \"fallback\" };\n }\n } catch (err) {\n log.debug(\n `extraction-faithfulness: fallback LLM failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n return { content: null, modelUsed: null };\n}\n\n/**\n * Call the local LLM's chatCompletion through a narrow, typed interface.\n * LocalLlmClient.chatCompletion accepts an options bag that includes an\n * `operation` discriminator; we forward it so the client can route\n * appropriately.\n */\ninterface LocalLlmCallOptions {\n temperature: number;\n maxTokens: number;\n responseFormat?: { type: string };\n timeoutMs: number;\n operation: string;\n}\n\ninterface LocalLlmCallResponse {\n content: string | null;\n modelUsed?: string;\n}\n\nasync function callLocalLlm(\n client: LocalLlmClient,\n messages: Array<{ role: \"system\" | \"user\"; content: string }>,\n options: LocalLlmCallOptions,\n): Promise<LocalLlmCallResponse> {\n // LocalLlmClient.chatCompletion returns { content: string } — we use a\n // typed call signature so no `any` leaks. The local client does not\n // expose modelUsed, so we return \"local\" as the model identifier.\n const result = await client.chatCompletion(messages, options);\n return {\n content: result?.content ?? null,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Core batch check\n// ---------------------------------------------------------------------------\n\n/**\n * Result of a batch faithfulness check — per-input results plus timing.\n */\nexport interface FaithfulnessBatchResult {\n results: FaithfulnessResult[];\n /** Wall-clock duration of the LLM call (0 when backend was not called). */\n elapsedMs: number;\n}\n\n/**\n * Evaluate a batch of (fact, quote) pairs for faithfulness.\n *\n * - Calls the LLM once with all pairs (bounded by `inputs.length`).\n * - On timeout → every input gets `{ ok: false, code: \"timeout\" }`.\n * - On backend unavailable → `{ ok: false, code: \"backend_unavailable\" }`.\n * - On malformed output → `{ ok: false, code: \"malformed_output\" }`.\n * - Inputs with empty quotes get `{ ok: false, code: \"no_span\" }` and are\n * NOT sent to the LLM.\n *\n * No module-level state: the caller (orchestrator) owns any caches.\n */\nexport async function checkFaithfulnessBatch(\n inputs: FaithfulnessCheckInput[],\n config: PluginConfig,\n localLlm: LocalLlmClient | null,\n fallbackLlm: FallbackLlmClient | null,\n): Promise<FaithfulnessBatchResult> {\n const timeoutMs =\n typeof config.extractionFaithfulnessTimeoutMs === \"number\" &&\n Number.isFinite(config.extractionFaithfulnessTimeoutMs) &&\n config.extractionFaithfulnessTimeoutMs > 0\n ? config.extractionFaithfulnessTimeoutMs\n : 8000;\n\n const contextChars =\n typeof config.extractionFaithfulnessContextChars === \"number\" &&\n Number.isFinite(config.extractionFaithfulnessContextChars) &&\n config.extractionFaithfulnessContextChars > 0\n ? config.extractionFaithfulnessContextChars\n : 400;\n\n // Partition: inputs with a real quote vs. inputs without one.\n const results: FaithfulnessResult[] = new Array(inputs.length);\n const checkableIndices: number[] = [];\n const checkableInputs: FaithfulnessCheckInput[] = [];\n\n for (let i = 0; i < inputs.length; i++) {\n const inp = inputs[i];\n if (!inp || !inp.quote || inp.quote.trim().length === 0) {\n results[i] = { ok: false, error: { code: \"no_span\" } };\n } else if (!inp.factText || inp.factText.trim().length === 0) {\n results[i] = { ok: false, error: { code: \"no_span\" } };\n } else {\n checkableIndices.push(i);\n checkableInputs.push(inp);\n }\n }\n\n if (checkableInputs.length === 0) {\n return { results, elapsedMs: 0 };\n }\n\n const userPrompt = buildBatchPrompt(checkableInputs, contextChars);\n const startedAt = Date.now();\n\n let timedOut = false;\n const controller = new AbortController();\n // Race the LLM call against the budget so the batch fails open at\n // `timeoutMs` regardless of which backend is in flight. The local backend\n // ignores the batch AbortSignal (it aborts each attempt via its own\n // controller keyed on `timeoutMs`), so awaiting callFaithfulnessLlm directly\n // could block past the budget on a slow/retrying local verifier. The timer\n // both aborts the fallback (which honors the signal) and resolves the race\n // so the batch returns promptly; the in-flight local call aborts on its own\n // per-attempt timeoutMs. (codex review PRRT_kwDORJXyws6ObgMJ.)\n // Manual deferred instead of Promise.withResolvers (ES2024) — plugin-openclaw's\n // standalone tsconfig targets ES2022 lib and this module is reachable from its\n // type graph, so withResolvers would TS2550 there.\n let resolveTimeout!: (value: true) => void;\n const racedTimeout = new Promise<true>((resolve) => {\n resolveTimeout = resolve;\n });\n const timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n resolveTimeout(true);\n }, timeoutMs);\n\n try {\n const callPromise = callFaithfulnessLlm(\n FAITHFULNESS_SYSTEM_PROMPT,\n userPrompt,\n config,\n localLlm,\n fallbackLlm,\n timeoutMs,\n controller.signal,\n );\n const settled = await Promise.race([\n callPromise.then(\n (r) => ({ done: true as const, result: r }),\n // An unexpected throw is treated as \"no content\" (backend_unavailable)\n // so the race never rejects and the outer catch is the final safety net.\n () => ({ done: true as const, result: { content: null, modelUsed: null } }),\n ),\n racedTimeout.then(() => ({ done: false as const })),\n ]);\n const elapsedMs = Date.now() - startedAt;\n\n if (!settled.done) {\n // The budget elapsed before any backend returned content. Fail open as\n // timeout; the orphaned callPromise resolves/rejects harmlessly (the\n // fallback was aborted; the local client aborts on its own timeoutMs).\n for (const idx of checkableIndices) {\n results[idx] = { ok: false, error: { code: \"timeout\" } };\n }\n return { results, elapsedMs };\n }\n\n const llmResult = settled.result;\n\n // Cursor review: if the LLM returned usable content, use it even when\n // the abort timer raced — a response that lands just as the timer fires\n // has real verdicts. Only fall back to timeout errors when there is no\n // content to parse (the call genuinely did not complete in time).\n if (timedOut && !llmResult.content) {\n for (const idx of checkableIndices) {\n results[idx] = { ok: false, error: { code: \"timeout\" } };\n }\n return { results, elapsedMs };\n }\n\n if (!llmResult.content) {\n for (const idx of checkableIndices) {\n results[idx] = { ok: false, error: { code: \"backend_unavailable\" } };\n }\n return { results, elapsedMs };\n }\n\n const parsed = parseFaithfulnessResponse(llmResult.content, checkableInputs.length);\n if (!parsed) {\n for (const idx of checkableIndices) {\n results[idx] = { ok: false, error: { code: \"malformed_output\" } };\n }\n return { results, elapsedMs };\n }\n\n for (let j = 0; j < checkableIndices.length; j++) {\n const inputIdx = checkableIndices[j];\n if (inputIdx === undefined) continue;\n const entry = parsed.get(j);\n if (entry) {\n results[inputIdx] = {\n ok: true,\n verdict: entry.verdict,\n model: llmResult.modelUsed ?? \"unknown\",\n ...(entry.rationale ? { rationale: entry.rationale } : {}),\n };\n } else {\n // LLM response was missing this index\n results[inputIdx] = { ok: false, error: { code: \"malformed_output\" } };\n }\n }\n\n return { results, elapsedMs };\n } catch (err) {\n const elapsedMs = Date.now() - startedAt;\n if (timedOut || isAbortError(err)) {\n for (const idx of checkableIndices) {\n results[idx] = { ok: false, error: { code: \"timeout\" } };\n }\n return { results, elapsedMs };\n }\n log.warn(\n `extraction-faithfulness: batch check threw unexpectedly: ${err instanceof Error ? err.message : String(err)}`,\n );\n for (const idx of checkableIndices) {\n results[idx] = { ok: false, error: { code: \"backend_unavailable\" } };\n }\n return { results, elapsedMs };\n } finally {\n clearTimeout(timer);\n }\n}\n\nfunction isAbortError(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const maybe = err as { name?: string; message?: string };\n return (\n maybe.name === \"AbortError\" ||\n maybe.message === \"This operation was aborted\" ||\n maybe.message === \"The operation was aborted\"\n );\n}\n\n\n// ---------------------------------------------------------------------------\n// Extraction-path orchestration helpers (issue #1576)\n//\n// These wrap checkFaithfulnessBatch + verdict application so the orchestrator\n// holds only thin delegation (ground rule 4: god files gain thin wiring only).\n// The core algorithm lives above; these are call-site glue.\n// ---------------------------------------------------------------------------\n\n/**\n * Telemetry counters for the faithfulness gate. Hang off the orchestrator\n * instance (rule 11: no module-level state) and surface via console_state so\n * `remnic doctor` renders the verdict distribution.\n */\nexport interface FaithfulnessGateCounters {\n entailed: number;\n contradicted: number;\n unsupported: number;\n unchecked: number;\n skippedNoSpan: number;\n}\n\n/**\n * Create a fresh zeroed counters object. Callers store it on the orchestrator\n * instance and pass it by reference to the gate helpers, which mutate it.\n */\nexport function createFaithfulnessCounters(): FaithfulnessGateCounters {\n return { entailed: 0, contradicted: 0, unsupported: 0, unchecked: 0, skippedNoSpan: 0 };\n}\n\n/**\n * Structural slice of an extracted fact that the gate reads. Kept loose so\n * this pure module does not depend on the full `ExtractedFact` type.\n */\nexport interface FaithfulnessGateFact {\n content: string;\n sources?: { quote?: string }[];\n}\n\n/**\n * Run the faithfulness batch over a list of extracted facts and return a map\n * keyed by the ORIGINAL fact index → result. Facts without a usable verified\n * source span are omitted from the batch (they are tagged `skipped_no_span`\n * at apply time, never gated — don't punish legacy data). Updates `counters`\n * for console_state telemetry.\n *\n * Fail-open: any pipeline error is caught, logged, and an empty map returned\n * so the caller records `unchecked`/`skipped_no_span` and proceeds — a gate\n * outage must never block memory writes (checklist §4).\n */\n// Common English stopwords — filtered before overlap scoring so function\n// words (the, a, is, ...) don't dilute the signal between a fact and its\n// source sentence.\nconst STOPWORDS = new Set([\n \"the\",\"a\",\"an\",\"and\",\"or\",\"but\",\"is\",\"are\",\"was\",\"were\",\"be\",\"been\",\"being\",\n \"to\",\"of\",\"in\",\"on\",\"at\",\"for\",\"with\",\"from\",\"by\",\"as\",\"it\",\"its\",\"this\",\"that\",\n \"these\",\"those\",\"i\",\"you\",\"he\",\"she\",\"we\",\"they\",\"my\",\"your\",\"his\",\"her\",\"our\",\"their\",\n \"has\",\"have\",\"had\",\"do\",\"does\",\"did\",\"will\",\"would\",\"can\",\"could\",\"should\",\"not\",\"no\",\n \"s\",\"very\",\"really\",\"just\",\"so\",\"than\",\"then\",\"there\",\"here\",\"about\",\"into\",\"over\",\"under\",\n]);\n\n/**\n * Crude stemmer: strip common suffixes so \"prefers\"/\"prefer\",\n * \"using\"/\"use\", \"started\"/\"start\" collapse to one token. Not a real\n * stemmer — just enough to raise recall for the interim locator (#1575 will\n * replace this with NLI-verified spans).\n */\nfunction crudeStem(word: string): string {\n if (word.length > 5 && word.endsWith(\"ing\")) return word.slice(0, -3);\n if (word.length > 4 && word.endsWith(\"ed\")) return word.slice(0, -2);\n if (word.length > 3 && (word.endsWith(\"s\")) && !word.endsWith(\"ss\")) return word.slice(0, -1);\n return word;\n}\n\n/**\n * Tokenize text into a lowercase, stopword-filtered, crudely-stemmed token\n * set for overlap scoring.\n */\nfunction tokenize(text: string): Set<string> {\n const tokens = new Set<string>();\n for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {\n if (raw.length <= 1) continue;\n if (STOPWORDS.has(raw)) continue;\n tokens.add(crudeStem(raw));\n }\n return tokens;\n}\n\n/**\n * Overlap coefficient: |A ∩ B| / min(|A|, |B|). Robust for paraphrase\n * matching where a short fact paraphrases a longer source sentence — a short\n * fact fully supported by a long sentence scores high, unrelated text scores 0.\n */\nfunction overlapCoefficient(a: Set<string>, b: Set<string>): number {\n if (a.size === 0 || b.size === 0) return 0;\n let intersection = 0;\n const [small, large] = a.size <= b.size ? [a, b] : [b, a];\n for (const t of small) if (large.has(t)) intersection++;\n return intersection / small.size;\n}\n\n/**\n * Minimum overlap for a located quote to be trusted as the fact's source span.\n * Below this the fact is treated as having no located span (skipped_no_span)\n * rather than judged against an unrelated sentence.\n */\nconst LOCATE_QUOTE_MIN_OVERLAP = 0.3;\n\n/**\n * Locate the best-matching verbatim span (sentence) from the source turn text\n * for an extracted fact, by token overlap. Returns the quote when a confident\n * match is found, `undefined` otherwise (the gate then records\n * skipped_no_span — never judged against an unrelated span).\n *\n * This is the interim source-text locator that makes the gate functional on\n * real extraction output (where #1575 has not yet attached per-fact\n * `sources`). It produces a genuine located quote per fact so the gate\n * consumes a span, not the whole conversation (issue #1576 design constraint).\n * #1575's NLI-verified per-fact locator will replace this when it lands.\n */\n/**\n * Extract a bounded window of `sourceText` centered on the located `quote`,\n * bounded by `contextChars` (the config's \"window around the quote\" semantics).\n *\n * Used by the fallback-locator path in `runFaithfulnessGateBatch` so the\n * verifier sees surrounding turn text — without it,\n * `extractionFaithfulnessContextChars` is effectively ignored in the\n * production path (codex review PRRT_kwDORJXyws6OblI1). Returns undefined when\n * the quote is absent from sourceText (e.g. it was truncated by maxQuoteChars).\n */\nexport function extractContextWindow(\n sourceText: string,\n quote: string,\n contextChars: number,\n /**\n * Character offset of `quote` within `sourceText` (from `locateFactQuote`).\n * When the quote string appears more than once — e.g. repeated anaphoric\n * lines after different entities — pass the matched occurrence's offset so\n * the context window centers on the actual match, not the first occurrence\n * (issue #1633, codex PRRT_kwDORJXyws6ObzwI). Defaults to the first\n * occurrence via indexOf for backward compatibility.\n */\n matchedOffset?: number,\n): string | undefined {\n if (!sourceText || !quote || !(contextChars > 0)) return undefined;\n let idx: number;\n if (matchedOffset !== undefined && matchedOffset >= 0) {\n const at = sourceText.indexOf(quote, matchedOffset);\n // Fall back to the first occurrence if the matched offset is stale (e.g.\n // the quote was bounded and is not a literal substring at that offset).\n idx = at >= 0 ? at : sourceText.indexOf(quote);\n } else {\n idx = sourceText.indexOf(quote);\n }\n if (idx < 0) return undefined;\n const quoteEnd = idx + quote.length;\n const center = Math.floor((idx + quoteEnd) / 2);\n const half = Math.floor(contextChars / 2);\n let start = Math.max(0, center - half);\n const end = Math.min(sourceText.length, start + contextChars);\n // Re-anchor start so the window uses the full budget when end clamped.\n start = Math.max(0, end - contextChars);\n const window = sourceText.slice(start, end).trim();\n return window.length > 0 ? window : undefined;\n}\n\n/**\n * A located fallback quote plus the offset it begins at in the source text.\n * `offset` lets `extractContextWindow` disambiguate repeated occurrences of\n * the same quote string (issue #1633).\n */\nexport interface LocatedQuote {\n /** Verbatim source span, centered on the matched terms when truncated. */\n quote: string;\n /** Character offset of `quote` within sourceText. */\n offset: number;\n}\n\ninterface SourceCandidate {\n /** Trimmed candidate text. */\n text: string;\n /** Character offset of `text` within the source string. */\n start: number;\n}\n\n/**\n * Split source text into candidate spans (sentences, then line segments as a\n * fallback for transcripts without sentence punctuation), tracking each\n * candidate's start offset so repeated occurrences can be disambiguated\n * (issue #1633).\n */\nfunction splitSourceCandidates(sourceText: string): SourceCandidate[] {\n const candidates: SourceCandidate[] = [];\n const re = /(?<=[.!?])\\s+|\\n+/g;\n let lastEnd = 0;\n let m: RegExpExecArray | null;\n while ((m = re.exec(sourceText)) !== null) {\n pushCandidate(candidates, sourceText, lastEnd, m.index);\n lastEnd = re.lastIndex;\n }\n pushCandidate(candidates, sourceText, lastEnd, sourceText.length);\n return candidates;\n}\n\nfunction pushCandidate(\n out: SourceCandidate[],\n source: string,\n begin: number,\n end: number,\n): void {\n const seg = source.slice(begin, end);\n const leadingWS = seg.length - seg.trimStart().length;\n const trimmed = seg.trim();\n if (trimmed.length > 0) {\n out.push({ text: trimmed, start: begin + leadingWS });\n }\n}\n\n/**\n * Locate the start offsets of fact-token matches inside a candidate. Used by\n * `locateFactQuote` to build a bounded window that is guaranteed to contain\n * real evidence (issue #1633, codex PRRT_kwDORJXyws6Obrwe / PRRT_kwDORJXyws6Oce-O).\n */\nfunction locateMatchedTokens(factTokens: Set<string>, candidate: string): number[] {\n const lower = candidate.toLowerCase();\n const wordRe = /[a-z0-9]+/g;\n const positions: number[] = [];\n let m: RegExpExecArray | null;\n while ((m = wordRe.exec(lower)) !== null) {\n const word = m[0];\n if (word.length <= 1) continue;\n if (STOPWORDS.has(word)) continue;\n if (factTokens.has(crudeStem(word))) {\n positions.push(m.index);\n }\n }\n return positions;\n}\n\n/**\n * Build a bounded window of `maxChars` from `text` that contains the densest\n * cluster of matched-token positions. Slides a maxChars-wide window anchored\n * at each matched token and keeps the one that captures the most matches\n * (tiebreak: earliest anchor). This guarantees the window includes real\n * evidence even when the matched span is wider than `maxChars` (e.g. fact\n * tokens at both ends of a long sentence with filler between). Centering on\n * the densest cluster's midpoint then uses any leftover budget as leading and\n * trailing context. Returns the window text and its start offset within `text`\n * so callers can translate the in-candidate offset back to a source-text offset.\n */\nfunction boundedWindow(\n text: string,\n matchedPositions: number[],\n maxChars: number,\n): { text: string; start: number } {\n if (matchedPositions.length === 0) {\n // Defensive: overlap scoring accepted the candidate but no individual\n // token matched (e.g. all matches were stopwords). Fall back to prefix.\n const end = Math.min(text.length, maxChars);\n return { text: text.slice(0, end), start: 0 };\n }\n // matchedPositions is sorted ascending (the regex scans left-to-right), so a\n // two-pointer sliding window finds the densest maxChars-wide cluster in O(n)\n // instead of O(n^2). This matters when a long unpunctuated candidate carries\n // many repeats of a fact token (pasted logs, minified text) — thousands of\n // positions would otherwise stall extraction before the LLM call (codex\n // PRRT_kwDORJXyws6Ocih3).\n let bestAnchor = matchedPositions[0]!;\n let bestCount = 0;\n let bestLast = bestAnchor;\n let j = 0;\n for (let i = 0; i < matchedPositions.length; i++) {\n const anchor = matchedPositions[i]!;\n const winEnd = anchor + maxChars;\n if (j < i) j = i;\n while (j + 1 < matchedPositions.length && matchedPositions[j + 1]! < winEnd) {\n j++;\n }\n const count = j - i + 1;\n if (count > bestCount) {\n bestCount = count;\n bestAnchor = anchor;\n bestLast = matchedPositions[j]!;\n }\n }\n // The densest cluster [bestAnchor, bestLast] fits within maxChars by\n // construction; center a maxChars window on its midpoint, clamped to text.\n const center = Math.floor((bestAnchor + bestLast) / 2);\n const half = Math.floor(maxChars / 2);\n let start = Math.max(0, center - half);\n const end = Math.min(text.length, start + maxChars);\n // Re-anchor start so the window uses the full budget when end clamped.\n start = Math.max(0, end - maxChars);\n return { text: text.slice(start, end), start };\n}\n\nexport function locateFactQuote(\n factText: string,\n sourceText: string,\n maxQuoteChars = 600,\n): LocatedQuote | undefined {\n if (!factText || !sourceText) return undefined;\n const factTokens = tokenize(factText);\n if (factTokens.size === 0) return undefined;\n const candidates = splitSourceCandidates(sourceText);\n if (candidates.length === 0) return undefined;\n // Pick the best-overlap candidate. Tiebreak equal own-overlap scores by a\n // \"context\" score that includes the immediately PRECEDING candidate's text,\n // so a repeated anaphoric line (\"It launched in March\") resolves to the\n // occurrence whose neighbor names the fact's entity (issue #1633, codex\n // PRRT_kwDORJXyws6Oce-S). Without this tiebreak the first occurrence wins\n // even when the second is the one the fact refers to.\n let best: { candidate: SourceCandidate; score: number; contextScore: number } | null = null;\n for (let i = 0; i < candidates.length; i++) {\n const candidate = candidates[i]!;\n const score = overlapCoefficient(factTokens, tokenize(candidate.text));\n const prevText = i > 0 ? candidates[i - 1]!.text : \"\";\n const contextText = prevText ? `${prevText} ${candidate.text}` : candidate.text;\n const contextScore = overlapCoefficient(factTokens, tokenize(contextText));\n if (\n !best ||\n score > best.score ||\n (score === best.score && contextScore > best.contextScore)\n ) {\n best = { candidate, score, contextScore };\n }\n }\n if (!best || best.score < LOCATE_QUOTE_MIN_OVERLAP) return undefined;\n const { text, start } = best.candidate;\n if (text.length <= maxQuoteChars) {\n return { quote: text, offset: start };\n }\n // Build a bounded window around the densest cluster of matched terms instead\n // of returning the first maxQuoteChars prefix (issue #1633). Returning the\n // prefix drops supporting words that fall after char ~600, routing\n // actually-entailed facts to pending_review in enforce mode.\n const matched = locateMatchedTokens(factTokens, text);\n const win = boundedWindow(text, matched, maxQuoteChars);\n return { quote: win.text, offset: start + win.start };\n}\nexport async function runFaithfulnessGateBatch(\n facts: readonly FaithfulnessGateFact[],\n mode: \"shadow\" | \"enforce\",\n config: PluginConfig,\n localLlm: LocalLlmClient | null,\n fallbackLlm: FallbackLlmClient | null,\n counters: FaithfulnessGateCounters,\n /**\n * The verbatim source turn text the facts were extracted from. When a fact\n * has no #1575 `sources`, locateFactQuote finds a fallback span here so the\n * gate runs on real extraction output. May be empty (replay/import paths\n * with no source turns) — facts then get skipped_no_span.\n */\n sourceText = \"\",\n): Promise<Map<number, FaithfulnessResult> | null> {\n // Phase 1 — build the checkable inputs. This is pure (no LLM, no throw):\n // locateFactQuote and the source/span selection never reject. Keeping it\n // outside the try lets the catch walk the same inputs to tag a pipeline\n // failure as \"unchecked\" rather than dropping it on the floor.\n const inputs: { factIndex: number; input: FaithfulnessCheckInput }[] = [];\n for (let fi = 0; fi < facts.length; fi++) {\n const f = facts[fi];\n if (!f || typeof f.content !== \"string\" || !f.content.trim()) continue;\n // Prefer #1575 verified spans; fall back to a located quote from the\n // source turn text so the gate runs even before per-fact sources are\n // attached. Without either, the fact is skipped_no_span (never gated).\n // A composite fact may be supported by multiple adjacent spans — collect\n // every valid source quote so the verifier sees the full evidence, not\n // just sources[0] (codex review PRRT_kwDORJXyws6ObYQ_).\n const sources = Array.isArray(f.sources) ? f.sources : [];\n const sourceQuotes = sources\n .map((s) => (s && typeof s.quote === \"string\" ? s.quote.trim() : \"\"))\n .filter((q) => q.length > 0);\n const usingFallbackLocator = sourceQuotes.length === 0;\n let quote: string;\n let matchedOffset: number | undefined;\n if (sourceQuotes.length > 0) {\n quote = sourceQuotes.join(\"\\n\");\n } else {\n // Fallback locator returns the matched span AND its offset so the\n // context window centers on the occurrence that actually matched the\n // fact, not the first indexOf hit (issue #1633).\n const located = locateFactQuote(f.content, sourceText);\n if (!located) continue; // no located span — applyFaithfulnessVerdict tags skipped_no_span\n quote = located.quote;\n matchedOffset = located.offset;\n }\n // Pass source context into the verifier so extractionFaithfulnessContextChars\n // actually applies in the fallback-locator path (codex P2\n // PRRT_kwDORJXyws6OblI1). #1575 verified spans already carry full evidence,\n // so context is only synthesized for the fallback locator.\n const fallbackContext =\n usingFallbackLocator\n ? extractContextWindow(\n sourceText,\n quote,\n config.extractionFaithfulnessContextChars,\n matchedOffset,\n )\n : undefined;\n inputs.push({\n factIndex: fi,\n input: {\n factText: f.content,\n quote,\n ...(fallbackContext ? { context: fallbackContext } : {}),\n },\n });\n }\n const resultsByFactIndex = new Map<number, FaithfulnessResult>();\n if (inputs.length === 0) return resultsByFactIndex;\n try {\n const batch = await checkFaithfulnessBatch(\n inputs.map((x) => x.input),\n config,\n localLlm,\n fallbackLlm,\n );\n for (let j = 0; j < inputs.length; j++) {\n const entry = inputs[j];\n if (entry) resultsByFactIndex.set(entry.factIndex, batch.results[j]!);\n }\n // Verdict-distribution counters are bumped in applyFaithfulnessVerdict\n // (at the per-fact apply point), NOT here, so console_state reflects\n // facts that actually reached verdict application — not facts later\n // dropped by dedup, importance, or judge gates (cursor review).\n log.info(\n `extraction-faithfulness[${mode}]: ${inputs.length} facts checked, ${batch.elapsedMs}ms`,\n );\n } catch (err) {\n // Fail-open: a pipeline error never blocks writes (checklist §4). Tag each\n // checkable fact as backend_unavailable so applyFaithfulnessVerdict records\n // \"unchecked\" (issue spec: backend failure → unchecked). Returning null\n // here would make a shadow/enforce batch failure indistinguishable from\n // gate-off, losing the telemetry signal (cursor review). Facts with no\n // located span stay absent → skipped_no_span at apply time.\n log.warn(\n `extraction-faithfulness: pipeline error, tagging ${inputs.length} checkable facts as unchecked (fail-open): ${err instanceof Error ? err.message : String(err)}`,\n );\n for (const { factIndex } of inputs) {\n resultsByFactIndex.set(factIndex, { ok: false, error: { code: \"backend_unavailable\" } });\n }\n }\n return resultsByFactIndex;\n}\n\n/**\n * Apply a pre-computed faithfulness verdict to a single fact, producing the\n * frontmatter record + an optional enforce-mode `pending_review` status.\n *\n * - `resultsByFactIndex` null (gate off) → nothing (rule 39: byte-identical).\n * - fact has no entry (no verified span) → `skipped_no_span`, never gated.\n * - backend failure (`ok: false`) → `unchecked`, fact proceeds (checklist §4).\n * - enforce + unsupported/contradicted → `pending_review` (memory persists,\n * enters the review queue, never silently dropped).\n *\n * Mutates `counters.skippedNoSpan` for facts without a span.\n */\nexport function applyFaithfulnessVerdict(\n resultsByFactIndex: Map<number, FaithfulnessResult> | null,\n factLoopIndex: number,\n mode: \"off\" | \"shadow\" | \"enforce\",\n factContent: string,\n counters: FaithfulnessGateCounters,\n): {\n faithfulness: FaithfulnessFrontmatter | undefined;\n enforceStatus: \"pending_review\" | undefined;\n} {\n if (!resultsByFactIndex) {\n return { faithfulness: undefined, enforceStatus: undefined };\n }\n const result = resultsByFactIndex.get(factLoopIndex);\n if (!result) {\n // No result for this fact index — it had no verified source span.\n counters.skippedNoSpan++;\n return {\n faithfulness: { verdict: \"skipped_no_span\", at: new Date().toISOString() },\n enforceStatus: undefined,\n };\n }\n if (result.ok) {\n if (result.verdict === \"entailed\") counters.entailed++;\n else if (result.verdict === \"contradicted\") counters.contradicted++;\n else if (result.verdict === \"unsupported\") counters.unsupported++;\n const fm: FaithfulnessFrontmatter = {\n verdict: result.verdict,\n ...(result.model ? { model: result.model } : {}),\n ...(result.rationale ? { rationale: result.rationale } : {}),\n at: new Date().toISOString(),\n };\n let enforceStatus: \"pending_review\" | undefined;\n if (\n mode === \"enforce\" &&\n (result.verdict === \"unsupported\" || result.verdict === \"contradicted\")\n ) {\n enforceStatus = \"pending_review\";\n log.info(\n `extraction-faithfulness[enforce]: routing \"${factContent.slice(0, 60)}…\" to pending_review (verdict=${result.verdict})`,\n );\n }\n return { faithfulness: fm, enforceStatus };\n }\n // Backend failure — record as unchecked, fact proceeds (graceful degradation).\n counters.unchecked++;\n return {\n faithfulness: { verdict: \"unchecked\", at: new Date().toISOString() },\n enforceStatus: undefined,\n };\n}\n// ---------------------------------------------------------------------------\n// Frontmatter serialization (single-line JSON, like provenance sources)\n// ---------------------------------------------------------------------------\n\n/**\n * Canonical key order for the serialized `faithfulness` frontmatter field.\n * Deterministic emission (rule 38).\n */\nconst FAITHFULNESS_KEY_ORDER = [\"verdict\", \"model\", \"rationale\", \"at\"] as const;\n\n/**\n * Serialize the `faithfulness` frontmatter field as a single JSON line,\n * appended to `lines`. Called from `serializeFrontmatter` in storage.ts.\n *\n * Contract: when `fm.faithfulness` is absent, no line is emitted — the\n * memory round-trips byte-identical to pre-#1576 behavior (rule 39).\n */\nexport function serializeFaithfulnessFields(fm: MemoryFrontmatter, lines: string[]): void {\n if (!fm.faithfulness) return;\n const fm2 = fm.faithfulness;\n const canonical: Record<string, unknown> = {};\n for (const key of FAITHFULNESS_KEY_ORDER) {\n const val = fm2[key];\n if (val !== undefined && val !== null && val !== \"\") {\n canonical[key] = val;\n }\n }\n // verdict is always present on a valid FaithfulnessFrontmatter\n if (!canonical.verdict) return;\n lines.push(`faithfulness: ${JSON.stringify(canonical)}`);\n}\n\n/**\n * Parse the `faithfulness` frontmatter line from its single-line JSON form.\n * Returns `undefined` for missing, blank, or corrupt values so a malformed\n * frontmatter never poisons downstream readers (rule 34 spirit).\n */\nexport function parseFaithfulnessField(\n raw: string | undefined,\n): FaithfulnessFrontmatter | undefined {\n if (!raw || typeof raw !== \"string\" || raw.trim().length === 0) return undefined;\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined;\n const obj = parsed as Record<string, unknown>;\n const verdictRaw = typeof obj.verdict === \"string\" ? obj.verdict : undefined;\n if (!verdictRaw) return undefined;\n const validVerdicts = new Set([\n \"entailed\",\n \"contradicted\",\n \"unsupported\",\n \"unchecked\",\n \"skipped_no_span\",\n ]);\n if (!validVerdicts.has(verdictRaw)) return undefined;\n const result: FaithfulnessFrontmatter = {\n verdict: verdictRaw as FaithfulnessFrontmatter[\"verdict\"],\n };\n if (typeof obj.model === \"string\" && obj.model.length > 0) {\n result.model = obj.model;\n }\n if (typeof obj.rationale === \"string\" && obj.rationale.length > 0) {\n result.rationale = obj.rationale.slice(0, 500);\n }\n if (typeof obj.at === \"string\" && obj.at.length > 0) {\n result.at = obj.at;\n }\n return result;\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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","names":["end"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provenance.ts","../src/connectors/coerce.ts"],"sourcesContent":["/**\n * Claim-level provenance spans (issue #1575 PR 1).\n *\n * Centralizes the parse/serialize logic for the `sources` array and the\n * coarse `provenance` strength tag, plus the `provenance` config-block\n * parser. Extracted from `storage.ts` and `config.ts` so those files keep\n * only thin delegation call-sites — the frontmatter round-trip and config\n * parsing growth lives here (issue #1520 ratchet discipline).\n *\n * Contract:\n * - When no provenance fields are present, output is byte-identical to\n * pre-feature behavior (rule 39).\n * - Corrupt `sources` lines / unknown `provenance` tags drop to\n * `undefined` on read so a malformed frontmatter never poisons\n * downstream readers (rule 34 spirit — drop corrupt rather than poison).\n * - Validation lives on the write path; this module only parses.\n * - Invariant (review round 5, cursor thread KQN): a `verified` or\n * `unverified` tag is NEVER persisted/read without surviving sources —\n * without excerpts the tag is indistinguishable from a grounded fact to\n * downstream faithfulness/correction/TrustScore surfaces. The invariant\n * is enforced symmetrically: `serializeProvenanceFields` (write) and\n * `reconcileProvenanceRead` (read) both downgrade the tag to `none`\n * when no source survives.\n */\n\nimport { z } from \"zod\";\n\nimport { coerceBool, coerceNumber } from \"./connectors/coerce.js\";\nimport { readEnvVar } from \"./runtime/env.js\";\nimport { collapseWhitespace } from \"./whitespace.js\";\nimport type { MemoryFrontmatter, ProvenanceConfig, ProvenanceSource } from \"./types.js\";\nimport { isSafeMemoryContent } from \"./sanitize.js\";\n\n/**\n * Canonical key order for a serialized `ProvenanceSource` (issue #1575).\n * Deterministic emission (rule 38) — readers and the byte-identical-when-off\n * contract (rule 39) depend on this order never drifting.\n */\nconst PROVENANCE_SOURCE_KEY_ORDER = [\n \"sessionKey\",\n \"turnId\",\n \"observedAt\",\n \"quote\",\n \"charStart\",\n \"charEnd\",\n] as const;\n\n/**\n * Build a single `ProvenanceSource` object whose keys appear in the canonical\n * order, omitting absent optional fields. The result is what gets fed to\n * `JSON.stringify` so the on-disk line is deterministic.\n */\nfunction canonicalProvenanceSource(src: ProvenanceSource): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const key of PROVENANCE_SOURCE_KEY_ORDER) {\n const value = src[key];\n if (value !== undefined) out[key] = value;\n }\n return out;\n}\n\n/**\n * Serialize the `sources` array (issue #1575) as a single JSON line, matching\n * the `structuredAttributes` precedent. Each entry is rebuilt in canonical key\n * order (rule 38) so the output is byte-stable. The `provenance` enum is\n * emitted bare (same style as `status`) — only the three documented values\n * are ever written.\n *\n * Verified-requires-evidence invariant (review round 5, cursor thread KQN):\n * a `verified`/`unverified` tag is downgraded to `none` whenever no source\n * survives write validation — whether sources were absent, an empty array,\n * or all entries failed the schema. This covers all three failure shapes the\n * earlier 3-branch logic left open (`{provenance:\"verified\"}`,\n * `{sources:[],provenance:\"verified\"}`, `{sources:[invalid…],provenance:\"verified\"}`).\n */\nexport function serializeProvenanceFields(fm: MemoryFrontmatter, lines: string[]): void {\n let hasValidSources = false;\n if (fm.sources && fm.sources.length > 0) {\n // Validate each entry against the same schema used on read so invalid\n // in-memory sources are dropped at write time, not silently lost on the\n // next read (review thread 4 — write-path validation parity).\n const canonical: Record<string, unknown>[] = [];\n for (const src of fm.sources) {\n const result = ProvenanceSourceSchema.safeParse(src);\n if (result.success) canonical.push(canonicalProvenanceSource(result.data));\n }\n if (canonical.length > 0) {\n lines.push(`sources: ${JSON.stringify(canonical)}`);\n hasValidSources = true;\n }\n }\n // A verified/unverified tag requires surviving evidence; without it the\n // tag is meaningless downstream (faithfulness/TrustScore cannot distinguish\n // it from a grounded fact). Downgrade to \"none\" regardless of WHY no source\n // survived (absent / empty / all-invalid) — single invariant, all cases.\n const tag =\n (fm.provenance === \"verified\" || fm.provenance === \"unverified\") && !hasValidSources\n ? \"none\"\n : fm.provenance;\n if (tag) {\n lines.push(`provenance: ${tag}`);\n }\n}\n\n/**\n * Parse the coarse `provenance` strength tag (issue #1575). Returns\n * `undefined` for missing/blank/unknown values so a corrupt or hand-edited\n * field fails safely to the legacy-equivalent `\"none\"` semantics on read\n * (rule 34 spirit — drop corrupt rather than poison).\n */\nexport function parseProvenanceTag(\n raw: string | undefined,\n): \"verified\" | \"unverified\" | \"none\" | undefined {\n if (raw === undefined) return undefined;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return undefined;\n if (trimmed === \"verified\" || trimmed === \"unverified\" || trimmed === \"none\") {\n return trimmed;\n }\n return undefined;\n}\n\n/**\n * Enforce the verified-requires-evidence invariant on the READ path.\n * `parseProvenanceTag` and `parseProvenanceSources` are independent (they\n * parse separate frontmatter lines), so a hand-edited or imported memory may\n * carry `provenance: verified` with no surviving `sources` — a corrupt line,\n * an empty array, or all-invalid entries. Downgrade such a tag to `none` so\n * the in-memory object never exposes an ungrounded \"verified\" fact\n * (review round 5, cursor thread KQN — read-path parity with the write-path\n * downgrade in `serializeProvenanceFields`). `none`/`undefined` tags pass\n * through unchanged.\n */\nexport function reconcileProvenanceRead(\n tag: \"verified\" | \"unverified\" | \"none\" | undefined,\n sources: ProvenanceSource[] | undefined,\n): \"verified\" | \"unverified\" | \"none\" | undefined {\n if ((tag === \"verified\" || tag === \"unverified\") && (!sources || sources.length === 0)) {\n return \"none\";\n }\n return tag;\n}\n\n/**\n * Strict ISO-8601 timestamp check (review round 6, codex thread OXPAp).\n * `Date.parse` accepts non-ISO strings (bare years like `\"123\"`) and\n * silently normalizes calendar overflow (`2026-02-30` -> March 2, hour 25\n * -> next day), so malformed provenance survives as if valid. Require the\n * full `YYYY-MM-DDTHH:MM:SS[.fff](Z|±HH:MM)` shape and reject overflow via a\n * `Date.UTC` round-trip component check — the offset does not affect whether\n * a wall-clock field overflows, so this is correct for any timezone suffix.\n */\nfunction isStrictIsoTimestamp(s: string): boolean {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$/.exec(s);\n if (!m) return false;\n const y = Number(m[1]), mo = Number(m[2]), da = Number(m[3]);\n const h = Number(m[4]), mi = Number(m[5]), se = Number(m[6]);\n // Date.UTC normalizes overflow (Feb 30 -> Mar 2); a component round-trip\n // catches what Date.parse silently accepts.\n const d = new Date(Date.UTC(y, mo - 1, da, h, mi, se));\n // The component round-trip validates wall-clock overflow (Feb 30 -> Mar 2);\n // Date.parse additionally rejects impossible timezone offsets such as\n // +99:99, which the regex accepts but the runtime treats as NaN\n // (codex thread OXQ0e).\n return (\n !Number.isNaN(Date.parse(s)) &&\n d.getUTCFullYear() === y &&\n d.getUTCMonth() === mo - 1 &&\n d.getUTCDate() === da &&\n d.getUTCHours() === h &&\n d.getUTCMinutes() === mi &&\n d.getUTCSeconds() === se\n );\n}\n\n/**\n * Coerce a turn timestamp to a strict ISO-8601 string (cursor thread Ocveu —\n * \"Non-ISO turn timestamps drop sources\"). The write-path `ProvenanceSourceSchema`\n * rejects anything `isStrictIsoTimestamp` fails, so a turn whose `timestamp`\n * parses via `new Date(...)` but isn't already strict ISO (e.g.\n * `\"2026/01/15 12:00:00\"` or `\"Jan 15 2026\"`) would be silently dropped at\n * serialization — clearing the whole `sources` array and downgrading the tag\n * to `\"none\"`. Normalizing at extraction time preserves the source.\n *\n * Returns the strict ISO string when the timestamp already passes or\n * `Date.parse` can round-trip it; `undefined` for empty / unparseable input\n * so callers can decide whether to skip the source or fall back.\n */\nfunction toStrictIsoTimestamp(ts: string | undefined | null): string | undefined {\n if (typeof ts !== \"string\" || ts.length === 0) return undefined;\n if (isStrictIsoTimestamp(ts)) return ts;\n const parsed = Date.parse(ts);\n if (Number.isNaN(parsed)) return undefined;\n // Reject bare-year / numeric-only strings that Date.parse accepts (e.g.\n // \"123\") — isStrictIsoTimestamp already rejected them, and the round-trip\n // below would otherwise resurrect them. Require at least one date/time\n // separator so a plain number never round-trips into a fake epoch.\n if (!/[-:T]/.test(ts)) return undefined;\n // Reject calendar-overflow dates (chatgpt-codex-connector thread dANc):\n // Date.parse silently rolls over invalid components — \"2026-02-30\" becomes\n // 2026-03-02 — fabricating the observation date. For strings that carry an\n // explicit numeric Y/M/D prefix (the common import/provider shape), validate\n // the calendar components are in range before accepting the shifted result.\n // isStrictIsoTimestamp already does this for full ISO strings; this closes\n // the gap for the non-ISO normalization path.\n const ymd = /^(\\d{4})\\D(\\d{1,2})\\D(\\d{1,2})/.exec(ts);\n if (ymd) {\n const [_, ys, ms, ds] = ymd;\n const y = Number(ys), mo = Number(ms), da = Number(ds);\n if (!isValidCalendarDate(y, mo, da)) return undefined;\n }\n // Also validate M/D/Y or D/M/Y formats (provider/import common shapes:\n // 02/30/2026, 30-02-2026, etc.). Date.parse silently shifts overflow in\n // these too. Try both month-first and day-first interpretations; if\n // neither is a valid calendar date, reject (chatgpt-codex-connector thread\n // dH47 — non-YMD overflow timestamps).\n const mdy = /^(\\d{1,2})\\D(\\d{1,2})\\D(\\d{4})/.exec(ts);\n if (mdy) {\n const a = Number(mdy[1]), b = Number(mdy[2]), yr = Number(mdy[3]);\n if (!isValidCalendarDate(yr, a, b) && !isValidCalendarDate(yr, b, a)) {\n return undefined;\n }\n }\n const iso = new Date(parsed).toISOString();\n return isStrictIsoTimestamp(iso) ? iso : undefined;\n}\n\n/**\n * Validate numeric calendar components without Date overflow (review thread\n * dANc). Date.UTC silently normalizes Feb 30 -> Mar 2; a component round-trip\n * catches what Date.parse accepts. Mirrors the same technique isStrictIsoTimestamp\n * uses for full ISO strings, applied here to the non-ISO normalization path.\n */\nfunction isValidCalendarDate(y: number, mo: number, da: number): boolean {\n if (mo < 1 || mo > 12 || da < 1) return false;\n const leap = (y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0)) ? 29 : 28;\n const daysInMonth = [31, leap, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n return da <= daysInMonth[mo - 1]!;\n}\n\n/**\n * Zod schema for a single `ProvenanceSource` entry (issue #1575). Parsed\n * JSON from frontmatter is external data, so each entry is validated here\n * rather than trusted via a cast (rule: no inline-cast-access on parsed\n * blobs). `safeParse` lets us drop corrupt entries individually instead of\n * failing the whole field.\n */\nconst ProvenanceSourceSchema = z\n .object({\n sessionKey: z.string().min(1),\n observedAt: z\n .string()\n .min(1)\n .refine(isStrictIsoTimestamp, \"must be a valid ISO 8601 timestamp (YYYY-MM-DDTHH:MM:SS[Z|±HH:MM], no calendar overflow)\"),\n quote: z.string().min(1),\n turnId: z.string().min(1).optional(),\n charStart: z.number().finite().nonnegative().int().optional(),\n charEnd: z.number().finite().nonnegative().int().optional(),\n })\n .refine(\n (src) => src.charStart === undefined || src.charEnd === undefined || src.charEnd >= src.charStart,\n { message: \"charEnd must be >= charStart (half-open interval, rule 35)\" },\n );\n\n/**\n * Parse the `sources` array (issue #1575) from its single-line JSON form.\n * Mirrors `parseStructuredAttributes` (JSON.parse) but validates each entry\n * against `ProvenanceSourceSchema` and DROPS corrupt ones rather than\n * poisoning downstream readers — the same \"drop corrupt rather than poison\"\n * contract as `parseMemoryWorthCounterField`. If no entry survives, the\n * whole field is `undefined` (legacy-equivalent).\n */\nexport function parseProvenanceSources(raw: string | undefined): ProvenanceSource[] | undefined {\n if (raw === undefined) return undefined;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n return undefined;\n }\n if (!Array.isArray(parsed)) return undefined;\n const sources: ProvenanceSource[] = [];\n for (const entry of parsed) {\n const result = ProvenanceSourceSchema.safeParse(entry);\n if (result.success) sources.push(result.data);\n }\n return sources.length > 0 ? sources : undefined;\n}\n\n/**\n * Parse the `provenance` config block (issue #1575 PR 1). Validates the\n * shape before applying defaults — a shorthand like `provenance: false` must\n * reject loudly rather than normalize to `{}` and silently enable the feature\n * (rule 51). Booleans coerce via `coerceBool` (rule 36); numeric cap clamps\n * at 1 (rule 28). `REMNIC_PROVENANCE_ENABLED` / `ENGRAM_PROVENANCE_ENABLED`\n * are honored only when the `enabled` key is omitted (explicit config wins).\n *\n * Schema-default note (review rounds 1–3, settled): `provenance.enabled` has\n * NO `\"default\"` in any plugin.json schema. OpenClaw's loader runs\n * `applyDefaults: true` before exposing `api.pluginConfig` (src/index.ts:1345,\n * PR #1593 round 8), so a schema default would be materialized into the\n * merged config and mask the `REMNIC_`/`ENGRAM_` env override. The code-level\n * default-on here (`return true` when `enabled` is omitted) supplies the\n * default-on behavior without that materialization. This matches the\n * emitLegacyTools/namespaceCatalogEnabled precedent, which omits the env\n * override only after a raw-vs-effective split — overkill for a single\n * boolean, so this field uses the simpler omit-the-default approach.\n */\nexport function parseProvenanceConfig(raw: unknown): ProvenanceConfig {\n if (\n raw !== undefined &&\n (raw === null || typeof raw !== \"object\" || Array.isArray(raw))\n ) {\n throw new Error(\n `provenance must be an object (got ${JSON.stringify(raw)}). Use provenance: { enabled: false } to opt out; omit the key to use the default-on behavior (issue #1575).`,\n );\n }\n const rawProvenance =\n raw && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n return {\n enabled: (() => {\n if (rawProvenance.enabled === undefined) {\n const envEnabled =\n readEnvVar(\"REMNIC_PROVENANCE_ENABLED\") ?? readEnvVar(\"ENGRAM_PROVENANCE_ENABLED\");\n if (envEnabled !== undefined) {\n const coerced = coerceBool(envEnabled);\n if (coerced === undefined) {\n throw new Error(\n `REMNIC_PROVENANCE_ENABLED must be a boolean-like value (true/false/1/0/yes/no/on/off); got ${JSON.stringify(envEnabled)}`,\n );\n }\n return coerced;\n }\n return true;\n }\n const coerced = coerceBool(rawProvenance.enabled);\n if (coerced === undefined) {\n throw new Error(\n `provenance.enabled must be a boolean or one of \"true\"/\"false\"/\"1\"/\"0\"/\"yes\"/\"no\"/\"on\"/\"off\" (got ${JSON.stringify(rawProvenance.enabled)}). Omit the key to use the default-on behavior (issue #1575).`,\n );\n }\n return coerced;\n })(),\n maxQuoteChars: (() => {\n if (rawProvenance.maxQuoteChars === undefined) return 300;\n const rawCap = coerceNumber(rawProvenance.maxQuoteChars);\n // Reject present-but-invalid rather than silently widening the cap\n // (AGENTS.md input-validation rule — a typo should not persist more\n // text than the operator configured).\n if (rawCap === undefined || !Number.isFinite(rawCap) || rawCap < 1 || !Number.isInteger(rawCap)) {\n throw new Error(\n `provenance.maxQuoteChars must be a positive integer >= 1 (got ${JSON.stringify(rawProvenance.maxQuoteChars)}).`,\n );\n }\n return rawCap;\n })(),\n requireSpans: (() => {\n if (rawProvenance.requireSpans === undefined) return false;\n const coerced = coerceBool(rawProvenance.requireSpans);\n if (coerced === undefined) {\n throw new Error(\n `provenance.requireSpans must be a boolean or one of \"true\"/\"false\"/\"1\"/\"0\"/\"yes\"/\"no\"/\"on\"/\"off\" (got ${JSON.stringify(rawProvenance.requireSpans)}).`,\n );\n }\n return coerced;\n })(),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Issue #1575 PR 2 — extraction-side post-parse validator.\n// ---------------------------------------------------------------------------\n//\n// This runs once at write time (inside `ExtractionEngine.extract`, after the\n// LLM output is parsed and sanitized, before the result is returned for\n// persistence). Its job: locate each fact's LLM-provided `quote` in the\n// buffered turn texts and build a `ProvenanceSource[]` with verified\n// offsets. Never throws, never drops a fact (rule 34 spirit — an\n// unverifiable span is a tagged state, not a silent failure).\n//\n// Reuses `collapseWhitespace` from `whitespace.ts` for normalization so there\n// is exactly one normalizer in the codebase (issue #1575 pitfall: \"do not\n// write a second normalizer\").\n// ---------------------------------------------------------------------------\n\n/**\n * A turn in the buffered conversation, reduced to the fields the provenance\n * validator needs. `ExtractionEngine.extract` maps its `BufferTurn[]` to this\n * shape so the validator is pure and testable without the full BufferTurn type.\n */\nexport interface ProvenanceTurnInput {\n content: string;\n sessionKey?: string;\n logicalSessionKey?: string;\n timestamp: string;\n turnId?: string;\n}\n\n/**\n * Result of building provenance for a single extracted fact.\n */\nexport interface ProvenanceBuildResult {\n /** Verified sources (one per matching turn). Absent when no quote survives. */\n sources?: ProvenanceSource[];\n /** Coarse strength tag persisted to frontmatter. */\n provenance: \"verified\" | \"unverified\" | \"none\";\n /**\n * Transient signal (never persisted): `true` when `config.requireSpans`\n * is enabled and the LLM-provided quote could not be located in ANY source\n * turn (the strict case `ProvenanceConfig.requireSpans` documents: \"facts\n * whose quote cannot be located are routed to `pending_review`\"). The\n * extraction consumer carries this onto the in-memory `ExtractedFact` so\n * the persist path can route the fact to the review queue. A quote that\n * WAS located but whose source was dropped (e.g. un-coercible timestamp)\n * does NOT set this flag — the span was found, so requireSpans is\n * satisfied (chatgpt-codex-connector thread 4xB).\n */\n requireSpansPending?: boolean;\n}\n\n/**\n * Cap a quote string at `maxChars`, truncating at the last word boundary that\n * fits and appending an ellipsis marker. Quotes at or under the cap pass\n * through unchanged. Operates on Unicode code points (not UTF-16 units) so\n * emoji and astral-plane characters are not split mid-glyph.\n */\nfunction capQuote(quote: string, maxChars: number): string {\n const glyphs = Array.from(quote);\n if (glyphs.length <= maxChars) return quote;\n // Walk backward from the cap to find a word boundary (space). If the entire\n // span is one long token (no spaces), cut at the cap — a hard cut is better\n // than no quote at all.\n let cut = maxChars;\n while (cut > 0 && !/\\s/.test(glyphs[cut - 1]!)) cut--;\n if (cut === 0) cut = maxChars; // no word boundary found\n return glyphs.slice(0, cut).join(\"\").trimEnd() + \"\\u2026\";\n}\n\n/**\n * Casefold a string for normalized matching. Uses `toLowerCase` (not\n * `toLocaleLowerCase`) for determinism across runtime locales — the existing\n * `normalizeFactKey` helper in extraction.ts follows the same convention.\n */\nfunction casefold(s: string): string {\n return s.toLowerCase();\n}\n\n/**\n * Locate `quote` within `text`, returning whether a match was found and, when\n * recoverable, the half-open `[start, end)` offsets. Tries exact substring\n * first, then whitespace/case-normalized match.\n *\n * For the normalized path, the mapping from the normalized string back to\n * original offsets is recovered by walking both strings in lockstep: we know\n * `collapseWhitespace(text)` is a subsequence of `text` with whitespace runs\n * collapsed, so we can track the original offset as we scan.\n *\n * Returns `{ matched: false }` when neither match succeeds. Returns\n * `{ matched: true, offsets }` when offsets are recoverable. Returns\n * `{ matched: true }` (offsets omitted) when the normalized substring was\n * found but original offsets could not be recovered — callers still treat\n * this as a verified match and record a source without offsets (cursor\n * thread Ocver — \"Normalized match drops verified provenance\").\n */\ntype LocateQuoteResult =\n | { matched: false }\n | { matched: true; offsets?: { charStart: number; charEnd: number } };\n\nfunction locateQuoteOffsets(quote: string, text: string): LocateQuoteResult {\n // 1. Exact substring match (handles unicode, curly quotes, emoji verbatim).\n const exactIdx = text.indexOf(quote);\n if (exactIdx >= 0) {\n return { matched: true, offsets: { charStart: exactIdx, charEnd: exactIdx + quote.length } };\n }\n\n // 2. Whitespace/case-normalized match. Collapse runs of whitespace and\n // casefold both sides, then find the normalized quote in the normalized\n // text. Recover original offsets by scanning forward from the normalized\n // match start through the original text, accumulating non-whitespace\n // glyphs until we've consumed the normalized quote length.\n const normQuote = collapseWhitespace(casefold(quote));\n if (normQuote.length === 0) return { matched: false };\n const normText = collapseWhitespace(casefold(text));\n const normIdx = normText.indexOf(normQuote);\n if (normIdx < 0) return { matched: false };\n\n // Recover original offsets: walk the original text, skipping leading\n // whitespace to align with the collapsed form, then track how many\n // normalized chars we've consumed.\n const normQuoteLen = normQuote.length;\n let origIdx = 0;\n // Skip leading whitespace in original text (collapseWhitespace trims both ends).\n while (origIdx < text.length && /\\s/.test(text[origIdx]!)) origIdx++;\n // Walk through original text, counting normalized chars consumed.\n // Each non-whitespace char in original = 1 normalized char.\n // Whitespace runs in original = 1 space in normalized (but only between non-ws).\n let normPos = 0;\n let origStart = -1;\n let origEnd = -1;\n let prevWasWs = true; // suppress the leading space in collapsed form\n while (origIdx < text.length && origEnd < 0) {\n const ch = text[origIdx]!;\n if (/\\s/.test(ch)) {\n if (!prevWasWs) {\n // This whitespace run collapses to a single space in normalized text.\n if (normPos === normIdx) origStart = origIdx; // normalized space aligns\n normPos++;\n prevWasWs = true;\n }\n origIdx++;\n continue;\n }\n // Non-whitespace char.\n if (normPos === normIdx && origStart < 0) {\n origStart = origIdx;\n }\n normPos++;\n prevWasWs = false;\n origIdx++;\n if (normPos >= normIdx + normQuoteLen) {\n origEnd = origIdx;\n }\n }\n if (origStart >= 0 && origEnd >= 0) {\n return { matched: true, offsets: { charStart: origStart, charEnd: origEnd } };\n }\n // Normalized substring was found, but original offsets are not recoverable\n // (edge case in the normalized mapping). The match still counts as verified\n // — record a source without offsets rather than dropping the turn entirely\n // (cursor thread Ocver). charStart/charEnd are best-effort debugging aids,\n // not a precondition for a verified source.\n return { matched: true };\n}\n\n/**\n * Build provenance sources for a single extracted fact by locating its\n * LLM-provided `quote` in the buffered turns (issue #1575 PR 2).\n *\n * Matching strategy (per issue design):\n * 1. Exact substring match in a turn → `provenance: \"verified\"` with\n * `charStart`/`charEnd` offsets.\n * 2. Whitespace/case-normalized match → `\"verified\"`, offsets recovered\n * when possible, else omitted.\n * 3. No match in any turn → `\"unverified\"` — the quote survives as a\n * source (the LLM vouched for it) but without located offsets.\n * 4. No quote provided by the LLM → `\"none\"` — no evidence to record.\n *\n * A quote appearing in multiple turns produces multiple sources (one per\n * matching turn) so a repeated utterance backs the fact from each occurrence.\n *\n * The quote is capped at `config.maxQuoteChars` (truncated at a word boundary\n * with an ellipsis marker) BEFORE storage. Locating uses the original\n * (untruncated) quote for maximum match fidelity; the capped excerpt is what\n * gets persisted.\n *\n * When `config.enabled === false`, returns `{ provenance: \"none\" }`\n * immediately — byte-identical to pre-feature extraction (rule 39).\n *\n * Never throws. An unexpected error degrades to `{ provenance: \"none\" }` so\n * extraction never crashes on a provenance hiccup (rule 13/18).\n */\n/**\n * Strip a leading extraction-prompt role label from a quote (cursor thread Oc3Z2\n * — \"Quote prompt mismatches validator\"). The extraction prompt renders each\n * buffered turn as `[role] content` (or `[context role] content`), so a\n * faithful LLM may include that prefix in its verbatim quote. buildFactProvenance\n * searches the raw `turn.content` (no prefix), so a quote carrying the label\n * would never match. Stripping the leading label lets the actual utterance\n * verify against the turn text. Only a SINGLE leading label is stripped — a\n * multi-turn quote (with embedded labels) is left untouched and will simply\n * fail to match a single turn, as before.\n *\n * The regex is constrained to the labels the prompt actually emits — `user`,\n * `assistant`, optionally prefixed with `context ` (extraction.ts renders\n * `[user]`, `[assistant]`, `[context user]`, `[context assistant]`). A\n * real utterance that happens to start with other bracketed text (e.g.\n * `[do not] deploy before approval`, `[P1] fix the cache`) is preserved\n * verbatim so the quote can match the turn and the persisted span retains its\n * original meaning (chatgpt-codex-connector thread 4xA — limiting role-prefix\n * stripping to actual prompt labels).\n */\nfunction stripLeadingRolePrefix(quote: string): string {\n return quote.replace(/^\\s*\\[(?:context\\s+)?(?:user|assistant)\\]\\s+/i, \"\");\n}\n\nexport function buildFactProvenance(\n factQuote: string | null | undefined,\n turns: ReadonlyArray<ProvenanceTurnInput>,\n config: ProvenanceConfig,\n): ProvenanceBuildResult {\n if (!config.enabled) return { provenance: \"none\" };\n const rawQuote = typeof factQuote === \"string\" ? factQuote.trim() : \"\";\n // requireSpans (chatgpt-codex-connector thread dEsu + cursor thread dGKJ):\n // every early exit that drops a fact's span (no quote, empty after label\n // strip, or unsafe quote) must flag requireSpansPending when the operator\n // opted into requireSpans, so the persist path routes the fact to\n // pending_review instead of active. Only the disabled-feature exit (above)\n // and the unexpected-error catch (below) omit the flag — those are\n // policy-neutral degradations, not a missing-span decision.\n const noneResult = (): ProvenanceBuildResult =>\n config.requireSpans === true\n ? { provenance: \"none\", requireSpansPending: true }\n : { provenance: \"none\" };\n if (rawQuote.length === 0) return noneResult();\n // Strip a leading prompt role label so a faithful quote verifies against\n // the raw turn content (cursor thread Oc3Z2). Prefer the RAW quote when it\n // matches at least one turn — an utterance that literally begins with\n // [user]/[assistant] must verify as-is, not be truncated to the post-label\n // text (cursor/codex thread dEsw). The strip handles the common case where\n // the LLM includes the prompt label; this preserves the rare case where the\n // utterance itself starts with that text.\n const strippedQuote = stripLeadingRolePrefix(rawQuote);\n const useRawQuote =\n rawQuote === strippedQuote ||\n turns.some(\n (t) =>\n typeof t?.content === \"string\" &&\n t.content.length > 0 &&\n locateQuoteOffsets(rawQuote, t.content).matched,\n );\n const quote = useRawQuote ? rawQuote : strippedQuote;\n if (quote.length === 0) return noneResult();\n // Sanitize the quote before persisting it as a provenance span\n // (chatgpt-codex-connector thread dANZ): the fact body is sanitized via\n // sanitizeMemoryContent, but sources[].quote was persisted verbatim,\n // reintroducing unsafe memory text (e.g. \"ignore previous instructions\")\n // through the provenance field exposed to memory_get/x-ray/faithfulness.\n // An unsafe quote cannot serve as evidence — drop the source entirely\n // (consistent with how the body is sanitized: unsafe text is redacted,\n // and a redacted quote is useless as a verbatim span).\n if (!isSafeMemoryContent(quote)) return noneResult();\n\n try {\n // Search every turn for the quote. Collect verified sources.\n const sources: ProvenanceSource[] = [];\n // Track the first turn where the quote was located even when its\n // timestamp can't be coerced (cursor thread 4Pj — \"Bad timestamp drops\n // matched source\"). Without this, a located-but-unverifiable quote falls\n // through to the unverified branch and is attributed to the *last* turn's\n // session, mislabeling the source's origin session. Also drives the\n // requireSpans signal: only a quote that was NOT located in any turn\n // qualifies for pending_review routing under requireSpans (thread 4xB).\n let locatedTurn: ProvenanceTurnInput | undefined;\n for (const turn of turns) {\n if (!turn || typeof turn.content !== \"string\" || turn.content.length === 0) continue;\n const located = locateQuoteOffsets(quote, turn.content);\n // cursor thread Ocver: a normalized match counts as verified even when\n // original offsets are unrecoverable — record the source without\n // charStart/charEnd instead of skipping the turn.\n if (!located.matched) continue;\n if (!locatedTurn) locatedTurn = turn;\n // cursor thread Ocveu: normalize the turn timestamp to strict ISO so\n // the write-path ProvenanceSourceSchema keeps the source. Skip the turn\n // when the timestamp can't be coerced — pushing it would guarantee a\n // serialization drop (and the tag downgrade) for this source.\n const observedAt = toStrictIsoTimestamp(turn.timestamp);\n if (!observedAt) continue;\n sources.push({\n sessionKey: turn.sessionKey ?? turn.logicalSessionKey ?? \"unknown\",\n ...(turn.turnId ? { turnId: turn.turnId } : {}),\n observedAt,\n quote: capQuote(quote, config.maxQuoteChars),\n ...(located.offsets\n ? { charStart: located.offsets.charStart, charEnd: located.offsets.charEnd }\n : {}),\n });\n }\n\n if (sources.length > 0) {\n return { sources, provenance: \"verified\" };\n }\n\n // Quote provided but could not be turned into a verified source.\n // Determine the session to attribute: prefer the turn where the quote was\n // LOCATED even if its timestamp couldn't be coerced (cursor thread 4Pj — a\n // located quote must not be tied to the *last* turn's session). If the\n // quote was not located in any turn at all, fall back to the last turn's\n // session (the documented unverified behavior: the LLM vouched for the\n // excerpt but we couldn't pin it to a character offset). Normalize the\n // timestamp (thread Ocveu); fall back to epoch when no turn supplies a\n // coercible timestamp so the unverified source still survives the\n // write-path schema.\n const fallbackTurn = locatedTurn ?? turns[turns.length - 1];\n const fallbackSessionKey = fallbackTurn\n ? (fallbackTurn.sessionKey ?? fallbackTurn.logicalSessionKey ?? \"unknown\")\n : \"unknown\";\n const fallbackObservedAt = fallbackTurn\n ? (toStrictIsoTimestamp(fallbackTurn.timestamp) ?? new Date(0).toISOString())\n : new Date(0).toISOString();\n // requireSpans signal (chatgpt-codex-connector thread 4xB): when an\n // operator opts into provenance.requireSpans, a fact whose quote could\n // not be located in ANY turn (locatedTurn undefined) is flagged so the\n // persist path routes it to pending_review instead of active. A quote\n // that WAS located (locatedTurn set) satisfies requireSpans even when\n // its source was dropped for an un-coercible timestamp — the span was\n // found, so the fact has the grounding requireSpans demands.\n const requireSpansPending =\n config.requireSpans === true && locatedTurn === undefined;\n return {\n sources: [\n {\n sessionKey: fallbackSessionKey,\n observedAt: fallbackObservedAt,\n quote: capQuote(quote, config.maxQuoteChars),\n },\n ],\n provenance: \"unverified\",\n ...(requireSpansPending ? { requireSpansPending: true } : {}),\n };\n } catch {\n // Never crash extraction on a provenance error (rule 13/18).\n return { provenance: \"none\" };\n }\n}\n","/**\n * Shared boolean coercion helpers.\n *\n * Extracted from connectors/index.ts so that both config.ts and\n * connectors/index.ts can import them without creating a circular dependency.\n */\n\n/**\n * Generic boolean coercion: converts string representations of booleans\n * (e.g. from CLI `--config someFlag=false`) to proper boolean values.\n * Accepts the same truthy/falsy strings that common shells and env vars use.\n *\n * Returns `undefined` when the value is neither a boolean nor a recognised\n * string, so callers can fall back to a default.\n *\n * CLAUDE.md gotcha #36: String \"false\" is truthy in JavaScript.\n * CLAUDE.md gotcha #28: Coerce CLI values to expected types at input boundaries.\n */\nexport function coerceBool(value: unknown): boolean | undefined {\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"string\") {\n const v = value.trim().toLowerCase();\n if ([\"false\", \"0\", \"no\", \"off\"].includes(v)) return false;\n if ([\"true\", \"1\", \"yes\", \"on\"].includes(v)) return true;\n }\n return undefined;\n}\n\n/**\n * Coerce the `installExtension` config value from a string (e.g. from CLI\n * `--config installExtension=false`) to a proper boolean.\n *\n * Delegates to the generic `coerceBool` helper. Kept for backward compatibility.\n */\nexport function coerceInstallExtension(value: unknown): boolean | undefined {\n return coerceBool(value);\n}\n\n/**\n * Generic numeric coercion: accepts a finite number or a string that\n * parses cleanly to one. Returns `undefined` otherwise so callers can\n * fall back to a default.\n *\n * Rules:\n * - number: returned as-is only if finite (NaN / ±Infinity → undefined).\n * - string: trimmed, parsed with `Number()`. Returns `undefined` on\n * empty, NaN, or Infinity.\n *\n * CLAUDE.md gotcha #28: Coerce CLI values to expected types at input boundaries.\n */\nexport function coerceNumber(value: unknown): number | undefined {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : undefined;\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed.length === 0) return undefined;\n const n = Number(trimmed);\n return Number.isFinite(n) ? n : undefined;\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;AAyBA,SAAS,SAAS;;;ACPX,SAAS,WAAW,OAAqC;AAC9D,MAAI,OAAO,UAAU,UAAW,QAAO;AACvC,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI,MAAM,KAAK,EAAE,YAAY;AACnC,QAAI,CAAC,SAAS,KAAK,MAAM,KAAK,EAAE,SAAS,CAAC,EAAG,QAAO;AACpD,QAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,EAAE,SAAS,CAAC,EAAG,QAAO;AAAA,EACrD;AACA,SAAO;AACT;AAQO,SAAS,uBAAuB,OAAqC;AAC1E,SAAO,WAAW,KAAK;AACzB;AAcO,SAAS,aAAa,OAAoC;AAC/D,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,UAAM,IAAI,OAAO,OAAO;AACxB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;;;ADvBA,IAAM,8BAA8B;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOA,SAAS,0BAA0B,KAAgD;AACjF,QAAM,MAA+B,CAAC;AACtC,aAAW,OAAO,6BAA6B;AAC7C,UAAM,QAAQ,IAAI,GAAG;AACrB,QAAI,UAAU,OAAW,KAAI,GAAG,IAAI;AAAA,EACtC;AACA,SAAO;AACT;AAgBO,SAAS,0BAA0B,IAAuB,OAAuB;AACtF,MAAI,kBAAkB;AACtB,MAAI,GAAG,WAAW,GAAG,QAAQ,SAAS,GAAG;AAIvC,UAAM,YAAuC,CAAC;AAC9C,eAAW,OAAO,GAAG,SAAS;AAC5B,YAAM,SAAS,uBAAuB,UAAU,GAAG;AACnD,UAAI,OAAO,QAAS,WAAU,KAAK,0BAA0B,OAAO,IAAI,CAAC;AAAA,IAC3E;AACA,QAAI,UAAU,SAAS,GAAG;AACxB,YAAM,KAAK,YAAY,KAAK,UAAU,SAAS,CAAC,EAAE;AAClD,wBAAkB;AAAA,IACpB;AAAA,EACF;AAKA,QAAM,OACH,GAAG,eAAe,cAAc,GAAG,eAAe,iBAAiB,CAAC,kBACjE,SACA,GAAG;AACT,MAAI,KAAK;AACP,UAAM,KAAK,eAAe,GAAG,EAAE;AAAA,EACjC;AACF;AAQO,SAAS,mBACd,KACgD;AAChD,MAAI,QAAQ,OAAW,QAAO;AAC9B,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,MAAI,YAAY,cAAc,YAAY,gBAAgB,YAAY,QAAQ;AAC5E,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAaO,SAAS,wBACd,KACA,SACgD;AAChD,OAAK,QAAQ,cAAc,QAAQ,kBAAkB,CAAC,WAAW,QAAQ,WAAW,IAAI;AACtF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAWA,SAAS,qBAAqB,GAAoB;AAChD,QAAM,IAAI,mFAAmF,KAAK,CAAC;AACnG,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,IAAI,OAAO,EAAE,CAAC,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC,CAAC;AAC3D,QAAM,IAAI,OAAO,EAAE,CAAC,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC,CAAC;AAG3D,QAAM,IAAI,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;AAKrD,SACE,CAAC,OAAO,MAAM,KAAK,MAAM,CAAC,CAAC,KAC3B,EAAE,eAAe,MAAM,KACvB,EAAE,YAAY,MAAM,KAAK,KACzB,EAAE,WAAW,MAAM,MACnB,EAAE,YAAY,MAAM,KACpB,EAAE,cAAc,MAAM,MACtB,EAAE,cAAc,MAAM;AAE1B;AAeA,SAAS,qBAAqB,IAAmD;AAC/E,MAAI,OAAO,OAAO,YAAY,GAAG,WAAW,EAAG,QAAO;AACtD,MAAI,qBAAqB,EAAE,EAAG,QAAO;AACrC,QAAM,SAAS,KAAK,MAAM,EAAE;AAC5B,MAAI,OAAO,MAAM,MAAM,EAAG,QAAO;AAKjC,MAAI,CAAC,QAAQ,KAAK,EAAE,EAAG,QAAO;AAQ9B,QAAM,MAAM,iCAAiC,KAAK,EAAE;AACpD,MAAI,KAAK;AACP,UAAM,CAAC,GAAG,IAAI,IAAI,EAAE,IAAI;AACxB,UAAM,IAAI,OAAO,EAAE,GAAG,KAAK,OAAO,EAAE,GAAG,KAAK,OAAO,EAAE;AACrD,QAAI,CAAC,oBAAoB,GAAG,IAAI,EAAE,EAAG,QAAO;AAAA,EAC9C;AAMA,QAAM,MAAM,iCAAiC,KAAK,EAAE;AACpD,MAAI,KAAK;AACP,UAAM,IAAI,OAAO,IAAI,CAAC,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC;AAChE,QAAI,CAAC,oBAAoB,IAAI,GAAG,CAAC,KAAK,CAAC,oBAAoB,IAAI,GAAG,CAAC,GAAG;AACpE,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,MAAM,IAAI,KAAK,MAAM,EAAE,YAAY;AACzC,SAAO,qBAAqB,GAAG,IAAI,MAAM;AAC3C;AAQA,SAAS,oBAAoB,GAAW,IAAY,IAAqB;AACvE,MAAI,KAAK,KAAK,KAAK,MAAM,KAAK,EAAG,QAAO;AACxC,QAAM,OAAQ,IAAI,MAAM,MAAM,IAAI,QAAQ,KAAK,IAAI,QAAQ,KAAM,KAAK;AACtE,QAAM,cAAc,CAAC,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE;AACrE,SAAO,MAAM,YAAY,KAAK,CAAC;AACjC;AASA,IAAM,yBAAyB,EAC5B,OAAO;AAAA,EACN,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC5B,YAAY,EACT,OAAO,EACP,IAAI,CAAC,EACL,OAAO,sBAAsB,6FAA0F;AAAA,EAC1H,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EACnC,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS;AAAA,EAC5D,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS;AAC5D,CAAC,EACA;AAAA,EACC,CAAC,QAAQ,IAAI,cAAc,UAAa,IAAI,YAAY,UAAa,IAAI,WAAW,IAAI;AAAA,EACxF,EAAE,SAAS,6DAA6D;AAC1E;AAUK,SAAS,uBAAuB,KAAyD;AAC9F,MAAI,QAAQ,OAAW,QAAO;AAC9B,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,OAAO;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AACnC,QAAM,UAA8B,CAAC;AACrC,aAAW,SAAS,QAAQ;AAC1B,UAAM,SAAS,uBAAuB,UAAU,KAAK;AACrD,QAAI,OAAO,QAAS,SAAQ,KAAK,OAAO,IAAI;AAAA,EAC9C;AACA,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;AAqBO,SAAS,sBAAsB,KAAgC;AACpE,MACE,QAAQ,WACP,QAAQ,QAAQ,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,IAC7D;AACA,UAAM,IAAI;AAAA,MACR,qCAAqC,KAAK,UAAU,GAAG,CAAC;AAAA,IAC1D;AAAA,EACF;AACA,QAAM,gBACJ,OAAO,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,GAAG,IAC/C,MACD,CAAC;AACP,SAAO;AAAA,IACL,UAAU,MAAM;AACd,UAAI,cAAc,YAAY,QAAW;AACvC,cAAM,aACJ,WAAW,2BAA2B,KAAK,WAAW,2BAA2B;AACnF,YAAI,eAAe,QAAW;AAC5B,gBAAMA,WAAU,WAAW,UAAU;AACrC,cAAIA,aAAY,QAAW;AACzB,kBAAM,IAAI;AAAA,cACR,8FAA8F,KAAK,UAAU,UAAU,CAAC;AAAA,YAC1H;AAAA,UACF;AACA,iBAAOA;AAAA,QACT;AACA,eAAO;AAAA,MACT;AACA,YAAM,UAAU,WAAW,cAAc,OAAO;AAChD,UAAI,YAAY,QAAW;AACzB,cAAM,IAAI;AAAA,UACR,oGAAoG,KAAK,UAAU,cAAc,OAAO,CAAC;AAAA,QAC3I;AAAA,MACF;AACA,aAAO;AAAA,IACT,GAAG;AAAA,IACH,gBAAgB,MAAM;AACpB,UAAI,cAAc,kBAAkB,OAAW,QAAO;AACtD,YAAM,SAAS,aAAa,cAAc,aAAa;AAIvD,UAAI,WAAW,UAAa,CAAC,OAAO,SAAS,MAAM,KAAK,SAAS,KAAK,CAAC,OAAO,UAAU,MAAM,GAAG;AAC/F,cAAM,IAAI;AAAA,UACR,iEAAiE,KAAK,UAAU,cAAc,aAAa,CAAC;AAAA,QAC9G;AAAA,MACF;AACA,aAAO;AAAA,IACT,GAAG;AAAA,IACH,eAAe,MAAM;AACnB,UAAI,cAAc,iBAAiB,OAAW,QAAO;AACrD,YAAM,UAAU,WAAW,cAAc,YAAY;AACrD,UAAI,YAAY,QAAW;AACzB,cAAM,IAAI;AAAA,UACR,yGAAyG,KAAK,UAAU,cAAc,YAAY,CAAC;AAAA,QACrJ;AAAA,MACF;AACA,aAAO;AAAA,IACT,GAAG;AAAA,EACL;AACF;AA2DA,SAAS,SAAS,OAAe,UAA0B;AACzD,QAAM,SAAS,MAAM,KAAK,KAAK;AAC/B,MAAI,OAAO,UAAU,SAAU,QAAO;AAItC,MAAI,MAAM;AACV,SAAO,MAAM,KAAK,CAAC,KAAK,KAAK,OAAO,MAAM,CAAC,CAAE,EAAG;AAChD,MAAI,QAAQ,EAAG,OAAM;AACrB,SAAO,OAAO,MAAM,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,QAAQ,IAAI;AACnD;AAOA,SAAS,SAAS,GAAmB;AACnC,SAAO,EAAE,YAAY;AACvB;AAuBA,SAAS,mBAAmB,OAAe,MAAiC;AAE1E,QAAM,WAAW,KAAK,QAAQ,KAAK;AACnC,MAAI,YAAY,GAAG;AACjB,WAAO,EAAE,SAAS,MAAM,SAAS,EAAE,WAAW,UAAU,SAAS,WAAW,MAAM,OAAO,EAAE;AAAA,EAC7F;AAOA,QAAM,YAAY,mBAAmB,SAAS,KAAK,CAAC;AACpD,MAAI,UAAU,WAAW,EAAG,QAAO,EAAE,SAAS,MAAM;AACpD,QAAM,WAAW,mBAAmB,SAAS,IAAI,CAAC;AAClD,QAAM,UAAU,SAAS,QAAQ,SAAS;AAC1C,MAAI,UAAU,EAAG,QAAO,EAAE,SAAS,MAAM;AAKzC,QAAM,eAAe,UAAU;AAC/B,MAAI,UAAU;AAEd,SAAO,UAAU,KAAK,UAAU,KAAK,KAAK,KAAK,OAAO,CAAE,EAAG;AAI3D,MAAI,UAAU;AACd,MAAI,YAAY;AAChB,MAAI,UAAU;AACd,MAAI,YAAY;AAChB,SAAO,UAAU,KAAK,UAAU,UAAU,GAAG;AAC3C,UAAM,KAAK,KAAK,OAAO;AACvB,QAAI,KAAK,KAAK,EAAE,GAAG;AACjB,UAAI,CAAC,WAAW;AAEd,YAAI,YAAY,QAAS,aAAY;AACrC;AACA,oBAAY;AAAA,MACd;AACA;AACA;AAAA,IACF;AAEA,QAAI,YAAY,WAAW,YAAY,GAAG;AACxC,kBAAY;AAAA,IACd;AACA;AACA,gBAAY;AACZ;AACA,QAAI,WAAW,UAAU,cAAc;AACrC,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,aAAa,KAAK,WAAW,GAAG;AAClC,WAAO,EAAE,SAAS,MAAM,SAAS,EAAE,WAAW,WAAW,SAAS,QAAQ,EAAE;AAAA,EAC9E;AAMA,SAAO,EAAE,SAAS,KAAK;AACzB;AAiDA,SAAS,uBAAuB,OAAuB;AACrD,SAAO,MAAM,QAAQ,iDAAiD,EAAE;AAC1E;AAEO,SAAS,oBACd,WACA,OACA,QACuB;AACvB,MAAI,CAAC,OAAO,QAAS,QAAO,EAAE,YAAY,OAAO;AACjD,QAAM,WAAW,OAAO,cAAc,WAAW,UAAU,KAAK,IAAI;AAQpE,QAAM,aAAa,MACjB,OAAO,iBAAiB,OACpB,EAAE,YAAY,QAAQ,qBAAqB,KAAK,IAChD,EAAE,YAAY,OAAO;AAC3B,MAAI,SAAS,WAAW,EAAG,QAAO,WAAW;AAQ7C,QAAM,gBAAgB,uBAAuB,QAAQ;AACrD,QAAM,cACJ,aAAa,iBACb,MAAM;AAAA,IACJ,CAAC,MACC,OAAO,GAAG,YAAY,YACtB,EAAE,QAAQ,SAAS,KACnB,mBAAmB,UAAU,EAAE,OAAO,EAAE;AAAA,EAC5C;AACF,QAAM,QAAQ,cAAc,WAAW;AACvC,MAAI,MAAM,WAAW,EAAG,QAAO,WAAW;AAS1C,MAAI,CAAC,oBAAoB,KAAK,EAAG,QAAO,WAAW;AAEnD,MAAI;AAEF,UAAM,UAA8B,CAAC;AAQrC,QAAI;AACJ,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,QAAQ,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,WAAW,EAAG;AAC5E,YAAM,UAAU,mBAAmB,OAAO,KAAK,OAAO;AAItD,UAAI,CAAC,QAAQ,QAAS;AACtB,UAAI,CAAC,YAAa,eAAc;AAKhC,YAAM,aAAa,qBAAqB,KAAK,SAAS;AACtD,UAAI,CAAC,WAAY;AACjB,cAAQ,KAAK;AAAA,QACX,YAAY,KAAK,cAAc,KAAK,qBAAqB;AAAA,QACzD,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,QAC7C;AAAA,QACA,OAAO,SAAS,OAAO,OAAO,aAAa;AAAA,QAC3C,GAAI,QAAQ,UACR,EAAE,WAAW,QAAQ,QAAQ,WAAW,SAAS,QAAQ,QAAQ,QAAQ,IACzE,CAAC;AAAA,MACP,CAAC;AAAA,IACH;AAEA,QAAI,QAAQ,SAAS,GAAG;AACtB,aAAO,EAAE,SAAS,YAAY,WAAW;AAAA,IAC3C;AAYA,UAAM,eAAe,eAAe,MAAM,MAAM,SAAS,CAAC;AAC1D,UAAM,qBAAqB,eACtB,aAAa,cAAc,aAAa,qBAAqB,YAC9D;AACJ,UAAM,qBAAqB,eACtB,qBAAqB,aAAa,SAAS,MAAK,oBAAI,KAAK,CAAC,GAAE,YAAY,KACzE,oBAAI,KAAK,CAAC,GAAE,YAAY;AAQ5B,UAAM,sBACJ,OAAO,iBAAiB,QAAQ,gBAAgB;AAClD,WAAO;AAAA,MACL,SAAS;AAAA,QACP;AAAA,UACE,YAAY;AAAA,UACZ,YAAY;AAAA,UACZ,OAAO,SAAS,OAAO,OAAO,aAAa;AAAA,QAC7C;AAAA,MACF;AAAA,MACA,YAAY;AAAA,MACZ,GAAI,sBAAsB,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAAA,IAC7D;AAAA,EACF,QAAQ;AAEN,WAAO,EAAE,YAAY,OAAO;AAAA,EAC9B;AACF;","names":["coerced"]}
|