@remnic/core 10.0.68 → 10.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.js +80 -77
- package/dist/access-authorization-probe.js +81 -78
- package/dist/access-boundary.js +80 -77
- package/dist/access-cli.js +194 -191
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +80 -77
- package/dist/access-http-briefing.js +81 -78
- package/dist/access-http-query.js +82 -79
- package/dist/access-http.js +104 -101
- package/dist/access-identity-continuity-surface.js +80 -77
- package/dist/access-lcm-surface.js +80 -77
- package/dist/access-mcp.js +93 -90
- package/dist/access-namespace-preflight.js +80 -77
- package/dist/access-observe-lcm-archive.d.ts +5 -0
- package/dist/access-observe-lcm-archive.d.ts.map +1 -0
- package/dist/access-observe-lcm-archive.js +9 -0
- package/dist/access-observe-objective-state.d.ts +8 -0
- package/dist/access-observe-objective-state.d.ts.map +1 -0
- package/dist/access-observe-objective-state.js +17 -0
- package/dist/access-observe-stage-timing.d.ts +11 -0
- package/dist/access-observe-stage-timing.d.ts.map +1 -0
- package/dist/access-observe-stage-timing.js +9 -0
- package/dist/access-observe-stage-timing.js.map +1 -0
- package/dist/access-observe-write-category.js +13 -13
- package/dist/access-observe-write-surface.d.ts.map +1 -1
- package/dist/access-observe-write-surface.js +80 -77
- package/dist/access-offline-manifest.js +8 -8
- package/dist/access-operations-batch.js +83 -80
- package/dist/access-operations.js +90 -87
- package/dist/access-recall-concurrency.js +80 -77
- package/dist/access-recall-response.js +80 -77
- package/dist/access-recall-surface.js +80 -77
- package/dist/access-schema.js +12 -12
- package/dist/access-service.d.ts +1 -0
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +80 -77
- package/dist/access-surface-catalog.js +82 -79
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +24 -24
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/briefing.js +15 -15
- package/dist/buffer.js +8 -8
- package/dist/calibration.js +4 -4
- package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/capsule-crypto-7FJQINUR.js.map +1 -0
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +20 -20
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +3 -3
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-NCZGGGWI.js → chunk-2BBMCMJS.js} +12 -12
- package/dist/{chunk-UVVFPCPX.js → chunk-2GV24KFD.js} +2 -2
- package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-QDZWQBLU.js → chunk-2R7OCBRR.js} +2 -2
- package/dist/{chunk-U2CAGMUD.js → chunk-2YPIFJHL.js} +2 -2
- package/dist/{chunk-4SOWMA55.js → chunk-3AASGFR4.js} +3 -3
- package/dist/{chunk-CI3MAAIY.js → chunk-3F7N4I5I.js} +2 -2
- package/dist/{chunk-PLCYKAWH.js → chunk-3ICAWZUU.js} +4 -4
- package/dist/{chunk-B4DXREWC.js → chunk-3W37NITP.js} +2 -2
- package/dist/{chunk-R4ZHF4K2.js → chunk-43FLJLT2.js} +4 -4
- package/dist/{chunk-RLVI2M5Q.js → chunk-4BOBW6H4.js} +2 -2
- package/dist/{chunk-HXFIILGF.js → chunk-4RASUAN3.js} +4 -4
- package/dist/{chunk-JLMOH5JA.js → chunk-5FMGPV34.js} +2 -2
- package/dist/{chunk-EI6M5NVK.js → chunk-5JBVPJCT.js} +2 -2
- package/dist/{chunk-74AY3S7U.js → chunk-5RW247F5.js} +10 -10
- package/dist/{chunk-P4TV3TQ2.js → chunk-5VQSKBNF.js} +5 -5
- package/dist/{chunk-H5DIRCZP.js → chunk-64U42JL5.js} +18 -18
- package/dist/{chunk-4XINO77L.js → chunk-66JOLMWS.js} +2 -2
- package/dist/{chunk-WHROVQ5Q.js → chunk-66PEACZP.js} +3 -3
- package/dist/{chunk-GKL6QYZO.js → chunk-6L63IN3M.js} +1 -1
- package/dist/{chunk-Y3XPSGNF.js → chunk-6QS5NQJH.js} +2 -2
- package/dist/{chunk-KOLJJN5G.js → chunk-AQNHEDXP.js} +11 -4
- package/dist/chunk-AQNHEDXP.js.map +1 -0
- package/dist/{chunk-6WKWL53V.js → chunk-B7YAIHAV.js} +127 -121
- package/dist/chunk-B7YAIHAV.js.map +1 -0
- package/dist/{chunk-M4IEZGCK.js → chunk-BFBS7OFQ.js} +7 -7
- package/dist/{chunk-4PG5R6BJ.js → chunk-BNU5TQLR.js} +2 -2
- package/dist/{chunk-B3MWELZ3.js → chunk-BW33YTF3.js} +2 -2
- package/dist/{chunk-H6VJSITJ.js → chunk-CIL4GHMD.js} +3 -3
- package/dist/{chunk-5UM4H64E.js → chunk-D3AGBN6G.js} +2 -2
- package/dist/{chunk-IG3EZ3TF.js → chunk-DM3UZUCN.js} +2 -2
- package/dist/{chunk-PMDOGXA2.js → chunk-EF3HNVL4.js} +2 -2
- package/dist/{chunk-TVE7W67N.js → chunk-EIZ4MK33.js} +2 -2
- package/dist/{chunk-AUFPPBC3.js → chunk-ENPJX3BB.js} +3 -3
- package/dist/{chunk-63A2BU2B.js → chunk-F4ZHKDBW.js} +5 -5
- package/dist/{chunk-3LBDEGQA.js → chunk-FDUCIUQ5.js} +2 -2
- package/dist/{chunk-CTH2CQCB.js → chunk-FM6UKZAE.js} +2 -2
- package/dist/{chunk-XVZA6VXC.js → chunk-FZVROZKC.js} +4 -4
- package/dist/{chunk-IAGVM7EY.js → chunk-GMSRLDVR.js} +2 -2
- package/dist/{chunk-HPB5TF55.js → chunk-GT4G2S2Y.js} +3 -3
- package/dist/{chunk-645WOU5I.js → chunk-H7BZBSP6.js} +4 -4
- package/dist/{chunk-5MGKGEL7.js → chunk-HNF7IMYQ.js} +2 -2
- package/dist/{chunk-W7K5BP3M.js → chunk-HQPFACXO.js} +2 -2
- package/dist/{chunk-TECK5IVY.js → chunk-HZNURRDS.js} +2 -2
- package/dist/{chunk-SXTVZ6JL.js → chunk-I6HRHJRA.js} +2 -2
- package/dist/{chunk-NHWDTOBN.js → chunk-ILD7N3XC.js} +20 -20
- package/dist/{chunk-4NV7O76Q.js → chunk-J5TVHCDG.js} +2 -2
- package/dist/{chunk-AL3TCNSJ.js → chunk-J6GZ2254.js} +4 -4
- package/dist/{chunk-OH55F76C.js → chunk-JEX2JL3U.js} +2 -2
- package/dist/{chunk-AVGDYYMA.js → chunk-JGQEF33K.js} +3 -3
- package/dist/{chunk-6P7U3HWG.js → chunk-K36RMINQ.js} +3 -3
- package/dist/{chunk-UW6LBFYN.js → chunk-K47G2CBP.js} +6 -6
- package/dist/{chunk-3RHU5MD7.js → chunk-K7LLGCPH.js} +2 -2
- package/dist/{chunk-BUKK5SWA.js → chunk-KQAFEZQX.js} +2 -2
- package/dist/chunk-L3E7MLMK.js +20 -0
- package/dist/chunk-L3E7MLMK.js.map +1 -0
- package/dist/{chunk-R2BIVU2Z.js → chunk-LTST4R7U.js} +2 -2
- package/dist/{chunk-IZBIDPMY.js → chunk-LYTKNK5O.js} +2 -2
- package/dist/{chunk-HH73M65H.js → chunk-MCFI6G7U.js} +3 -3
- package/dist/{chunk-V6SBA5O7.js → chunk-MFKBYXBY.js} +5 -5
- package/dist/{chunk-AYJQ644F.js → chunk-MK7HXGM6.js} +2 -2
- package/dist/{chunk-MOIJWWNA.js → chunk-MKMIY3E7.js} +13 -13
- package/dist/{chunk-EXQJMFRF.js → chunk-MRN2MKB7.js} +2 -2
- package/dist/{chunk-W26D2USW.js → chunk-MUH7RR6T.js} +6 -6
- package/dist/{chunk-64JLBFCQ.js → chunk-N7NAGECH.js} +2 -2
- package/dist/{chunk-75DCOS6I.js → chunk-OAKOAOEG.js} +67 -67
- package/dist/{chunk-FAF4A76Q.js → chunk-OAQO5AZJ.js} +512 -378
- package/dist/chunk-OAQO5AZJ.js.map +1 -0
- package/dist/{chunk-OPFVXM2D.js → chunk-OX6GY22Q.js} +2 -2
- package/dist/chunk-QCFZDYYG.js +34 -0
- package/dist/chunk-QCFZDYYG.js.map +1 -0
- package/dist/{chunk-MMCO4SRG.js → chunk-QNN4FYBD.js} +19 -11
- package/dist/chunk-QNN4FYBD.js.map +1 -0
- package/dist/{chunk-XWE3EOWX.js → chunk-RZED7FAU.js} +2 -2
- package/dist/{chunk-YSATDEBY.js → chunk-SBRUTD5O.js} +5 -5
- package/dist/{chunk-WVP2I6C5.js → chunk-SDL22RRW.js} +50 -11
- package/dist/chunk-SDL22RRW.js.map +1 -0
- package/dist/{chunk-ZORS5BG5.js → chunk-SQO5SEKA.js} +10 -10
- package/dist/{chunk-PRKNAIGZ.js → chunk-T6T2RMWW.js} +5 -5
- package/dist/{chunk-O5ZINWW3.js → chunk-THFSCUFC.js} +2 -2
- package/dist/{chunk-CIEEYVUB.js → chunk-TPX7XXY4.js} +2 -2
- package/dist/{chunk-OCK4D5PC.js → chunk-TSZP7GGJ.js} +2 -2
- package/dist/{chunk-L334PSLW.js → chunk-U2A56LLF.js} +8 -8
- package/dist/{chunk-TGQW3AMK.js → chunk-UIFWZMO7.js} +23 -23
- package/dist/{chunk-LN4X2NDA.js → chunk-UJ2FP2RV.js} +10 -10
- package/dist/{chunk-4MPAKYZU.js → chunk-VDYRXI3E.js} +5 -5
- package/dist/{chunk-IHCXMYSE.js → chunk-VPEIWMUT.js} +2 -2
- package/dist/{chunk-ELHJK7LM.js → chunk-WBN66HPI.js} +6 -6
- package/dist/{chunk-BJLCV2P3.js → chunk-WGRAB3KB.js} +2 -2
- package/dist/{chunk-IVUVQPTQ.js → chunk-WIW4LOZL.js} +2 -2
- package/dist/{chunk-5KDDAQ6K.js → chunk-WN2DASTS.js} +10 -10
- package/dist/chunk-WZU5AGO6.js +38 -0
- package/dist/chunk-WZU5AGO6.js.map +1 -0
- package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-5Y6VA6X2.js → chunk-XDUP6SWG.js} +2 -2
- package/dist/{chunk-GHHQLSSY.js → chunk-XR4QR3JT.js} +3 -3
- package/dist/{chunk-64S6X4IU.js → chunk-XTN54NML.js} +4 -4
- package/dist/{chunk-QGFR73SN.js → chunk-YIG4HPY4.js} +2 -2
- package/dist/{chunk-UYUFDTIX.js → chunk-YIXWY4OZ.js} +2 -2
- package/dist/{chunk-GJ4HU6LK.js → chunk-YLMTFRZR.js} +2 -2
- package/dist/{chunk-GGD2YDS6.js → chunk-YRMSHA7H.js} +2 -2
- package/dist/{chunk-Y7427SL7.js → chunk-ZG4GNPRE.js} +216 -50
- package/dist/chunk-ZG4GNPRE.js.map +1 -0
- package/dist/{chunk-KZZ5EZ7O.js → chunk-ZS5LREUZ.js} +2 -2
- package/dist/{chunk-JLGBJ4AX.js → chunk-ZYEWTVNQ.js} +10 -10
- package/dist/cli.js +131 -128
- package/dist/coding/export-okf-codegraph.js +14 -14
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +16 -16
- package/dist/config.js +24 -24
- package/dist/connectors/codex-materialize-runner.js +17 -17
- package/dist/connectors/codex-materialize.js +2 -2
- package/dist/connectors/index.js +18 -18
- package/dist/consolidation-provenance-check.js +2 -2
- package/dist/convergence-refresh.js +18 -18
- package/dist/corpus-watermark.js +18 -18
- package/dist/deep-recall-http-glue.js +13 -13
- package/dist/deep-recall.js +2 -2
- package/dist/embedding-fallback.js +2 -2
- package/dist/entity-retrieval.js +18 -18
- package/dist/event-order-recall.js +2 -2
- package/dist/external-wiki-access.js +81 -78
- package/dist/extraction-span-materialize.js +2 -2
- package/dist/extraction.js +30 -30
- package/dist/{graph-edge-decay-OEX5OGMI.js → graph-edge-decay-CXTNJ7IQ.js} +6 -6
- package/dist/graph-edge-decay-CXTNJ7IQ.js.map +1 -0
- package/dist/graph-jsonl.js +4 -4
- package/dist/graph-snapshot.js +4 -4
- package/dist/graph.js +3 -3
- package/dist/harmonic-retrieval.js +15 -15
- package/dist/index.js +279 -276
- package/dist/lcm/archive.d.ts +12 -2
- package/dist/lcm/archive.d.ts.map +1 -1
- package/dist/lcm/archive.js +2 -2
- package/dist/lcm/engine.d.ts +59 -8
- package/dist/lcm/engine.d.ts.map +1 -1
- package/dist/lcm/engine.js +6 -6
- package/dist/lcm/index.js +16 -16
- package/dist/lcm/queue.d.ts +7 -3
- package/dist/lcm/queue.d.ts.map +1 -1
- package/dist/lcm/queue.js +1 -1
- package/dist/lcm/summarizer.d.ts +8 -1
- package/dist/lcm/summarizer.d.ts.map +1 -1
- package/dist/lcm/summarizer.js +3 -3
- package/dist/local-llm.js +4 -4
- package/dist/location/index.js +17 -17
- package/dist/maintenance/memory-governance.js +16 -16
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -15
- package/dist/maintenance/rebuild-memory-projection-runner.js +18 -18
- package/dist/maintenance/rebuild-memory-projection.js +17 -17
- package/dist/memory-browse-operations.js +81 -78
- package/dist/memory-browse.js +6 -6
- package/dist/namespaces/migrate.js +16 -16
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.js +15 -15
- package/dist/offline-sync-changeset-generations.js +5 -5
- package/dist/offline-sync-embedding-generation.js +4 -4
- package/dist/offline-sync-snapshot-stream.js +7 -7
- package/dist/offline-sync.js +9 -9
- package/dist/operator-doctor-corpus.js +19 -19
- package/dist/operator-toolkit.js +39 -39
- package/dist/orchestration/buffer-drain-runner.js +194 -191
- package/dist/orchestration/buffer-drain-runner.js.map +1 -1
- package/dist/orchestration/extraction-persist.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +31 -31
- package/dist/orchestration/promotion-dedup-window.d.ts +118 -0
- package/dist/orchestration/promotion-dedup-window.d.ts.map +1 -0
- package/dist/orchestrator.js +195 -192
- package/dist/qmd.js +4 -4
- package/dist/recall-navigation-operations.js +81 -78
- package/dist/recall-navigation.js +80 -77
- package/dist/recall-state-view-admission.js +6 -6
- package/dist/recall-why-service.js +6 -6
- package/dist/resume-bundles.js +27 -27
- package/dist/search/factory.js +6 -6
- package/dist/search/index.js +12 -12
- package/dist/semantic-consolidation.js +23 -23
- package/dist/semantic-rule-promotion.js +15 -15
- package/dist/semantic-rule-verifier.js +15 -15
- package/dist/source-agent-qualifier.js +22 -22
- package/dist/standup/build.js +15 -15
- package/dist/storage.js +14 -14
- package/dist/summarizer.js +6 -6
- package/dist/support-passport/index.js +83 -80
- 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.js +15 -15
- package/dist/verified-recall.js +15 -15
- package/dist/who-knows-cli.js +16 -16
- package/dist/who-knows.js +15 -15
- package/package.json +2 -2
- package/src/access-observe-lcm-archive.ts +30 -0
- package/src/access-observe-lcm-fail-closed.test.ts +118 -0
- package/src/access-observe-objective-state.ts +40 -0
- package/src/access-observe-stage-timing.test.ts +246 -0
- package/src/access-observe-stage-timing.ts +58 -0
- package/src/access-observe-write-surface.ts +45 -45
- package/src/access-service.ts +1 -0
- package/src/extraction-shared-promotion-parallel-dedup.test.ts +212 -0
- package/src/lcm/archive.ts +59 -7
- package/src/lcm/engine.ts +256 -53
- package/src/lcm/queue.test.ts +32 -14
- package/src/lcm/queue.ts +24 -16
- package/src/lcm/summarizer.ts +15 -2
- package/src/lcm-durability.test.ts +856 -0
- package/src/lcm-engine.test.ts +12 -3
- package/src/orchestration/extraction-persist.ts +40 -35
- package/src/orchestration/promotion-dedup-window.ts +319 -0
- package/dist/chunk-6WKWL53V.js.map +0 -1
- package/dist/chunk-FAF4A76Q.js.map +0 -1
- package/dist/chunk-KOLJJN5G.js.map +0 -1
- package/dist/chunk-MMCO4SRG.js.map +0 -1
- package/dist/chunk-WVP2I6C5.js.map +0 -1
- package/dist/chunk-Y7427SL7.js.map +0 -1
- /package/dist/{capsule-crypto-GWVG7LGC.js.map → access-observe-lcm-archive.js.map} +0 -0
- /package/dist/{graph-edge-decay-OEX5OGMI.js.map → access-observe-objective-state.js.map} +0 -0
- /package/dist/{chunk-NCZGGGWI.js.map → chunk-2BBMCMJS.js.map} +0 -0
- /package/dist/{chunk-UVVFPCPX.js.map → chunk-2GV24KFD.js.map} +0 -0
- /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-QDZWQBLU.js.map → chunk-2R7OCBRR.js.map} +0 -0
- /package/dist/{chunk-U2CAGMUD.js.map → chunk-2YPIFJHL.js.map} +0 -0
- /package/dist/{chunk-4SOWMA55.js.map → chunk-3AASGFR4.js.map} +0 -0
- /package/dist/{chunk-CI3MAAIY.js.map → chunk-3F7N4I5I.js.map} +0 -0
- /package/dist/{chunk-PLCYKAWH.js.map → chunk-3ICAWZUU.js.map} +0 -0
- /package/dist/{chunk-B4DXREWC.js.map → chunk-3W37NITP.js.map} +0 -0
- /package/dist/{chunk-R4ZHF4K2.js.map → chunk-43FLJLT2.js.map} +0 -0
- /package/dist/{chunk-RLVI2M5Q.js.map → chunk-4BOBW6H4.js.map} +0 -0
- /package/dist/{chunk-HXFIILGF.js.map → chunk-4RASUAN3.js.map} +0 -0
- /package/dist/{chunk-JLMOH5JA.js.map → chunk-5FMGPV34.js.map} +0 -0
- /package/dist/{chunk-EI6M5NVK.js.map → chunk-5JBVPJCT.js.map} +0 -0
- /package/dist/{chunk-74AY3S7U.js.map → chunk-5RW247F5.js.map} +0 -0
- /package/dist/{chunk-P4TV3TQ2.js.map → chunk-5VQSKBNF.js.map} +0 -0
- /package/dist/{chunk-H5DIRCZP.js.map → chunk-64U42JL5.js.map} +0 -0
- /package/dist/{chunk-4XINO77L.js.map → chunk-66JOLMWS.js.map} +0 -0
- /package/dist/{chunk-WHROVQ5Q.js.map → chunk-66PEACZP.js.map} +0 -0
- /package/dist/{chunk-GKL6QYZO.js.map → chunk-6L63IN3M.js.map} +0 -0
- /package/dist/{chunk-Y3XPSGNF.js.map → chunk-6QS5NQJH.js.map} +0 -0
- /package/dist/{chunk-M4IEZGCK.js.map → chunk-BFBS7OFQ.js.map} +0 -0
- /package/dist/{chunk-4PG5R6BJ.js.map → chunk-BNU5TQLR.js.map} +0 -0
- /package/dist/{chunk-B3MWELZ3.js.map → chunk-BW33YTF3.js.map} +0 -0
- /package/dist/{chunk-H6VJSITJ.js.map → chunk-CIL4GHMD.js.map} +0 -0
- /package/dist/{chunk-5UM4H64E.js.map → chunk-D3AGBN6G.js.map} +0 -0
- /package/dist/{chunk-IG3EZ3TF.js.map → chunk-DM3UZUCN.js.map} +0 -0
- /package/dist/{chunk-PMDOGXA2.js.map → chunk-EF3HNVL4.js.map} +0 -0
- /package/dist/{chunk-TVE7W67N.js.map → chunk-EIZ4MK33.js.map} +0 -0
- /package/dist/{chunk-AUFPPBC3.js.map → chunk-ENPJX3BB.js.map} +0 -0
- /package/dist/{chunk-63A2BU2B.js.map → chunk-F4ZHKDBW.js.map} +0 -0
- /package/dist/{chunk-3LBDEGQA.js.map → chunk-FDUCIUQ5.js.map} +0 -0
- /package/dist/{chunk-CTH2CQCB.js.map → chunk-FM6UKZAE.js.map} +0 -0
- /package/dist/{chunk-XVZA6VXC.js.map → chunk-FZVROZKC.js.map} +0 -0
- /package/dist/{chunk-IAGVM7EY.js.map → chunk-GMSRLDVR.js.map} +0 -0
- /package/dist/{chunk-HPB5TF55.js.map → chunk-GT4G2S2Y.js.map} +0 -0
- /package/dist/{chunk-645WOU5I.js.map → chunk-H7BZBSP6.js.map} +0 -0
- /package/dist/{chunk-5MGKGEL7.js.map → chunk-HNF7IMYQ.js.map} +0 -0
- /package/dist/{chunk-W7K5BP3M.js.map → chunk-HQPFACXO.js.map} +0 -0
- /package/dist/{chunk-TECK5IVY.js.map → chunk-HZNURRDS.js.map} +0 -0
- /package/dist/{chunk-SXTVZ6JL.js.map → chunk-I6HRHJRA.js.map} +0 -0
- /package/dist/{chunk-NHWDTOBN.js.map → chunk-ILD7N3XC.js.map} +0 -0
- /package/dist/{chunk-4NV7O76Q.js.map → chunk-J5TVHCDG.js.map} +0 -0
- /package/dist/{chunk-AL3TCNSJ.js.map → chunk-J6GZ2254.js.map} +0 -0
- /package/dist/{chunk-OH55F76C.js.map → chunk-JEX2JL3U.js.map} +0 -0
- /package/dist/{chunk-AVGDYYMA.js.map → chunk-JGQEF33K.js.map} +0 -0
- /package/dist/{chunk-6P7U3HWG.js.map → chunk-K36RMINQ.js.map} +0 -0
- /package/dist/{chunk-UW6LBFYN.js.map → chunk-K47G2CBP.js.map} +0 -0
- /package/dist/{chunk-3RHU5MD7.js.map → chunk-K7LLGCPH.js.map} +0 -0
- /package/dist/{chunk-BUKK5SWA.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-R2BIVU2Z.js.map → chunk-LTST4R7U.js.map} +0 -0
- /package/dist/{chunk-IZBIDPMY.js.map → chunk-LYTKNK5O.js.map} +0 -0
- /package/dist/{chunk-HH73M65H.js.map → chunk-MCFI6G7U.js.map} +0 -0
- /package/dist/{chunk-V6SBA5O7.js.map → chunk-MFKBYXBY.js.map} +0 -0
- /package/dist/{chunk-AYJQ644F.js.map → chunk-MK7HXGM6.js.map} +0 -0
- /package/dist/{chunk-MOIJWWNA.js.map → chunk-MKMIY3E7.js.map} +0 -0
- /package/dist/{chunk-EXQJMFRF.js.map → chunk-MRN2MKB7.js.map} +0 -0
- /package/dist/{chunk-W26D2USW.js.map → chunk-MUH7RR6T.js.map} +0 -0
- /package/dist/{chunk-64JLBFCQ.js.map → chunk-N7NAGECH.js.map} +0 -0
- /package/dist/{chunk-75DCOS6I.js.map → chunk-OAKOAOEG.js.map} +0 -0
- /package/dist/{chunk-OPFVXM2D.js.map → chunk-OX6GY22Q.js.map} +0 -0
- /package/dist/{chunk-XWE3EOWX.js.map → chunk-RZED7FAU.js.map} +0 -0
- /package/dist/{chunk-YSATDEBY.js.map → chunk-SBRUTD5O.js.map} +0 -0
- /package/dist/{chunk-ZORS5BG5.js.map → chunk-SQO5SEKA.js.map} +0 -0
- /package/dist/{chunk-PRKNAIGZ.js.map → chunk-T6T2RMWW.js.map} +0 -0
- /package/dist/{chunk-O5ZINWW3.js.map → chunk-THFSCUFC.js.map} +0 -0
- /package/dist/{chunk-CIEEYVUB.js.map → chunk-TPX7XXY4.js.map} +0 -0
- /package/dist/{chunk-OCK4D5PC.js.map → chunk-TSZP7GGJ.js.map} +0 -0
- /package/dist/{chunk-L334PSLW.js.map → chunk-U2A56LLF.js.map} +0 -0
- /package/dist/{chunk-TGQW3AMK.js.map → chunk-UIFWZMO7.js.map} +0 -0
- /package/dist/{chunk-LN4X2NDA.js.map → chunk-UJ2FP2RV.js.map} +0 -0
- /package/dist/{chunk-4MPAKYZU.js.map → chunk-VDYRXI3E.js.map} +0 -0
- /package/dist/{chunk-IHCXMYSE.js.map → chunk-VPEIWMUT.js.map} +0 -0
- /package/dist/{chunk-ELHJK7LM.js.map → chunk-WBN66HPI.js.map} +0 -0
- /package/dist/{chunk-BJLCV2P3.js.map → chunk-WGRAB3KB.js.map} +0 -0
- /package/dist/{chunk-IVUVQPTQ.js.map → chunk-WIW4LOZL.js.map} +0 -0
- /package/dist/{chunk-5KDDAQ6K.js.map → chunk-WN2DASTS.js.map} +0 -0
- /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-5Y6VA6X2.js.map → chunk-XDUP6SWG.js.map} +0 -0
- /package/dist/{chunk-GHHQLSSY.js.map → chunk-XR4QR3JT.js.map} +0 -0
- /package/dist/{chunk-64S6X4IU.js.map → chunk-XTN54NML.js.map} +0 -0
- /package/dist/{chunk-QGFR73SN.js.map → chunk-YIG4HPY4.js.map} +0 -0
- /package/dist/{chunk-UYUFDTIX.js.map → chunk-YIXWY4OZ.js.map} +0 -0
- /package/dist/{chunk-GJ4HU6LK.js.map → chunk-YLMTFRZR.js.map} +0 -0
- /package/dist/{chunk-GGD2YDS6.js.map → chunk-YRMSHA7H.js.map} +0 -0
- /package/dist/{chunk-KZZ5EZ7O.js.map → chunk-ZS5LREUZ.js.map} +0 -0
- /package/dist/{chunk-JLGBJ4AX.js.map → chunk-ZYEWTVNQ.js.map} +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Noncontent stage timing for slow/failed observe requests (25s incident).
|
|
3
|
+
* A failed request must record which stage blocked it (phase attribution) in
|
|
4
|
+
* the `access-observe-failed` log line without leaking message content,
|
|
5
|
+
* session keys, namespaces or principals, and must still throw unchanged.
|
|
6
|
+
*/
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import test from "node:test";
|
|
12
|
+
|
|
13
|
+
import { EngramAccessService, NamespaceNotWritableError } from "./access-service.js";
|
|
14
|
+
import { initLogger, resetLogger, type LoggerBackend } from "./logger.js";
|
|
15
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
16
|
+
import { parseConfig } from "./config.js";
|
|
17
|
+
import type { EngramAccessObserveRequest } from "./access-service.js";
|
|
18
|
+
import type { CodingContext, PluginConfig } from "./types.js";
|
|
19
|
+
|
|
20
|
+
interface OrchestratorInternals {
|
|
21
|
+
config: PluginConfig;
|
|
22
|
+
_codingContextBySession: Map<string, CodingContext>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeService(memoryDir: string): EngramAccessService {
|
|
26
|
+
// SAFETY: the probe drives only the observe scope path on this orchestrator.
|
|
27
|
+
const orch = Object.create(Orchestrator.prototype) as Orchestrator;
|
|
28
|
+
// SAFETY: the probe sets exactly these two internal fields.
|
|
29
|
+
const internals: OrchestratorInternals = {
|
|
30
|
+
config: parseConfig({
|
|
31
|
+
namespacesEnabled: true,
|
|
32
|
+
defaultNamespace: "default",
|
|
33
|
+
sharedNamespace: "shared",
|
|
34
|
+
namespacePolicies: [],
|
|
35
|
+
codingMode: { projectScope: true },
|
|
36
|
+
transcriptEnabled: false,
|
|
37
|
+
memoryDir,
|
|
38
|
+
}),
|
|
39
|
+
_codingContextBySession: new Map(),
|
|
40
|
+
};
|
|
41
|
+
Object.assign(orch, internals);
|
|
42
|
+
return new EngramAccessService(orch);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
test("a rejected observe logs phase attribution with stage durations and still throws", async () => {
|
|
46
|
+
const dir = await mkdtemp(path.join(tmpdir(), "remnic-observe-timing-"));
|
|
47
|
+
const lines: string[] = [];
|
|
48
|
+
const backend: LoggerBackend = {
|
|
49
|
+
info(msg) {
|
|
50
|
+
lines.push(msg);
|
|
51
|
+
},
|
|
52
|
+
warn(msg) {
|
|
53
|
+
lines.push(msg);
|
|
54
|
+
},
|
|
55
|
+
error(msg) {
|
|
56
|
+
lines.push(msg);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
initLogger(backend, false, { timestamps: false });
|
|
60
|
+
try {
|
|
61
|
+
const service = makeService(dir);
|
|
62
|
+
// SAFETY: the literal sets the request fields the scope path reads.
|
|
63
|
+
const rejectedRequest = {
|
|
64
|
+
sessionKey: "sess-timing-1",
|
|
65
|
+
authenticatedPrincipal: "alice",
|
|
66
|
+
namespace: "victim-secret",
|
|
67
|
+
messages: [
|
|
68
|
+
{ role: "user", content: "first secret message body" },
|
|
69
|
+
{ role: "assistant", content: "second secret message body" },
|
|
70
|
+
],
|
|
71
|
+
} as EngramAccessObserveRequest;
|
|
72
|
+
await assert.rejects(
|
|
73
|
+
service.observe(rejectedRequest),
|
|
74
|
+
NamespaceNotWritableError
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const failed = lines.filter((l) => l.includes("access-observe-failed"));
|
|
78
|
+
assert.equal(failed.length, 1, `expected exactly one access-observe-failed line, got: ${failed}`);
|
|
79
|
+
const line = failed[0] ?? "";
|
|
80
|
+
// Phase attribution: the blocked stage (scope) plus total and message count.
|
|
81
|
+
assert.match(line, /totalMs=\d+/);
|
|
82
|
+
assert.match(line, /messages=2/);
|
|
83
|
+
assert.match(line, /scopeMs=\d+/);
|
|
84
|
+
// Noncontent: no message bodies, session key, namespace or principal.
|
|
85
|
+
assert.ok(!line.includes("secret"), `leaked content in: ${line}`);
|
|
86
|
+
assert.ok(!line.includes("sess-timing-1"), `leaked session key in: ${line}`);
|
|
87
|
+
assert.ok(!line.includes("victim-secret"), `leaked namespace in: ${line}`);
|
|
88
|
+
assert.ok(!line.includes("alice"), `leaked principal in: ${line}`);
|
|
89
|
+
} finally {
|
|
90
|
+
resetLogger();
|
|
91
|
+
await rm(dir, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Plain-object orchestrator for full observe success/failure paths (mirrors
|
|
96
|
+
// the access-service-observe-lcm-parity harness).
|
|
97
|
+
function makeProbe(options: {
|
|
98
|
+
enqueueObserveMessages: (sessionKey: string, messages: unknown[]) => Promise<void> | void;
|
|
99
|
+
}) {
|
|
100
|
+
const config = parseConfig({
|
|
101
|
+
namespacesEnabled: true,
|
|
102
|
+
defaultNamespace: "default",
|
|
103
|
+
sharedNamespace: "shared",
|
|
104
|
+
namespacePolicies: [],
|
|
105
|
+
defaultRecallNamespaces: ["self", "shared"],
|
|
106
|
+
codingMode: { projectScope: true },
|
|
107
|
+
transcriptEnabled: false,
|
|
108
|
+
memoryDir: "/synthetic/remnic-observe-timing",
|
|
109
|
+
objectiveStateMemoryEnabled: false,
|
|
110
|
+
objectiveStateSnapshotWritesEnabled: false,
|
|
111
|
+
principalFromSessionKeyMode: "prefix",
|
|
112
|
+
principalFromSessionKeyRules: [],
|
|
113
|
+
recallCrossNamespaceBudgetEnabled: false,
|
|
114
|
+
recallCrossNamespaceBudgetWindowMs: 60_000,
|
|
115
|
+
recallCrossNamespaceBudgetSoftLimit: 10,
|
|
116
|
+
recallCrossNamespaceBudgetHardLimit: 30,
|
|
117
|
+
});
|
|
118
|
+
const contexts = new Map<string, CodingContext>();
|
|
119
|
+
const extractionCalls: string[][] = [];
|
|
120
|
+
// SAFETY: the probe exercises the observe surface; the rest of Orchestrator
|
|
121
|
+
// is unused on this path.
|
|
122
|
+
const orch = {
|
|
123
|
+
config,
|
|
124
|
+
getCodingContextForSession: (sk: string | undefined) => (sk ? contexts.get(sk) : null) ?? null,
|
|
125
|
+
setCodingContextForSession: (sk: string, ctx: CodingContext | null) => {
|
|
126
|
+
if (ctx === null) contexts.delete(sk);
|
|
127
|
+
else contexts.set(sk, ctx);
|
|
128
|
+
},
|
|
129
|
+
applyCodingNamespaceOverlay: (sk: string | undefined, base: string) =>
|
|
130
|
+
Orchestrator.prototype.applyCodingNamespaceOverlay.call(orch, sk, base),
|
|
131
|
+
resolvePrincipal: (sk?: string) => Orchestrator.prototype.resolvePrincipal.call(orch, sk),
|
|
132
|
+
resolveSelfNamespace: (sk?: string) => Orchestrator.prototype.resolveSelfNamespace.call(orch, sk),
|
|
133
|
+
lcmEngine: {
|
|
134
|
+
enabled: true,
|
|
135
|
+
enqueueObserveMessages: options.enqueueObserveMessages,
|
|
136
|
+
},
|
|
137
|
+
ingestReplayBatch: async (turns: Array<{ sessionKey: string }>) => {
|
|
138
|
+
extractionCalls.push(turns.map((t) => t.sessionKey));
|
|
139
|
+
},
|
|
140
|
+
} as any;
|
|
141
|
+
return { orch, extractionCalls };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
test("a slow successful observe logs access-observe-slow with per-stage durations", async () => {
|
|
145
|
+
const lines: string[] = [];
|
|
146
|
+
const backend: LoggerBackend = {
|
|
147
|
+
info(msg) {
|
|
148
|
+
lines.push(msg);
|
|
149
|
+
},
|
|
150
|
+
warn(msg) {
|
|
151
|
+
lines.push(msg);
|
|
152
|
+
},
|
|
153
|
+
error(msg) {
|
|
154
|
+
lines.push(msg);
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
initLogger(backend, false, { timestamps: false });
|
|
158
|
+
// Deterministic monotonic clock: every read advances 6s, so each timed
|
|
159
|
+
// stage records exactly 6000ms and the total exceeds the slow threshold.
|
|
160
|
+
let now = 0;
|
|
161
|
+
const realPerformanceNow = performance.now;
|
|
162
|
+
performance.now = () => (now += 6000);
|
|
163
|
+
try {
|
|
164
|
+
const { orch } = makeProbe({
|
|
165
|
+
enqueueObserveMessages: (_sessionKey) => {},
|
|
166
|
+
});
|
|
167
|
+
const service = new EngramAccessService(orch);
|
|
168
|
+
// SAFETY: the literal sets the request fields the observe path reads.
|
|
169
|
+
const slowRequest = {
|
|
170
|
+
sessionKey: "alice:sess-slow",
|
|
171
|
+
authenticatedPrincipal: "alice",
|
|
172
|
+
messages: [
|
|
173
|
+
{ role: "user", content: "slow path message body" },
|
|
174
|
+
{ role: "assistant", content: "reply body" },
|
|
175
|
+
],
|
|
176
|
+
} as EngramAccessObserveRequest;
|
|
177
|
+
const res = await service.observe(slowRequest);
|
|
178
|
+
assert.equal(res.accepted, 2);
|
|
179
|
+
|
|
180
|
+
const slow = lines.filter((l) => l.includes("access-observe-slow"));
|
|
181
|
+
assert.equal(slow.length, 1, `expected one access-observe-slow line, got: ${lines}`);
|
|
182
|
+
const line = slow[0] ?? "";
|
|
183
|
+
assert.match(line, /totalMs=(\d+)/);
|
|
184
|
+
const totalMs = Number(/totalMs=(\d+)/.exec(line)?.[1]);
|
|
185
|
+
assert.ok(totalMs >= 10_000, `slow line must exceed threshold, got totalMs=${totalMs}`);
|
|
186
|
+
assert.match(line, /messages=2/);
|
|
187
|
+
// Real stages are attributed, including the durable-buffer terminal stage.
|
|
188
|
+
assert.match(line, /scopeMs=6000/);
|
|
189
|
+
assert.match(line, /durable-bufferMs=6000/);
|
|
190
|
+
assert.ok(!line.includes("sess-slow") && !line.includes("alice") && !line.includes("body"));
|
|
191
|
+
} finally {
|
|
192
|
+
performance.now = realPerformanceNow;
|
|
193
|
+
resetLogger();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("a failed awaited LCM archival records the lcm-accept phase and still throws", async () => {
|
|
198
|
+
const lines: string[] = [];
|
|
199
|
+
const backend: LoggerBackend = {
|
|
200
|
+
info(msg) {
|
|
201
|
+
lines.push(msg);
|
|
202
|
+
},
|
|
203
|
+
warn(msg) {
|
|
204
|
+
lines.push(msg);
|
|
205
|
+
},
|
|
206
|
+
error(msg) {
|
|
207
|
+
lines.push(msg);
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
initLogger(backend, false, { timestamps: false });
|
|
211
|
+
try {
|
|
212
|
+
const { orch } = makeProbe({
|
|
213
|
+
enqueueObserveMessages: async () => {
|
|
214
|
+
throw new Error("lcm archival append failed");
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
const service = new EngramAccessService(orch);
|
|
218
|
+
// SAFETY: the literal sets the request fields the observe path reads.
|
|
219
|
+
const failedLcmRequest = {
|
|
220
|
+
sessionKey: "alice:sess-lcmfail",
|
|
221
|
+
authenticatedPrincipal: "alice",
|
|
222
|
+
messages: [
|
|
223
|
+
{ role: "user", content: "lcm failure message body" },
|
|
224
|
+
{ role: "assistant", content: "reply body" },
|
|
225
|
+
],
|
|
226
|
+
} as EngramAccessObserveRequest;
|
|
227
|
+
await assert.rejects(
|
|
228
|
+
service.observe(failedLcmRequest),
|
|
229
|
+
/lcm archival append failed/
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const failed = lines.filter((l) => l.includes("access-observe-failed"));
|
|
233
|
+
assert.equal(failed.length, 1, `expected exactly one access-observe-failed line, got: ${failed}`);
|
|
234
|
+
const line = failed[0] ?? "";
|
|
235
|
+
assert.match(line, /totalMs=\d+/);
|
|
236
|
+
assert.match(line, /messages=2/);
|
|
237
|
+
// The rejected LCM accept is attributed with its duration, alongside earlier stages.
|
|
238
|
+
assert.match(line, /scopeMs=\d+/);
|
|
239
|
+
assert.match(line, /lcm-acceptMs=\d+/);
|
|
240
|
+
assert.ok(!line.includes("sess-lcmfail"), `leaked session key in: ${line}`);
|
|
241
|
+
assert.ok(!line.includes("alice"), `leaked principal in: ${line}`);
|
|
242
|
+
assert.ok(!line.includes("body"), `leaked content in: ${line}`);
|
|
243
|
+
} finally {
|
|
244
|
+
resetLogger();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Noncontent stage timing for the observe surface.
|
|
3
|
+
*
|
|
4
|
+
* Durations only — never message content, session keys, namespaces or
|
|
5
|
+
* principals. A monotonic clock (`performance.now`) keeps phase durations
|
|
6
|
+
* sane under wall-clock corrections.
|
|
7
|
+
*
|
|
8
|
+
* A slow observe logs `access-observe-slow`; a failed observe always logs
|
|
9
|
+
* `access-observe-failed`, so a 25s-class stall attributes to a phase
|
|
10
|
+
* instead of a guess.
|
|
11
|
+
*/
|
|
12
|
+
import { log } from "./logger.js";
|
|
13
|
+
|
|
14
|
+
/** Observes slower than this log `access-observe-slow` with per-stage durations. */
|
|
15
|
+
const OBSERVE_SLOW_LOG_MS = 10_000;
|
|
16
|
+
|
|
17
|
+
export interface ObserveStageTimer {
|
|
18
|
+
/** Run one stage, recording its duration even when the stage rejects. */
|
|
19
|
+
timed<T>(stage: string, fn: () => Promise<T>): Promise<T>;
|
|
20
|
+
/** Log a slow successful observe with per-stage durations. */
|
|
21
|
+
logSlow(messages: number): void;
|
|
22
|
+
/** Log a failed observe with per-stage durations. */
|
|
23
|
+
logFailed(messages: number): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Timer for one observe request. */
|
|
27
|
+
export function createObserveStageTimer(): ObserveStageTimer {
|
|
28
|
+
const startedAtMs = performance.now();
|
|
29
|
+
const stageMs: Record<string, number> = {};
|
|
30
|
+
|
|
31
|
+
const formatStages = (): string =>
|
|
32
|
+
Object.entries(stageMs)
|
|
33
|
+
.map(([stage, ms]) => `${stage}Ms=${Math.round(ms)}`)
|
|
34
|
+
.join(" ");
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
async timed<T>(stage: string, fn: () => Promise<T>): Promise<T> {
|
|
38
|
+
const stageStartMs = performance.now();
|
|
39
|
+
try {
|
|
40
|
+
return await fn();
|
|
41
|
+
} finally {
|
|
42
|
+
stageMs[stage] = (stageMs[stage] ?? 0) + (performance.now() - stageStartMs);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
logSlow(messages: number): void {
|
|
46
|
+
const totalMs = Math.round(performance.now() - startedAtMs);
|
|
47
|
+
if (totalMs < OBSERVE_SLOW_LOG_MS) return;
|
|
48
|
+
log.warn(
|
|
49
|
+
`access-observe-slow totalMs=${totalMs} messages=${messages} ${formatStages()}`,
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
logFailed(messages: number): void {
|
|
53
|
+
log.warn(
|
|
54
|
+
`access-observe-failed totalMs=${Math.round(performance.now() - startedAtMs)} messages=${messages} ${formatStages()}`,
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -34,6 +34,9 @@ import { abortError } from "./abort-error.js";
|
|
|
34
34
|
import { withBriefingDeadline } from "./briefing-deadline.js";
|
|
35
35
|
import { extractionForceFlush } from "./access-extraction-force-flush.js";
|
|
36
36
|
import { ObserveTranscriptPersister, observeTranscriptSessionKey } from "./access-observe-transcript.js";
|
|
37
|
+
import { createObserveStageTimer } from "./access-observe-stage-timing.js";
|
|
38
|
+
import { archiveObservedTurns } from "./access-observe-lcm-archive.js";
|
|
39
|
+
import { recordObserveObjectiveState } from "./access-observe-objective-state.js";
|
|
37
40
|
import { FileCalendarSource, buildBriefing, parseBriefingFocus, parseBriefingWindow } from "./briefing.js";
|
|
38
41
|
import {
|
|
39
42
|
resolveCompressionCapabilities,
|
|
@@ -708,10 +711,16 @@ export class AccessObserveWriteSurface {
|
|
|
708
711
|
request.sessionKey,
|
|
709
712
|
pendingObserveScopeHint(request)
|
|
710
713
|
);
|
|
714
|
+
// Noncontent stage timing for slow/failed observes: durations only, logged
|
|
715
|
+
// in the `access-observe-failed` line (always) and `access-observe-slow`
|
|
716
|
+
// (successes at or above the slow threshold). Never message content,
|
|
717
|
+
// session keys, namespaces or principals.
|
|
718
|
+
const stageTimer = createObserveStageTimer();
|
|
719
|
+
const timed = stageTimer.timed.bind(stageTimer);
|
|
711
720
|
try {
|
|
712
721
|
let scope: MemoryScopePlan;
|
|
713
722
|
try {
|
|
714
|
-
scope = await this.deps.resolveMemoryScopePlan(request);
|
|
723
|
+
scope = await timed("scope", () => this.deps.resolveMemoryScopePlan(request));
|
|
715
724
|
} catch (err) {
|
|
716
725
|
// A replay re-submit sets suppressQuarantine so a still-unwritable target
|
|
717
726
|
// propagates instead of re-parking (#1888); observe has no dryRun path.
|
|
@@ -749,10 +758,12 @@ export class AccessObserveWriteSurface {
|
|
|
749
758
|
// on the same session is project-scoped (rule 42: same namespace layer as
|
|
750
759
|
// recall). Done AFTER the scope plan authorized the write, so a rejected
|
|
751
760
|
// request never leaves orphaned context on the session.
|
|
752
|
-
await
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
761
|
+
await timed("context", () =>
|
|
762
|
+
this.deps.maybeAttachCodingContext(request.sessionKey, {
|
|
763
|
+
cwd: request.cwd,
|
|
764
|
+
projectTag: request.projectTag,
|
|
765
|
+
})
|
|
766
|
+
);
|
|
756
767
|
|
|
757
768
|
// Prefix sessionKey with the EFFECTIVE write namespace for LCM archival so
|
|
758
769
|
// observed turns are scoped to the same namespace project-scoped recall
|
|
@@ -771,51 +782,35 @@ export class AccessObserveWriteSurface {
|
|
|
771
782
|
// (#928 contract, already auth-checked inside the scope plan), not the
|
|
772
783
|
// general default-store write namespace.
|
|
773
784
|
if (shouldWriteObjectiveState) {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
.objectiveStateMemory,
|
|
783
|
-
objectiveStateSnapshotWritesEnabled: resolveObjectiveStateCapabilities(this.deps.orchestrator.config)
|
|
784
|
-
.objectiveStateSnapshotWrites,
|
|
785
|
-
sessionKey: request.sessionKey,
|
|
786
|
-
recordedAt: new Date().toISOString(),
|
|
787
|
-
messages: request.messages,
|
|
788
|
-
});
|
|
789
|
-
} catch (err) {
|
|
790
|
-
log.error(`access-observe objective-state snapshot write failed: ${err}`);
|
|
791
|
-
}
|
|
785
|
+
await timed("objective", () =>
|
|
786
|
+
recordObserveObjectiveState(
|
|
787
|
+
this.deps.orchestrator,
|
|
788
|
+
this.deps.objectiveStateStoreLocationForNamespace,
|
|
789
|
+
scope,
|
|
790
|
+
request,
|
|
791
|
+
),
|
|
792
|
+
);
|
|
792
793
|
}
|
|
793
794
|
|
|
794
795
|
// 5. LCM archival → effective write namespace.
|
|
795
|
-
//
|
|
796
|
-
//
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
// block the HTTP response — the caller only needs acknowledgment.
|
|
802
|
-
try {
|
|
803
|
-
this.deps.orchestrator.lcmEngine.enqueueObserveMessages(lcmSessionKey, request.messages);
|
|
804
|
-
lcmArchived = true;
|
|
805
|
-
} catch (err) {
|
|
806
|
-
log.error(`access-observe LCM enqueue failed: ${err}`);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
796
|
+
// The archival append is durable before this returns: the queue holds
|
|
797
|
+
// only a session reference, and summary DAGs are built in the
|
|
798
|
+
// background, so the caller is never blocked on inference.
|
|
799
|
+
const lcmArchived = await timed("lcm-accept", () =>
|
|
800
|
+
archiveObservedTurns(this.deps.orchestrator.lcmEngine, lcmSessionKey, request.messages),
|
|
801
|
+
);
|
|
809
802
|
|
|
810
803
|
// 5b. Transcript persistence → the per-session transcript store the
|
|
811
|
-
// hourly summarizer reads (issue #2783). Best-effort
|
|
812
|
-
//
|
|
804
|
+
// hourly summarizer reads (issue #2783). Best-effort: a transcript
|
|
805
|
+
// failure must not fail the observe.
|
|
813
806
|
const transcriptPersisted = resolvePresentationCapabilities(this.deps.orchestrator.config).transcript
|
|
814
|
-
? await
|
|
815
|
-
this.
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
807
|
+
? await timed("transcript", () =>
|
|
808
|
+
this.observeTranscriptPersister.persist(
|
|
809
|
+
this.deps.orchestrator,
|
|
810
|
+
observeTranscriptSessionKey(request.sessionKey, writeNamespace, this.deps.orchestrator.config),
|
|
811
|
+
request.messages,
|
|
812
|
+
request.sessionKey
|
|
813
|
+
)
|
|
819
814
|
)
|
|
820
815
|
: false;
|
|
821
816
|
|
|
@@ -895,10 +890,11 @@ export class AccessObserveWriteSurface {
|
|
|
895
890
|
extractionQueued = true;
|
|
896
891
|
};
|
|
897
892
|
}
|
|
898
|
-
await this.deps.orchestrator.ingestReplayBatch(turns, ingestOptions);
|
|
893
|
+
await timed("durable-buffer", () => this.deps.orchestrator.ingestReplayBatch(turns, ingestOptions));
|
|
899
894
|
// A native trigger may leave the durable capture pending rather than queued.
|
|
900
895
|
}
|
|
901
896
|
|
|
897
|
+
stageTimer.logSlow(request.messages.length);
|
|
902
898
|
log.info(
|
|
903
899
|
`access-observe namespace=${namespace} effectiveNamespace=${writeNamespace} sessionKey=${request.sessionKey} messages=${request.messages.length} lcm=${lcmArchived} extraction=${extractionQueued} transcript=${transcriptPersisted}`
|
|
904
900
|
);
|
|
@@ -924,6 +920,10 @@ export class AccessObserveWriteSurface {
|
|
|
924
920
|
extractionQueued,
|
|
925
921
|
transcriptPersisted,
|
|
926
922
|
};
|
|
923
|
+
} catch (err) {
|
|
924
|
+
// Failed requests always get phase attribution (noncontent), regardless of duration.
|
|
925
|
+
stageTimer.logFailed(request.messages.length);
|
|
926
|
+
throw err;
|
|
927
927
|
} finally {
|
|
928
928
|
observePreparation.release();
|
|
929
929
|
}
|
package/src/access-service.ts
CHANGED
|
@@ -1079,6 +1079,7 @@ export interface EngramAccessObserveResponse {
|
|
|
1079
1079
|
effectiveNamespace: string;
|
|
1080
1080
|
/** Additive diagnostic view of the resolved scope plan (#1495). */
|
|
1081
1081
|
scopeDebug?: EngramAccessScopeDebug;
|
|
1082
|
+
/** True when the observed turns are durably archived in LCM — the archive append completed before the response was produced. */
|
|
1082
1083
|
lcmArchived: boolean;
|
|
1083
1084
|
extractionQueued: boolean;
|
|
1084
1085
|
/** True when at least one observe-derived turn was appended to the transcript store (issue #2783). False when the transcript capability is off, every message was filtered/deduped, or every append failed. */
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex P2 PRRT_kwDORJXyws6zzdG4 (#3311) — promotion-target writes must share
|
|
3
|
+
* the fact-dedup commit window.
|
|
4
|
+
*
|
|
5
|
+
* With extractionQueueConcurrency > 1 and shared auto-promotion enabled, two
|
|
6
|
+
* sessions extracting the same fact into DIFFERENT source namespaces can both
|
|
7
|
+
* pass the shared-namespace dedup gate (`hasFactContentHash`) before either
|
|
8
|
+
* promotion writes: the persist-time gate runs outside the commit window that
|
|
9
|
+
* protects the source writes, and the promotion destination write ran without
|
|
10
|
+
* any window at all. writeMemory mints a fresh id per write, so both
|
|
11
|
+
* promotions land distinct active shared copies — exactly the duplicate class
|
|
12
|
+
* `persistUnderFactDedupWindow` exists to prevent for source writes.
|
|
13
|
+
*
|
|
14
|
+
* The fix wraps each promotion destination write in
|
|
15
|
+
* `persistUnderFactDedupWindow` keyed on the DESTINATION storage dir, so the
|
|
16
|
+
* second promotion's in-window recheck observes the first promotion's
|
|
17
|
+
* committed hash and suppresses instead of duplicating. The invariant these
|
|
18
|
+
* tests assert — exactly one active shared copy survives a concurrent
|
|
19
|
+
* same-fact promotion pair — holds for every interleaving once the window is
|
|
20
|
+
* in place, because recheck+write are atomic per destination dir; before the
|
|
21
|
+
* fix the duplicate depends on the interleaving (two symmetric pipelines in
|
|
22
|
+
* lockstep both gate-miss before either write commits).
|
|
23
|
+
*
|
|
24
|
+
* These tests drive the real persistExtraction path (no LLM network; every
|
|
25
|
+
* gate that would call one is off) with two real source namespaces and the
|
|
26
|
+
* real shared destination, and assert the durable outcome.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import assert from "node:assert/strict";
|
|
30
|
+
import os from "node:os";
|
|
31
|
+
import path from "node:path";
|
|
32
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
33
|
+
import test from "node:test";
|
|
34
|
+
|
|
35
|
+
import { parseConfig } from "./config.js";
|
|
36
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
37
|
+
import type { ExtractionResult, PluginConfig } from "./types.js";
|
|
38
|
+
import type { LocalLlmClient } from "./local-llm.js";
|
|
39
|
+
import type { PersistExtractionFn } from "./testing/orchestrator-lite.js";
|
|
40
|
+
import type { StorageManager } from "./storage.js";
|
|
41
|
+
|
|
42
|
+
// Fixture contract for the private Orchestrator surface this suite drives.
|
|
43
|
+
interface OrchestratorFixture {
|
|
44
|
+
localLlm?: unknown;
|
|
45
|
+
chatCompletion?: unknown;
|
|
46
|
+
getStorage?: unknown;
|
|
47
|
+
persistExtraction?: unknown;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Named owner contract for the private internals this suite drives.
|
|
51
|
+
interface OrchestratorInternals {
|
|
52
|
+
localLlm: LocalLlmClient;
|
|
53
|
+
getStorage: (ns: string) => Promise<StorageManager>;
|
|
54
|
+
persistExtraction: PersistExtractionFn;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function asInternals<T extends object>(value: OrchestratorFixture): T {
|
|
58
|
+
// SAFETY: the fixture provides the members the persist path reads; the
|
|
59
|
+
// remaining contract members are unused by these tests.
|
|
60
|
+
return value as T;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function makeHarness(): Promise<{
|
|
64
|
+
orchestrator: Orchestrator;
|
|
65
|
+
persist: OrchestratorInternals;
|
|
66
|
+
memoryDir: string;
|
|
67
|
+
shared: StorageManager;
|
|
68
|
+
teamA: StorageManager;
|
|
69
|
+
teamB: StorageManager;
|
|
70
|
+
}> {
|
|
71
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-promotion-dedup-"));
|
|
72
|
+
const config: PluginConfig = parseConfig({
|
|
73
|
+
openaiApiKey: "sk-test",
|
|
74
|
+
memoryDir,
|
|
75
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
76
|
+
// Minimize every gate that needs a model or corpus-scale machinery so the
|
|
77
|
+
// fact reaches the normal (non-chunked) write path.
|
|
78
|
+
extractionJudgeEnabled: false,
|
|
79
|
+
temporalSupersessionEnabled: false,
|
|
80
|
+
contradictionDetectionEnabled: false,
|
|
81
|
+
chunkingEnabled: false,
|
|
82
|
+
embeddingFallbackEnabled: false,
|
|
83
|
+
qmdEnabled: false,
|
|
84
|
+
extractionMinChars: 0,
|
|
85
|
+
extractionMinImportanceLevel: "trivial",
|
|
86
|
+
inlineSourceAttributionEnabled: false,
|
|
87
|
+
// Shared auto-promotion from every source namespace into the default
|
|
88
|
+
// "shared" namespace (both default off — the deployment profile this
|
|
89
|
+
// finding targets).
|
|
90
|
+
namespacesEnabled: true,
|
|
91
|
+
autoPromoteToSharedEnabled: true,
|
|
92
|
+
});
|
|
93
|
+
const orchestrator = new Orchestrator(config);
|
|
94
|
+
const internals = asInternals<OrchestratorInternals>(orchestrator);
|
|
95
|
+
internals.localLlm = asInternals<LocalLlmClient>({ chatCompletion: async () => null });
|
|
96
|
+
const shared = await internals.getStorage("shared");
|
|
97
|
+
const teamA = await internals.getStorage("team-a");
|
|
98
|
+
const teamB = await internals.getStorage("team-b");
|
|
99
|
+
await Promise.all([shared, teamA, teamB].map((s) => s.ensureDirectories()));
|
|
100
|
+
return { orchestrator, persist: internals, memoryDir, shared, teamA, teamB };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function factResult(content: string): ExtractionResult {
|
|
104
|
+
const result = {
|
|
105
|
+
facts: [
|
|
106
|
+
{
|
|
107
|
+
content,
|
|
108
|
+
category: "fact",
|
|
109
|
+
// confidenceTier(0.96) === "explicit", which the default
|
|
110
|
+
// autoPromoteMinConfidenceTier ("explicit") admits.
|
|
111
|
+
confidence: 0.96,
|
|
112
|
+
tags: [],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
entities: [],
|
|
116
|
+
relationships: [],
|
|
117
|
+
questions: [],
|
|
118
|
+
profileUpdates: [],
|
|
119
|
+
};
|
|
120
|
+
// SAFETY: literal carries the full extraction-result shape persistExtraction
|
|
121
|
+
// consumes for a single fact candidate.
|
|
122
|
+
return result as ExtractionResult;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function activeFactCopies(storage: StorageManager): Promise<number> {
|
|
126
|
+
const memories = await storage.readAllMemories();
|
|
127
|
+
return memories.filter(
|
|
128
|
+
(m) => m.frontmatter.category === "fact" && (m.frontmatter.status ?? "active") === "active",
|
|
129
|
+
).length;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const FACT = "Deployment rollbacks complete within five minutes.";
|
|
133
|
+
|
|
134
|
+
test("the same fact promoted from two namespaces lands once in shared (serial baseline)", async () => {
|
|
135
|
+
const { persist, memoryDir, shared, teamA, teamB } = await makeHarness();
|
|
136
|
+
try {
|
|
137
|
+
const first = await persist.persistExtraction(
|
|
138
|
+
factResult(FACT),
|
|
139
|
+
teamA,
|
|
140
|
+
null,
|
|
141
|
+
{ sessionKey: "agent:test:a", principal: "test" },
|
|
142
|
+
undefined,
|
|
143
|
+
null,
|
|
144
|
+
"User mentioned that deployment rollbacks complete within five minutes.",
|
|
145
|
+
);
|
|
146
|
+
const second = await persist.persistExtraction(
|
|
147
|
+
factResult(FACT),
|
|
148
|
+
teamB,
|
|
149
|
+
null,
|
|
150
|
+
{ sessionKey: "agent:test:b", principal: "test" },
|
|
151
|
+
undefined,
|
|
152
|
+
null,
|
|
153
|
+
"User mentioned that deployment rollbacks complete within five minutes.",
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Each source namespace keeps its own copy (cross-namespace source writes
|
|
157
|
+
// never dedup), and the second promotion dedups through the shared gate.
|
|
158
|
+
assert.equal(first.persistedIds.length, 1, "first source write persists");
|
|
159
|
+
assert.equal(second.persistedIds.length, 1, "second source write persists in its own namespace");
|
|
160
|
+
assert.equal(await activeFactCopies(teamA), 1);
|
|
161
|
+
assert.equal(await activeFactCopies(teamB), 1);
|
|
162
|
+
assert.equal(await activeFactCopies(shared), 1, "shared namespace holds exactly one promoted copy");
|
|
163
|
+
} finally {
|
|
164
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("concurrent same-fact promotions from different namespaces leave exactly one active shared copy", async () => {
|
|
169
|
+
const { persist, memoryDir, shared, teamA, teamB } = await makeHarness();
|
|
170
|
+
try {
|
|
171
|
+
const sourceText = "User mentioned that deployment rollbacks complete within five minutes.";
|
|
172
|
+
// Two real sessions, different source namespaces, same fact, one shared
|
|
173
|
+
// destination. Before the fix this interleaving (both pipelines gate-miss
|
|
174
|
+
// in lockstep before either promotion writes) landed two distinct active
|
|
175
|
+
// shared copies; after the fix the destination window serializes the
|
|
176
|
+
// promotions and the invariant holds for every interleaving.
|
|
177
|
+
const [a, b] = await Promise.all([
|
|
178
|
+
persist.persistExtraction(
|
|
179
|
+
factResult(FACT),
|
|
180
|
+
teamA,
|
|
181
|
+
null,
|
|
182
|
+
{ sessionKey: "agent:test:a", principal: "test" },
|
|
183
|
+
undefined,
|
|
184
|
+
null,
|
|
185
|
+
sourceText,
|
|
186
|
+
),
|
|
187
|
+
persist.persistExtraction(
|
|
188
|
+
factResult(FACT),
|
|
189
|
+
teamB,
|
|
190
|
+
null,
|
|
191
|
+
{ sessionKey: "agent:test:b", principal: "test" },
|
|
192
|
+
undefined,
|
|
193
|
+
null,
|
|
194
|
+
sourceText,
|
|
195
|
+
),
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
// Each source namespace still persists its own copy — the window only
|
|
199
|
+
// serializes the shared DESTINATION.
|
|
200
|
+
assert.equal(await activeFactCopies(teamA), 1, "team-a keeps its source copy");
|
|
201
|
+
assert.equal(await activeFactCopies(teamB), 1, "team-b keeps its source copy");
|
|
202
|
+
assert.equal(a.persistedIds.length, 1);
|
|
203
|
+
assert.equal(b.persistedIds.length, 1);
|
|
204
|
+
assert.equal(
|
|
205
|
+
await activeFactCopies(shared),
|
|
206
|
+
1,
|
|
207
|
+
"exactly one active shared copy survives the concurrent promotion pair",
|
|
208
|
+
);
|
|
209
|
+
} finally {
|
|
210
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
});
|