@remnic/core 9.69.36 → 9.69.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +12 -12
- package/dist/access-admin-ops-surface.js +35 -35
- package/dist/access-authorization-probe.d.ts +12 -12
- package/dist/access-authorization-probe.js +36 -36
- package/dist/access-boundary.d.ts +12 -12
- package/dist/access-boundary.js +35 -35
- package/dist/access-cli.js +116 -111
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.d.ts +1 -1
- package/dist/access-extraction-force-flush.d.ts +12 -12
- package/dist/access-extraction-force-flush.js +35 -35
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-briefing.d.ts +12 -12
- package/dist/access-http-briefing.js +36 -36
- package/dist/access-http-lcm-compaction.d.ts +12 -12
- package/dist/access-http-lifecycle-flush.d.ts +12 -12
- package/dist/access-http-offline-stream.d.ts +12 -12
- package/dist/access-http-query.d.ts +2 -2
- package/dist/access-http-query.js +37 -37
- package/dist/access-http.d.ts +13 -13
- package/dist/access-http.js +55 -55
- package/dist/access-identity-continuity-surface.d.ts +10 -10
- package/dist/access-identity-continuity-surface.js +35 -35
- package/dist/access-lcm-surface.d.ts +12 -12
- package/dist/access-lcm-surface.js +35 -35
- package/dist/access-mcp.d.ts +12 -12
- package/dist/access-mcp.js +44 -44
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-D8qri11P.d.ts → access-namespace-preflight-CV5cHujx.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +35 -35
- package/dist/access-observe-transcript.d.ts +10 -10
- package/dist/access-observe-write-surface.d.ts +12 -12
- package/dist/access-observe-write-surface.js +35 -35
- package/dist/access-offline-manifest.d.ts +10 -10
- package/dist/access-offline-manifest.js +2 -2
- package/dist/access-operations-batch.js +38 -38
- package/dist/access-operations.d.ts +12 -12
- package/dist/access-operations.js +43 -43
- package/dist/access-recall-concurrency.d.ts +12 -12
- package/dist/access-recall-concurrency.js +35 -35
- package/dist/access-recall-response.d.ts +12 -12
- package/dist/access-recall-response.js +35 -35
- package/dist/access-recall-surface.d.ts +12 -12
- package/dist/access-recall-surface.js +35 -35
- package/dist/access-schema.d.ts +5 -5
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-Cl_x57xR.d.ts → access-service-DwHC7lcV.d.ts} +7 -7
- package/dist/access-service-helpers.d.ts +12 -12
- package/dist/access-service.d.ts +12 -12
- package/dist/access-service.js +35 -35
- package/dist/access-surface-catalog.d.ts +12 -12
- package/dist/access-surface-catalog.js +37 -37
- package/dist/access-wearables-meetings-surface.d.ts +3 -3
- 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 -3
- package/dist/active-recall.js.map +1 -1
- package/dist/activity/digest.d.ts +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/{auto-sync-4YFATLFQ.js → auto-sync-FSRJDU63.js} +4 -4
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +10 -10
- package/dist/briefing-window.d.ts +2 -2
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +9 -9
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-turn-helpers.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/calibration.js +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/{catalog-Bq1AxZwF.d.ts → catalog-Cl4H8N89.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 +12 -12
- 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-RUXPVSML.js → chunk-2AR5JFD7.js} +2 -2
- package/dist/{chunk-YNN26CIZ.js → chunk-2JGGZICG.js} +2 -2
- package/dist/{chunk-A7GQBJP5.js → chunk-2L5FSNPC.js} +10 -10
- package/dist/{chunk-XPJNT6PI.js → chunk-2PUE5P7C.js} +7 -3
- package/dist/chunk-2PUE5P7C.js.map +1 -0
- package/dist/{chunk-27IOSH2W.js → chunk-375ARKRS.js} +32 -11
- package/dist/chunk-375ARKRS.js.map +1 -0
- package/dist/{chunk-ZTG4P7NZ.js → chunk-3IXB77DA.js} +67 -67
- package/dist/{chunk-WHHEPAO3.js → chunk-3K6UFW5B.js} +3 -3
- package/dist/{chunk-UBG4GAEP.js → chunk-3OHISDDW.js} +5 -1
- package/dist/chunk-3OHISDDW.js.map +1 -0
- package/dist/chunk-3SL52JKD.js +160 -0
- package/dist/chunk-3SL52JKD.js.map +1 -0
- package/dist/{chunk-EEBQFG5E.js → chunk-3YMDY3ZF.js} +10 -10
- package/dist/{chunk-5JG5EBVL.js → chunk-4DPDIOVA.js} +2 -2
- package/dist/{chunk-UDZYNIDJ.js → chunk-4WGAMREC.js} +2 -2
- package/dist/{chunk-HXSAWV3A.js → chunk-57GDHV2D.js} +3 -3
- package/dist/{chunk-Z7ACR6MJ.js → chunk-5LLQAUMI.js} +2 -2
- package/dist/{chunk-P3ONDDIJ.js → chunk-5SYI2F47.js} +2 -2
- package/dist/{chunk-DGS34QSY.js → chunk-6E5GYDIE.js} +2 -2
- package/dist/{chunk-J7PB5UQ4.js → chunk-6Z3WW2MW.js} +2 -2
- package/dist/{chunk-GLZH5VEN.js → chunk-76DRU7C4.js} +2 -2
- package/dist/{chunk-L2QLDZJC.js → chunk-7MJBKZPU.js} +2 -2
- package/dist/{chunk-Y63ZFP4X.js → chunk-7RKBPXY6.js} +2 -2
- package/dist/chunk-A6CXG5WI.js +51 -0
- package/dist/chunk-A6CXG5WI.js.map +1 -0
- package/dist/{chunk-JSRVXI6E.js → chunk-B2YKTE7P.js} +4 -4
- package/dist/{chunk-Z3O53WZR.js → chunk-BICNRVEC.js} +2 -2
- package/dist/{chunk-BNGHPYZU.js → chunk-BLHX5FQW.js} +2 -2
- package/dist/{chunk-7CBKAQIG.js → chunk-BOADKFN4.js} +28 -4
- package/dist/chunk-BOADKFN4.js.map +1 -0
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-776GVKL7.js → chunk-CD3C4BJN.js} +2 -2
- package/dist/{chunk-LMCD5W5B.js → chunk-CIYUWSMZ.js} +2 -2
- package/dist/{chunk-YJ6VCDMZ.js → chunk-CQ7SSBO4.js} +2 -2
- package/dist/{chunk-CNV2A557.js → chunk-CYEMS2PG.js} +2 -2
- package/dist/{chunk-DVOBLPSQ.js → chunk-D2DIYEFW.js} +6 -6
- package/dist/chunk-D2DIYEFW.js.map +1 -0
- package/dist/{chunk-OQ7SSRXP.js → chunk-DOKKRZPH.js} +21 -17
- package/dist/chunk-DOKKRZPH.js.map +1 -0
- package/dist/{chunk-UYUCCQ46.js → chunk-DOQLHCSS.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/{chunk-RRZKTSFG.js → chunk-FCFAYMTU.js} +2 -2
- package/dist/{chunk-R4OCGPSH.js → chunk-FJMYMJLR.js} +2 -2
- package/dist/{chunk-TKFSXDOB.js → chunk-FLLORID2.js} +2 -2
- package/dist/{chunk-QATYVTP7.js → chunk-FPQDZCUX.js} +2 -2
- package/dist/{chunk-L2WQVSKP.js → chunk-G5MO3VQA.js} +2 -2
- package/dist/{chunk-LNDLXMH6.js → chunk-GDDSXVWR.js} +2 -2
- package/dist/{chunk-JZ2CHRLB.js → chunk-GHX5PNXW.js} +2 -2
- package/dist/{chunk-2BQ4ZIWC.js → chunk-GNR6YEWZ.js} +5 -5
- package/dist/{chunk-5FAGCOZD.js → chunk-GWK7FEUL.js} +4 -4
- package/dist/{chunk-SCXURCVT.js → chunk-GWQVMKUJ.js} +3 -3
- package/dist/{chunk-QBLDCI6E.js → chunk-HHDTBYDJ.js} +2 -2
- package/dist/{chunk-2YLMUME2.js → chunk-IQZ6ISFX.js} +2 -2
- package/dist/chunk-JE26AUX6.js +16 -0
- package/dist/chunk-JE26AUX6.js.map +1 -0
- package/dist/{chunk-AJTG3CP7.js → chunk-JHU4EYDV.js} +11 -1
- package/dist/chunk-JHU4EYDV.js.map +1 -0
- package/dist/{chunk-JHZCXUQI.js → chunk-JLXWRQNR.js} +33 -33
- package/dist/{chunk-FVJ4D6L7.js → chunk-JMPYZJQN.js} +1 -1
- package/dist/{chunk-HC4GFLMQ.js → chunk-JO5TQAKW.js} +3 -3
- package/dist/{chunk-ZBEGS7OB.js → chunk-JPKZTCWZ.js} +3 -3
- package/dist/{chunk-AZSKROVQ.js → chunk-JYR4LH76.js} +2 -2
- package/dist/{chunk-ECKG5WSI.js → chunk-JZYECQWN.js} +2 -2
- package/dist/{chunk-NPNSKE76.js → chunk-KTRXQQFN.js} +2 -2
- package/dist/{chunk-LEMD7WZJ.js → chunk-KUK56OPR.js} +62 -17
- package/dist/chunk-KUK56OPR.js.map +1 -0
- package/dist/{chunk-HUT2JR6I.js → chunk-L5OXE4PX.js} +2 -2
- package/dist/{chunk-B3O3IRZN.js → chunk-LMMQKZK3.js} +1 -1
- package/dist/{chunk-QURNMKME.js → chunk-LSDEIZ76.js} +2 -2
- package/dist/{chunk-XCOV45MO.js → chunk-MR6LQVZ5.js} +2 -2
- package/dist/chunk-OEBLZZKK.js +53 -0
- package/dist/chunk-OEBLZZKK.js.map +1 -0
- package/dist/{chunk-6J6K3STG.js → chunk-OTPXEE6W.js} +6 -6
- package/dist/{chunk-LYDJRF45.js → chunk-PNEDMJ53.js} +2 -2
- package/dist/{chunk-MPAJFFP3.js → chunk-PPTOAHTZ.js} +4 -4
- package/dist/{chunk-MPAJFFP3.js.map → chunk-PPTOAHTZ.js.map} +1 -1
- package/dist/{chunk-JRWW7GM6.js → chunk-Q6D7EUSQ.js} +7 -7
- package/dist/{chunk-VYXDUPYE.js → chunk-Q7JK6YEB.js} +2 -2
- package/dist/chunk-QJ2AX6DU.js +19 -0
- package/dist/chunk-QJ2AX6DU.js.map +1 -0
- package/dist/{chunk-4MDKNUV4.js → chunk-RN3MVLW3.js} +1 -1
- package/dist/chunk-RN3MVLW3.js.map +1 -0
- package/dist/{chunk-YXLUTALO.js → chunk-SAKA6PPL.js} +2 -2
- package/dist/{chunk-HWLIMQE2.js → chunk-SF6RFI5Z.js} +2 -2
- package/dist/{chunk-OS2AT2JU.js → chunk-SROIT6QY.js} +5 -5
- package/dist/{chunk-S5G2LAL7.js → chunk-SV4Z2S67.js} +4 -4
- package/dist/{chunk-DK4X5B6I.js → chunk-TFRDUD3M.js} +2 -2
- package/dist/{chunk-JR6H5CYS.js → chunk-TL7NLM2L.js} +2 -2
- package/dist/{chunk-NN4JEFON.js → chunk-TRU6JBOI.js} +2 -2
- package/dist/{chunk-PHP3MUGF.js → chunk-UYVXIY6C.js} +2 -2
- package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
- package/dist/{chunk-ZHXSOMYH.js → chunk-VVX5PZFS.js} +2 -2
- package/dist/{chunk-BJETIR53.js → chunk-WS3WNLST.js} +3 -3
- package/dist/{chunk-3FJKJ5UZ.js → chunk-X7BCM2NA.js} +2 -2
- package/dist/{chunk-Q4AUIFUA.js → chunk-XAITGGX5.js} +4 -4
- package/dist/{chunk-WPDKBUWX.js → chunk-XCF3SFHD.js} +2 -2
- package/dist/{chunk-LO4UKPKX.js → chunk-YHU5XUF4.js} +2 -2
- package/dist/{chunk-F6IVBYFS.js → chunk-YIZ2EUUL.js} +51 -51
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-5VAF4ID4.js → chunk-ZEPVWLCN.js} +9 -9
- package/dist/{cli-p813oxbm.d.ts → cli-1mzd5TM4.d.ts} +5 -5
- package/dist/cli.d.ts +14 -14
- package/dist/cli.js +79 -78
- package/dist/coding/export-okf-codegraph.d.ts +1 -1
- package/dist/coding/export-okf-codegraph.js +8 -8
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +9 -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 -3
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +9 -9
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +9 -9
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +3 -3
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +11 -11
- 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/corpus-watermark.d.ts +1 -1
- package/dist/corpus-watermark.js +11 -11
- package/dist/cross-lingual-recall.d.ts +1 -1
- package/dist/dashboard-runtime.js +2 -2
- package/dist/day-summary.d.ts +1 -1
- package/dist/deep-recall-http-glue.d.ts +12 -12
- package/dist/deep-recall-http-glue.js +4 -4
- package/dist/deep-recall-mcp-tools.d.ts +12 -12
- package/dist/deep-recall-policy-llm.d.ts +1 -1
- package/dist/deep-recall-seeds.d.ts +1 -1
- package/dist/deep-recall.js +2 -2
- package/dist/delinearize.d.ts +1 -1
- package/dist/dependency-propagation-config.d.ts +1 -1
- package/dist/{dependency-propagation-delivery-DLiAtj25.d.ts → dependency-propagation-delivery-Bua2ApHV.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-origin-fields.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +9 -9
- package/dist/entity-schema.d.ts +1 -1
- package/dist/episodic-context.d.ts +1 -1
- package/dist/eval-trajectory-contracts.d.ts +1 -1
- package/dist/eval-trajectory-store.d.ts +1 -1
- package/dist/eval-trajectory.d.ts +1 -1
- package/dist/explicit-capture.d.ts +10 -10
- package/dist/external-wiki-access.d.ts +12 -12
- package/dist/external-wiki-access.js +36 -36
- package/dist/external-wiki-collection-registration.d.ts +1 -1
- package/dist/external-wiki-collection.d.ts +1 -1
- package/dist/external-wiki-mcp-tools.d.ts +12 -12
- package/dist/extraction-error-classification.d.ts +1 -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-judge.js +2 -2
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-normalization.d.ts +1 -1
- package/dist/extraction-normalization.js +2 -2
- package/dist/extraction-prompt.d.ts +11 -2
- package/dist/extraction-prompt.js +7 -3
- package/dist/extraction-source-grounding-rules.d.ts +3 -2
- package/dist/extraction-source-grounding-rules.js +1 -1
- package/dist/extraction-source-grounding.d.ts +2 -2
- package/dist/extraction-source-grounding.js +2 -2
- package/dist/extraction-span-config.d.ts +15 -0
- package/dist/extraction-span-config.js +15 -0
- package/dist/extraction-span-materialize.d.ts +105 -0
- package/dist/extraction-span-materialize.js +30 -0
- package/dist/extraction-span-materialize.js.map +1 -0
- package/dist/extraction-span-slice.js +3 -12
- package/dist/extraction-span-slice.js.map +1 -1
- package/dist/extraction-span-source-hash.js +4 -46
- package/dist/extraction-span-source-hash.js.map +1 -1
- package/dist/extraction-span-validate.js +3 -15
- package/dist/extraction-span-validate.js.map +1 -1
- package/dist/extraction.d.ts +7 -1
- package/dist/extraction.js +20 -16
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-L3UFX5CU.js → first-start-migration-GZ45G5FS.js} +4 -4
- package/dist/{generic-recall-paths-DbitKJI5.d.ts → generic-recall-paths-DJBADojG.d.ts} +1 -1
- package/dist/graph-append-error.d.ts +1 -1
- 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-MKIRZ762.js → graph-edge-decay-ROSUPDX3.js} +5 -5
- package/dist/graph-edge-decay-ROSUPDX3.js.map +1 -0
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-jsonl.d.ts +1 -1
- package/dist/graph-jsonl.js +3 -3
- package/dist/graph-path-reconstruction.d.ts +1 -1
- package/dist/graph-path-scoring.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/harmonic-retrieval.js +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/importers/test-utils.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/{index-BINUgPFo.d.ts → index-Bsy8ygLL.d.ts} +2 -2
- package/dist/index.d.ts +24 -24
- package/dist/index.js +147 -142
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +4 -4
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +9 -9
- 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 +1 -1
- package/dist/location/index.d.ts +3 -3
- package/dist/location/index.js +11 -11
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +9 -9
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +9 -9
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +10 -10
- package/dist/{maintenance-ag1gYztC.d.ts → maintenance-DItyBwTC.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +12 -12
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-controller.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-snapshot.d.ts +1 -1
- package/dist/memory-subject-cli.d.ts +12 -12
- package/dist/memory-subject.d.ts +12 -12
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +10 -10
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +5 -5
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +9 -9
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +12 -12
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +23 -22
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +17 -17
- package/dist/{orchestrator-Cf3G-3Ed.d.ts → orchestrator-DBUX-aAM.d.ts} +9 -9
- package/dist/orchestrator.d.ts +10 -10
- package/dist/orchestrator.js +117 -112
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-AwB03fw_.d.ts → pipeline-BqgcOk1Z.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{public-http-C3bLjE-L.d.ts → public-http-CqtZ5kv3.d.ts} +1 -1
- package/dist/{qmd-Bfaydrfm.d.ts → qmd-BD0ikV_Y.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.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-memory-map.d.ts +1 -1
- package/dist/recall-navigate-access.d.ts +1 -1
- package/dist/recall-navigate-access.js +2 -2
- package/dist/recall-navigate-disclosure.d.ts +1 -1
- package/dist/recall-navigate-disclosure.js +1 -1
- package/dist/recall-navigate.d.ts +1 -1
- package/dist/recall-navigate.js +2 -2
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state-view-admission.d.ts +2 -2
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.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/reconcile/cursor.d.ts +1 -1
- package/dist/reconcile/manifest.d.ts +11 -2
- package/dist/reconcile/manifest.js +5 -3
- package/dist/reconcile/plan.d.ts +1 -1
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +5 -4
- package/dist/retrieval-agents.d.ts +2 -2
- 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/salvage-envelope.d.ts +1 -1
- package/dist/schemas.d.ts +212 -2
- package/dist/schemas.js +1 -1
- package/dist/{scope-profiles-nxTKvjE-.d.ts → scope-profiles-BIJMsd0B.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 +4 -4
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +10 -10
- 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-consolidation-C83DVakV.d.ts → semantic-consolidation-D7sJocll.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +10 -10
- package/dist/semantic-rule-promotion.js +9 -9
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +9 -9
- package/dist/{service-q-xtOoAr.d.ts → service-fwgEngUj.d.ts} +2 -2
- 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/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +12 -12
- package/dist/standup/build.js +9 -9
- package/dist/{storage-Ca0MbTfm.d.ts → storage-Cbll3-Tk.d.ts} +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +8 -8
- package/dist/subject-config.d.ts +1 -1
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/support-passport/index.d.ts +15 -15
- package/dist/support-passport/index.js +38 -38
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.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 +2 -2
- 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 +2 -2
- package/dist/transfer/export-okf.d.ts +1 -1
- package/dist/transfer/export-okf.js +9 -9
- package/dist/transfer/types.d.ts +20 -20
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-yDgWDkrJ.d.ts → types-48sPkWB0.d.ts} +49 -10
- 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 +9 -9
- package/dist/who-knows-cli.d.ts +12 -12
- package/dist/who-knows-cli.js +10 -10
- package/dist/who-knows.d.ts +12 -12
- package/dist/who-knows.js +9 -9
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +42 -2
- package/src/access-http.test.ts +43 -5
- package/src/access-http.ts +2 -0
- package/src/config.ts +2 -2
- package/src/extraction-prompt.ts +37 -3
- package/src/extraction-source-grounding-rules.ts +46 -9
- package/src/extraction-source-grounding.ts +2 -1
- package/src/extraction-span-config.ts +81 -0
- package/src/extraction-span-materialize.test.ts +376 -0
- package/src/extraction-span-materialize.ts +241 -0
- package/src/extraction.ts +70 -25
- package/src/reconcile/manifest.test.ts +11 -1
- package/src/reconcile/manifest.ts +12 -0
- package/src/schemas.ts +14 -0
- package/src/source-agent-qualifier.ts +5 -1
- package/src/testing/subjects/extraction-lifecycle.test.ts +27 -10
- package/src/types.ts +9 -9
- package/dist/chunk-27IOSH2W.js.map +0 -1
- package/dist/chunk-4MDKNUV4.js.map +0 -1
- package/dist/chunk-7CBKAQIG.js.map +0 -1
- package/dist/chunk-AJTG3CP7.js.map +0 -1
- package/dist/chunk-DVOBLPSQ.js.map +0 -1
- package/dist/chunk-LEMD7WZJ.js.map +0 -1
- package/dist/chunk-OQ7SSRXP.js.map +0 -1
- package/dist/chunk-UBG4GAEP.js.map +0 -1
- package/dist/chunk-XPJNT6PI.js.map +0 -1
- /package/dist/{auto-sync-4YFATLFQ.js.map → auto-sync-FSRJDU63.js.map} +0 -0
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
- /package/dist/{chunk-RUXPVSML.js.map → chunk-2AR5JFD7.js.map} +0 -0
- /package/dist/{chunk-YNN26CIZ.js.map → chunk-2JGGZICG.js.map} +0 -0
- /package/dist/{chunk-A7GQBJP5.js.map → chunk-2L5FSNPC.js.map} +0 -0
- /package/dist/{chunk-ZTG4P7NZ.js.map → chunk-3IXB77DA.js.map} +0 -0
- /package/dist/{chunk-WHHEPAO3.js.map → chunk-3K6UFW5B.js.map} +0 -0
- /package/dist/{chunk-EEBQFG5E.js.map → chunk-3YMDY3ZF.js.map} +0 -0
- /package/dist/{chunk-5JG5EBVL.js.map → chunk-4DPDIOVA.js.map} +0 -0
- /package/dist/{chunk-UDZYNIDJ.js.map → chunk-4WGAMREC.js.map} +0 -0
- /package/dist/{chunk-HXSAWV3A.js.map → chunk-57GDHV2D.js.map} +0 -0
- /package/dist/{chunk-Z7ACR6MJ.js.map → chunk-5LLQAUMI.js.map} +0 -0
- /package/dist/{chunk-P3ONDDIJ.js.map → chunk-5SYI2F47.js.map} +0 -0
- /package/dist/{chunk-DGS34QSY.js.map → chunk-6E5GYDIE.js.map} +0 -0
- /package/dist/{chunk-J7PB5UQ4.js.map → chunk-6Z3WW2MW.js.map} +0 -0
- /package/dist/{chunk-GLZH5VEN.js.map → chunk-76DRU7C4.js.map} +0 -0
- /package/dist/{chunk-L2QLDZJC.js.map → chunk-7MJBKZPU.js.map} +0 -0
- /package/dist/{chunk-Y63ZFP4X.js.map → chunk-7RKBPXY6.js.map} +0 -0
- /package/dist/{chunk-JSRVXI6E.js.map → chunk-B2YKTE7P.js.map} +0 -0
- /package/dist/{chunk-Z3O53WZR.js.map → chunk-BICNRVEC.js.map} +0 -0
- /package/dist/{chunk-BNGHPYZU.js.map → chunk-BLHX5FQW.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-776GVKL7.js.map → chunk-CD3C4BJN.js.map} +0 -0
- /package/dist/{chunk-LMCD5W5B.js.map → chunk-CIYUWSMZ.js.map} +0 -0
- /package/dist/{chunk-YJ6VCDMZ.js.map → chunk-CQ7SSBO4.js.map} +0 -0
- /package/dist/{chunk-CNV2A557.js.map → chunk-CYEMS2PG.js.map} +0 -0
- /package/dist/{chunk-UYUCCQ46.js.map → chunk-DOQLHCSS.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-RRZKTSFG.js.map → chunk-FCFAYMTU.js.map} +0 -0
- /package/dist/{chunk-R4OCGPSH.js.map → chunk-FJMYMJLR.js.map} +0 -0
- /package/dist/{chunk-TKFSXDOB.js.map → chunk-FLLORID2.js.map} +0 -0
- /package/dist/{chunk-QATYVTP7.js.map → chunk-FPQDZCUX.js.map} +0 -0
- /package/dist/{chunk-L2WQVSKP.js.map → chunk-G5MO3VQA.js.map} +0 -0
- /package/dist/{chunk-LNDLXMH6.js.map → chunk-GDDSXVWR.js.map} +0 -0
- /package/dist/{chunk-JZ2CHRLB.js.map → chunk-GHX5PNXW.js.map} +0 -0
- /package/dist/{chunk-2BQ4ZIWC.js.map → chunk-GNR6YEWZ.js.map} +0 -0
- /package/dist/{chunk-5FAGCOZD.js.map → chunk-GWK7FEUL.js.map} +0 -0
- /package/dist/{chunk-SCXURCVT.js.map → chunk-GWQVMKUJ.js.map} +0 -0
- /package/dist/{chunk-QBLDCI6E.js.map → chunk-HHDTBYDJ.js.map} +0 -0
- /package/dist/{chunk-2YLMUME2.js.map → chunk-IQZ6ISFX.js.map} +0 -0
- /package/dist/{chunk-JHZCXUQI.js.map → chunk-JLXWRQNR.js.map} +0 -0
- /package/dist/{chunk-FVJ4D6L7.js.map → chunk-JMPYZJQN.js.map} +0 -0
- /package/dist/{chunk-HC4GFLMQ.js.map → chunk-JO5TQAKW.js.map} +0 -0
- /package/dist/{chunk-ZBEGS7OB.js.map → chunk-JPKZTCWZ.js.map} +0 -0
- /package/dist/{chunk-AZSKROVQ.js.map → chunk-JYR4LH76.js.map} +0 -0
- /package/dist/{chunk-ECKG5WSI.js.map → chunk-JZYECQWN.js.map} +0 -0
- /package/dist/{chunk-NPNSKE76.js.map → chunk-KTRXQQFN.js.map} +0 -0
- /package/dist/{chunk-HUT2JR6I.js.map → chunk-L5OXE4PX.js.map} +0 -0
- /package/dist/{chunk-B3O3IRZN.js.map → chunk-LMMQKZK3.js.map} +0 -0
- /package/dist/{chunk-QURNMKME.js.map → chunk-LSDEIZ76.js.map} +0 -0
- /package/dist/{chunk-XCOV45MO.js.map → chunk-MR6LQVZ5.js.map} +0 -0
- /package/dist/{chunk-6J6K3STG.js.map → chunk-OTPXEE6W.js.map} +0 -0
- /package/dist/{chunk-LYDJRF45.js.map → chunk-PNEDMJ53.js.map} +0 -0
- /package/dist/{chunk-JRWW7GM6.js.map → chunk-Q6D7EUSQ.js.map} +0 -0
- /package/dist/{chunk-VYXDUPYE.js.map → chunk-Q7JK6YEB.js.map} +0 -0
- /package/dist/{chunk-YXLUTALO.js.map → chunk-SAKA6PPL.js.map} +0 -0
- /package/dist/{chunk-HWLIMQE2.js.map → chunk-SF6RFI5Z.js.map} +0 -0
- /package/dist/{chunk-OS2AT2JU.js.map → chunk-SROIT6QY.js.map} +0 -0
- /package/dist/{chunk-S5G2LAL7.js.map → chunk-SV4Z2S67.js.map} +0 -0
- /package/dist/{chunk-DK4X5B6I.js.map → chunk-TFRDUD3M.js.map} +0 -0
- /package/dist/{chunk-JR6H5CYS.js.map → chunk-TL7NLM2L.js.map} +0 -0
- /package/dist/{chunk-NN4JEFON.js.map → chunk-TRU6JBOI.js.map} +0 -0
- /package/dist/{chunk-PHP3MUGF.js.map → chunk-UYVXIY6C.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-ZHXSOMYH.js.map → chunk-VVX5PZFS.js.map} +0 -0
- /package/dist/{chunk-BJETIR53.js.map → chunk-WS3WNLST.js.map} +0 -0
- /package/dist/{chunk-3FJKJ5UZ.js.map → chunk-X7BCM2NA.js.map} +0 -0
- /package/dist/{chunk-Q4AUIFUA.js.map → chunk-XAITGGX5.js.map} +0 -0
- /package/dist/{chunk-WPDKBUWX.js.map → chunk-XCF3SFHD.js.map} +0 -0
- /package/dist/{chunk-LO4UKPKX.js.map → chunk-YHU5XUF4.js.map} +0 -0
- /package/dist/{chunk-F6IVBYFS.js.map → chunk-YIZ2EUUL.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-5VAF4ID4.js.map → chunk-ZEPVWLCN.js.map} +0 -0
- /package/dist/{graph-edge-decay-MKIRZ762.js.map → extraction-span-config.js.map} +0 -0
- /package/dist/{first-start-migration-L3UFX5CU.js.map → first-start-migration-GZ45G5FS.js.map} +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Span-mode materialization tests (issue #2333 Phase B).
|
|
3
|
+
*
|
|
4
|
+
* Covers the Phase B test contract: bounds off-by-one at both ends
|
|
5
|
+
* ([charStart, charEnd) end-exclusive), hash-mismatch (offset drift)
|
|
6
|
+
* rejection, per-fact fail-open, shadow-mode zero-diff vs "off", oversized
|
|
7
|
+
* span rejection, frame word-count limits, untrusted-span stripping in "off",
|
|
8
|
+
* and strict config enum handling.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import test from "node:test";
|
|
13
|
+
import { stampSpanSource } from "./extraction-span-source-hash.js";
|
|
14
|
+
import {
|
|
15
|
+
applyExtractionSpanMaterialization,
|
|
16
|
+
bindLocalExtractionPrompt,
|
|
17
|
+
buildSpanMaterializeTurns,
|
|
18
|
+
factCarriesSpan,
|
|
19
|
+
materializeFactSpan,
|
|
20
|
+
spanAgreesWithContent,
|
|
21
|
+
stripUntrustedFactSpans,
|
|
22
|
+
SPAN_MAX_SLICE_CHARS,
|
|
23
|
+
type SpanMaterializeTurn,
|
|
24
|
+
} from "./extraction-span-materialize.js";
|
|
25
|
+
import { renderExtractionConversation } from "./source-agent-qualifier.js";
|
|
26
|
+
import { parseConfig } from "./config.js";
|
|
27
|
+
import type { BufferTurn, ExtractedFact } from "./types.js";
|
|
28
|
+
import type { ExtractedFactSpanRef } from "./extraction-span-config.js";
|
|
29
|
+
|
|
30
|
+
const TURN_TEXT = "Maya moved to Seattle last spring for a new senior role.";
|
|
31
|
+
|
|
32
|
+
function turns(...texts: string[]): SpanMaterializeTurn[] {
|
|
33
|
+
return buildSpanMaterializeTurns(texts);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function fact(overrides: {
|
|
37
|
+
charStart: number;
|
|
38
|
+
charEnd: number;
|
|
39
|
+
frame?: string;
|
|
40
|
+
content?: string;
|
|
41
|
+
sourceMessageIndex?: number;
|
|
42
|
+
sourceHash?: string;
|
|
43
|
+
sourceLength?: number;
|
|
44
|
+
}): ExtractedFact {
|
|
45
|
+
const span: ExtractedFactSpanRef = {
|
|
46
|
+
sourceMessageIndex: overrides.sourceMessageIndex ?? 0,
|
|
47
|
+
charStart: overrides.charStart,
|
|
48
|
+
charEnd: overrides.charEnd,
|
|
49
|
+
frame: overrides.frame ?? "Maya's relocation",
|
|
50
|
+
...(overrides.sourceHash !== undefined && overrides.sourceLength !== undefined
|
|
51
|
+
? { sourceHash: overrides.sourceHash, sourceLength: overrides.sourceLength }
|
|
52
|
+
: {}),
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
category: "fact",
|
|
56
|
+
content: overrides.content ?? "Maya moved to Seattle last spring for a new senior role.",
|
|
57
|
+
confidence: 0.9,
|
|
58
|
+
tags: ["relocation"],
|
|
59
|
+
span,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test("on-mode materializes frame + verbatim slice and sets the grounding quote", () => {
|
|
64
|
+
const start = TURN_TEXT.indexOf("Seattle");
|
|
65
|
+
const result = materializeFactSpan(
|
|
66
|
+
fact({ charStart: start, charEnd: start + "Seattle".length }),
|
|
67
|
+
turns(TURN_TEXT),
|
|
68
|
+
"on",
|
|
69
|
+
);
|
|
70
|
+
assert.equal(result.outcome, "span");
|
|
71
|
+
assert.equal(result.fact.content, "Maya's relocation: Seattle");
|
|
72
|
+
assert.equal(result.fact.quote, "Seattle");
|
|
73
|
+
assert.equal(result.fact.span, undefined, "offsets die at materialization");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("[charStart, charEnd) is end-exclusive — off-by-one at both ends", () => {
|
|
77
|
+
const start = TURN_TEXT.indexOf("Seattle");
|
|
78
|
+
const messages = turns(TURN_TEXT);
|
|
79
|
+
const inner = materializeFactSpan(fact({ charStart: start + 1, charEnd: start + 6 }), messages, "on");
|
|
80
|
+
assert.equal(inner.fact.quote, "eattl");
|
|
81
|
+
const lastChar = materializeFactSpan(
|
|
82
|
+
fact({ charStart: TURN_TEXT.length - 1, charEnd: TURN_TEXT.length }),
|
|
83
|
+
messages,
|
|
84
|
+
"on",
|
|
85
|
+
);
|
|
86
|
+
assert.equal(lastChar.outcome, "span");
|
|
87
|
+
assert.equal(lastChar.fact.quote, ".");
|
|
88
|
+
assert.equal(
|
|
89
|
+
materializeFactSpan(fact({ charStart: 0, charEnd: TURN_TEXT.length + 1 }), messages, "on").outcome,
|
|
90
|
+
"fallback",
|
|
91
|
+
);
|
|
92
|
+
assert.equal(
|
|
93
|
+
materializeFactSpan(fact({ charStart: -1, charEnd: 4 }), messages, "on").outcome,
|
|
94
|
+
"fallback",
|
|
95
|
+
);
|
|
96
|
+
assert.equal(
|
|
97
|
+
materializeFactSpan(fact({ charStart: 5, charEnd: 5 }), messages, "on").outcome,
|
|
98
|
+
"fallback",
|
|
99
|
+
);
|
|
100
|
+
assert.equal(
|
|
101
|
+
materializeFactSpan(fact({ charStart: 9, charEnd: 4 }), messages, "on").outcome,
|
|
102
|
+
"fallback",
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("offset drift (turn text changed after stamping) is rejected", () => {
|
|
107
|
+
// The stamp was computed over the exact double-spaced string the model saw;
|
|
108
|
+
// the turn text here has been renormalized — verify must fail.
|
|
109
|
+
const drifted: SpanMaterializeTurn[] = [
|
|
110
|
+
{ text: "Maya moved to Seattle.", stamp: stampSpanSource("Maya moved to Seattle.") },
|
|
111
|
+
];
|
|
112
|
+
assert.notEqual(drifted[0].stamp.hash, stampSpanSource(drifted[0].text).hash);
|
|
113
|
+
const result = materializeFactSpan(fact({ charStart: 0, charEnd: 4 }), drifted, "on");
|
|
114
|
+
assert.equal(result.outcome, "fallback");
|
|
115
|
+
assert.equal(result.fact.content, "Maya moved to Seattle last spring for a new senior role.");
|
|
116
|
+
assert.equal(result.fact.span, undefined);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("oversized slice (>400 chars) is rejected; exactly 400 passes", () => {
|
|
120
|
+
const long = "a".repeat(600);
|
|
121
|
+
const messages = turns(long);
|
|
122
|
+
assert.equal(
|
|
123
|
+
materializeFactSpan(fact({ charStart: 0, charEnd: SPAN_MAX_SLICE_CHARS + 1 }), messages, "on").outcome,
|
|
124
|
+
"fallback",
|
|
125
|
+
);
|
|
126
|
+
assert.equal(
|
|
127
|
+
materializeFactSpan(fact({ charStart: 0, charEnd: SPAN_MAX_SLICE_CHARS }), messages, "on").outcome,
|
|
128
|
+
"span",
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("whitespace-only slice and frame word-count limits are rejected", () => {
|
|
133
|
+
const messages = turns("word1 word2 word3");
|
|
134
|
+
const words = (count: number) => Array.from({ length: count }, (_, i) => `w${i}`).join(" ");
|
|
135
|
+
assert.equal(
|
|
136
|
+
materializeFactSpan(fact({ charStart: 5, charEnd: 7 }), messages, "on").outcome,
|
|
137
|
+
"fallback",
|
|
138
|
+
);
|
|
139
|
+
assert.equal(
|
|
140
|
+
materializeFactSpan(fact({ charStart: 0, charEnd: 5, frame: words(15) }), messages, "on").outcome,
|
|
141
|
+
"span",
|
|
142
|
+
);
|
|
143
|
+
assert.equal(
|
|
144
|
+
materializeFactSpan(fact({ charStart: 0, charEnd: 5, frame: " " }), messages, "on").outcome,
|
|
145
|
+
"fallback",
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("per-fact fail-open: one invalid span among three persists all three", () => {
|
|
150
|
+
const messages = turns(TURN_TEXT);
|
|
151
|
+
const seattle = TURN_TEXT.indexOf("Seattle");
|
|
152
|
+
const facts = [
|
|
153
|
+
fact({ charStart: seattle, charEnd: seattle + 7, content: "fallback A" }),
|
|
154
|
+
fact({ charStart: 0, charEnd: TURN_TEXT.length + 5, content: "fallback B" }),
|
|
155
|
+
fact({ charStart: 0, charEnd: 4, content: "fallback C" }),
|
|
156
|
+
];
|
|
157
|
+
const outcomes = facts.map((f) => materializeFactSpan(f, messages, "on"));
|
|
158
|
+
assert.deepEqual(
|
|
159
|
+
outcomes.map((o) => o.outcome),
|
|
160
|
+
["span", "fallback", "span"],
|
|
161
|
+
);
|
|
162
|
+
for (const outcome of outcomes) {
|
|
163
|
+
assert.ok(outcome.fact.content.length > 0);
|
|
164
|
+
assert.equal(outcome.fact.span, undefined);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("shadow mode is zero-diff: byte-identical facts vs the same input", () => {
|
|
169
|
+
const messages = turns(TURN_TEXT);
|
|
170
|
+
const seattle = TURN_TEXT.indexOf("Seattle");
|
|
171
|
+
const spanFact = fact({ charStart: seattle, charEnd: seattle + 7, content: "generated restatement" });
|
|
172
|
+
const shadow = materializeFactSpan(spanFact, messages, "shadow");
|
|
173
|
+
assert.equal(shadow.outcome, "span");
|
|
174
|
+
assert.equal(shadow.fact.content, "generated restatement", "shadow persists generated content");
|
|
175
|
+
assert.ok(!("quote" in shadow.fact), "shadow does not touch the grounding quote");
|
|
176
|
+
assert.equal(shadow.fact.span, undefined);
|
|
177
|
+
// Zero-diff vs "off": the persisted fact is byte-identical to simply
|
|
178
|
+
// stripping the span — same content, same fields, nothing added.
|
|
179
|
+
const stripped = stripUntrustedFactSpans([spanFact])[0];
|
|
180
|
+
assert.deepEqual(shadow.fact, stripped);
|
|
181
|
+
// And the comparison materialization is still produced for telemetry.
|
|
182
|
+
assert.equal(shadow.materialized, "Maya's relocation: Seattle");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("shadow agreement predicate: exact or containment", () => {
|
|
186
|
+
assert.equal(spanAgreesWithContent("a: b c", "b c"), true);
|
|
187
|
+
assert.equal(spanAgreesWithContent("b c", "a: b c"), true);
|
|
188
|
+
assert.equal(spanAgreesWithContent("a: b c", "a: b c"), true);
|
|
189
|
+
assert.equal(spanAgreesWithContent("a: b c", "z z z"), false);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("stray span in off-mode is stripped untrusted; factCarriesSpan detects spans", () => {
|
|
193
|
+
const messages = turns(TURN_TEXT);
|
|
194
|
+
const stray = fact({ charStart: 0, charEnd: 900, content: "kept" });
|
|
195
|
+
assert.ok(factCarriesSpan(stray));
|
|
196
|
+
const strippedFacts = stripUntrustedFactSpans([stray]);
|
|
197
|
+
assert.equal(strippedFacts[0].span, undefined);
|
|
198
|
+
assert.equal(strippedFacts[0].content, "kept");
|
|
199
|
+
assert.ok(!factCarriesSpan(strippedFacts[0]));
|
|
200
|
+
// materializeFactSpan in off mode never validates — callers strip instead;
|
|
201
|
+
// a fallback here still must not lose content.
|
|
202
|
+
const result = materializeFactSpan(stray, messages, "off");
|
|
203
|
+
assert.equal(result.fact.content, "kept");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("config: extraction.spanMode default off, valid values accepted, unknown rejected", () => {
|
|
207
|
+
assert.equal(parseConfig({}).extraction.spanMode, "off");
|
|
208
|
+
assert.equal(parseConfig({}).extractionLiveness.enabled, true);
|
|
209
|
+
assert.equal(parseConfig({ extractionLiveness: { enabled: false } }).extractionLiveness.enabled, false);
|
|
210
|
+
assert.equal(parseConfig({ extraction: { spanMode: "shadow" } }).extraction.spanMode, "shadow");
|
|
211
|
+
assert.equal(parseConfig({ extraction: { spanMode: "on" } }).extraction.spanMode, "on");
|
|
212
|
+
assert.equal(parseConfig({ extraction: {} }).extraction.spanMode, "off");
|
|
213
|
+
assert.throws(
|
|
214
|
+
() => parseConfig({ extraction: { spanMode: "sometimes" } }),
|
|
215
|
+
/extraction\.spanMode must be one of "off", "shadow", "on"/,
|
|
216
|
+
);
|
|
217
|
+
assert.throws(
|
|
218
|
+
() => parseConfig({ extraction: { spanMode: 1 } }),
|
|
219
|
+
/extraction\.spanMode must be one of "off", "shadow", "on"/,
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("config: extraction block must be an object, not an array or scalar", () => {
|
|
224
|
+
assert.throws(() => parseConfig({ extraction: [] }), /extraction must be an object/);
|
|
225
|
+
assert.throws(() => parseConfig({ extraction: "on" }), /extraction must be an object/);
|
|
226
|
+
assert.throws(() => parseConfig({ extraction: null }), /extraction must be an object/);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("config: unknown extraction keys including spanMode typos are rejected", () => {
|
|
230
|
+
assert.throws(
|
|
231
|
+
() => parseConfig({ extraction: { spanMdoe: "on" } }),
|
|
232
|
+
/extraction has unknown property: spanMdoe/,
|
|
233
|
+
);
|
|
234
|
+
assert.throws(
|
|
235
|
+
() => parseConfig({ extraction: { spanMode: "on", extra: true } }),
|
|
236
|
+
/extraction has unknown property: extra/,
|
|
237
|
+
);
|
|
238
|
+
assert.equal(parseConfig({ extraction: { spanMode: "on" } }).extraction.spanMode, "on");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("applyExtractionSpanMaterialization uses captured prompt stamps, not a restamp", () => {
|
|
242
|
+
const generated = "Maya moved to Seattle last spring for a new senior role.";
|
|
243
|
+
const result = {
|
|
244
|
+
facts: [fact({ charStart: 0, charEnd: 4, content: generated })],
|
|
245
|
+
entities: [],
|
|
246
|
+
questions: [],
|
|
247
|
+
profileUpdates: [],
|
|
248
|
+
};
|
|
249
|
+
const captured = buildSpanMaterializeTurns([TURN_TEXT]);
|
|
250
|
+
const ok = applyExtractionSpanMaterialization(result, captured, "on");
|
|
251
|
+
assert.equal(ok.facts[0]?.content, "Maya's relocation: Maya");
|
|
252
|
+
|
|
253
|
+
const drifted = [{ text: TURN_TEXT, stamp: stampSpanSource("different prompt text") }];
|
|
254
|
+
const fallback = applyExtractionSpanMaterialization(result, drifted, "on");
|
|
255
|
+
assert.equal(fallback.facts[0]?.content, generated);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("span sourceHash/sourceLength must match the captured prompt stamp", () => {
|
|
259
|
+
const captured = buildSpanMaterializeTurns([TURN_TEXT]);
|
|
260
|
+
const stamp = captured[0]?.stamp;
|
|
261
|
+
assert.ok(stamp);
|
|
262
|
+
const matching = materializeFactSpan(
|
|
263
|
+
fact({ charStart: 0, charEnd: 4, sourceHash: stamp.hash, sourceLength: stamp.length }),
|
|
264
|
+
captured,
|
|
265
|
+
"on",
|
|
266
|
+
);
|
|
267
|
+
assert.equal(matching.outcome, "span");
|
|
268
|
+
const mismatched = materializeFactSpan(
|
|
269
|
+
fact({ charStart: 0, charEnd: 4, sourceHash: "0".repeat(64), sourceLength: stamp.length }),
|
|
270
|
+
captured,
|
|
271
|
+
"on",
|
|
272
|
+
);
|
|
273
|
+
assert.equal(mismatched.outcome, "fallback");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("local prompt bind stamps the truncated visible prefix, not the unseen suffix", () => {
|
|
277
|
+
const visible = "Maya moved to Seattle last spring";
|
|
278
|
+
const unseen = " for a new senior role. UNSEEN_SUFFIX";
|
|
279
|
+
const content = `${visible}${unseen}`;
|
|
280
|
+
const turns: BufferTurn[] = [{ role: "user", content, timestamp: "2026-05-21T00:00:00.000Z" }];
|
|
281
|
+
const { conversation, renderedConversation } = renderExtractionConversation(turns, undefined);
|
|
282
|
+
const cut = conversation.indexOf(unseen);
|
|
283
|
+
assert.ok(cut > 0);
|
|
284
|
+
const bound = bindLocalExtractionPrompt(conversation, turns, cut, renderedConversation);
|
|
285
|
+
assert.match(bound.promptConversation, /\[truncated\]$/);
|
|
286
|
+
assert.equal(bound.spanTurns[0]?.text, visible);
|
|
287
|
+
assert.equal(bound.spanTurns[0]?.stamp.length, visible.length);
|
|
288
|
+
assert.notEqual(bound.spanTurns[0]?.stamp.hash, stampSpanSource(content).hash);
|
|
289
|
+
|
|
290
|
+
const leakStart = content.indexOf("UNSEEN_SUFFIX");
|
|
291
|
+
const againstFull = materializeFactSpan(
|
|
292
|
+
fact({ charStart: leakStart, charEnd: content.length, content: "kept-frame" }),
|
|
293
|
+
buildSpanMaterializeTurns([content]),
|
|
294
|
+
"on",
|
|
295
|
+
);
|
|
296
|
+
assert.equal(againstFull.outcome, "span");
|
|
297
|
+
assert.match(againstFull.fact.content ?? "", /UNSEEN_SUFFIX/);
|
|
298
|
+
|
|
299
|
+
const againstBound = materializeFactSpan(
|
|
300
|
+
fact({ charStart: leakStart, charEnd: content.length, content: "kept-frame" }),
|
|
301
|
+
bound.spanTurns,
|
|
302
|
+
"on",
|
|
303
|
+
);
|
|
304
|
+
assert.equal(againstBound.outcome, "fallback");
|
|
305
|
+
assert.equal(againstBound.fact.content, "kept-frame");
|
|
306
|
+
|
|
307
|
+
const visibleOk = materializeFactSpan(
|
|
308
|
+
fact({
|
|
309
|
+
charStart: 0,
|
|
310
|
+
charEnd: visible.length,
|
|
311
|
+
content: "kept-frame",
|
|
312
|
+
sourceHash: bound.spanTurns[0]?.stamp.hash,
|
|
313
|
+
sourceLength: bound.spanTurns[0]?.stamp.length,
|
|
314
|
+
}),
|
|
315
|
+
bound.spanTurns,
|
|
316
|
+
"on",
|
|
317
|
+
);
|
|
318
|
+
assert.equal(visibleOk.outcome, "span");
|
|
319
|
+
assert.equal(visibleOk.fact.quote, visible);
|
|
320
|
+
const fullStamp = stampSpanSource(content);
|
|
321
|
+
const wrongHash = materializeFactSpan(
|
|
322
|
+
fact({
|
|
323
|
+
charStart: 0,
|
|
324
|
+
charEnd: visible.length,
|
|
325
|
+
content: "kept-frame",
|
|
326
|
+
sourceHash: fullStamp.hash,
|
|
327
|
+
sourceLength: fullStamp.length,
|
|
328
|
+
}),
|
|
329
|
+
bound.spanTurns,
|
|
330
|
+
"on",
|
|
331
|
+
);
|
|
332
|
+
assert.equal(wrongHash.outcome, "fallback");
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("on-mode facts that omit span are dropped instead of persisting the frame", () => {
|
|
336
|
+
const generated = {
|
|
337
|
+
facts: [
|
|
338
|
+
{
|
|
339
|
+
category: "fact" as const,
|
|
340
|
+
content: "Maya's relocation",
|
|
341
|
+
confidence: 0.9,
|
|
342
|
+
tags: ["relocation"],
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
entities: [],
|
|
346
|
+
questions: [],
|
|
347
|
+
profileUpdates: [],
|
|
348
|
+
};
|
|
349
|
+
const on = applyExtractionSpanMaterialization(generated, turns(TURN_TEXT), "on");
|
|
350
|
+
assert.equal(on.facts.length, 0);
|
|
351
|
+
const shadow = applyExtractionSpanMaterialization(generated, turns(TURN_TEXT), "shadow");
|
|
352
|
+
assert.equal(shadow.facts[0]?.content, "Maya's relocation");
|
|
353
|
+
const off = applyExtractionSpanMaterialization(generated, turns(TURN_TEXT), "off");
|
|
354
|
+
assert.equal(off.facts[0]?.content, "Maya's relocation");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("on-mode mixed batch keeps validated spans and drops omitted spans", () => {
|
|
358
|
+
const seattle = TURN_TEXT.indexOf("Seattle");
|
|
359
|
+
const result = {
|
|
360
|
+
facts: [
|
|
361
|
+
fact({ charStart: seattle, charEnd: seattle + 7 }),
|
|
362
|
+
{
|
|
363
|
+
category: "fact" as const,
|
|
364
|
+
content: "Maya's relocation",
|
|
365
|
+
confidence: 0.9,
|
|
366
|
+
tags: [],
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
entities: [],
|
|
370
|
+
questions: [],
|
|
371
|
+
profileUpdates: [],
|
|
372
|
+
};
|
|
373
|
+
const on = applyExtractionSpanMaterialization(result, turns(TURN_TEXT), "on");
|
|
374
|
+
assert.equal(on.facts.length, 1);
|
|
375
|
+
assert.equal(on.facts[0]?.content, "Maya's relocation: Seattle");
|
|
376
|
+
});
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Span-mode materialization for production extraction (issue #2333 Phase B).
|
|
3
|
+
*
|
|
4
|
+
* Runs immediately after the extraction result is parsed and BEFORE
|
|
5
|
+
* sanitize/grounding/judge/dedup/persistence, so everything downstream sees
|
|
6
|
+
* exactly one content form and the content-hash dedup invariant (checklist
|
|
7
|
+
* §13) holds by construction. Offsets die here — nothing offset-based is
|
|
8
|
+
* stored.
|
|
9
|
+
*
|
|
10
|
+
* Modes:
|
|
11
|
+
* - "off": spans are never requested; a stray span is stripped untrusted.
|
|
12
|
+
* - "shadow": spans are requested alongside full content; materialization
|
|
13
|
+
* runs for comparison only, the generated content is persisted
|
|
14
|
+
* unchanged (zero behavior change), agreement telemetry logged.
|
|
15
|
+
* - "on": materialized frame+span content is persisted; per-fact fail-open
|
|
16
|
+
* to the generated content (the frame) on any validation failure.
|
|
17
|
+
*
|
|
18
|
+
* Validation (identical to the Phase A bench contract):
|
|
19
|
+
* - `0 <= charStart < charEnd <= turn.text.length`, end-exclusive, integers;
|
|
20
|
+
* - slice length <= 400 chars and non-whitespace;
|
|
21
|
+
* - frame 1..15 words;
|
|
22
|
+
* - the turn text's stamp (sha256 + length of the exact in-memory string the
|
|
23
|
+
* model saw) must verify — offset drift is rejected, never guessed around.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { log } from "./logger.js";
|
|
27
|
+
import { stampSpanSource, verifySpanSource, type SpanSourceStamp } from "./extraction-span-source-hash.js";
|
|
28
|
+
import { validateSpanOffsets } from "./extraction-span-validate.js";
|
|
29
|
+
import { sliceSpanText } from "./extraction-span-slice.js";
|
|
30
|
+
import type { ExtractionResult } from "./types.js";
|
|
31
|
+
import type { ExtractedFactSpanRef } from "./extraction-span-config.js";
|
|
32
|
+
|
|
33
|
+
export const SPAN_MAX_SLICE_CHARS = 400;
|
|
34
|
+
export const SPAN_MAX_FRAME_WORDS = 15;
|
|
35
|
+
|
|
36
|
+
export type SpanMode = "off" | "shadow" | "on";
|
|
37
|
+
|
|
38
|
+
/** A source turn exactly as rendered into the extraction prompt. */
|
|
39
|
+
export interface SpanMaterializeTurn {
|
|
40
|
+
text: string;
|
|
41
|
+
stamp: SpanSourceStamp;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SpanMaterializeFact {
|
|
45
|
+
content: string;
|
|
46
|
+
span?: ExtractedFactSpanRef | null;
|
|
47
|
+
quote?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SpanMaterializeTelemetry {
|
|
51
|
+
/** Facts that carried a span ref. */
|
|
52
|
+
attempts: number;
|
|
53
|
+
/** Spans that failed validation and fell back to generated content. */
|
|
54
|
+
fallbacks: number;
|
|
55
|
+
/** Shadow mode only: comparisons made / exact-or-containment agreements. */
|
|
56
|
+
shadowComparisons: number;
|
|
57
|
+
shadowAgreements: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Strip span refs from facts that were never given the span prompt
|
|
61
|
+
* (e.g. the proactive second pass). Untrusted: never validated, never used.
|
|
62
|
+
* The key is omitted entirely so no `span: undefined` reaches persistence. */
|
|
63
|
+
export function stripUntrustedFactSpans<T extends { span?: unknown }>(facts: readonly T[]): T[] {
|
|
64
|
+
return facts.map((fact) => {
|
|
65
|
+
if (fact.span === undefined || fact.span === null) return fact;
|
|
66
|
+
const { span: _span, ...rest } = fact;
|
|
67
|
+
return rest as T;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function buildSpanMaterializeTurns(texts: readonly string[]): SpanMaterializeTurn[] {
|
|
72
|
+
return texts.map((text) => ({ text, stamp: stampSpanSource(text) }));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function truncateLocalExtractionConversation(
|
|
76
|
+
conversation: string,
|
|
77
|
+
maxConversationChars: number,
|
|
78
|
+
): string {
|
|
79
|
+
return conversation.length > maxConversationChars
|
|
80
|
+
? `${conversation.slice(0, maxConversationChars)}\n\n[truncated]`
|
|
81
|
+
: conversation;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function bindLocalExtractionPrompt(
|
|
85
|
+
conversation: string,
|
|
86
|
+
turns: readonly { role: string; content: string; extractionContextOnly?: boolean }[],
|
|
87
|
+
maxConversationChars: number,
|
|
88
|
+
renderedConversation: string = conversation,
|
|
89
|
+
): { promptConversation: string; spanTurns: SpanMaterializeTurn[] } {
|
|
90
|
+
const promptConversation = truncateLocalExtractionConversation(conversation, maxConversationChars);
|
|
91
|
+
if (conversation.length <= maxConversationChars || !conversation.endsWith(renderedConversation)) {
|
|
92
|
+
return { promptConversation, spanTurns: buildSpanMaterializeTurns(turns.map((turn) => turn.content)) };
|
|
93
|
+
}
|
|
94
|
+
const renderedStart = conversation.length - renderedConversation.length;
|
|
95
|
+
const limit = maxConversationChars;
|
|
96
|
+
const texts: string[] = [];
|
|
97
|
+
let pos = 0;
|
|
98
|
+
for (let index = 0; index < turns.length; index += 1) {
|
|
99
|
+
const turn = turns[index];
|
|
100
|
+
if (turn === undefined) {
|
|
101
|
+
texts.push("");
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (index > 0) pos += 2;
|
|
105
|
+
const label = turn.extractionContextOnly === true ? `context ${turn.role}` : turn.role;
|
|
106
|
+
pos += `[${label}] `.length;
|
|
107
|
+
const absStart = renderedStart + pos;
|
|
108
|
+
const absEnd = absStart + turn.content.length;
|
|
109
|
+
texts.push(absEnd > absStart && limit > absStart ? conversation.slice(absStart, Math.min(absEnd, limit)) : "");
|
|
110
|
+
pos += turn.content.length;
|
|
111
|
+
}
|
|
112
|
+
return { promptConversation, spanTurns: buildSpanMaterializeTurns(texts) };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function factCarriesSpan(fact: { span?: ExtractedFactSpanRef | null }): boolean {
|
|
116
|
+
return fact.span !== undefined && fact.span !== null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Materialize one fact's span. Never throws: any validation failure falls
|
|
121
|
+
* open to the fact's generated content and is counted.
|
|
122
|
+
*/
|
|
123
|
+
export function materializeFactSpan<T extends SpanMaterializeFact>(
|
|
124
|
+
fact: T,
|
|
125
|
+
turns: readonly SpanMaterializeTurn[],
|
|
126
|
+
mode: SpanMode,
|
|
127
|
+
): { fact: T; outcome: "span" | "fallback"; materialized?: string } {
|
|
128
|
+
const span = fact.span ?? null;
|
|
129
|
+
if (span === null) {
|
|
130
|
+
return { fact, outcome: "fallback" };
|
|
131
|
+
}
|
|
132
|
+
const { sourceMessageIndex, charStart, charEnd, frame, sourceHash, sourceLength } = span;
|
|
133
|
+
const turn = turns[sourceMessageIndex];
|
|
134
|
+
if (
|
|
135
|
+
!Number.isInteger(sourceMessageIndex) ||
|
|
136
|
+
sourceMessageIndex < 0 ||
|
|
137
|
+
sourceMessageIndex >= turns.length ||
|
|
138
|
+
turn === undefined
|
|
139
|
+
) {
|
|
140
|
+
return { fact: stripSpan(fact), outcome: "fallback" };
|
|
141
|
+
}
|
|
142
|
+
const stampCheck = verifySpanSource(turn.text, turn.stamp);
|
|
143
|
+
if (!stampCheck.ok) {
|
|
144
|
+
return { fact: stripSpan(fact), outcome: "fallback" };
|
|
145
|
+
}
|
|
146
|
+
if (
|
|
147
|
+
(sourceHash !== undefined && sourceHash !== turn.stamp.hash) ||
|
|
148
|
+
(sourceLength !== undefined && sourceLength !== turn.stamp.length)
|
|
149
|
+
) {
|
|
150
|
+
return { fact: stripSpan(fact), outcome: "fallback" };
|
|
151
|
+
}
|
|
152
|
+
const offsetCheck = validateSpanOffsets({ start: charStart, end: charEnd, textLength: turn.text.length });
|
|
153
|
+
if (!offsetCheck.ok) {
|
|
154
|
+
return { fact: stripSpan(fact), outcome: "fallback" };
|
|
155
|
+
}
|
|
156
|
+
const slice = sliceSpanText({ text: turn.text, start: charStart, end: charEnd });
|
|
157
|
+
if (slice.length > SPAN_MAX_SLICE_CHARS || slice.trim().length === 0) {
|
|
158
|
+
return { fact: stripSpan(fact), outcome: "fallback" };
|
|
159
|
+
}
|
|
160
|
+
const frameWords = frame.trim().split(/\s+/).filter(Boolean);
|
|
161
|
+
if (frameWords.length === 0 || frameWords.length > SPAN_MAX_FRAME_WORDS) {
|
|
162
|
+
return { fact: stripSpan(fact), outcome: "fallback" };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const trimmedFrame = frameWords.join(" ");
|
|
166
|
+
const materialized = /[:—-]$/.test(trimmedFrame)
|
|
167
|
+
? `${trimmedFrame} ${slice}`
|
|
168
|
+
: `${trimmedFrame}: ${slice}`;
|
|
169
|
+
|
|
170
|
+
if (mode === "on") {
|
|
171
|
+
return {
|
|
172
|
+
fact: { ...stripSpan(fact), content: materialized, quote: slice },
|
|
173
|
+
outcome: "span",
|
|
174
|
+
materialized,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// shadow: persist generated content unchanged; materialize for comparison.
|
|
178
|
+
return { fact: stripSpan(fact), outcome: "span", materialized };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Shadow agreement: exact match or containment either way. */
|
|
182
|
+
export function spanAgreesWithContent(materialized: string, content: string): boolean {
|
|
183
|
+
const left = materialized.trim();
|
|
184
|
+
const right = content.trim();
|
|
185
|
+
return left === right || left.includes(right) || right.includes(left);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Whole-result span materialization pass (issue #2333 Phase B). Called from
|
|
190
|
+
* `ExtractionEngine.applySpanMaterialization` after parse, before sanitize /
|
|
191
|
+
* grounding / judge / dedup / persist. "off" strips stray spans untrusted.
|
|
192
|
+
* Shadow logs agreement telemetry and persists generated content. "on"
|
|
193
|
+
* persists materialized frame+span content; facts that omit the optional
|
|
194
|
+
* span are dropped so a short frame cannot persist as a standalone memory.
|
|
195
|
+
*/
|
|
196
|
+
export function applyExtractionSpanMaterialization(
|
|
197
|
+
result: ExtractionResult,
|
|
198
|
+
turns: readonly SpanMaterializeTurn[],
|
|
199
|
+
mode: SpanMode,
|
|
200
|
+
): ExtractionResult {
|
|
201
|
+
if (mode === "off") {
|
|
202
|
+
return { ...result, facts: stripUntrustedFactSpans(result.facts) };
|
|
203
|
+
}
|
|
204
|
+
let attempts = 0;
|
|
205
|
+
let fallbacks = 0;
|
|
206
|
+
let agreements = 0;
|
|
207
|
+
const facts = result.facts.flatMap((fact) => {
|
|
208
|
+
if (!factCarriesSpan(fact)) {
|
|
209
|
+
return mode === "on" ? [] : [fact];
|
|
210
|
+
}
|
|
211
|
+
attempts += 1;
|
|
212
|
+
const { fact: materialized, outcome, materialized: text } = materializeFactSpan(
|
|
213
|
+
fact,
|
|
214
|
+
turns,
|
|
215
|
+
mode,
|
|
216
|
+
);
|
|
217
|
+
if (outcome === "fallback") {
|
|
218
|
+
fallbacks += 1;
|
|
219
|
+
} else if (mode === "shadow" && text !== undefined) {
|
|
220
|
+
if (spanAgreesWithContent(text, fact.content)) {
|
|
221
|
+
agreements += 1;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return [materialized];
|
|
225
|
+
});
|
|
226
|
+
if (mode === "shadow") {
|
|
227
|
+
log.info(
|
|
228
|
+
`extraction span-mode shadow: ${agreements}/${attempts} span facts agree with generated content (${fallbacks} span validation fallbacks)`,
|
|
229
|
+
);
|
|
230
|
+
} else if (fallbacks > 0) {
|
|
231
|
+
log.info(
|
|
232
|
+
`extraction span-mode: ${fallbacks}/${attempts} span facts fell back to generated content`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return { ...result, facts };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function stripSpan<T extends SpanMaterializeFact>(fact: T): T {
|
|
239
|
+
const { span: _span, ...rest } = fact;
|
|
240
|
+
return rest as T;
|
|
241
|
+
}
|