@remnic/core 9.3.700 → 9.3.702
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 +7 -7
- package/dist/access-boundary.js +17 -14
- package/dist/access-cli.js +40 -37
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +7 -7
- package/dist/access-http.js +20 -17
- package/dist/access-mcp.d.ts +7 -7
- package/dist/access-mcp.js +19 -16
- package/dist/access-operations.d.ts +7 -7
- package/dist/access-operations.js +18 -15
- package/dist/{access-service-Cte3ol0W.d.ts → access-service-COCzhgEL.d.ts} +397 -19
- package/dist/access-service.d.ts +6 -6
- package/dist/access-service.js +16 -13
- package/dist/access-surface-catalog.d.ts +7 -7
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-memory-bridge.js +2 -2
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +7 -6
- package/dist/active-recall.js.map +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +5 -5
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +8 -7
- 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/capabilities.d.ts +1 -1
- package/dist/{catalog-DN1PzThs.d.ts → catalog-DxCzhjE6.d.ts} +28 -54
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +9 -8
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-XJNBEDFE.js → chunk-3FAMU5TX.js} +31 -74
- package/dist/chunk-3FAMU5TX.js.map +1 -0
- package/dist/{chunk-PCZR32VL.js → chunk-3JJWNZTT.js} +2 -2
- package/dist/{chunk-YXLT4EMM.js → chunk-4463LUIE.js} +12 -2
- package/dist/chunk-4463LUIE.js.map +1 -0
- package/dist/{chunk-R5DB26G6.js → chunk-4SBW47WK.js} +9 -23
- package/dist/chunk-4SBW47WK.js.map +1 -0
- package/dist/{chunk-CCOXIDRM.js → chunk-5VXVXJH6.js} +149 -6
- package/dist/{chunk-CCOXIDRM.js.map → chunk-5VXVXJH6.js.map} +1 -1
- package/dist/{chunk-PQG4T5V3.js → chunk-64XXOQRW.js} +49 -45
- package/dist/chunk-64XXOQRW.js.map +1 -0
- package/dist/{chunk-YXIFA36P.js → chunk-6TAETM63.js} +2 -2
- package/dist/{chunk-6JDGADXK.js → chunk-6VVP6NK7.js} +2 -2
- package/dist/{chunk-FN2SM5SN.js → chunk-ABCGDW3A.js} +75 -12
- package/dist/chunk-ABCGDW3A.js.map +1 -0
- package/dist/{chunk-CHM274U6.js → chunk-AMNLZ6SF.js} +2 -2
- package/dist/{chunk-JX3YZVII.js → chunk-CZJ6QSKG.js} +3 -3
- package/dist/{chunk-GYVVQYA3.js → chunk-EOOVJK2U.js} +3 -3
- package/dist/{chunk-JKW5XSWC.js → chunk-EYJD6KIO.js} +2 -2
- package/dist/{chunk-XY4WJTEX.js → chunk-EZR35XHX.js} +2 -2
- package/dist/{chunk-UU6MVCJ6.js → chunk-FIOYURII.js} +32 -25
- package/dist/chunk-FIOYURII.js.map +1 -0
- package/dist/{chunk-2NWHLAXX.js → chunk-FVI5B7DE.js} +2 -2
- package/dist/{chunk-X74FJSW7.js → chunk-GFIARMA7.js} +16 -8
- package/dist/chunk-GFIARMA7.js.map +1 -0
- package/dist/{chunk-PONNZ54D.js → chunk-GY3SKOS4.js} +4 -4
- package/dist/{chunk-U33LWTQQ.js → chunk-HV57RHMD.js} +4 -4
- package/dist/{chunk-RC3CNIPK.js → chunk-IO5NQEGZ.js} +2 -2
- package/dist/{chunk-IKNQAGBV.js → chunk-IX3UQT4H.js} +1 -1
- package/dist/{chunk-IKNQAGBV.js.map → chunk-IX3UQT4H.js.map} +1 -1
- package/dist/{chunk-HDLC75NX.js → chunk-IX72AAMZ.js} +2 -2
- package/dist/{chunk-DR2JTSLZ.js → chunk-JMA4RYRN.js} +62 -163
- package/dist/chunk-JMA4RYRN.js.map +1 -0
- package/dist/{chunk-G5PKTQ5J.js → chunk-JTKFZMZ7.js} +2 -2
- package/dist/{chunk-YMTGXDN6.js → chunk-KC6TCAWV.js} +5 -5
- package/dist/{chunk-ROZJACKP.js → chunk-KKK7YTYN.js} +4 -1
- package/dist/chunk-KKK7YTYN.js.map +1 -0
- package/dist/{chunk-EC2AYKRX.js → chunk-L6W77GWW.js} +10 -24
- package/dist/chunk-L6W77GWW.js.map +1 -0
- package/dist/{chunk-ED35D32I.js → chunk-LQ4J7ELC.js} +2 -2
- package/dist/{chunk-YPR7DOPD.js → chunk-LTJAMRGI.js} +4 -4
- package/dist/{chunk-YPR7DOPD.js.map → chunk-LTJAMRGI.js.map} +1 -1
- package/dist/chunk-LUPVCGYK.js +63 -0
- package/dist/chunk-LUPVCGYK.js.map +1 -0
- package/dist/{chunk-RJ2THZ4H.js → chunk-MBUM2Y3L.js} +2 -2
- package/dist/{chunk-T5QAZIBO.js → chunk-MOXFPLD6.js} +3 -3
- package/dist/{chunk-O54DY26V.js → chunk-MXEWQKM7.js} +2 -2
- package/dist/{chunk-33L6XHU2.js → chunk-NUIJEGVD.js} +6 -6
- package/dist/{chunk-3E5WRQNQ.js → chunk-OXEAMU42.js} +529 -9
- package/dist/chunk-OXEAMU42.js.map +1 -0
- package/dist/{chunk-EOBJRBLC.js → chunk-QGJAGC2J.js} +2 -2
- package/dist/{chunk-SMIVW7XC.js → chunk-QIMFOCSH.js} +2 -2
- package/dist/{chunk-IJEZMWKA.js → chunk-SFOAQQDJ.js} +3 -3
- package/dist/{chunk-HRUULBBV.js → chunk-SHRRWOVY.js} +81 -4
- package/dist/chunk-SHRRWOVY.js.map +1 -0
- package/dist/{chunk-NINRTFSV.js → chunk-SINGJCUR.js} +5 -5
- package/dist/{chunk-ZPQVJEVQ.js → chunk-SK2CR6MW.js} +146 -2
- package/dist/chunk-SK2CR6MW.js.map +1 -0
- package/dist/{chunk-SEWF2O74.js → chunk-TGAHHCB6.js} +2 -2
- package/dist/{chunk-D75JXBV4.js → chunk-WN4GHSDH.js} +2 -2
- package/dist/{chunk-K4DWSPMW.js → chunk-WRGPE6AW.js} +2 -2
- package/dist/{chunk-GA5A6MJH.js → chunk-XGMCUY5P.js} +62 -116
- package/dist/chunk-XGMCUY5P.js.map +1 -0
- package/dist/{chunk-SDPDU2PM.js → chunk-YTMDF6S7.js} +2 -2
- package/dist/{chunk-ZDK2IW5F.js → chunk-Z7XEIAV4.js} +2 -2
- package/dist/{chunk-NHBEO3F3.js → chunk-ZCEI242W.js} +24 -24
- package/dist/{cli--yVN9yEV.d.ts → cli-BM4xQPp4.d.ts} +3 -3
- package/dist/cli.d.ts +7 -7
- package/dist/cli.js +35 -34
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +8 -7
- 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 +8 -7
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +9 -8
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/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 +8 -7
- package/dist/entity-schema.d.ts +1 -1
- package/dist/event-order-recall.js +2 -1
- package/dist/explicit-capture.d.ts +5 -5
- package/dist/explicit-capture.js +2 -2
- package/dist/explicit-cue-recall.js +2 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- 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.d.ts +14 -1
- package/dist/extraction.js +5 -3
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{forget-BEXG5PQC.js → forget-6SOIPUMQ.js} +3 -3
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +123 -123
- package/dist/index.js +57 -56
- 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/index.d.ts +1 -1
- 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/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +8 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +8 -7
- package/dist/maintenance/rebuild-memory-projection.js +9 -8
- package/dist/mcp-memory-inspector-app.d.ts +7 -7
- 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 +9 -8
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +14 -3
- package/dist/namespaces/storage.js +8 -7
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +14 -13
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +10 -9
- package/dist/{orchestrator-CJI4xdqV.d.ts → orchestrator-C9CDWAm6.d.ts} +4 -4
- package/dist/orchestrator.d.ts +5 -5
- package/dist/orchestrator.js +33 -31
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +36 -2
- package/dist/provenance.js +5 -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-pipeline-stages.d.ts +15 -0
- package/dist/recall-pipeline-stages.js +3 -56
- package/dist/recall-pipeline-stages.js.map +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- 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/response-guidance-recall.js +2 -1
- package/dist/resume-bundles.js +7 -5
- 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/schemas.d.ts +19 -0
- package/dist/schemas.js +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-DJR8_DMQ.d.ts → semantic-SLAa_prH.d.ts} +1 -1
- package/dist/{semantic-consolidation-BtUfv-AL.d.ts → semantic-consolidation-DgFXyALl.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +9 -8
- package/dist/semantic-rule-promotion.js +8 -7
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +8 -7
- 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 +13 -1
- package/dist/storage.js +7 -6
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -2
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/summary-snapshot.js +2 -1
- package/dist/targeted-fact-recall.js +2 -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/transcript.js +2 -2
- package/dist/{types-PuiPZ9iE.d.ts → types-MWKPZnM0.d.ts} +25 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/utils/serialize-mutations.js +1 -1
- package/dist/verified-recall.js +8 -7
- package/package.json +2 -2
- package/src/access-http.ts +162 -0
- package/src/access-service.ts +215 -0
- package/src/admin/admin-surfaces.test.ts +458 -0
- package/src/admin/admin-surfaces.ts +819 -0
- package/src/event-order-recall.test.ts +53 -0
- package/src/event-order-recall.ts +61 -37
- package/src/explicit-cue-recall.ts +21 -1
- package/src/extraction.ts +91 -6
- package/src/namespaces/catalog.test.ts +222 -14
- package/src/namespaces/catalog.ts +54 -187
- package/src/namespaces/storage.ts +87 -80
- package/src/orchestrator.ts +46 -5
- package/src/provenance-extraction.test.ts +838 -0
- package/src/provenance.ts +413 -0
- package/src/recall-pipeline-parity.test.ts +288 -0
- package/src/recall-pipeline-stages.test.ts +43 -0
- package/src/recall-pipeline-stages.ts +22 -0
- package/src/response-guidance-recall.ts +32 -36
- package/src/schemas.ts +7 -0
- package/src/storage.ts +23 -0
- package/src/summary-snapshot.test.ts +63 -1
- package/src/summary-snapshot.ts +61 -80
- package/src/targeted-fact-recall.ts +25 -32
- package/src/types.ts +24 -0
- package/src/utils/serialize-mutations.ts +10 -6
- package/dist/chunk-3E5WRQNQ.js.map +0 -1
- package/dist/chunk-DR2JTSLZ.js.map +0 -1
- package/dist/chunk-EC2AYKRX.js.map +0 -1
- package/dist/chunk-FN2SM5SN.js.map +0 -1
- package/dist/chunk-GA5A6MJH.js.map +0 -1
- package/dist/chunk-HRUULBBV.js.map +0 -1
- package/dist/chunk-PQG4T5V3.js.map +0 -1
- package/dist/chunk-R5DB26G6.js.map +0 -1
- package/dist/chunk-ROZJACKP.js.map +0 -1
- package/dist/chunk-UU6MVCJ6.js.map +0 -1
- package/dist/chunk-X74FJSW7.js.map +0 -1
- package/dist/chunk-XJNBEDFE.js.map +0 -1
- package/dist/chunk-YXLT4EMM.js.map +0 -1
- package/dist/chunk-ZPQVJEVQ.js.map +0 -1
- /package/dist/{chunk-PCZR32VL.js.map → chunk-3JJWNZTT.js.map} +0 -0
- /package/dist/{chunk-YXIFA36P.js.map → chunk-6TAETM63.js.map} +0 -0
- /package/dist/{chunk-6JDGADXK.js.map → chunk-6VVP6NK7.js.map} +0 -0
- /package/dist/{chunk-CHM274U6.js.map → chunk-AMNLZ6SF.js.map} +0 -0
- /package/dist/{chunk-JX3YZVII.js.map → chunk-CZJ6QSKG.js.map} +0 -0
- /package/dist/{chunk-GYVVQYA3.js.map → chunk-EOOVJK2U.js.map} +0 -0
- /package/dist/{chunk-JKW5XSWC.js.map → chunk-EYJD6KIO.js.map} +0 -0
- /package/dist/{chunk-XY4WJTEX.js.map → chunk-EZR35XHX.js.map} +0 -0
- /package/dist/{chunk-2NWHLAXX.js.map → chunk-FVI5B7DE.js.map} +0 -0
- /package/dist/{chunk-PONNZ54D.js.map → chunk-GY3SKOS4.js.map} +0 -0
- /package/dist/{chunk-U33LWTQQ.js.map → chunk-HV57RHMD.js.map} +0 -0
- /package/dist/{chunk-RC3CNIPK.js.map → chunk-IO5NQEGZ.js.map} +0 -0
- /package/dist/{chunk-HDLC75NX.js.map → chunk-IX72AAMZ.js.map} +0 -0
- /package/dist/{chunk-G5PKTQ5J.js.map → chunk-JTKFZMZ7.js.map} +0 -0
- /package/dist/{chunk-YMTGXDN6.js.map → chunk-KC6TCAWV.js.map} +0 -0
- /package/dist/{chunk-ED35D32I.js.map → chunk-LQ4J7ELC.js.map} +0 -0
- /package/dist/{chunk-RJ2THZ4H.js.map → chunk-MBUM2Y3L.js.map} +0 -0
- /package/dist/{chunk-T5QAZIBO.js.map → chunk-MOXFPLD6.js.map} +0 -0
- /package/dist/{chunk-O54DY26V.js.map → chunk-MXEWQKM7.js.map} +0 -0
- /package/dist/{chunk-33L6XHU2.js.map → chunk-NUIJEGVD.js.map} +0 -0
- /package/dist/{chunk-EOBJRBLC.js.map → chunk-QGJAGC2J.js.map} +0 -0
- /package/dist/{chunk-SMIVW7XC.js.map → chunk-QIMFOCSH.js.map} +0 -0
- /package/dist/{chunk-IJEZMWKA.js.map → chunk-SFOAQQDJ.js.map} +0 -0
- /package/dist/{chunk-NINRTFSV.js.map → chunk-SINGJCUR.js.map} +0 -0
- /package/dist/{chunk-SEWF2O74.js.map → chunk-TGAHHCB6.js.map} +0 -0
- /package/dist/{chunk-D75JXBV4.js.map → chunk-WN4GHSDH.js.map} +0 -0
- /package/dist/{chunk-K4DWSPMW.js.map → chunk-WRGPE6AW.js.map} +0 -0
- /package/dist/{chunk-SDPDU2PM.js.map → chunk-YTMDF6S7.js.map} +0 -0
- /package/dist/{chunk-ZDK2IW5F.js.map → chunk-Z7XEIAV4.js.map} +0 -0
- /package/dist/{chunk-NHBEO3F3.js.map → chunk-ZCEI242W.js.map} +0 -0
- /package/dist/{forget-BEXG5PQC.js.map → forget-6SOIPUMQ.js.map} +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// Issue #1539 PRs 3–6 — migration parity guards.
|
|
2
|
+
//
|
|
3
|
+
// Each recall pipeline (targeted-fact, response-guidance, explicit-cue,
|
|
4
|
+
// event-order) was migrated from an inline dedup/score/threshold/sort block to
|
|
5
|
+
// a thin config object handed to `unifiedDedupeAndRank` (the spine). The
|
|
6
|
+
// per-tier test files (targeted-fact-recall.test.ts etc.) are the end-to-end
|
|
7
|
+
// characterization — they exercise buildXxxRecallSection on full fixtures. This
|
|
8
|
+
// file is the SEAM guard: it pins, per pipeline, that the declared config shape
|
|
9
|
+
// routes through the spine with the correct divergence (sort direction,
|
|
10
|
+
// content-dedup on/off, rank threshold, transform-applied-to-output-but-not-
|
|
11
|
+
// score). If a future refactor flips one of these silently, the matching test
|
|
12
|
+
// here fails BEFORE the end-to-end suites have a chance to mask it.
|
|
13
|
+
//
|
|
14
|
+
// These tests do NOT re-implement the per-tier scorers — those are genuinely
|
|
15
|
+
// per-tier policy and stay in the per-tier test files. Here we use small
|
|
16
|
+
// representative scorers that exercise the divergence dimension each pipeline
|
|
17
|
+
// declares on the spine.
|
|
18
|
+
|
|
19
|
+
import assert from "node:assert/strict";
|
|
20
|
+
import test from "node:test";
|
|
21
|
+
|
|
22
|
+
import type { EvidencePackItem } from "./evidence-pack.js";
|
|
23
|
+
import { unifiedDedupeAndRank } from "./recall-pipeline-stages.js";
|
|
24
|
+
|
|
25
|
+
function item(
|
|
26
|
+
turnIndex: number,
|
|
27
|
+
content: string,
|
|
28
|
+
overrides: Partial<EvidencePackItem> = {},
|
|
29
|
+
): EvidencePackItem {
|
|
30
|
+
return {
|
|
31
|
+
id: `s1:${turnIndex}`,
|
|
32
|
+
sessionId: "s1",
|
|
33
|
+
turnIndex,
|
|
34
|
+
role: "user",
|
|
35
|
+
content,
|
|
36
|
+
...overrides,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// PR3 — targeted-fact shape: DESC, content-dedup, transform-applied-to-output,
|
|
42
|
+
// score-on-original. No rank threshold.
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
test("parity targeted-fact: DESC ordering on score ties (latest turnIndex first)", () => {
|
|
46
|
+
const items = [
|
|
47
|
+
item(1, "early turn"),
|
|
48
|
+
item(5, "late turn"),
|
|
49
|
+
item(3, "middle turn"),
|
|
50
|
+
];
|
|
51
|
+
// Constant scorer forces score ties so turnIndex DESC is the deciding key.
|
|
52
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
53
|
+
query: "",
|
|
54
|
+
intents: [],
|
|
55
|
+
scoreEvidence: () => 10,
|
|
56
|
+
});
|
|
57
|
+
assert.equal(ranked[0].turnIndex, 5);
|
|
58
|
+
assert.equal(ranked[1].turnIndex, 3);
|
|
59
|
+
assert.equal(ranked[2].turnIndex, 1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("parity targeted-fact: content transform applied to output, NOT to scorer input", () => {
|
|
63
|
+
const items = [item(2, "Revenue was $500")];
|
|
64
|
+
// A scorer that reads ORIGINAL content must NOT see the appended cue suffix.
|
|
65
|
+
let scorerObserved = "";
|
|
66
|
+
unifiedDedupeAndRank(items, {
|
|
67
|
+
query: "",
|
|
68
|
+
intents: [],
|
|
69
|
+
scoreEvidence: (original) => {
|
|
70
|
+
scorerObserved = original.content;
|
|
71
|
+
return 1;
|
|
72
|
+
},
|
|
73
|
+
transformContent: (content) => `${content} [CUE]`,
|
|
74
|
+
});
|
|
75
|
+
assert.equal(scorerObserved, "Revenue was $500");
|
|
76
|
+
// ...and the output content carries the transform:
|
|
77
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
78
|
+
query: "",
|
|
79
|
+
intents: [],
|
|
80
|
+
scoreEvidence: () => 1,
|
|
81
|
+
transformContent: (content) => `${content} [CUE]`,
|
|
82
|
+
});
|
|
83
|
+
assert.equal(ranked[0].content, "Revenue was $500 [CUE]");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("parity targeted-fact: duplicate normalized content collapses (default dedupByContent)", () => {
|
|
87
|
+
const items = [
|
|
88
|
+
item(1, "Same content"),
|
|
89
|
+
item(2, "same content"), // whitespace/case differences normalize equal
|
|
90
|
+
item(3, "Distinct"),
|
|
91
|
+
];
|
|
92
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
93
|
+
query: "",
|
|
94
|
+
intents: [],
|
|
95
|
+
scoreEvidence: () => 10,
|
|
96
|
+
});
|
|
97
|
+
assert.equal(ranked.length, 2);
|
|
98
|
+
// First-seen wins: turnIndex 1 is kept, turnIndex 2 (same normalized content)
|
|
99
|
+
// is dropped. Output is then sorted DESC by turnIndex on the score tie.
|
|
100
|
+
assert.deepEqual(
|
|
101
|
+
ranked.map((r) => r.turnIndex),
|
|
102
|
+
[3, 1],
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// PR4 — response-guidance shape: same as targeted-fact (DESC + content-dedup +
|
|
108
|
+
// transform + score-on-original) but with guidance-cue transform. Pin the shape
|
|
109
|
+
// matches; the guidance-specific transform is exercised end-to-end in
|
|
110
|
+
// response-guidance-recall.test.ts.
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
test("parity response-guidance: DESC ordering on score ties", () => {
|
|
114
|
+
const items = [
|
|
115
|
+
item(2, "guidance b"),
|
|
116
|
+
item(7, "guidance a"),
|
|
117
|
+
];
|
|
118
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
119
|
+
query: "",
|
|
120
|
+
intents: [],
|
|
121
|
+
scoreEvidence: () => 5,
|
|
122
|
+
});
|
|
123
|
+
assert.equal(ranked[0].turnIndex, 7);
|
|
124
|
+
assert.equal(ranked[1].turnIndex, 2);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("parity response-guidance: rank primary key always DESC regardless of turnIndex", () => {
|
|
128
|
+
const items = [
|
|
129
|
+
item(1, "low rank high turnIndex"),
|
|
130
|
+
item(9, "high rank low turnIndex"),
|
|
131
|
+
];
|
|
132
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
133
|
+
query: "",
|
|
134
|
+
intents: [],
|
|
135
|
+
scoreEvidence: (original) => (original.content.includes("high rank") ? 20 : 1),
|
|
136
|
+
});
|
|
137
|
+
assert.equal(ranked[0].turnIndex, 9);
|
|
138
|
+
assert.equal(ranked[1].turnIndex, 1);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// PR5 — explicit-cue shape: the pipeline that OPTS OUT of scoring AND sorting.
|
|
143
|
+
// Its deliberate value ordering (turn references -> content cues -> lexical
|
|
144
|
+
// cues, by evidence type then read-key priority) is the highest-value design
|
|
145
|
+
// choice — preserved verbatim via preserveInsertionOrder + constant scorer +
|
|
146
|
+
// id-only dedup (the spine call is behavior-idempotent; items are already
|
|
147
|
+
// deduped by seenTurns during collection).
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
test("parity explicit-cue: preserveInsertionOrder keeps collection order intact", () => {
|
|
151
|
+
// Insertion order is 2, 4, 1. A default sort would reorder to 4, 2, 1.
|
|
152
|
+
// explicit-cue must NOT reorder — the collection order IS the value order.
|
|
153
|
+
const items = [
|
|
154
|
+
item(2, "explicit cue b", { id: "s:2" }),
|
|
155
|
+
item(4, "explicit cue a", { id: "s:4" }),
|
|
156
|
+
item(1, "explicit cue c", { id: "s:1" }),
|
|
157
|
+
];
|
|
158
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
159
|
+
query: "",
|
|
160
|
+
intents: [],
|
|
161
|
+
scoreEvidence: () => 0,
|
|
162
|
+
dedupByContent: false,
|
|
163
|
+
preserveInsertionOrder: true,
|
|
164
|
+
});
|
|
165
|
+
assert.deepEqual(
|
|
166
|
+
ranked.map((r) => r.turnIndex),
|
|
167
|
+
[2, 4, 1],
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("parity explicit-cue: id-only dedup (dedupByContent false) keeps distinct turns", () => {
|
|
172
|
+
const items = [
|
|
173
|
+
item(1, "same body", { id: "s:1" }),
|
|
174
|
+
item(2, "same body", { id: "s:2" }),
|
|
175
|
+
];
|
|
176
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
177
|
+
query: "",
|
|
178
|
+
intents: [],
|
|
179
|
+
scoreEvidence: () => 0,
|
|
180
|
+
dedupByContent: false,
|
|
181
|
+
preserveInsertionOrder: true,
|
|
182
|
+
});
|
|
183
|
+
assert.equal(ranked.length, 2);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// PR6 — event-order shape: ASC ordering, NO content-dedup (dedupByContent:false
|
|
188
|
+
// keeps distinct turns even when cue-appended bodies match), declared
|
|
189
|
+
// rankThreshold of 6, transform applied to output.
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
test("parity event-order: ASC ordering on rank ties (earliest turnIndex first)", () => {
|
|
193
|
+
const items = [
|
|
194
|
+
item(5, "turn five"),
|
|
195
|
+
item(1, "turn one"),
|
|
196
|
+
item(3, "turn three"),
|
|
197
|
+
];
|
|
198
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
199
|
+
query: "",
|
|
200
|
+
intents: [],
|
|
201
|
+
scoreEvidence: () => 10,
|
|
202
|
+
turnIndexSortDirection: "asc",
|
|
203
|
+
});
|
|
204
|
+
assert.deepEqual(
|
|
205
|
+
ranked.map((r) => r.turnIndex),
|
|
206
|
+
[1, 3, 5],
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("parity event-order: dedupByContent false keeps distinct turns with identical bodies", () => {
|
|
211
|
+
// Two distinct turns (different ids/turnIndex) but identical cue-appended body.
|
|
212
|
+
// Event-order MUST keep both — they are legitimate repeated turns in a
|
|
213
|
+
// chronological transcript. This is the cursor-bugbot a4299851 regression guard.
|
|
214
|
+
const items = [
|
|
215
|
+
item(1, "the user asked again"),
|
|
216
|
+
item(4, "the user asked again"),
|
|
217
|
+
];
|
|
218
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
219
|
+
query: "",
|
|
220
|
+
intents: [],
|
|
221
|
+
scoreEvidence: () => 10,
|
|
222
|
+
dedupByContent: false,
|
|
223
|
+
turnIndexSortDirection: "asc",
|
|
224
|
+
});
|
|
225
|
+
assert.equal(ranked.length, 2);
|
|
226
|
+
assert.deepEqual(
|
|
227
|
+
ranked.map((r) => r.turnIndex),
|
|
228
|
+
[1, 4],
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("parity event-order: declared rankThreshold (6) drops below-threshold items", () => {
|
|
233
|
+
const items = [
|
|
234
|
+
item(1, "strong", { id: "s:1" }),
|
|
235
|
+
item(2, "weak", { id: "s:2" }),
|
|
236
|
+
item(3, "borderline", { id: "s:3" }),
|
|
237
|
+
];
|
|
238
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
239
|
+
query: "",
|
|
240
|
+
intents: [],
|
|
241
|
+
scoreEvidence: (original) => {
|
|
242
|
+
if (original.content === "strong") return 10;
|
|
243
|
+
if (original.content === "borderline") return 6; // exactly threshold -> kept (>=)
|
|
244
|
+
return 5; // weak -> dropped
|
|
245
|
+
},
|
|
246
|
+
rankThreshold: 6,
|
|
247
|
+
turnIndexSortDirection: "asc",
|
|
248
|
+
});
|
|
249
|
+
// rank is ALWAYS DESC primary: strong (10) outranks borderline (6).
|
|
250
|
+
// turnIndex ASC only breaks ties within the same rank.
|
|
251
|
+
assert.deepEqual(
|
|
252
|
+
ranked.map((r) => r.content),
|
|
253
|
+
["strong", "borderline"],
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Cross-pipeline divergence guard: the SAME corpus produces DIFFERENT orderings
|
|
259
|
+
// under DESC vs ASC config. This is the core #1539 defect class — copy-pasting
|
|
260
|
+
// the comparator into a chronological pipeline silently inverts ordering.
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
test("parity divergence: DESC vs ASC configs on the same corpus invert turnIndex ordering", () => {
|
|
264
|
+
const corpus = [
|
|
265
|
+
item(1, "a", { id: "x:1" }),
|
|
266
|
+
item(2, "b", { id: "x:2" }),
|
|
267
|
+
item(3, "c", { id: "x:3" }),
|
|
268
|
+
];
|
|
269
|
+
const desc = unifiedDedupeAndRank(corpus, {
|
|
270
|
+
query: "",
|
|
271
|
+
intents: [],
|
|
272
|
+
scoreEvidence: () => 10,
|
|
273
|
+
});
|
|
274
|
+
const asc = unifiedDedupeAndRank(corpus, {
|
|
275
|
+
query: "",
|
|
276
|
+
intents: [],
|
|
277
|
+
scoreEvidence: () => 10,
|
|
278
|
+
turnIndexSortDirection: "asc",
|
|
279
|
+
});
|
|
280
|
+
assert.deepEqual(
|
|
281
|
+
desc.map((r) => r.turnIndex),
|
|
282
|
+
[3, 2, 1],
|
|
283
|
+
);
|
|
284
|
+
assert.deepEqual(
|
|
285
|
+
asc.map((r) => r.turnIndex),
|
|
286
|
+
[1, 2, 3],
|
|
287
|
+
);
|
|
288
|
+
});
|
|
@@ -267,3 +267,46 @@ test("unifiedDedupeAndRank: dedupByContent true (default) still collapses identi
|
|
|
267
267
|
});
|
|
268
268
|
assert.equal(resultExplicit.length, 1, "explicit dedupByContent: true collapses identical content");
|
|
269
269
|
});
|
|
270
|
+
|
|
271
|
+
test("unifiedDedupeAndRank: preserveInsertionOrder skips the sort stage (explicit-cue shape)", () => {
|
|
272
|
+
// Insertion order is turn-index 3, 1, 2. A DESC sort would reorder to 3, 2, 1.
|
|
273
|
+
// preserveInsertionOrder must keep the original (post-dedup) order: 3, 1, 2.
|
|
274
|
+
const items: EvidencePackItem[] = [
|
|
275
|
+
item(3, "turn ref", { id: "s:3" }),
|
|
276
|
+
item(1, "content cue", { id: "s:1" }),
|
|
277
|
+
item(2, "lexical hit", { id: "s:2" }),
|
|
278
|
+
];
|
|
279
|
+
const result = unifiedDedupeAndRank(items, {
|
|
280
|
+
query: "q",
|
|
281
|
+
intents: NO_INTENTS,
|
|
282
|
+
scoreEvidence: () => 0, // explicit-cue does not score
|
|
283
|
+
dedupByContent: false, // explicit-cue dedupes by id only (seenTurns)
|
|
284
|
+
preserveInsertionOrder: true,
|
|
285
|
+
});
|
|
286
|
+
assert.deepEqual(
|
|
287
|
+
result.map((r) => r.id),
|
|
288
|
+
["s:3", "s:1", "s:2"],
|
|
289
|
+
"insertion order is preserved — no sort applied",
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("unifiedDedupeAndRank: preserveInsertionOrder still applies threshold filter", () => {
|
|
294
|
+
const items: EvidencePackItem[] = [
|
|
295
|
+
item(1, "strong", { id: "s:1" }),
|
|
296
|
+
item(2, "weak", { id: "s:2" }),
|
|
297
|
+
item(3, "medium", { id: "s:3" }),
|
|
298
|
+
];
|
|
299
|
+
const result = unifiedDedupeAndRank(items, {
|
|
300
|
+
query: "q",
|
|
301
|
+
intents: NO_INTENTS,
|
|
302
|
+
scoreEvidence: (original) => (original.content === "strong" ? 10 : original.content === "medium" ? 5 : 1),
|
|
303
|
+
rankThreshold: 5,
|
|
304
|
+
dedupByContent: false,
|
|
305
|
+
preserveInsertionOrder: true,
|
|
306
|
+
});
|
|
307
|
+
// threshold drops "weak" (rank 1); insertion order of survivors preserved.
|
|
308
|
+
assert.deepEqual(
|
|
309
|
+
result.map((r) => r.id),
|
|
310
|
+
["s:1", "s:3"],
|
|
311
|
+
);
|
|
312
|
+
});
|
|
@@ -125,6 +125,21 @@ export interface UnifiedRankConfig<TIntent> {
|
|
|
125
125
|
* The rank primary key is ALWAYS DESC regardless of this setting.
|
|
126
126
|
*/
|
|
127
127
|
turnIndexSortDirection?: TurnIndexSortDirection;
|
|
128
|
+
/**
|
|
129
|
+
* When `true`, skip the sort stage entirely and return items in their
|
|
130
|
+
* post-dedup insertion order (first-seen first). Used by explicit-cue,
|
|
131
|
+
* whose deliberate value ordering — turn references → content cues →
|
|
132
|
+
* lexical cues, gathered by evidence TYPE then read-key priority — is the
|
|
133
|
+
* pipeline's highest-value design choice. A rank-based sort would invert
|
|
134
|
+
* it (turn references are deliberately UNSCORED while lexical hits carry
|
|
135
|
+
* numeric scores; sorting by score would demote turn references below
|
|
136
|
+
* weak lexical hits — cursor[bot] / codex P2 on #1505).
|
|
137
|
+
*
|
|
138
|
+
* Score and threshold-filter still apply when declared; only the sort is
|
|
139
|
+
* skipped. The constant scorer (`scoreEvidence: () => 0`) makes the
|
|
140
|
+
* no-scoring policy explicit alongside this flag.
|
|
141
|
+
*/
|
|
142
|
+
preserveInsertionOrder?: boolean;
|
|
128
143
|
}
|
|
129
144
|
|
|
130
145
|
/**
|
|
@@ -232,6 +247,13 @@ export function unifiedDedupeAndRank<TIntent>(
|
|
|
232
247
|
// turnIndex direction is configurable: DESC (relevance) or ASC (chronology).
|
|
233
248
|
// The tertiary tiebreaker follows the direction:
|
|
234
249
|
// DESC → score DESC; ASC → content.localeCompare.
|
|
250
|
+
//
|
|
251
|
+
// explicit-cue opts out of sorting entirely (preserveInsertionOrder) — its
|
|
252
|
+
// deliberate value ordering is the collection order (turn references →
|
|
253
|
+
// content cues → lexical cues), and a rank-based sort would invert it.
|
|
254
|
+
if (config.preserveInsertionOrder) {
|
|
255
|
+
return filtered;
|
|
256
|
+
}
|
|
235
257
|
return filtered.sort(makeComparator(direction));
|
|
236
258
|
}
|
|
237
259
|
|
|
@@ -5,6 +5,11 @@ import {
|
|
|
5
5
|
resolveLcmReadSessionIds,
|
|
6
6
|
} from "./lcm-fallback-read.js";
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
unifiedDedupeAndRank,
|
|
10
|
+
type RankedEvidenceItem,
|
|
11
|
+
} from "./recall-pipeline-stages.js";
|
|
12
|
+
|
|
8
13
|
export interface ResponseGuidanceRecallOptions {
|
|
9
14
|
engine: ExplicitCueRecallEngine | null | undefined;
|
|
10
15
|
sessionId?: string;
|
|
@@ -24,10 +29,6 @@ export interface ResponseGuidanceRecallOptions {
|
|
|
24
29
|
forceGeneric?: boolean;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
interface RankedGuidanceItem extends EvidencePackItem {
|
|
28
|
-
rank: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
32
|
type GuidanceIntent =
|
|
32
33
|
| "dates"
|
|
33
34
|
| "editing"
|
|
@@ -341,42 +342,37 @@ async function collectGuidanceScanItems(
|
|
|
341
342
|
return items;
|
|
342
343
|
}
|
|
343
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Issue #1539 PR4: response-guidance now routes dedup/score/threshold/sort through
|
|
347
|
+
* the unified spine (`unifiedDedupeAndRank`). The divergence that lived inline
|
|
348
|
+
* here — append-guidance-cues content transform (two-arg: content + intents),
|
|
349
|
+
* relevance scoring on ORIGINAL content, default DESC turn-index ordering with
|
|
350
|
+
* -1 sentinel for missing turns — is now declared config on the spine. The score
|
|
351
|
+
* and appendGuidanceCues helpers stay in this module because they are genuinely
|
|
352
|
+
* per-tier policy; only the duplicated dedup/sort/threshold machinery moved out.
|
|
353
|
+
*
|
|
354
|
+
* Behavior is byte-for-byte identical to the inline implementation it replaces
|
|
355
|
+
* (the spine was extracted to match it exactly; see recall-pipeline-stages.ts).
|
|
356
|
+
* The response-guidance-recall.test.ts suite is the characterization.
|
|
357
|
+
*
|
|
358
|
+
* Note: response-guidance (like the pre-migration code) dedups on the TRANSFORMED
|
|
359
|
+
* (cue-appended) content, not the original — this matches the spine's default
|
|
360
|
+
* (transform-then-dedup) exactly. No divergence from prior behavior.
|
|
361
|
+
*/
|
|
344
362
|
function rankAndDedupeGuidanceItems(
|
|
345
363
|
items: EvidencePackItem[],
|
|
346
364
|
query: string,
|
|
347
365
|
intents: readonly GuidanceIntent[],
|
|
348
|
-
):
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
);
|
|
359
|
-
if (id && seenIds.has(id)) continue;
|
|
360
|
-
|
|
361
|
-
const enhancedContent = appendGuidanceCues(item.content, intents);
|
|
362
|
-
const contentKey = enhancedContent.toLowerCase().replace(/\s+/g, " ").trim();
|
|
363
|
-
if (seenContent.has(contentKey)) continue;
|
|
364
|
-
|
|
365
|
-
if (id) seenIds.add(id);
|
|
366
|
-
seenContent.add(contentKey);
|
|
367
|
-
ranked.push({
|
|
368
|
-
...item,
|
|
369
|
-
content: enhancedContent,
|
|
370
|
-
rank: scoreGuidanceEvidence(item, query, intents),
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return ranked.sort((left, right) => {
|
|
375
|
-
if (right.rank !== left.rank) return right.rank - left.rank;
|
|
376
|
-
const leftTurn = typeof left.turnIndex === "number" ? left.turnIndex : -1;
|
|
377
|
-
const rightTurn = typeof right.turnIndex === "number" ? right.turnIndex : -1;
|
|
378
|
-
if (rightTurn !== leftTurn) return rightTurn - leftTurn;
|
|
379
|
-
return (right.score ?? 0) - (left.score ?? 0);
|
|
366
|
+
): RankedEvidenceItem[] {
|
|
367
|
+
// The spine's config.intents is typed TIntent[] (readability contract — it is
|
|
368
|
+
// never mutated). Cast the readonly array through; safe because the spine only
|
|
369
|
+
// forwards intents to the score/transform callbacks.
|
|
370
|
+
const mutableIntents = intents as GuidanceIntent[];
|
|
371
|
+
return unifiedDedupeAndRank(items, {
|
|
372
|
+
query,
|
|
373
|
+
intents: mutableIntents,
|
|
374
|
+
scoreEvidence: (item, q, intentArr) => scoreGuidanceEvidence(item, q, intentArr),
|
|
375
|
+
transformContent: (content, intentArr) => appendGuidanceCues(content, intentArr),
|
|
380
376
|
});
|
|
381
377
|
}
|
|
382
378
|
|
package/src/schemas.ts
CHANGED
|
@@ -93,6 +93,13 @@ export const ExtractedFactSchema = z.object({
|
|
|
93
93
|
.describe(
|
|
94
94
|
'Scope classification: "global" for cross-project knowledge (framework bugs, library behavior, API patterns, user preferences, tool configs, general coding patterns); "project" for project-specific knowledge (file paths, env configs, deployment details, project workarounds). Defaults to "project" when a coding context is active.',
|
|
95
95
|
),
|
|
96
|
+
quote: z
|
|
97
|
+
.string()
|
|
98
|
+
.optional()
|
|
99
|
+
.nullable()
|
|
100
|
+
.describe(
|
|
101
|
+
"The EXACT verbatim words from the conversation that support this fact. Copy a contiguous span from a single speaker turn (not a paraphrase). Cap at ~300 characters. This is the grounding evidence for downstream faithfulness verification (issue #1575).",
|
|
102
|
+
),
|
|
96
103
|
structuredAttributes: z
|
|
97
104
|
.record(z.string(), z.string())
|
|
98
105
|
.optional()
|
package/src/storage.ts
CHANGED
|
@@ -88,6 +88,7 @@ import type {
|
|
|
88
88
|
ScoredEntity,
|
|
89
89
|
TopicScore,
|
|
90
90
|
FileHygieneConfig,
|
|
91
|
+
ProvenanceSource,
|
|
91
92
|
} from "./types.js";
|
|
92
93
|
import { confidenceTier, SPECULATIVE_TTL_DAYS } from "./types.js";
|
|
93
94
|
import {
|
|
@@ -3608,6 +3609,14 @@ export class StorageManager {
|
|
|
3608
3609
|
* can consume it. Absent = gate was off or fact predates #1576.
|
|
3609
3610
|
*/
|
|
3610
3611
|
faithfulness?: import("./types.js").FaithfulnessFrontmatter;
|
|
3612
|
+
/**
|
|
3613
|
+
* Claim-level provenance spans (issue #1575 PR 2). When provided,
|
|
3614
|
+
* persisted to frontmatter so downstream readers (memory_get, x-ray,
|
|
3615
|
+
* faithfulness gate #1576) can consume them. Absent = fact predates
|
|
3616
|
+
* #1575 or provenance was disabled.
|
|
3617
|
+
*/
|
|
3618
|
+
sources?: ProvenanceSource[];
|
|
3619
|
+
provenance?: "verified" | "unverified" | "none";
|
|
3611
3620
|
} = {},
|
|
3612
3621
|
): Promise<string> {
|
|
3613
3622
|
await this.ensureDirectories();
|
|
@@ -3681,6 +3690,14 @@ export class StorageManager {
|
|
|
3681
3690
|
if (options.faithfulness !== undefined) {
|
|
3682
3691
|
fm.faithfulness = options.faithfulness;
|
|
3683
3692
|
}
|
|
3693
|
+
// Claim-level provenance (issue #1575 PR 2). Wire through to frontmatter
|
|
3694
|
+
// so verified spans survive extraction → storage → memory_get end-to-end.
|
|
3695
|
+
if (options.sources !== undefined) {
|
|
3696
|
+
fm.sources = options.sources;
|
|
3697
|
+
}
|
|
3698
|
+
if (options.provenance !== undefined) {
|
|
3699
|
+
fm.provenance = options.provenance;
|
|
3700
|
+
}
|
|
3684
3701
|
|
|
3685
3702
|
// Append structured attributes as searchable suffix so QMD indexes them.
|
|
3686
3703
|
// normalizeAttributePairs sorts and lowercases keys so the enriched content
|
|
@@ -7338,6 +7355,10 @@ export class StorageManager {
|
|
|
7338
7355
|
status?: import("./types.js").MemoryStatus;
|
|
7339
7356
|
/** Faithfulness gate verdict (issue #1576), propagated from the parent fact. */
|
|
7340
7357
|
faithfulness?: import("./types.js").FaithfulnessFrontmatter;
|
|
7358
|
+
/** Claim-level provenance spans (issue #1575 PR 2), propagated from the parent fact so a chunk surfaced independently (memory_get/x-ray) preserves them (chatgpt-codex-connector thread Ocvmo). */
|
|
7359
|
+
sources?: ProvenanceSource[];
|
|
7360
|
+
/** Coarse provenance tag (issue #1575 PR 2), propagated from the parent. */
|
|
7361
|
+
provenance?: "verified" | "unverified" | "none";
|
|
7341
7362
|
} = {},
|
|
7342
7363
|
): Promise<string> {
|
|
7343
7364
|
await this.ensureDirectories();
|
|
@@ -7369,6 +7390,8 @@ export class StorageManager {
|
|
|
7369
7390
|
valid_at: validAt,
|
|
7370
7391
|
...(options.status ? { status: options.status } : {}),
|
|
7371
7392
|
...(options.faithfulness ? { faithfulness: options.faithfulness } : {}),
|
|
7393
|
+
...(options.sources ? { sources: options.sources } : {}),
|
|
7394
|
+
...(options.provenance ? { provenance: options.provenance } : {}),
|
|
7372
7395
|
};
|
|
7373
7396
|
|
|
7374
7397
|
const sanitized = sanitizeMemoryContent(content);
|
|
@@ -2,7 +2,7 @@ import test from "node:test";
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { mkdtemp, mkdir, open, readFile, unlink, writeFile } from "node:fs/promises";
|
|
5
|
+
import { mkdtemp, mkdir, open, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
6
6
|
import { HourlySummarizer } from "./summarizer.js";
|
|
7
7
|
import {
|
|
8
8
|
readSummarySnapshot,
|
|
@@ -756,3 +756,65 @@ test("hourly transcript lookup reads encoded transcript channel directories", as
|
|
|
756
756
|
["encoded transcript"],
|
|
757
757
|
);
|
|
758
758
|
});
|
|
759
|
+
|
|
760
|
+
// ── Issue #1524 adoption prove-fail: summary-snapshot upserts must run through
|
|
761
|
+
// the shared serializeMutations chain (rejection-recovering). The defect class
|
|
762
|
+
// is a naive bare-.then(fn) chain that silently drops subsequent tasks after a
|
|
763
|
+
// rejection: if upsert A rejects (e.g. transient I/O error) and the chain does
|
|
764
|
+
// not recover, upsert B queued behind it never runs — its hour goes missing
|
|
765
|
+
// from the snapshot. We seed the failure by rejecting the first upsert (a
|
|
766
|
+
// hostile lock directory that throws on mkdir), then upsert B must STILL land
|
|
767
|
+
// its hour. Pre-fix (a poison chain) B would be skipped.
|
|
768
|
+
test("upsertSummarySnapshot recovers after a prior upsert rejects (issue #1524 poison-chain prove-fail)", async () => {
|
|
769
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "engram-summary-poison-chain-"));
|
|
770
|
+
const sessionKey = "session-poison";
|
|
771
|
+
try {
|
|
772
|
+
// The lock directory for sessionKey is <root>/state/summaries/<encoded>.lock.
|
|
773
|
+
// Pre-create a FILE at the summaries root so the upsert's mkdir(dirname)
|
|
774
|
+
// inside writeSummarySnapshot throws ENOTDIR — a deterministic rejection
|
|
775
|
+
// that does not depend on filesystem timing. Both upserts target the SAME
|
|
776
|
+
// sessionKey so they serialize through the same chain.
|
|
777
|
+
const summariesRoot = path.join(memoryDir, "state", "summaries");
|
|
778
|
+
await mkdir(path.dirname(summariesRoot), { recursive: true });
|
|
779
|
+
await writeFile(summariesRoot, "not-a-dir", "utf-8");
|
|
780
|
+
|
|
781
|
+
const summaryA = {
|
|
782
|
+
hour: "2026-03-26T08:00:00.000Z",
|
|
783
|
+
sessionKey,
|
|
784
|
+
bullets: ["A"],
|
|
785
|
+
turnCount: 1,
|
|
786
|
+
generatedAt: "2026-03-26T08:15:00.000Z",
|
|
787
|
+
};
|
|
788
|
+
const summaryB = {
|
|
789
|
+
hour: "2026-03-26T09:00:00.000Z",
|
|
790
|
+
sessionKey,
|
|
791
|
+
bullets: ["B"],
|
|
792
|
+
turnCount: 1,
|
|
793
|
+
generatedAt: "2026-03-26T09:15:00.000Z",
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
// Upsert A rejects (hostile summaries root). We MUST catch — the contract
|
|
797
|
+
// is that the chain recovers, not that the failing op silently succeeds.
|
|
798
|
+
await upsertSummarySnapshot(memoryDir, summaryA).then(
|
|
799
|
+
() => assert.fail("upsert A should have rejected on the hostile summaries root"),
|
|
800
|
+
() => undefined,
|
|
801
|
+
);
|
|
802
|
+
|
|
803
|
+
// Restore a usable summaries root so upsert B can actually write.
|
|
804
|
+
await unlink(summariesRoot);
|
|
805
|
+
await mkdir(summariesRoot, { recursive: true });
|
|
806
|
+
|
|
807
|
+
// Upsert B MUST still run despite A's rejection. With a poison chain B
|
|
808
|
+
// would be skipped (no snapshot, no B-hour bullet).
|
|
809
|
+
await upsertSummarySnapshot(memoryDir, summaryB);
|
|
810
|
+
|
|
811
|
+
const snapshot = await readSummarySnapshot(memoryDir, sessionKey);
|
|
812
|
+
assert.ok(snapshot, "upsert B landed after upsert A rejected (chain recovered)");
|
|
813
|
+
assert.ok(
|
|
814
|
+
snapshot.some((s) => s.bullets.includes("B")),
|
|
815
|
+
"upsert B's hour survived — the chain was not poisoned by A's rejection",
|
|
816
|
+
);
|
|
817
|
+
} finally {
|
|
818
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
819
|
+
}
|
|
820
|
+
});
|