@remnic/core 9.3.684 → 9.3.686
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-boundary.d.ts +4 -3
- package/dist/access-boundary.js +23 -23
- package/dist/access-cli.js +152 -71
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +3 -2
- package/dist/access-http.js +26 -26
- package/dist/access-mcp.d.ts +14 -3
- package/dist/access-mcp.js +25 -25
- package/dist/access-operations.d.ts +10 -4
- package/dist/access-operations.js +26 -24
- package/dist/access-schema.d.ts +4 -4
- package/dist/{access-service-D-siI-xJ.d.ts → access-service-DmCHJ4cH.d.ts} +106 -39
- package/dist/access-service.d.ts +3 -2
- package/dist/access-service.js +22 -22
- package/dist/access-surface-catalog.d.ts +3 -2
- package/dist/access-surface-catalog.js +2 -0
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +2 -2
- package/dist/{auto-sync-5CJBJMPZ.js → auto-sync-NUQWSFQD.js} +7 -7
- package/dist/bootstrap.d.ts +2 -1
- package/dist/bootstrap.js +2 -2
- package/dist/boxes.js +2 -2
- package/dist/briefing.js +3 -3
- package/dist/buffer.js +2 -2
- package/dist/calibration.js +4 -4
- package/dist/catalog-COqWZlZ6.d.ts +456 -0
- package/dist/causal-behavior.js +4 -4
- package/dist/causal-chain.js +4 -4
- package/dist/causal-consolidation.js +11 -11
- package/dist/causal-retrieval.js +4 -4
- package/dist/causal-trajectory-graph.js +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-ROHLEUTH.js → chunk-2KAYTPPT.js} +10 -10
- package/dist/{chunk-NHQGDVJF.js → chunk-2SJCWLQD.js} +3 -3
- package/dist/{chunk-YTWNKQ2G.js → chunk-3FC6LW6T.js} +2 -2
- package/dist/{chunk-5OE4PYY5.js → chunk-473JIN2U.js} +61 -11
- package/dist/chunk-473JIN2U.js.map +1 -0
- package/dist/{chunk-XEA4Z7JU.js → chunk-4FE2K57M.js} +3 -3
- package/dist/{chunk-WI7JKV2T.js → chunk-4N3TFFPH.js} +2 -2
- package/dist/{chunk-BTVX7ZXZ.js → chunk-4NFVPDIL.js} +4 -4
- package/dist/{chunk-OUWAQVDJ.js → chunk-5CEJH5ZN.js} +2 -2
- package/dist/{chunk-6QM24CP7.js → chunk-6GJS4BFH.js} +2 -2
- package/dist/{chunk-J2FBJ63F.js → chunk-6O6A6YUO.js} +4 -4
- package/dist/{chunk-HQ6NIBL6.js → chunk-7FL4CNPV.js} +2 -2
- package/dist/{chunk-5VDJMYTF.js → chunk-7WWURLG6.js} +3 -3
- package/dist/{chunk-QWRC7GIO.js → chunk-A4HH2EWA.js} +5 -5
- package/dist/{chunk-4SKKVWLQ.js → chunk-AGJH5ISO.js} +2 -2
- package/dist/{chunk-2L3KLWOV.js → chunk-B43NZNMG.js} +54 -92
- package/dist/chunk-B43NZNMG.js.map +1 -0
- package/dist/{chunk-WRE3JPAW.js → chunk-B4XVLHJA.js} +3 -3
- package/dist/{chunk-53FDU4CE.js → chunk-BLIWOONZ.js} +39 -36
- package/dist/chunk-BLIWOONZ.js.map +1 -0
- package/dist/{chunk-AJU4PJGY.js → chunk-BVKCV2ZY.js} +2 -2
- package/dist/{chunk-DQY7NJ5L.js → chunk-CTOQEZSN.js} +2 -2
- package/dist/{chunk-7OGJQP7T.js → chunk-DCWIQFNA.js} +4 -4
- package/dist/{chunk-MHQC2WU2.js → chunk-DKTSR7EK.js} +2 -2
- package/dist/{chunk-RN7MUWON.js → chunk-EHISUJFN.js} +2 -2
- package/dist/{chunk-WLEB7WCG.js → chunk-EO5QWINU.js} +2 -2
- package/dist/{chunk-XKXKSQU7.js → chunk-EXM3CQTZ.js} +2 -2
- package/dist/{chunk-M3FWYURP.js → chunk-FE6DQUNJ.js} +9 -9
- package/dist/{chunk-LCC5EZTT.js → chunk-FIVDN2SM.js} +4 -4
- package/dist/{chunk-3MY4W5V4.js → chunk-FUCUR2OZ.js} +550 -63
- package/dist/chunk-FUCUR2OZ.js.map +1 -0
- package/dist/{chunk-452WDNFO.js → chunk-GG6AJN7A.js} +2 -2
- package/dist/{chunk-2IBGHRIO.js → chunk-GS55WYRL.js} +3 -3
- package/dist/{chunk-IBTZEBUD.js → chunk-HYNHLBKA.js} +2 -2
- package/dist/{chunk-EVWIEEKZ.js → chunk-IQ7WCZRW.js} +2 -2
- package/dist/{chunk-B5XMS73R.js → chunk-IQVQJJL7.js} +2 -2
- package/dist/{chunk-6RHNCKHG.js → chunk-K43PI6DQ.js} +2 -2
- package/dist/{chunk-OIF36KGD.js → chunk-KCQA46NR.js} +2 -2
- package/dist/{chunk-2LDBXPLB.js → chunk-KF74X62T.js} +1 -1
- package/dist/{chunk-3EVIMVQU.js → chunk-KFBOZYME.js} +42 -3
- package/dist/chunk-KFBOZYME.js.map +1 -0
- package/dist/{chunk-MAV46GWQ.js → chunk-KYYL4U6X.js} +2 -2
- package/dist/{chunk-6GC5SGFE.js → chunk-L24JROPR.js} +2 -2
- package/dist/{chunk-Q5ZU3RNY.js → chunk-LQ6JI4VH.js} +2 -2
- package/dist/{chunk-GWKCEM3S.js → chunk-MCQDSY4G.js} +3 -3
- package/dist/{chunk-HP5FMB6L.js → chunk-MDJURR27.js} +2 -2
- package/dist/{chunk-FYEVFGJD.js → chunk-NN7QYW5W.js} +2 -2
- package/dist/chunk-NN7QYW5W.js.map +1 -0
- package/dist/{chunk-2ODBA7MQ.js → chunk-NU3CSQ4H.js} +5 -5
- package/dist/chunk-NU3CSQ4H.js.map +1 -0
- package/dist/{chunk-T2PO5MUF.js → chunk-O7GOFAM3.js} +2 -2
- package/dist/{chunk-Z2OXSMZK.js → chunk-OBXTMFZQ.js} +3 -3
- package/dist/{chunk-K6ZN34WC.js → chunk-OV4D5T7V.js} +3 -3
- package/dist/{chunk-OMLIFZ4I.js → chunk-PH3HOKYW.js} +2 -2
- package/dist/{chunk-C3IW2F5Z.js → chunk-PLBIPT6I.js} +2 -2
- package/dist/{chunk-QY7YA7OL.js → chunk-PNLCEFE4.js} +2 -2
- package/dist/{chunk-AGRPGAKR.js → chunk-PONNZ54D.js} +2 -2
- package/dist/{chunk-XZ4WBBB5.js → chunk-PWFWCGOO.js} +2 -2
- package/dist/{chunk-XWEXT4XU.js → chunk-QANVLERJ.js} +4 -4
- package/dist/{chunk-W4RVMTHR.js → chunk-QRDOSYOR.js} +2 -2
- package/dist/{chunk-OXNOINIP.js → chunk-QVMXQGT7.js} +24 -24
- package/dist/chunk-QVMXQGT7.js.map +1 -0
- package/dist/{chunk-6IMKOIZ6.js → chunk-R6OVFAX6.js} +2 -2
- package/dist/{chunk-5N5DXYDW.js → chunk-S2OU5DZY.js} +31 -10
- package/dist/chunk-S2OU5DZY.js.map +1 -0
- package/dist/{chunk-JOASJWQR.js → chunk-SANZHXY2.js} +2 -2
- package/dist/{chunk-7DTASS5T.js → chunk-SJHM6I4J.js} +2 -2
- package/dist/{chunk-M6BVYHBU.js → chunk-STOEE37X.js} +4 -4
- package/dist/{chunk-GKKAXVAJ.js → chunk-U33LWTQQ.js} +1 -7
- package/dist/chunk-U33LWTQQ.js.map +1 -0
- package/dist/{chunk-LXH3DIF2.js → chunk-U7D7NP4J.js} +2 -2
- package/dist/{chunk-DRD2Q7HQ.js → chunk-UFS7OXGL.js} +2 -2
- package/dist/{chunk-H3HDXD3U.js → chunk-UPTZYUYJ.js} +2 -2
- package/dist/{chunk-3Z7NPD5T.js → chunk-UTYBJR7M.js} +2 -2
- package/dist/{chunk-LN4YGHTM.js → chunk-UUH4YQOF.js} +2 -2
- package/dist/{chunk-6VF75M3X.js → chunk-VGUOEDTU.js} +2 -2
- package/dist/{chunk-44VFF3BB.js → chunk-VILEUJXC.js} +2 -2
- package/dist/{chunk-7SI52C65.js → chunk-VL7DP3OW.js} +2 -2
- package/dist/{chunk-7DHTMOND.js → chunk-VQ34TERH.js} +2 -2
- package/dist/{chunk-6VMIHVGO.js → chunk-VX6OBUDW.js} +2 -2
- package/dist/{chunk-EW5KFXHL.js → chunk-WDXCNJSF.js} +7 -7
- package/dist/{chunk-FMEKEF47.js → chunk-WIHPNY65.js} +79 -3
- package/dist/chunk-WIHPNY65.js.map +1 -0
- package/dist/{chunk-X6IRLNOO.js → chunk-WIWPSQYU.js} +2 -2
- package/dist/{chunk-DOCTITOP.js → chunk-WRFKZEO6.js} +2 -2
- package/dist/{chunk-E6ZDCOHM.js → chunk-XBZQRZ6G.js} +2 -2
- package/dist/{chunk-7YX23JBA.js → chunk-XHYGJVXL.js} +2 -2
- package/dist/{chunk-JD4SCARD.js → chunk-YN4ZT4CW.js} +1 -1
- package/dist/{chunk-YXWAILM4.js → chunk-YOI3ELXF.js} +2 -2
- package/dist/{chunk-XCAZF7KQ.js → chunk-ZA2S2VLL.js} +2 -2
- package/dist/{chunk-BEUDU7Y4.js → chunk-ZCWIH4LH.js} +2 -2
- package/dist/{chunk-V25ZAOSB.js → chunk-ZPTISBQU.js} +5 -5
- package/dist/{cli-ooj6JQBS.d.ts → cli-D8nZ2MPH.d.ts} +2 -2
- package/dist/cli.d.ts +4 -3
- package/dist/cli.js +44 -44
- package/dist/compounding/engine.js +4 -4
- package/dist/compounding/preference-consolidator.js +1 -1
- package/dist/config.js +2 -2
- package/dist/connectors/codex-materialize-runner.js +4 -4
- package/dist/connectors/codex-materialize.js +2 -2
- package/dist/connectors/index.js +5 -5
- package/dist/contradiction/index.js +3 -3
- package/dist/{contradiction-scan-AZTGFMPY.js → contradiction-scan-HWGEOUDS.js} +3 -3
- package/dist/conversation-index/backend.js +5 -5
- package/dist/conversation-index/cleanup.js +2 -2
- package/dist/conversation-index/faiss-adapter.js +2 -2
- package/dist/conversation-index/indexer.js +2 -2
- package/dist/conversation-index/search.js +2 -2
- package/dist/day-summary.js +2 -2
- package/dist/embedding-fallback.js +2 -2
- package/dist/entity-retrieval.js +4 -4
- package/dist/explicit-capture.d.ts +2 -1
- package/dist/explicit-capture.js +1 -1
- package/dist/extraction-judge-telemetry.js +2 -2
- package/dist/extraction-judge-training.js +2 -2
- package/dist/extraction-judge.js +5 -5
- package/dist/extraction.js +9 -9
- package/dist/fallback-llm.js +4 -4
- package/dist/{graph-edge-decay-KSVJGCZW.js → graph-edge-decay-D7OESCBR.js} +2 -2
- package/dist/graph-snapshot.js +2 -2
- package/dist/graph.js +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +80 -80
- package/dist/lcm/archive.js +2 -2
- package/dist/lcm/engine.js +5 -5
- package/dist/lcm/index.js +5 -5
- package/dist/lcm/schema.js +2 -2
- package/dist/lcm/summarizer.js +3 -3
- package/dist/local-llm.js +2 -2
- package/dist/logger.js +1 -1
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +5 -5
- package/dist/mcp-memory-inspector-app.d.ts +3 -2
- package/dist/model-registry.js +2 -2
- package/dist/models-json.js +2 -2
- package/dist/namespaces/migrate.d.ts +1 -0
- package/dist/namespaces/migrate.js +16 -16
- package/dist/namespaces/search.js +13 -13
- package/dist/namespaces/storage.d.ts +42 -1
- package/dist/namespaces/storage.js +3 -3
- package/dist/native-knowledge.js +2 -2
- package/dist/negative.js +2 -2
- package/dist/operator-toolkit.js +22 -22
- package/dist/{orchestrator-DIDDvwDw.d.ts → orchestrator-CA6ouzBn.d.ts} +3 -464
- package/dist/orchestrator.d.ts +2 -1
- package/dist/orchestrator.js +61 -61
- package/dist/profiling.js +2 -2
- package/dist/qmd.js +2 -2
- package/dist/recall-planner-llm.js +4 -4
- package/dist/recall-qos.js +2 -2
- package/dist/recall-state.js +2 -2
- package/dist/relevance.js +2 -2
- package/dist/{resolution-IDTEBJFS.js → resolution-MN36NW5P.js} +3 -3
- package/dist/resolve-provider-secret.js +2 -2
- package/dist/resume-bundles.js +4 -4
- package/dist/retrieval-agents.js +2 -2
- package/dist/routing/store.js +2 -2
- package/dist/schemas.d.ts +38 -38
- package/dist/search/embed-helper.js +2 -2
- package/dist/search/factory.js +12 -12
- package/dist/search/index.js +12 -12
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/remote-backend.js +2 -2
- package/dist/semantic-consolidation.js +5 -5
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/session-observer-state.js +2 -2
- package/dist/session-transcript-migration.js +2 -2
- package/dist/shared-context/manager.js +2 -2
- package/dist/storage.d.ts +4 -0
- package/dist/storage.js +2 -2
- package/dist/summarizer.js +7 -7
- package/dist/temporal-supersession.js +2 -2
- package/dist/threading.js +2 -2
- package/dist/transcript.js +2 -2
- package/dist/transfer/types.d.ts +22 -22
- package/dist/verified-recall.js +4 -4
- package/package.json +2 -2
- package/src/access-boundary.ts +2 -1
- package/src/access-cli.ts +94 -4
- package/src/access-http.ts +40 -2
- package/src/access-mcp.ts +55 -2
- package/src/access-operations.ts +66 -0
- package/src/access-service.ts +148 -73
- package/src/access-surface-catalog.test.ts +1 -1
- package/src/access-surface-catalog.ts +2 -0
- package/src/cli.ts +2 -1
- package/src/coding/decision-surfaces.test.ts +279 -0
- package/src/coding/decision-surfaces.ts +475 -0
- package/src/explicit-capture.ts +3 -12
- package/src/logger.ts +13 -4
- package/src/namespaces/catalog.test.ts +2 -2
- package/src/namespaces/storage.ts +81 -0
- package/src/orchestrator.ts +22 -64
- package/src/storage.ts +36 -36
- package/dist/chunk-2L3KLWOV.js.map +0 -1
- package/dist/chunk-2ODBA7MQ.js.map +0 -1
- package/dist/chunk-3EVIMVQU.js.map +0 -1
- package/dist/chunk-3MY4W5V4.js.map +0 -1
- package/dist/chunk-53FDU4CE.js.map +0 -1
- package/dist/chunk-5N5DXYDW.js.map +0 -1
- package/dist/chunk-5OE4PYY5.js.map +0 -1
- package/dist/chunk-FMEKEF47.js.map +0 -1
- package/dist/chunk-FYEVFGJD.js.map +0 -1
- package/dist/chunk-GKKAXVAJ.js.map +0 -1
- package/dist/chunk-OXNOINIP.js.map +0 -1
- /package/dist/{auto-sync-5CJBJMPZ.js.map → auto-sync-NUQWSFQD.js.map} +0 -0
- /package/dist/{chunk-ROHLEUTH.js.map → chunk-2KAYTPPT.js.map} +0 -0
- /package/dist/{chunk-NHQGDVJF.js.map → chunk-2SJCWLQD.js.map} +0 -0
- /package/dist/{chunk-YTWNKQ2G.js.map → chunk-3FC6LW6T.js.map} +0 -0
- /package/dist/{chunk-XEA4Z7JU.js.map → chunk-4FE2K57M.js.map} +0 -0
- /package/dist/{chunk-WI7JKV2T.js.map → chunk-4N3TFFPH.js.map} +0 -0
- /package/dist/{chunk-BTVX7ZXZ.js.map → chunk-4NFVPDIL.js.map} +0 -0
- /package/dist/{chunk-OUWAQVDJ.js.map → chunk-5CEJH5ZN.js.map} +0 -0
- /package/dist/{chunk-6QM24CP7.js.map → chunk-6GJS4BFH.js.map} +0 -0
- /package/dist/{chunk-J2FBJ63F.js.map → chunk-6O6A6YUO.js.map} +0 -0
- /package/dist/{chunk-HQ6NIBL6.js.map → chunk-7FL4CNPV.js.map} +0 -0
- /package/dist/{chunk-5VDJMYTF.js.map → chunk-7WWURLG6.js.map} +0 -0
- /package/dist/{chunk-QWRC7GIO.js.map → chunk-A4HH2EWA.js.map} +0 -0
- /package/dist/{chunk-4SKKVWLQ.js.map → chunk-AGJH5ISO.js.map} +0 -0
- /package/dist/{chunk-WRE3JPAW.js.map → chunk-B4XVLHJA.js.map} +0 -0
- /package/dist/{chunk-AJU4PJGY.js.map → chunk-BVKCV2ZY.js.map} +0 -0
- /package/dist/{chunk-DQY7NJ5L.js.map → chunk-CTOQEZSN.js.map} +0 -0
- /package/dist/{chunk-7OGJQP7T.js.map → chunk-DCWIQFNA.js.map} +0 -0
- /package/dist/{chunk-MHQC2WU2.js.map → chunk-DKTSR7EK.js.map} +0 -0
- /package/dist/{chunk-RN7MUWON.js.map → chunk-EHISUJFN.js.map} +0 -0
- /package/dist/{chunk-WLEB7WCG.js.map → chunk-EO5QWINU.js.map} +0 -0
- /package/dist/{chunk-XKXKSQU7.js.map → chunk-EXM3CQTZ.js.map} +0 -0
- /package/dist/{chunk-M3FWYURP.js.map → chunk-FE6DQUNJ.js.map} +0 -0
- /package/dist/{chunk-LCC5EZTT.js.map → chunk-FIVDN2SM.js.map} +0 -0
- /package/dist/{chunk-452WDNFO.js.map → chunk-GG6AJN7A.js.map} +0 -0
- /package/dist/{chunk-2IBGHRIO.js.map → chunk-GS55WYRL.js.map} +0 -0
- /package/dist/{chunk-IBTZEBUD.js.map → chunk-HYNHLBKA.js.map} +0 -0
- /package/dist/{chunk-EVWIEEKZ.js.map → chunk-IQ7WCZRW.js.map} +0 -0
- /package/dist/{chunk-B5XMS73R.js.map → chunk-IQVQJJL7.js.map} +0 -0
- /package/dist/{chunk-6RHNCKHG.js.map → chunk-K43PI6DQ.js.map} +0 -0
- /package/dist/{chunk-OIF36KGD.js.map → chunk-KCQA46NR.js.map} +0 -0
- /package/dist/{chunk-2LDBXPLB.js.map → chunk-KF74X62T.js.map} +0 -0
- /package/dist/{chunk-MAV46GWQ.js.map → chunk-KYYL4U6X.js.map} +0 -0
- /package/dist/{chunk-6GC5SGFE.js.map → chunk-L24JROPR.js.map} +0 -0
- /package/dist/{chunk-Q5ZU3RNY.js.map → chunk-LQ6JI4VH.js.map} +0 -0
- /package/dist/{chunk-GWKCEM3S.js.map → chunk-MCQDSY4G.js.map} +0 -0
- /package/dist/{chunk-HP5FMB6L.js.map → chunk-MDJURR27.js.map} +0 -0
- /package/dist/{chunk-T2PO5MUF.js.map → chunk-O7GOFAM3.js.map} +0 -0
- /package/dist/{chunk-Z2OXSMZK.js.map → chunk-OBXTMFZQ.js.map} +0 -0
- /package/dist/{chunk-K6ZN34WC.js.map → chunk-OV4D5T7V.js.map} +0 -0
- /package/dist/{chunk-OMLIFZ4I.js.map → chunk-PH3HOKYW.js.map} +0 -0
- /package/dist/{chunk-C3IW2F5Z.js.map → chunk-PLBIPT6I.js.map} +0 -0
- /package/dist/{chunk-QY7YA7OL.js.map → chunk-PNLCEFE4.js.map} +0 -0
- /package/dist/{chunk-AGRPGAKR.js.map → chunk-PONNZ54D.js.map} +0 -0
- /package/dist/{chunk-XZ4WBBB5.js.map → chunk-PWFWCGOO.js.map} +0 -0
- /package/dist/{chunk-XWEXT4XU.js.map → chunk-QANVLERJ.js.map} +0 -0
- /package/dist/{chunk-W4RVMTHR.js.map → chunk-QRDOSYOR.js.map} +0 -0
- /package/dist/{chunk-6IMKOIZ6.js.map → chunk-R6OVFAX6.js.map} +0 -0
- /package/dist/{chunk-JOASJWQR.js.map → chunk-SANZHXY2.js.map} +0 -0
- /package/dist/{chunk-7DTASS5T.js.map → chunk-SJHM6I4J.js.map} +0 -0
- /package/dist/{chunk-M6BVYHBU.js.map → chunk-STOEE37X.js.map} +0 -0
- /package/dist/{chunk-LXH3DIF2.js.map → chunk-U7D7NP4J.js.map} +0 -0
- /package/dist/{chunk-DRD2Q7HQ.js.map → chunk-UFS7OXGL.js.map} +0 -0
- /package/dist/{chunk-H3HDXD3U.js.map → chunk-UPTZYUYJ.js.map} +0 -0
- /package/dist/{chunk-3Z7NPD5T.js.map → chunk-UTYBJR7M.js.map} +0 -0
- /package/dist/{chunk-LN4YGHTM.js.map → chunk-UUH4YQOF.js.map} +0 -0
- /package/dist/{chunk-6VF75M3X.js.map → chunk-VGUOEDTU.js.map} +0 -0
- /package/dist/{chunk-44VFF3BB.js.map → chunk-VILEUJXC.js.map} +0 -0
- /package/dist/{chunk-7SI52C65.js.map → chunk-VL7DP3OW.js.map} +0 -0
- /package/dist/{chunk-7DHTMOND.js.map → chunk-VQ34TERH.js.map} +0 -0
- /package/dist/{chunk-6VMIHVGO.js.map → chunk-VX6OBUDW.js.map} +0 -0
- /package/dist/{chunk-EW5KFXHL.js.map → chunk-WDXCNJSF.js.map} +0 -0
- /package/dist/{chunk-X6IRLNOO.js.map → chunk-WIWPSQYU.js.map} +0 -0
- /package/dist/{chunk-DOCTITOP.js.map → chunk-WRFKZEO6.js.map} +0 -0
- /package/dist/{chunk-E6ZDCOHM.js.map → chunk-XBZQRZ6G.js.map} +0 -0
- /package/dist/{chunk-7YX23JBA.js.map → chunk-XHYGJVXL.js.map} +0 -0
- /package/dist/{chunk-JD4SCARD.js.map → chunk-YN4ZT4CW.js.map} +0 -0
- /package/dist/{chunk-YXWAILM4.js.map → chunk-YOI3ELXF.js.map} +0 -0
- /package/dist/{chunk-XCAZF7KQ.js.map → chunk-ZA2S2VLL.js.map} +0 -0
- /package/dist/{chunk-BEUDU7Y4.js.map → chunk-ZCWIH4LH.js.map} +0 -0
- /package/dist/{chunk-V25ZAOSB.js.map → chunk-ZPTISBQU.js.map} +0 -0
- /package/dist/{contradiction-scan-AZTGFMPY.js.map → contradiction-scan-HWGEOUDS.js.map} +0 -0
- /package/dist/{graph-edge-decay-KSVJGCZW.js.map → graph-edge-decay-D7OESCBR.js.map} +0 -0
- /package/dist/{resolution-IDTEBJFS.js.map → resolution-MN36NW5P.js.map} +0 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision-record surface contract + handler (issue #1548 Track A PR 2).
|
|
3
|
+
*
|
|
4
|
+
* Rule 39: one gate predicate, checked identically on every surface. Rule 22
|
|
5
|
+
* spirit: one implementation behind three thin wirings. The service holds
|
|
6
|
+
* only a thin delegate that builds a {@link DecisionSurfaceContext} and calls
|
|
7
|
+
* {@link handleCodingDecision}; the handler logic lives here so the
|
|
8
|
+
* access-service god file gains thin wiring only.
|
|
9
|
+
*
|
|
10
|
+
* No orchestrator imports (rule 11 — no shared mutable state). No circular
|
|
11
|
+
* dependency on access-service.ts: validation errors are thrown via
|
|
12
|
+
* `ctx.throwInputError`, which the service wires to EngramAccessInputError.
|
|
13
|
+
*/
|
|
14
|
+
import type { CodingKnowledgeConfig, CodingContext, MemoryFile, MemoryFrontmatter, MemoryStatus } from "../types.js";
|
|
15
|
+
import {
|
|
16
|
+
ACTIVE_DECISION_STATUSES,
|
|
17
|
+
DEFAULT_DECISION_STATUS,
|
|
18
|
+
isDecisionStatus,
|
|
19
|
+
parseDecisionRecord,
|
|
20
|
+
serializeDecisionRecord,
|
|
21
|
+
type DecisionRecord,
|
|
22
|
+
type DecisionStatus,
|
|
23
|
+
} from "./decision-records.js";
|
|
24
|
+
import { log } from "../logger.js";
|
|
25
|
+
|
|
26
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
27
|
+
// Subcommands
|
|
28
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
export const DECISION_SUBCOMMANDS = [
|
|
31
|
+
"list",
|
|
32
|
+
"get",
|
|
33
|
+
"record",
|
|
34
|
+
"supersede",
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
export type DecisionSubcommand = (typeof DECISION_SUBCOMMANDS)[number];
|
|
38
|
+
|
|
39
|
+
const SUBCOMMAND_VALUES = DECISION_SUBCOMMANDS as readonly string[];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Type guard — narrows an unknown subcommand string to the
|
|
43
|
+
* {@link DecisionSubcommand} union.
|
|
44
|
+
*/
|
|
45
|
+
export function isDecisionSubcommand(value: unknown): value is DecisionSubcommand {
|
|
46
|
+
return typeof value === "string" && SUBCOMMAND_VALUES.includes(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Human-readable subcommand list for error messages (rule 51 — list valid
|
|
51
|
+
* options so the caller can correct rather than guess).
|
|
52
|
+
*/
|
|
53
|
+
export function formatDecisionSubcommands(): string {
|
|
54
|
+
return DECISION_SUBCOMMANDS.join(", ");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
58
|
+
// Gate predicate — rule 39: one predicate, identical on every surface
|
|
59
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The single decision-record surface gate. Returns `true` only when:
|
|
63
|
+
* 1. `codingKnowledge.enabled` is on (the master Track A gate),
|
|
64
|
+
* 2. `codingKnowledge.decisionRecords` is on (the feature switch), AND
|
|
65
|
+
* 3. A coding context is attached (the session is project/branch scoped —
|
|
66
|
+
* decision records live *in* the coding namespace, rule 42).
|
|
67
|
+
*
|
|
68
|
+
* Every surface — MCP `engram.coding_decision`, HTTP
|
|
69
|
+
* `POST /engram/v1/coding/decisions`, CLI `engram-access decision` — MUST call
|
|
70
|
+
* this predicate (or the handler that embeds it) before dispatching. The
|
|
71
|
+
* tool-visibility gate in the MCP constructor checks conditions 1–2 only
|
|
72
|
+
* (coding context is per-session and cannot be evaluated at construction
|
|
73
|
+
* time); the call-time gate checks all three.
|
|
74
|
+
*/
|
|
75
|
+
export function isDecisionRecordSurfaceEnabled(
|
|
76
|
+
config: CodingKnowledgeConfig,
|
|
77
|
+
codingContext: CodingContext | null | undefined,
|
|
78
|
+
): boolean {
|
|
79
|
+
return (
|
|
80
|
+
config.enabled === true &&
|
|
81
|
+
config.decisionRecords === true &&
|
|
82
|
+
codingContext != null
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Config-only visibility gate — used by the MCP constructor to decide whether
|
|
88
|
+
* to advertise `engram.coding_decision` in `tools/list`. When this returns
|
|
89
|
+
* `false` the tools array is byte-identical to pre-feature (rule 39).
|
|
90
|
+
*/
|
|
91
|
+
export function isDecisionRecordSurfaceVisible(
|
|
92
|
+
config: CodingKnowledgeConfig,
|
|
93
|
+
): boolean {
|
|
94
|
+
return config.enabled === true && config.decisionRecords === true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
98
|
+
// Surface request / response shapes
|
|
99
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Canonical surface request — one shape for all three transports. The
|
|
103
|
+
* `subcommand` field selects which operation runs; the remaining fields are
|
|
104
|
+
* optional depending on the subcommand.
|
|
105
|
+
*
|
|
106
|
+
* `sessionKey` identifies the session whose coding context scopes the
|
|
107
|
+
* operation. `namespace` overrides the coding-scoped namespace (same
|
|
108
|
+
* precedence as `memory_store` — explicit namespace wins).
|
|
109
|
+
*/
|
|
110
|
+
export interface DecisionSurfaceRequest {
|
|
111
|
+
subcommand: DecisionSubcommand;
|
|
112
|
+
sessionKey?: string;
|
|
113
|
+
namespace?: string;
|
|
114
|
+
// get / supersede
|
|
115
|
+
id?: string;
|
|
116
|
+
// record
|
|
117
|
+
title?: string;
|
|
118
|
+
status?: string;
|
|
119
|
+
context?: string;
|
|
120
|
+
decision?: string;
|
|
121
|
+
consequences?: string;
|
|
122
|
+
entityRefs?: string[];
|
|
123
|
+
// supersede
|
|
124
|
+
supersedesId?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Surface response — a discriminated union on `subcommand`. Each surface
|
|
129
|
+
* serializes this to its transport-appropriate shape.
|
|
130
|
+
*/
|
|
131
|
+
export type DecisionSurfaceResponse =
|
|
132
|
+
| { subcommand: "list"; records: DecisionSurfaceRecord[]; count: number }
|
|
133
|
+
| { subcommand: "get"; found: boolean; record?: DecisionSurfaceRecord }
|
|
134
|
+
| { subcommand: "record"; memoryId: string; status: string }
|
|
135
|
+
| {
|
|
136
|
+
subcommand: "supersede";
|
|
137
|
+
supersededMemoryId: string;
|
|
138
|
+
replacementMemoryId: string;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Flattened record projection surfaced to clients. Stored as markdown +
|
|
143
|
+
* frontmatter memory files (category `"decision"`) — this shape is the
|
|
144
|
+
* read-side projection, not the storage format.
|
|
145
|
+
*/
|
|
146
|
+
export interface DecisionSurfaceRecord {
|
|
147
|
+
id: string;
|
|
148
|
+
title: string;
|
|
149
|
+
status: string;
|
|
150
|
+
context?: string;
|
|
151
|
+
decision?: string;
|
|
152
|
+
consequences?: string;
|
|
153
|
+
entityRefs: string[];
|
|
154
|
+
supersedes?: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
158
|
+
// Handler — the single implementation behind all three surfaces (rule 22)
|
|
159
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Structural subset of StorageManager the decision handler reads or writes.
|
|
163
|
+
* Kept narrow so the module stays decoupled from storage.ts and is
|
|
164
|
+
* unit-testable with a stub.
|
|
165
|
+
*/
|
|
166
|
+
export interface DecisionSurfaceStorage {
|
|
167
|
+
readonly dir: string;
|
|
168
|
+
/** The resolved namespace — used for catalog write recording. */
|
|
169
|
+
readonly namespace: string;
|
|
170
|
+
readAllMemories(): Promise<readonly MemoryFile[]>;
|
|
171
|
+
getMemoryById(id: string): Promise<MemoryFile | null>;
|
|
172
|
+
writeMemory(
|
|
173
|
+
category: "decision",
|
|
174
|
+
content: string,
|
|
175
|
+
options: {
|
|
176
|
+
confidence?: number;
|
|
177
|
+
tags?: string[];
|
|
178
|
+
source?: string;
|
|
179
|
+
/** Outer memory lifecycle status — set to "archived" for inactive
|
|
180
|
+
* decisions so generic recall/search/maintenance exclude them
|
|
181
|
+
* (review P2: persist inactive decision statuses in frontmatter). */
|
|
182
|
+
status?: MemoryStatus;
|
|
183
|
+
/** Decision-specific lifecycle marker, mirrored from the serialized
|
|
184
|
+
* body so the list/get projection has one authoritative source. */
|
|
185
|
+
structuredAttributes?: Record<string, string>;
|
|
186
|
+
},
|
|
187
|
+
): Promise<string>;
|
|
188
|
+
writeMemoryFrontmatter(
|
|
189
|
+
memory: MemoryFile,
|
|
190
|
+
patch: Partial<MemoryFrontmatter>,
|
|
191
|
+
): Promise<unknown>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Dependencies the handler borrows from the service. The service constructs
|
|
196
|
+
* this context per call; the handler never touches the orchestrator directly.
|
|
197
|
+
* `throwInputError` lets the handler raise the surface-appropriate error
|
|
198
|
+
* class without importing access-service.ts (no circular dependency).
|
|
199
|
+
*/
|
|
200
|
+
export interface DecisionSurfaceContext {
|
|
201
|
+
readonly codingKnowledge: CodingKnowledgeConfig;
|
|
202
|
+
getCodingContext(sessionKey: string): CodingContext | null;
|
|
203
|
+
/** Resolve storage through the SAME namespace path as memory_store
|
|
204
|
+
* (principal ACL + coding overlay + default fallback). The #1522 storage
|
|
205
|
+
* chokepoint records the catalog write automatically on every
|
|
206
|
+
* storage.writeMemory, so the handler does NOT touch the catalog itself. */
|
|
207
|
+
resolveStorage(request: DecisionSurfaceRequest): Promise<DecisionSurfaceStorage>;
|
|
208
|
+
/** Throw the surface-appropriate input-validation error. */
|
|
209
|
+
throwInputError(message: string): never;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The single shared implementation behind the MCP, HTTP, and CLI
|
|
214
|
+
* decision-record surfaces. All three transports dispatch through the
|
|
215
|
+
* `coding_decision` boundary operation, which calls this function via the
|
|
216
|
+
* service delegate.
|
|
217
|
+
*
|
|
218
|
+
* Gate (rule 39): `codingKnowledge.enabled + decisionRecords + coding
|
|
219
|
+
* context`. Persistence (rule 43): records are written through the storage
|
|
220
|
+
* manager's normal persist pipeline with category `"decision"` — no direct
|
|
221
|
+
* `fs` writes of memory content. Supersede (rule 25): the replacement is
|
|
222
|
+
* written BEFORE the old record's `structuredAttributes.decisionStatus` is
|
|
223
|
+
* set to `"superseded"` — the structuredAttribute is the authoritative
|
|
224
|
+
* lifecycle marker; content is never rewritten.
|
|
225
|
+
*/
|
|
226
|
+
export async function handleCodingDecision(
|
|
227
|
+
request: DecisionSurfaceRequest,
|
|
228
|
+
ctx: DecisionSurfaceContext,
|
|
229
|
+
): Promise<DecisionSurfaceResponse> {
|
|
230
|
+
const codingContext = request.sessionKey
|
|
231
|
+
? ctx.getCodingContext(request.sessionKey)
|
|
232
|
+
: null;
|
|
233
|
+
if (!isDecisionRecordSurfaceEnabled(ctx.codingKnowledge, codingContext)) {
|
|
234
|
+
ctx.throwInputError(
|
|
235
|
+
"coding_decision requires codingKnowledge.enabled, codingKnowledge.decisionRecords, and an attached coding context",
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
switch (request.subcommand) {
|
|
239
|
+
case "list":
|
|
240
|
+
return decisionList(request, ctx);
|
|
241
|
+
case "get":
|
|
242
|
+
return decisionGet(request, ctx);
|
|
243
|
+
case "record":
|
|
244
|
+
return decisionRecord(request, ctx);
|
|
245
|
+
case "supersede":
|
|
246
|
+
return decisionSupersede(request, ctx);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function decisionList(
|
|
251
|
+
request: DecisionSurfaceRequest,
|
|
252
|
+
ctx: DecisionSurfaceContext,
|
|
253
|
+
): Promise<DecisionSurfaceResponse> {
|
|
254
|
+
const storage = await ctx.resolveStorage(request);
|
|
255
|
+
const memories = await storage.readAllMemories();
|
|
256
|
+
const records: DecisionSurfaceRecord[] = [];
|
|
257
|
+
for (const m of memories) {
|
|
258
|
+
if (m.frontmatter.category !== "decision") continue;
|
|
259
|
+
// Exclude lifecycle-retired memories. Any outer frontmatter.status other
|
|
260
|
+
// than undefined/"active" (archived, superseded, forgotten, rejected,
|
|
261
|
+
// quarantined, pending_review) means the generic memory lifecycle has
|
|
262
|
+
// intervened — hide the decision until that resolves. The decision-specific
|
|
263
|
+
// lifecycle marker lives in structuredAttributes.decisionStatus (review:
|
|
264
|
+
// hide all non-active outer statuses from decisions).
|
|
265
|
+
const memStatus = m.frontmatter.status;
|
|
266
|
+
if (memStatus && memStatus !== "active") continue;
|
|
267
|
+
const parsed = safeParseDecisionRecord(m.content);
|
|
268
|
+
if (!parsed) continue;
|
|
269
|
+
const structStatus = m.frontmatter.structuredAttributes?.decisionStatus;
|
|
270
|
+
const effectiveStatus = structStatus ?? parsed.status;
|
|
271
|
+
records.push({
|
|
272
|
+
id: m.frontmatter.id,
|
|
273
|
+
title: parsed.title,
|
|
274
|
+
status: effectiveStatus,
|
|
275
|
+
entityRefs: parsed.entityRefs,
|
|
276
|
+
supersedes: parsed.supersedes,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const visible = records.filter((r) =>
|
|
280
|
+
ACTIVE_DECISION_STATUSES.has(r.status as DecisionStatus),
|
|
281
|
+
);
|
|
282
|
+
return { subcommand: "list", records: visible, count: visible.length };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function decisionGet(
|
|
286
|
+
request: DecisionSurfaceRequest,
|
|
287
|
+
ctx: DecisionSurfaceContext,
|
|
288
|
+
): Promise<DecisionSurfaceResponse> {
|
|
289
|
+
if (!request.id?.trim()) {
|
|
290
|
+
ctx.throwInputError("id is required for the 'get' subcommand");
|
|
291
|
+
}
|
|
292
|
+
const storage = await ctx.resolveStorage(request);
|
|
293
|
+
const memory = await storage.getMemoryById(request.id!);
|
|
294
|
+
if (!memory || memory.frontmatter.category !== "decision") {
|
|
295
|
+
return { subcommand: "get", found: false };
|
|
296
|
+
}
|
|
297
|
+
const parsed = safeParseDecisionRecord(memory.content);
|
|
298
|
+
if (!parsed) {
|
|
299
|
+
return { subcommand: "get", found: false };
|
|
300
|
+
}
|
|
301
|
+
const structStatus = memory.frontmatter.structuredAttributes?.decisionStatus;
|
|
302
|
+
return {
|
|
303
|
+
subcommand: "get",
|
|
304
|
+
found: true,
|
|
305
|
+
record: {
|
|
306
|
+
id: memory.frontmatter.id,
|
|
307
|
+
title: parsed.title,
|
|
308
|
+
status: structStatus ?? parsed.status,
|
|
309
|
+
context: parsed.context,
|
|
310
|
+
decision: parsed.decision,
|
|
311
|
+
consequences: parsed.consequences,
|
|
312
|
+
entityRefs: parsed.entityRefs,
|
|
313
|
+
supersedes: parsed.supersedes,
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function decisionRecord(
|
|
319
|
+
request: DecisionSurfaceRequest,
|
|
320
|
+
ctx: DecisionSurfaceContext,
|
|
321
|
+
): Promise<DecisionSurfaceResponse> {
|
|
322
|
+
if (!request.title?.trim()) {
|
|
323
|
+
ctx.throwInputError("title is required for the 'record' subcommand");
|
|
324
|
+
}
|
|
325
|
+
if (!request.decision?.trim()) {
|
|
326
|
+
ctx.throwInputError("decision is required for the 'record' subcommand");
|
|
327
|
+
}
|
|
328
|
+
const status: DecisionStatus = request.status?.trim()
|
|
329
|
+
? isDecisionStatus(request.status)
|
|
330
|
+
? request.status
|
|
331
|
+
: raiseInvalidStatus(request.status, ctx)
|
|
332
|
+
: DEFAULT_DECISION_STATUS;
|
|
333
|
+
const record: DecisionRecord = {
|
|
334
|
+
id: "",
|
|
335
|
+
title: request.title.trim(),
|
|
336
|
+
status,
|
|
337
|
+
context: request.context?.trim() ?? "",
|
|
338
|
+
decision: request.decision.trim(),
|
|
339
|
+
consequences: request.consequences?.trim() ?? "",
|
|
340
|
+
entityRefs: request.entityRefs ?? [],
|
|
341
|
+
};
|
|
342
|
+
const content = serializeDecisionRecord(record);
|
|
343
|
+
const storage = await ctx.resolveStorage(request);
|
|
344
|
+
const isActive = ACTIVE_DECISION_STATUSES.has(status);
|
|
345
|
+
const memoryId = await storage.writeMemory("decision", content, {
|
|
346
|
+
confidence: 1.0,
|
|
347
|
+
tags: ["decision-record"],
|
|
348
|
+
source: "coding-decision",
|
|
349
|
+
// Persist the decision lifecycle in BOTH places so generic
|
|
350
|
+
// recall/search/maintenance (which read frontmatter.status) and the
|
|
351
|
+
// decision list/get projection (which reads structuredAttributes) agree:
|
|
352
|
+
// - structuredAttributes.decisionStatus is the authoritative decision
|
|
353
|
+
// marker, mirrored from the serialized body (one source of truth);
|
|
354
|
+
// - frontmatter.status is set to "archived" for inactive decisions
|
|
355
|
+
// (rejected/superseded) so the outer memory pipeline excludes them
|
|
356
|
+
// from the active corpus exactly like a supersede does (review P2:
|
|
357
|
+
// persist inactive decision statuses in frontmatter).
|
|
358
|
+
structuredAttributes: { decisionStatus: status },
|
|
359
|
+
status: isActive ? undefined : "archived",
|
|
360
|
+
});
|
|
361
|
+
log.info(
|
|
362
|
+
`access-write op=coding_decision/record memoryId=${memoryId} status=${status}`,
|
|
363
|
+
);
|
|
364
|
+
return { subcommand: "record", memoryId, status };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function decisionSupersede(
|
|
368
|
+
request: DecisionSurfaceRequest,
|
|
369
|
+
ctx: DecisionSurfaceContext,
|
|
370
|
+
): Promise<DecisionSurfaceResponse> {
|
|
371
|
+
// The schema advertises `supersedesId` for MCP/HTTP clients that name it
|
|
372
|
+
// explicitly; treat it as an alias for `id` when `id` is absent (review P2).
|
|
373
|
+
const targetId = request.id?.trim() || request.supersedesId?.trim();
|
|
374
|
+
if (!targetId) {
|
|
375
|
+
ctx.throwInputError(
|
|
376
|
+
"id (or supersedesId) is required for the 'supersede' subcommand (the record being superseded)",
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
if (!request.title?.trim()) {
|
|
380
|
+
ctx.throwInputError(
|
|
381
|
+
"title is required for the 'supersede' subcommand (the replacement record)",
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
if (!request.decision?.trim()) {
|
|
385
|
+
ctx.throwInputError("decision is required for the 'supersede' subcommand");
|
|
386
|
+
}
|
|
387
|
+
const storage = await ctx.resolveStorage(request);
|
|
388
|
+
const oldMemory = await storage.getMemoryById(targetId);
|
|
389
|
+
if (!oldMemory || oldMemory.frontmatter.category !== "decision") {
|
|
390
|
+
ctx.throwInputError(`decision record not found: ${targetId}`);
|
|
391
|
+
}
|
|
392
|
+
const oldParsed = safeParseDecisionRecord(oldMemory.content);
|
|
393
|
+
if (!oldParsed) {
|
|
394
|
+
ctx.throwInputError(
|
|
395
|
+
`decision record is corrupted and cannot be superseded: ${targetId}`,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
// Rule 25: write the replacement BEFORE mutating the old record's status.
|
|
399
|
+
const replacement: DecisionRecord = {
|
|
400
|
+
id: "",
|
|
401
|
+
title: request.title.trim(),
|
|
402
|
+
status: "accepted",
|
|
403
|
+
context: request.context?.trim() ?? "",
|
|
404
|
+
decision: request.decision.trim(),
|
|
405
|
+
consequences: request.consequences?.trim() ?? "",
|
|
406
|
+
entityRefs: request.entityRefs ?? [],
|
|
407
|
+
supersedes: targetId,
|
|
408
|
+
};
|
|
409
|
+
const replacementContent = serializeDecisionRecord(replacement);
|
|
410
|
+
const replacementId = await storage.writeMemory(
|
|
411
|
+
"decision",
|
|
412
|
+
replacementContent,
|
|
413
|
+
{
|
|
414
|
+
confidence: 1.0,
|
|
415
|
+
tags: ["decision-record"],
|
|
416
|
+
source: "coding-decision",
|
|
417
|
+
// Mirror decisionRecord: persist structuredAttributes.decisionStatus on
|
|
418
|
+
// the replacement so list/get projection and QMD indexing see the
|
|
419
|
+
// authoritative marker (review: supersede omits decisionStatus attrs).
|
|
420
|
+
structuredAttributes: { decisionStatus: "accepted" },
|
|
421
|
+
},
|
|
422
|
+
);
|
|
423
|
+
// Mark the old record superseded: set BOTH frontmatter.status (so
|
|
424
|
+
// recall/search/maintenance exclude it from the active corpus — review P2)
|
|
425
|
+
// AND structuredAttributes.decisionStatus (the decision-specific lifecycle
|
|
426
|
+
// marker used by list/get projection). The content body is not mutated.
|
|
427
|
+
// Rule 25: the replacement is written BEFORE the old record is mutated so
|
|
428
|
+
// a frontmatter-write failure leaves a harmless duplicate, not a missing
|
|
429
|
+
// record. Best-effort: log the failure but don't roll back the replacement
|
|
430
|
+
// (review: cursor partial-write thread).
|
|
431
|
+
try {
|
|
432
|
+
await storage.writeMemoryFrontmatter(oldMemory, {
|
|
433
|
+
status: "archived",
|
|
434
|
+
// Refresh the updated timestamp so the archive/supersede lifecycle event
|
|
435
|
+
// and browse/maintenance sort key reflect when the decision was retired,
|
|
436
|
+
// not when it was originally recorded (review: set updated timestamp when
|
|
437
|
+
// retiring old decisions).
|
|
438
|
+
updated: new Date().toISOString(),
|
|
439
|
+
structuredAttributes: {
|
|
440
|
+
...(oldMemory.frontmatter.structuredAttributes ?? {}),
|
|
441
|
+
decisionStatus: "superseded",
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
} catch (err) {
|
|
445
|
+
log.warn(
|
|
446
|
+
`coding_decision/supersede: replacement ${replacementId} written but old record ${targetId} status update failed — old record will still appear until retried: ${err instanceof Error ? err.message : String(err)}`,
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
log.info(
|
|
450
|
+
`access-write op=coding_decision/supersede superseded=${targetId} replacement=${replacementId}`,
|
|
451
|
+
);
|
|
452
|
+
return {
|
|
453
|
+
subcommand: "supersede",
|
|
454
|
+
supersededMemoryId: targetId,
|
|
455
|
+
replacementMemoryId: replacementId,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
460
|
+
// Local helpers
|
|
461
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
462
|
+
|
|
463
|
+
function safeParseDecisionRecord(content: string): DecisionRecord | null {
|
|
464
|
+
try {
|
|
465
|
+
return parseDecisionRecord(content);
|
|
466
|
+
} catch {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function raiseInvalidStatus(value: string, ctx: DecisionSurfaceContext): never {
|
|
472
|
+
ctx.throwInputError(
|
|
473
|
+
`invalid decision status "${value}". Valid options: proposed, accepted, superseded, rejected`,
|
|
474
|
+
);
|
|
475
|
+
}
|
package/src/explicit-capture.ts
CHANGED
|
@@ -435,15 +435,8 @@ export async function persistExplicitCapture(
|
|
|
435
435
|
expiresAt: candidate.expiresAt,
|
|
436
436
|
source: source === "inline" ? "explicit-inline" : "explicit",
|
|
437
437
|
});
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
// never updates `lastWriteAt`. An undefined namespace means the DEFAULT root
|
|
441
|
-
// (round 6, codex P2), which recordCatalogWrite resolves. The method is an
|
|
442
|
-
// optional best-effort hook — guard so Orchestrator-like callers without it
|
|
443
|
-
// don't break (rule #33). Best-effort and failure-tolerant.
|
|
444
|
-
if (typeof orchestrator.recordCatalogWrite === "function") {
|
|
445
|
-
orchestrator.recordCatalogWrite(resolvedNamespace, storage.dir);
|
|
446
|
-
}
|
|
438
|
+
// #1522: catalog touch handled at the storage chokepoint — the StorageManager's
|
|
439
|
+
// post-write hook records the namespace touch automatically.
|
|
447
440
|
|
|
448
441
|
const created = new Date().toISOString();
|
|
449
442
|
const event: MemoryLifecycleEvent = {
|
|
@@ -559,9 +552,7 @@ export async function queueExplicitCaptureForReview(
|
|
|
559
552
|
// `writeMemory` returns an id. If the later pending-review frontmatter update
|
|
560
553
|
// fails, the memory file is still durable and must not disappear from
|
|
561
554
|
// writtenSince/maintenance scheduling. Guarded optional hook (rule #33).
|
|
562
|
-
|
|
563
|
-
orchestrator.recordCatalogWrite(queueNamespace, storage.dir);
|
|
564
|
-
}
|
|
555
|
+
// #1522: catalog touch handled at the storage chokepoint.
|
|
565
556
|
}
|
|
566
557
|
const event: MemoryLifecycleEvent = {
|
|
567
558
|
eventId: `mle-${randomUUID()}`,
|
package/src/logger.ts
CHANGED
|
@@ -15,11 +15,20 @@ const NOOP_LOGGER: LoggerBackend = {
|
|
|
15
15
|
let _backend: LoggerBackend = NOOP_LOGGER;
|
|
16
16
|
let _debug = false;
|
|
17
17
|
|
|
18
|
+
// Late-bind console.* rather than capturing references at module import
|
|
19
|
+
// time. In production this is behaviour-identical to the pre-bound
|
|
20
|
+
// `.bind(console)` form — `console.info(msg, ...)` resolves `this` to
|
|
21
|
+
// `console` whether called directly or via an arrow wrapper — but it lets
|
|
22
|
+
// harnesses that swap console methods (e.g. runCli in @remnic/cli) route
|
|
23
|
+
// logger output through their capture sinks. With the old pre-bound form,
|
|
24
|
+
// any command that called initLogger() pinned the logger to the original
|
|
25
|
+
// console methods before the harness could swap them, so log.warn /
|
|
26
|
+
// log.info output escaped the capture buffer.
|
|
18
27
|
const CONSOLE_LOGGER: LoggerBackend = {
|
|
19
|
-
info: console.info
|
|
20
|
-
warn: console.warn
|
|
21
|
-
error: console.error
|
|
22
|
-
debug: console.debug
|
|
28
|
+
info: (msg, ...args) => console.info(msg, ...args),
|
|
29
|
+
warn: (msg, ...args) => console.warn(msg, ...args),
|
|
30
|
+
error: (msg, ...args) => console.error(msg, ...args),
|
|
31
|
+
debug: (msg, ...args) => console.debug(msg, ...args),
|
|
23
32
|
};
|
|
24
33
|
|
|
25
34
|
export function initLogger(backend?: LoggerBackend, debug?: boolean): void {
|
|
@@ -539,7 +539,7 @@ test("register does not overwrite prior discoveredBy on an existing record", asy
|
|
|
539
539
|
|
|
540
540
|
// A record first PRE-REGISTERED via the router's onResolve hook (config) is
|
|
541
541
|
// UPGRADED to "write" by a later real write touch (round 6, codex P2 — NBPmT):
|
|
542
|
-
// `storageFor()` fires registerResolved (config) before
|
|
542
|
+
// `storageFor()` fires registerResolved (config) before the storage chokepoint runs,
|
|
543
543
|
// so without the upgrade `listNamespaces({ discoveredBy: "write" })` would miss a
|
|
544
544
|
// genuinely-written namespace. A non-write touch (read/register) still preserves
|
|
545
545
|
// provenance.
|
|
@@ -689,7 +689,7 @@ test("chunked write path contract: markWrite updates lastWriteAt for the namespa
|
|
|
689
689
|
const ns = "project-origin-chunked";
|
|
690
690
|
const storageDir = path.join(memoryDir, "namespaces", namespaceIdentityToken(ns));
|
|
691
691
|
// This is exactly the call the orchestrator chunked branch now makes
|
|
692
|
-
// (
|
|
692
|
+
// (storage chokepoint -> markWrite with discoveredBy "write" + storageDir).
|
|
693
693
|
await catalog.markWrite(ns, { discoveredBy: "write", storageDir });
|
|
694
694
|
const record = await catalog.getNamespaceRecord(ns);
|
|
695
695
|
assert.ok(record?.lastWriteAt, "chunked write must update lastWriteAt");
|
|
@@ -5,6 +5,7 @@ import { StorageManager } from "../storage.js";
|
|
|
5
5
|
import type { PluginConfig } from "../types.js";
|
|
6
6
|
import { ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
|
|
7
7
|
import { namespaceIdentityToken, normalizeNamespaceIdentity } from "./identity.js";
|
|
8
|
+
import type { NamespaceCatalog } from "./catalog.js";
|
|
8
9
|
|
|
9
10
|
async function exists(p: string): Promise<boolean> {
|
|
10
11
|
try {
|
|
@@ -228,6 +229,9 @@ export class NamespaceStorageRouter {
|
|
|
228
229
|
// `whenResolveHooksSettled`). Entries are removed as each hook settles, so the
|
|
229
230
|
// set holds at most one promise per concurrently-resolving namespace.
|
|
230
231
|
private readonly pendingResolveHooks = new Set<Promise<unknown>>();
|
|
232
|
+
// Pending post-write catalog touch promises (#1522). Like pendingResolveHooks,
|
|
233
|
+
// lets tests await fire-and-forget write touches deterministically.
|
|
234
|
+
private readonly pendingWriteTouches = new Set<Promise<unknown>>();
|
|
231
235
|
|
|
232
236
|
// Normalized (trimmed) default namespace identity (NH-FH). `storageFor`
|
|
233
237
|
// normalizes its input, so default-namespace branches must compare against the
|
|
@@ -238,6 +242,8 @@ export class NamespaceStorageRouter {
|
|
|
238
242
|
constructor(
|
|
239
243
|
private readonly config: PluginConfig,
|
|
240
244
|
private readonly hooks: NamespaceStorageRouterHooks = {},
|
|
245
|
+
/** Catalog reference for post-write/read touches (issue #1522 chokepoint). */
|
|
246
|
+
private readonly catalog?: NamespaceCatalog,
|
|
241
247
|
) {
|
|
242
248
|
this.defaultNamespaceIdentity = normalizeNamespaceIdentity(config.defaultNamespace);
|
|
243
249
|
}
|
|
@@ -288,6 +294,9 @@ export class NamespaceStorageRouter {
|
|
|
288
294
|
// (used by extraction and shared-promotion paths) strip citations consistently,
|
|
289
295
|
// matching the behaviour of the primary this.storage instance in the orchestrator.
|
|
290
296
|
sm.citationTemplate = this.config.inlineSourceAttributionFormat;
|
|
297
|
+
// #1522: install the post-write catalog touch at the chokepoint — every
|
|
298
|
+
// successful write on this StorageManager records the namespace touch.
|
|
299
|
+
this.bindCatalogWriteHook(sm, ns);
|
|
291
300
|
this.cache.set(ns, sm);
|
|
292
301
|
this.notifyResolved(ns, root);
|
|
293
302
|
return sm;
|
|
@@ -369,6 +378,78 @@ export class NamespaceStorageRouter {
|
|
|
369
378
|
}
|
|
370
379
|
}
|
|
371
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Install the post-write catalog touch hook on an externally-constructed
|
|
383
|
+
* StorageManager (issue #1522). Used by the orchestrator for the legacy
|
|
384
|
+
* default-namespace storage (`this.storage`) that bypasses the router.
|
|
385
|
+
*/
|
|
386
|
+
bindCatalogWriteHook(sm: StorageManager, namespace: string): void {
|
|
387
|
+
sm.onCatalogWrite = () => this.touchCatalogWrite(namespace, sm.dir);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Post-write catalog touch (issue #1522 chokepoint). Called by every
|
|
392
|
+
* StorageManager's post-write hook AFTER a successful write. Best-effort
|
|
393
|
+
* and failure-tolerant — a catalog error MUST NOT affect the primary write
|
|
394
|
+
* (gotcha #13, rule #40). Fire-and-forget by design.
|
|
395
|
+
*/
|
|
396
|
+
private touchCatalogWrite(namespace: string, storageDir: string): void {
|
|
397
|
+
if (!this.catalog) return;
|
|
398
|
+
const touch = this.catalog
|
|
399
|
+
.markWrite(namespace, { discoveredBy: "write", storageDir })
|
|
400
|
+
.catch(() => undefined);
|
|
401
|
+
this.pendingWriteTouches.add(touch);
|
|
402
|
+
void touch.then(
|
|
403
|
+
() => { this.pendingWriteTouches.delete(touch); },
|
|
404
|
+
() => { this.pendingWriteTouches.delete(touch); },
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Record a namespace read in the catalog (issue #1522 chokepoint move).
|
|
410
|
+
* Best-effort and failure-tolerant. Used by recall paths so the read touch
|
|
411
|
+
* lives in the storage layer, not at the caller.
|
|
412
|
+
*/
|
|
413
|
+
recordRead(namespace: string, storageDir?: string): void {
|
|
414
|
+
if (!this.catalog) return;
|
|
415
|
+
const ns = normalizeNamespaceIdentity(namespace || this.config.defaultNamespace);
|
|
416
|
+
void this.catalog
|
|
417
|
+
.markRead(ns, { discoveredBy: "read", storageDir })
|
|
418
|
+
.catch(() => undefined);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Record a namespace write touch in the catalog (issue #1522). Best-effort
|
|
422
|
+
* and failure-tolerant. Used by consolidation/cleanup passes that may mutate
|
|
423
|
+
* the store via delete-only paths (e.g. entity-file merges, TTL cleanup) so
|
|
424
|
+
* the namespace's lastWriteAt stays fresh even when no explicit write went
|
|
425
|
+
* through the storage chokepoint. This is NOT a substitute for the chokepoint
|
|
426
|
+
* — it's a belt-and-suspenders for paths the chokepoint doesn't cover.
|
|
427
|
+
*/
|
|
428
|
+
recordWrite(namespace: string, storageDir?: string): void {
|
|
429
|
+
if (!this.catalog) return;
|
|
430
|
+
const ns = normalizeNamespaceIdentity(namespace || this.config.defaultNamespace);
|
|
431
|
+
const touch = this.catalog
|
|
432
|
+
.markWrite(ns, { discoveredBy: "write", storageDir })
|
|
433
|
+
.catch(() => undefined);
|
|
434
|
+
this.pendingWriteTouches.add(touch);
|
|
435
|
+
void touch.then(
|
|
436
|
+
() => { this.pendingWriteTouches.delete(touch); },
|
|
437
|
+
() => { this.pendingWriteTouches.delete(touch); },
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Resolve once every in-flight post-write catalog touch has settled (#1522).
|
|
443
|
+
* Mirrors `whenResolveHooksSettled()`: the StorageManager's post-write hook
|
|
444
|
+
* fires the catalog touch fire-and-forget, so tests asserting lastWriteAt
|
|
445
|
+
* moved should await this instead of racing a timer.
|
|
446
|
+
*/
|
|
447
|
+
async whenWriteTouchesSettled(): Promise<void> {
|
|
448
|
+
while (this.pendingWriteTouches.size > 0) {
|
|
449
|
+
await Promise.allSettled([...this.pendingWriteTouches]);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
372
453
|
/**
|
|
373
454
|
* Resolve once every in-flight `onResolve` registration has settled.
|
|
374
455
|
*
|