@remnic/core 9.3.613 → 9.3.615
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-cli.js +59 -58
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +4 -2
- package/dist/access-http.js +23 -23
- package/dist/access-mcp.d.ts +9 -2
- package/dist/access-mcp.js +20 -20
- package/dist/access-schema.d.ts +26 -14
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-D2J9dh_9.d.ts → access-service-CBNEKjzN.d.ts} +71 -6
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +17 -17
- package/dist/active-recall.js +20 -3
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/behavior-learner.js +2 -3
- package/dist/behavior-learner.js.map +1 -1
- package/dist/bootstrap.d.ts +1 -1
- package/dist/briefing.js +3 -3
- package/dist/buffer.d.ts +1 -1
- package/dist/buffer.js +1 -1
- package/dist/calibration.d.ts +5 -2
- package/dist/calibration.js +7 -5
- package/dist/calibration.js.map +1 -1
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/causal-consolidation.d.ts +8 -2
- package/dist/causal-consolidation.js +13 -11
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-3BP57I6J.js → chunk-2F6NP3NT.js} +2 -1
- package/dist/{chunk-3BP57I6J.js.map → chunk-2F6NP3NT.js.map} +1 -1
- package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
- package/dist/{chunk-HSVJGWYS.js → chunk-2ROPI5OE.js} +2 -2
- package/dist/{chunk-C4SQJZAF.js → chunk-2SGJY2UY.js} +6 -3
- package/dist/chunk-2SGJY2UY.js.map +1 -0
- package/dist/{chunk-ZDTVJXIP.js → chunk-3MAONBX3.js} +13 -5
- package/dist/chunk-3MAONBX3.js.map +1 -0
- package/dist/{chunk-G3Z3QEF5.js → chunk-3PY7VHV7.js} +2 -2
- package/dist/chunk-3PY7VHV7.js.map +1 -0
- package/dist/{chunk-CF3ZF2YU.js → chunk-3QSU4NFF.js} +3 -3
- package/dist/{chunk-AJA46VX5.js → chunk-3T74IZB3.js} +11 -2
- package/dist/chunk-3T74IZB3.js.map +1 -0
- package/dist/{chunk-KVEVLBKC.js → chunk-4HFJQCJZ.js} +13 -8
- package/dist/chunk-4HFJQCJZ.js.map +1 -0
- package/dist/{chunk-KGK2QKWL.js → chunk-4R4KTDIE.js} +1 -1
- package/dist/chunk-4R4KTDIE.js.map +1 -0
- package/dist/{chunk-OI27U2HT.js → chunk-5BTCT236.js} +2 -2
- package/dist/{chunk-TH67Q46T.js → chunk-5OHHEORR.js} +64 -21
- package/dist/chunk-5OHHEORR.js.map +1 -0
- package/dist/{chunk-CO7ZO4TU.js → chunk-5VDJMYTF.js} +2 -2
- package/dist/{chunk-BFBF3XEF.js → chunk-6BDVBBBY.js} +33 -25
- package/dist/{chunk-BFBF3XEF.js.map → chunk-6BDVBBBY.js.map} +1 -1
- package/dist/{chunk-EAZGEEG2.js → chunk-6L46YAEZ.js} +45 -9
- package/dist/chunk-6L46YAEZ.js.map +1 -0
- package/dist/{chunk-YFS5OEKO.js → chunk-7MLB4NCL.js} +2 -2
- package/dist/{chunk-LZ3VEOU5.js → chunk-AL4RAJL5.js} +22 -5
- package/dist/chunk-AL4RAJL5.js.map +1 -0
- package/dist/{chunk-557IAFPD.js → chunk-APRRL26Q.js} +2 -2
- package/dist/{chunk-QDDHYAKV.js → chunk-AZDOWD2L.js} +2 -2
- package/dist/{chunk-MLT75J5S.js → chunk-B6SU7YSE.js} +3 -3
- package/dist/{chunk-FXKPZ3H6.js → chunk-BPSGLMQ4.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-NOMEVTUD.js → chunk-C6C7XVKG.js} +5 -4
- package/dist/chunk-C6C7XVKG.js.map +1 -0
- package/dist/{chunk-XKIQZXUB.js → chunk-CI7RKSRE.js} +7 -1
- package/dist/chunk-CI7RKSRE.js.map +1 -0
- package/dist/{chunk-IK34DVAC.js → chunk-CIOMS6DI.js} +2 -2
- package/dist/{chunk-2I5JGH3M.js → chunk-CYEPCZN5.js} +2 -2
- package/dist/{chunk-2I5JGH3M.js.map → chunk-CYEPCZN5.js.map} +1 -1
- package/dist/{chunk-JHMFYY7L.js → chunk-DCGT4FPP.js} +13 -5
- package/dist/chunk-DCGT4FPP.js.map +1 -0
- package/dist/{chunk-7DZRO2DC.js → chunk-DEPRLVLK.js} +2 -2
- package/dist/{chunk-CSKLPDN6.js → chunk-DEVUWMME.js} +52 -19
- package/dist/chunk-DEVUWMME.js.map +1 -0
- package/dist/{chunk-DHGSZ3UD.js → chunk-DGNQRNLL.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/{chunk-HJNQQICM.js → chunk-EXUAP5LH.js} +108 -51
- package/dist/chunk-EXUAP5LH.js.map +1 -0
- package/dist/chunk-FAV25DUZ.js +12 -0
- package/dist/chunk-FAV25DUZ.js.map +1 -0
- package/dist/{chunk-ETUPBUHB.js → chunk-GDASG7NC.js} +2 -2
- package/dist/{chunk-L227SKTB.js → chunk-GDB4J2H3.js} +17 -1
- package/dist/chunk-GDB4J2H3.js.map +1 -0
- package/dist/{chunk-IP73YCZP.js → chunk-GLPBYIXN.js} +4 -2
- package/dist/chunk-GLPBYIXN.js.map +1 -0
- package/dist/{chunk-4HP7HIE3.js → chunk-HP5FMB6L.js} +2 -2
- package/dist/{chunk-EVZFIAPG.js → chunk-IBTZEBUD.js} +23 -10
- package/dist/chunk-IBTZEBUD.js.map +1 -0
- package/dist/{chunk-DOX2CG6Y.js → chunk-IEUU7O4F.js} +2 -2
- package/dist/{chunk-EUML3N6B.js → chunk-IMA6GU4Y.js} +3 -3
- package/dist/chunk-IMA6GU4Y.js.map +1 -0
- package/dist/{chunk-JNANKJLN.js → chunk-JOASJWQR.js} +2 -2
- package/dist/chunk-JOASJWQR.js.map +1 -0
- package/dist/{chunk-WSGF57U2.js → chunk-JQDZQ4TB.js} +2 -2
- package/dist/{chunk-HINSGUA7.js → chunk-KBL3JJR6.js} +9 -13
- package/dist/chunk-KBL3JJR6.js.map +1 -0
- package/dist/{chunk-IOTENEVL.js → chunk-KGLPJROV.js} +57 -50
- package/dist/chunk-KGLPJROV.js.map +1 -0
- package/dist/{chunk-W7L6HXUC.js → chunk-LXOM6IQU.js} +2 -2
- package/dist/{chunk-G6R5UD3Q.js → chunk-MGN7VHWQ.js} +42 -1
- package/dist/{chunk-G6R5UD3Q.js.map → chunk-MGN7VHWQ.js.map} +1 -1
- package/dist/{chunk-DLJ4IR6M.js → chunk-MHQC2WU2.js} +2 -2
- package/dist/chunk-MHQC2WU2.js.map +1 -0
- package/dist/{chunk-5RPTH6AU.js → chunk-NM5NQYJE.js} +20 -19
- package/dist/chunk-NM5NQYJE.js.map +1 -0
- package/dist/{chunk-6JGNHWCI.js → chunk-OBIRVF36.js} +3 -3
- package/dist/{chunk-CHCA44C3.js → chunk-ODPLEWB6.js} +3 -3
- package/dist/chunk-ODPLEWB6.js.map +1 -0
- package/dist/{chunk-HENLZHIT.js → chunk-OIF36KGD.js} +7 -4
- package/dist/chunk-OIF36KGD.js.map +1 -0
- package/dist/{chunk-GUPISBV2.js → chunk-PP2JH3GP.js} +2 -2
- package/dist/{chunk-OXJBNGBK.js → chunk-PSUB67YB.js} +2 -2
- package/dist/{chunk-UWY7GIVS.js → chunk-PYIFUBRK.js} +45 -13
- package/dist/chunk-PYIFUBRK.js.map +1 -0
- package/dist/{chunk-KIB7SDIJ.js → chunk-Q6YIJGXJ.js} +2 -2
- package/dist/{chunk-ZT3EGNLR.js → chunk-QPD426WT.js} +2 -2
- package/dist/{chunk-RLV3PQGH.js → chunk-QVO4YOB7.js} +6 -6
- package/dist/{chunk-GMAG2HS4.js → chunk-RG3LBSGH.js} +46 -9
- package/dist/chunk-RG3LBSGH.js.map +1 -0
- package/dist/{chunk-XSWKORGM.js → chunk-S53OYO3F.js} +3 -1
- package/dist/chunk-S53OYO3F.js.map +1 -0
- package/dist/{chunk-YCN4BVDK.js → chunk-SCPFRKIT.js} +4 -2
- package/dist/chunk-SCPFRKIT.js.map +1 -0
- package/dist/{chunk-NZPF2SYV.js → chunk-T7N6KQGS.js} +138 -5
- package/dist/chunk-T7N6KQGS.js.map +1 -0
- package/dist/{chunk-VJXSUAO7.js → chunk-TNOWU6RP.js} +13 -10
- package/dist/chunk-TNOWU6RP.js.map +1 -0
- package/dist/{chunk-PCI747N2.js → chunk-TZVQQTG4.js} +48 -19
- package/dist/chunk-TZVQQTG4.js.map +1 -0
- package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
- package/dist/{chunk-IK7DCC5H.js → chunk-VMGLYN42.js} +2 -2
- package/dist/{chunk-KM2A35EO.js → chunk-WB3LYXC5.js} +11 -7
- package/dist/chunk-WB3LYXC5.js.map +1 -0
- package/dist/{chunk-PPPZY2EU.js → chunk-WD2W4234.js} +9 -3
- package/dist/chunk-WD2W4234.js.map +1 -0
- package/dist/{chunk-NSKYFGDL.js → chunk-X4QQB7O6.js} +2 -2
- package/dist/{chunk-HPWVAEET.js → chunk-X6IRLNOO.js} +3 -7
- package/dist/chunk-X6IRLNOO.js.map +1 -0
- package/dist/{chunk-46GJIW5M.js → chunk-XAZOWLW4.js} +5 -5
- package/dist/{chunk-46GJIW5M.js.map → chunk-XAZOWLW4.js.map} +1 -1
- package/dist/{chunk-XPSVGJYA.js → chunk-YRMKDTKF.js} +12 -9
- package/dist/chunk-YRMKDTKF.js.map +1 -0
- package/dist/{chunk-6ZZP4EJF.js → chunk-ZJR7VG5L.js} +3 -3
- package/dist/{chunk-6ZZP4EJF.js.map → chunk-ZJR7VG5L.js.map} +1 -1
- package/dist/{chunk-2QANQKSQ.js → chunk-ZK32E74R.js} +156 -45
- package/dist/chunk-ZK32E74R.js.map +1 -0
- package/dist/{cli-OrfKXNU4.d.ts → cli-Cw729yLf.d.ts} +6 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +61 -60
- package/dist/compounding/engine.js +3 -3
- package/dist/compounding/preference-consolidator.js +39 -11
- package/dist/compounding/preference-consolidator.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.js +1 -1
- package/dist/contradiction/index.js +4 -4
- package/dist/conversation-index/backend.js +2 -2
- package/dist/conversation-index/indexer.js +1 -1
- package/dist/cross-namespace-budget.js +1 -1
- package/dist/enrichment/index.js +1 -1
- package/dist/entity-retrieval.js +3 -3
- package/dist/evals.js +1 -1
- package/dist/explicit-capture.d.ts +11 -1
- package/dist/explicit-capture.js +1 -1
- package/dist/extraction-judge.js +8 -1
- package/dist/extraction.js +2 -2
- package/dist/fallback-llm.d.ts +23 -6
- package/dist/fallback-llm.js +5 -3
- package/dist/{first-start-migration-GYJWIH36.js → first-start-migration-FF7YFGRP.js} +6 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +95 -94
- package/dist/index.js.map +1 -1
- package/dist/lcm/archive.js +2 -2
- package/dist/lcm/engine.js +5 -5
- package/dist/lcm/index.js +7 -7
- package/dist/lcm/summarizer.js +3 -3
- package/dist/maintenance/memory-governance-cron.d.ts +6 -4
- package/dist/maintenance/memory-governance-cron.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 +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/mcp-memory-inspector-app.js +1 -1
- package/dist/migrate/from-engram.js +1 -1
- package/dist/namespaces/migrate.js +16 -15
- package/dist/namespaces/search.js +12 -11
- package/dist/namespaces/storage.js +3 -3
- package/dist/network/webdav.d.ts +2 -0
- package/dist/network/webdav.js +1 -1
- package/dist/objective-state-writers.js +2 -2
- package/dist/operator-toolkit.d.ts +3 -1
- package/dist/operator-toolkit.js +21 -20
- package/dist/{orchestrator-DTRQG75J.d.ts → orchestrator-CqWOjfgl.d.ts} +46 -3
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +48 -45
- package/dist/patterns-cli.js +1 -1
- package/dist/qmd-recall-cache.d.ts +2 -0
- package/dist/qmd-recall-cache.js +1 -1
- package/dist/qmd.d.ts +37 -2
- package/dist/qmd.js +4 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +57 -0
- package/dist/recall-planner-llm.js +167 -0
- package/dist/recall-planner-llm.js.map +1 -0
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.js +2 -2
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.js +2 -2
- package/dist/routing/store.js +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/search/factory.js +11 -10
- package/dist/search/index.js +11 -10
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +3 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +3 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/noop-backend.js +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +3 -2
- package/dist/search/port.d.ts +6 -1
- package/dist/search/port.js +7 -0
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/search/remote-backend.js +1 -1
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/session-observer-state.js +1 -1
- package/dist/storage.js +2 -2
- package/dist/summarizer.js +2 -2
- package/dist/temporal-index.js +1 -1
- package/dist/{tier-stats-SKML2OSF.js → tier-stats-3LYQ3VV5.js} +3 -3
- 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-sqlite.js +1 -1
- package/dist/transfer/types.d.ts +12 -12
- package/dist/types.d.ts +32 -0
- package/dist/types.js +1 -1
- package/dist/utility-learner.js +1 -1
- package/dist/utility-runtime.js +2 -2
- package/dist/verified-recall.js +3 -3
- package/dist/work/board.js +2 -2
- package/dist/work/storage.d.ts +2 -0
- package/dist/work/storage.js +1 -1
- package/package.json +1 -1
- package/src/access-http.ts +24 -10
- package/src/access-mcp.test.ts +160 -0
- package/src/access-mcp.ts +72 -7
- package/src/access-schema.ts +11 -0
- package/src/access-service-coding-write.test.ts +478 -0
- package/src/access-service.ts +237 -32
- package/src/active-recall.test.ts +40 -0
- package/src/active-recall.ts +19 -2
- package/src/behavior-learner.ts +5 -3
- package/src/buffer-session.test.ts +58 -0
- package/src/buffer-surprise-trigger.test.ts +4 -18
- package/src/buffer.ts +39 -11
- package/src/calibration.ts +10 -4
- package/src/causal-consolidation.test.ts +47 -2
- package/src/causal-consolidation.ts +13 -9
- package/src/cli.ts +19 -4
- package/src/compounding/engine.ts +2 -0
- package/src/compounding/preference-consolidator.test.ts +292 -0
- package/src/compounding/preference-consolidator.ts +55 -19
- package/src/config.test.ts +213 -0
- package/src/config.ts +175 -4
- package/src/connectors/codex-materialize-runner.ts +7 -4
- package/src/consolidation-provenance-check.ts +24 -5
- package/src/conversation-index/indexer.test.ts +22 -0
- package/src/conversation-index/indexer.ts +7 -3
- package/src/cross-namespace-budget.test.ts +44 -21
- package/src/cross-namespace-budget.ts +2 -2
- package/src/enrichment/pipeline.ts +11 -16
- package/src/evals.ts +1 -1
- package/src/explicit-capture.ts +19 -2
- package/src/extraction-judge-chain.test.ts +55 -0
- package/src/extraction-judge.ts +7 -9
- package/src/extraction.ts +16 -5
- package/src/fallback-llm.test.ts +600 -1
- package/src/fallback-llm.ts +91 -22
- package/src/maintenance/memory-governance-cron.ts +39 -29
- package/src/mcp-memory-inspector-app.ts +54 -12
- package/src/message-parts/index.ts +6 -0
- package/src/message-parts/message-parts.test.ts +30 -0
- package/src/migrate/from-engram.ts +19 -5
- package/src/namespaces/search.test.ts +15 -2
- package/src/namespaces/search.ts +1 -1
- package/src/network/webdav.ts +61 -21
- package/src/operator-toolkit.ts +6 -2
- package/src/orchestrator.ts +173 -20
- package/src/qmd-client.test.ts +85 -0
- package/src/qmd-recall-cache.test.ts +16 -0
- package/src/qmd-recall-cache.ts +7 -0
- package/src/qmd.test.ts +54 -0
- package/src/qmd.ts +119 -19
- package/src/recall-planner-llm.test.ts +224 -0
- package/src/recall-planner-llm.ts +289 -0
- package/src/routing/store.ts +4 -8
- package/src/search/factory.ts +3 -0
- package/src/search/lancedb-backend.ts +15 -3
- package/src/search/meilisearch-backend.ts +70 -7
- package/src/search/noop-backend.ts +5 -1
- package/src/search/orama-backend.ts +15 -3
- package/src/search/port.ts +15 -0
- package/src/search/remote-backend.ts +5 -1
- package/src/session-observer-state.ts +1 -1
- package/src/summarizer.ts +3 -3
- package/src/temporal-index.test.ts +18 -0
- package/src/temporal-index.ts +45 -0
- package/src/training-export/cli-date-validation.test.ts +36 -0
- package/src/training-export/date-parse.ts +21 -2
- package/src/transfer/export-sqlite.ts +3 -0
- package/src/types.ts +35 -0
- package/src/utility-learner.ts +1 -0
- package/src/work/storage.ts +23 -0
- package/dist/chunk-2QANQKSQ.js.map +0 -1
- package/dist/chunk-5RPTH6AU.js.map +0 -1
- package/dist/chunk-AJA46VX5.js.map +0 -1
- package/dist/chunk-C4SQJZAF.js.map +0 -1
- package/dist/chunk-CHCA44C3.js.map +0 -1
- package/dist/chunk-CSKLPDN6.js.map +0 -1
- package/dist/chunk-DLJ4IR6M.js.map +0 -1
- package/dist/chunk-EAZGEEG2.js.map +0 -1
- package/dist/chunk-EUML3N6B.js.map +0 -1
- package/dist/chunk-EVZFIAPG.js.map +0 -1
- package/dist/chunk-G3Z3QEF5.js.map +0 -1
- package/dist/chunk-GMAG2HS4.js.map +0 -1
- package/dist/chunk-HENLZHIT.js.map +0 -1
- package/dist/chunk-HINSGUA7.js.map +0 -1
- package/dist/chunk-HJNQQICM.js.map +0 -1
- package/dist/chunk-HPWVAEET.js.map +0 -1
- package/dist/chunk-IOTENEVL.js.map +0 -1
- package/dist/chunk-IP73YCZP.js.map +0 -1
- package/dist/chunk-JHMFYY7L.js.map +0 -1
- package/dist/chunk-JNANKJLN.js.map +0 -1
- package/dist/chunk-KGK2QKWL.js.map +0 -1
- package/dist/chunk-KM2A35EO.js.map +0 -1
- package/dist/chunk-KVEVLBKC.js.map +0 -1
- package/dist/chunk-L227SKTB.js.map +0 -1
- package/dist/chunk-LZ3VEOU5.js.map +0 -1
- package/dist/chunk-NOMEVTUD.js.map +0 -1
- package/dist/chunk-NZPF2SYV.js.map +0 -1
- package/dist/chunk-PCI747N2.js.map +0 -1
- package/dist/chunk-PPPZY2EU.js.map +0 -1
- package/dist/chunk-TH67Q46T.js.map +0 -1
- package/dist/chunk-UWY7GIVS.js.map +0 -1
- package/dist/chunk-VJXSUAO7.js.map +0 -1
- package/dist/chunk-XKIQZXUB.js.map +0 -1
- package/dist/chunk-XPSVGJYA.js.map +0 -1
- package/dist/chunk-XSWKORGM.js.map +0 -1
- package/dist/chunk-YCN4BVDK.js.map +0 -1
- package/dist/chunk-ZDTVJXIP.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
- /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
- /package/dist/{chunk-HSVJGWYS.js.map → chunk-2ROPI5OE.js.map} +0 -0
- /package/dist/{chunk-CF3ZF2YU.js.map → chunk-3QSU4NFF.js.map} +0 -0
- /package/dist/{chunk-OI27U2HT.js.map → chunk-5BTCT236.js.map} +0 -0
- /package/dist/{chunk-CO7ZO4TU.js.map → chunk-5VDJMYTF.js.map} +0 -0
- /package/dist/{chunk-YFS5OEKO.js.map → chunk-7MLB4NCL.js.map} +0 -0
- /package/dist/{chunk-557IAFPD.js.map → chunk-APRRL26Q.js.map} +0 -0
- /package/dist/{chunk-QDDHYAKV.js.map → chunk-AZDOWD2L.js.map} +0 -0
- /package/dist/{chunk-MLT75J5S.js.map → chunk-B6SU7YSE.js.map} +0 -0
- /package/dist/{chunk-FXKPZ3H6.js.map → chunk-BPSGLMQ4.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-IK34DVAC.js.map → chunk-CIOMS6DI.js.map} +0 -0
- /package/dist/{chunk-7DZRO2DC.js.map → chunk-DEPRLVLK.js.map} +0 -0
- /package/dist/{chunk-DHGSZ3UD.js.map → chunk-DGNQRNLL.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-ETUPBUHB.js.map → chunk-GDASG7NC.js.map} +0 -0
- /package/dist/{chunk-4HP7HIE3.js.map → chunk-HP5FMB6L.js.map} +0 -0
- /package/dist/{chunk-DOX2CG6Y.js.map → chunk-IEUU7O4F.js.map} +0 -0
- /package/dist/{chunk-WSGF57U2.js.map → chunk-JQDZQ4TB.js.map} +0 -0
- /package/dist/{chunk-W7L6HXUC.js.map → chunk-LXOM6IQU.js.map} +0 -0
- /package/dist/{chunk-6JGNHWCI.js.map → chunk-OBIRVF36.js.map} +0 -0
- /package/dist/{chunk-GUPISBV2.js.map → chunk-PP2JH3GP.js.map} +0 -0
- /package/dist/{chunk-OXJBNGBK.js.map → chunk-PSUB67YB.js.map} +0 -0
- /package/dist/{chunk-KIB7SDIJ.js.map → chunk-Q6YIJGXJ.js.map} +0 -0
- /package/dist/{chunk-ZT3EGNLR.js.map → chunk-QPD426WT.js.map} +0 -0
- /package/dist/{chunk-RLV3PQGH.js.map → chunk-QVO4YOB7.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-IK7DCC5H.js.map → chunk-VMGLYN42.js.map} +0 -0
- /package/dist/{chunk-NSKYFGDL.js.map → chunk-X4QQB7O6.js.map} +0 -0
- /package/dist/{first-start-migration-GYJWIH36.js.map → first-start-migration-FF7YFGRP.js.map} +0 -0
- /package/dist/{tier-stats-SKML2OSF.js.map → tier-stats-3LYQ3VV5.js.map} +0 -0
package/src/access-mcp.ts
CHANGED
|
@@ -81,11 +81,15 @@ function toLegacyToolName(name: string): string {
|
|
|
81
81
|
: name;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function withToolAliases(tool: McpTool): McpTool[] {
|
|
84
|
+
function withToolAliases(tool: McpTool, emitLegacyTools = true): McpTool[] {
|
|
85
85
|
const canonicalName = toCanonicalToolName(tool.name);
|
|
86
86
|
const canonicalTool = canonicalName === tool.name ? tool : { ...tool, name: canonicalName };
|
|
87
87
|
if (canonicalName === tool.name) return [canonicalTool];
|
|
88
|
-
|
|
88
|
+
// Issue #1427: when legacy aliases are opted out, advertise only the
|
|
89
|
+
// canonical `remnic.*` name and drop the `engram.*` duplicate. Tool *calls*
|
|
90
|
+
// still accept both names (the dispatch canonicalizes), so suppressing the
|
|
91
|
+
// alias only trims `tools/list`, not callability.
|
|
92
|
+
return emitLegacyTools ? [canonicalTool, tool] : [canonicalTool];
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
function resolveChatGptInspectorRecallSessionKey(
|
|
@@ -111,6 +115,8 @@ const STRICT_MCP_SCHEMA_KEYS: Partial<Record<SchemaName, readonly string[]>> = {
|
|
|
111
115
|
"entityRef",
|
|
112
116
|
"ttl",
|
|
113
117
|
"sourceReason",
|
|
118
|
+
"cwd",
|
|
119
|
+
"projectTag",
|
|
114
120
|
],
|
|
115
121
|
suggestionSubmit: [
|
|
116
122
|
"schemaVersion",
|
|
@@ -125,6 +131,8 @@ const STRICT_MCP_SCHEMA_KEYS: Partial<Record<SchemaName, readonly string[]>> = {
|
|
|
125
131
|
"entityRef",
|
|
126
132
|
"ttl",
|
|
127
133
|
"sourceReason",
|
|
134
|
+
"cwd",
|
|
135
|
+
"projectTag",
|
|
128
136
|
],
|
|
129
137
|
capsuleExport: [
|
|
130
138
|
"name",
|
|
@@ -134,9 +142,40 @@ const STRICT_MCP_SCHEMA_KEYS: Partial<Record<SchemaName, readonly string[]>> = {
|
|
|
134
142
|
"peerIds",
|
|
135
143
|
"includeTranscripts",
|
|
136
144
|
"encrypt",
|
|
145
|
+
"cwd",
|
|
146
|
+
"projectTag",
|
|
137
147
|
],
|
|
138
|
-
capsuleImport: ["archivePath", "namespace", "mode", "passphrase"],
|
|
139
|
-
capsuleList: ["namespace"],
|
|
148
|
+
capsuleImport: ["archivePath", "namespace", "mode", "passphrase", "cwd", "projectTag"],
|
|
149
|
+
capsuleList: ["namespace", "cwd", "projectTag"],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Shared JSON-schema fragments for the client-injected git/project context
|
|
153
|
+
// fields (#1434). Declared once to avoid drift across tool definitions.
|
|
154
|
+
// `_SCOPED` is for write tools that resolve a project namespace from these
|
|
155
|
+
// fields; `_IGNORED` is for tools that merely tolerate them for MCP client
|
|
156
|
+
// compatibility (clients like Pi MCPorter auto-inject `cwd` on every call).
|
|
157
|
+
const MCP_GIT_CONTEXT_SCHEMA_PROPS_SCOPED: Record<string, unknown> = {
|
|
158
|
+
cwd: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description:
|
|
161
|
+
"Optional working directory. When no explicit namespace is given, resolves the project namespace this write is stored in (mirrors recall/observe).",
|
|
162
|
+
},
|
|
163
|
+
projectTag: {
|
|
164
|
+
type: "string",
|
|
165
|
+
description:
|
|
166
|
+
"Optional project tag for non-git project scoping. When no explicit namespace is given, routes this write to the tagged project namespace.",
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
const MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED: Record<string, unknown> = {
|
|
170
|
+
cwd: {
|
|
171
|
+
type: "string",
|
|
172
|
+
description:
|
|
173
|
+
"Accepted for MCP client compatibility (git-context auto-injection); ignored by this tool.",
|
|
174
|
+
},
|
|
175
|
+
projectTag: {
|
|
176
|
+
type: "string",
|
|
177
|
+
description: "Accepted for MCP client compatibility; ignored by this tool.",
|
|
178
|
+
},
|
|
140
179
|
};
|
|
141
180
|
|
|
142
181
|
function parseMcpRequest<N extends SchemaName>(
|
|
@@ -212,13 +251,25 @@ export class EngramMcpServer {
|
|
|
212
251
|
private readonly citationsEnabled: boolean;
|
|
213
252
|
/** Whether to auto-enable citations for Codex adapter connections. */
|
|
214
253
|
private readonly citationsAutoDetect: boolean;
|
|
254
|
+
/**
|
|
255
|
+
* Whether to advertise legacy `engram.*` tool aliases alongside the canonical
|
|
256
|
+
* `remnic.*` names (issue #1427). Defaults to true for backward compatibility;
|
|
257
|
+
* set false to halve the advertised `tools/list` surface.
|
|
258
|
+
*/
|
|
259
|
+
private readonly emitLegacyTools: boolean;
|
|
215
260
|
|
|
216
261
|
constructor(
|
|
217
262
|
private readonly service: EngramAccessService,
|
|
218
|
-
options: {
|
|
263
|
+
options: {
|
|
264
|
+
principal?: string;
|
|
265
|
+
citationsEnabled?: boolean;
|
|
266
|
+
citationsAutoDetect?: boolean;
|
|
267
|
+
emitLegacyTools?: boolean;
|
|
268
|
+
} = {},
|
|
219
269
|
) {
|
|
220
270
|
this.citationsEnabled = options.citationsEnabled === true;
|
|
221
271
|
this.citationsAutoDetect = options.citationsAutoDetect !== false;
|
|
272
|
+
this.emitLegacyTools = options.emitLegacyTools !== false;
|
|
222
273
|
this.authenticatedPrincipal =
|
|
223
274
|
options.principal?.trim() ||
|
|
224
275
|
readEnvVar("OPENCLAW_ENGRAM_ACCESS_PRINCIPAL")?.trim() ||
|
|
@@ -598,6 +649,7 @@ export class EngramMcpServer {
|
|
|
598
649
|
},
|
|
599
650
|
includeTranscripts: { type: "boolean" },
|
|
600
651
|
encrypt: { type: "boolean" },
|
|
652
|
+
...MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED,
|
|
601
653
|
},
|
|
602
654
|
required: ["name"],
|
|
603
655
|
additionalProperties: false,
|
|
@@ -623,6 +675,7 @@ export class EngramMcpServer {
|
|
|
623
675
|
type: "string",
|
|
624
676
|
description: "Passphrase for encrypted capsule archives.",
|
|
625
677
|
},
|
|
678
|
+
...MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED,
|
|
626
679
|
},
|
|
627
680
|
required: ["archivePath"],
|
|
628
681
|
additionalProperties: false,
|
|
@@ -635,6 +688,7 @@ export class EngramMcpServer {
|
|
|
635
688
|
type: "object",
|
|
636
689
|
properties: {
|
|
637
690
|
namespace: { type: "string" },
|
|
691
|
+
...MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED,
|
|
638
692
|
},
|
|
639
693
|
additionalProperties: false,
|
|
640
694
|
},
|
|
@@ -739,6 +793,7 @@ export class EngramMcpServer {
|
|
|
739
793
|
entityRef: { type: "string" },
|
|
740
794
|
ttl: { type: "string" },
|
|
741
795
|
sourceReason: { type: "string" },
|
|
796
|
+
...MCP_GIT_CONTEXT_SCHEMA_PROPS_SCOPED,
|
|
742
797
|
},
|
|
743
798
|
required: ["content"],
|
|
744
799
|
additionalProperties: false,
|
|
@@ -762,6 +817,7 @@ export class EngramMcpServer {
|
|
|
762
817
|
entityRef: { type: "string" },
|
|
763
818
|
ttl: { type: "string" },
|
|
764
819
|
sourceReason: { type: "string" },
|
|
820
|
+
...MCP_GIT_CONTEXT_SCHEMA_PROPS_SCOPED,
|
|
765
821
|
},
|
|
766
822
|
required: ["content"],
|
|
767
823
|
additionalProperties: false,
|
|
@@ -1703,7 +1759,7 @@ export class EngramMcpServer {
|
|
|
1703
1759
|
additionalProperties: false,
|
|
1704
1760
|
},
|
|
1705
1761
|
},
|
|
1706
|
-
].flatMap((tool) => withToolAliases(tool));
|
|
1762
|
+
].flatMap((tool) => withToolAliases(tool, this.emitLegacyTools));
|
|
1707
1763
|
}
|
|
1708
1764
|
|
|
1709
1765
|
/** Get clientInfo for a specific MCP session. Returns undefined for non-MCP requests. */
|
|
@@ -1985,7 +2041,12 @@ export class EngramMcpServer {
|
|
|
1985
2041
|
}
|
|
1986
2042
|
|
|
1987
2043
|
private toolAcceptsArgument(name: string, key: string): boolean {
|
|
1988
|
-
|
|
2044
|
+
// Match by canonical name so argument validation resolves whether the
|
|
2045
|
+
// caller used the `engram.*` or `remnic.*` name and regardless of whether
|
|
2046
|
+
// legacy aliases are advertised (issue #1427) — a tool stays callable under
|
|
2047
|
+
// both names even when only the canonical alias appears in `tools/list`.
|
|
2048
|
+
const target = toCanonicalToolName(name);
|
|
2049
|
+
const tool = this.tools.find((entry) => toCanonicalToolName(entry.name) === target);
|
|
1989
2050
|
const inputSchema = getObjectProperties(tool?.inputSchema);
|
|
1990
2051
|
const properties = getObjectProperties(inputSchema?.properties);
|
|
1991
2052
|
if (properties && Object.prototype.hasOwnProperty.call(properties, key)) {
|
|
@@ -2481,6 +2542,8 @@ export class EngramMcpServer {
|
|
|
2481
2542
|
entityRef: body.entityRef,
|
|
2482
2543
|
ttl: body.ttl,
|
|
2483
2544
|
sourceReason: body.sourceReason,
|
|
2545
|
+
cwd: body.cwd,
|
|
2546
|
+
projectTag: body.projectTag,
|
|
2484
2547
|
});
|
|
2485
2548
|
}
|
|
2486
2549
|
case "engram.suggestion_submit": {
|
|
@@ -2499,6 +2562,8 @@ export class EngramMcpServer {
|
|
|
2499
2562
|
entityRef: body.entityRef,
|
|
2500
2563
|
ttl: body.ttl,
|
|
2501
2564
|
sourceReason: body.sourceReason,
|
|
2565
|
+
cwd: body.cwd,
|
|
2566
|
+
projectTag: body.projectTag,
|
|
2502
2567
|
});
|
|
2503
2568
|
}
|
|
2504
2569
|
case "engram.entity_get":
|
package/src/access-schema.ts
CHANGED
|
@@ -243,6 +243,17 @@ export const memoryStoreRequestSchema = z.object({
|
|
|
243
243
|
entityRef: entityRefSchema,
|
|
244
244
|
ttl: ttlSchema,
|
|
245
245
|
sourceReason: sourceReasonSchema,
|
|
246
|
+
// Git/project context for project-scoped writes (#1434). When no explicit
|
|
247
|
+
// `namespace` is given, these route the write to the same project namespace
|
|
248
|
+
// recall/observe resolve from `cwd`/`projectTag` (issue #569, rule 42). Also
|
|
249
|
+
// lets MCP clients that auto-inject `cwd` (e.g. Pi MCPorter) call write tools.
|
|
250
|
+
cwd: z.string().trim().min(1, "cwd must be non-empty when provided").max(2048).optional(),
|
|
251
|
+
projectTag: z
|
|
252
|
+
.string()
|
|
253
|
+
.trim()
|
|
254
|
+
.min(1, "projectTag must be non-empty when provided")
|
|
255
|
+
.max(256)
|
|
256
|
+
.optional(),
|
|
246
257
|
});
|
|
247
258
|
|
|
248
259
|
export const suggestionSubmitRequestSchema = memoryStoreRequestSchema;
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1434: explicit-write tools (memory_store / suggestion_submit) must resolve
|
|
3
|
+
* their write namespace through the SAME project-scope overlay the read path
|
|
4
|
+
* uses, so a memory stored with a client-injected `cwd`/`projectTag` is
|
|
5
|
+
* discoverable by project-scoped recall (rule 42 symmetry). Previously these
|
|
6
|
+
* tools ignored coding context and always wrote to the base namespace.
|
|
7
|
+
*
|
|
8
|
+
* Invariants verified here (review hardening on PR #1444):
|
|
9
|
+
* - Symmetry: a `projectTag`/`cwd` (or an existing session context) overlays
|
|
10
|
+
* the project namespace onto the principal self base — the SAME namespace
|
|
11
|
+
* recall/observe/buffer use — so scoped stores are found by scoped recall.
|
|
12
|
+
* - Base: the principal self namespace (defaultNamespaceForPrincipal), which
|
|
13
|
+
* collapses to `config.defaultNamespace` when namespaces are disabled or the
|
|
14
|
+
* principal has no self policy (the common deployment is unchanged).
|
|
15
|
+
* - Read-only: the resolver NEVER mutates session coding context, so
|
|
16
|
+
* idempotency peeks / dryRun preflights are side-effect free (Codex review).
|
|
17
|
+
* - Persist: a pre-resolved project namespace reaches storage instead of being
|
|
18
|
+
* rejected by the static policy allow-list (Codex P1 / Cursor High).
|
|
19
|
+
* - Precedence: explicit `namespace` wins; namespaces-disabled is a no-op.
|
|
20
|
+
*/
|
|
21
|
+
import assert from "node:assert/strict";
|
|
22
|
+
import test from "node:test";
|
|
23
|
+
|
|
24
|
+
import { EngramAccessService } from "./access-service.js";
|
|
25
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
26
|
+
import { persistExplicitCapture } from "./explicit-capture.js";
|
|
27
|
+
import type { ValidExplicitCapture } from "./explicit-capture.js";
|
|
28
|
+
import {
|
|
29
|
+
combineNamespaces,
|
|
30
|
+
projectNamespaceName,
|
|
31
|
+
projectTagProjectId,
|
|
32
|
+
} from "./coding/coding-namespace.js";
|
|
33
|
+
import type { CodingContext, PluginConfig } from "./types.js";
|
|
34
|
+
|
|
35
|
+
function makeOrchestratorStub(overrides: Partial<PluginConfig> = {}): Orchestrator {
|
|
36
|
+
const orch = Object.create(Orchestrator.prototype) as Orchestrator;
|
|
37
|
+
const internals = orch as unknown as {
|
|
38
|
+
config: PluginConfig;
|
|
39
|
+
_codingContextBySession: Map<string, CodingContext>;
|
|
40
|
+
};
|
|
41
|
+
internals.config = {
|
|
42
|
+
namespacesEnabled: true,
|
|
43
|
+
defaultNamespace: "default",
|
|
44
|
+
sharedNamespace: "shared",
|
|
45
|
+
namespacePolicies: [],
|
|
46
|
+
codingMode: { projectScope: true },
|
|
47
|
+
memoryDir: "/synthetic/remnic-coding-write",
|
|
48
|
+
recallCrossNamespaceBudgetEnabled: false,
|
|
49
|
+
recallCrossNamespaceBudgetWindowMs: 60_000,
|
|
50
|
+
recallCrossNamespaceBudgetSoftLimit: 10,
|
|
51
|
+
recallCrossNamespaceBudgetHardLimit: 30,
|
|
52
|
+
...overrides,
|
|
53
|
+
} as unknown as PluginConfig;
|
|
54
|
+
internals._codingContextBySession = new Map();
|
|
55
|
+
return orch;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolver(service: EngramAccessService) {
|
|
59
|
+
return (req: unknown) =>
|
|
60
|
+
(
|
|
61
|
+
service as unknown as {
|
|
62
|
+
resolveCodingScopedWriteNamespace: (r: unknown) => Promise<string>;
|
|
63
|
+
}
|
|
64
|
+
).resolveCodingScopedWriteNamespace(req);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function projectNamespaceFor(tag: string): string {
|
|
68
|
+
// projectScope (no branch scope) overlay namespace == projectNamespaceName.
|
|
69
|
+
return combineNamespaces("default", projectNamespaceName(projectTagProjectId(tag)));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
test("#1434 projectTag scopes the write to the project namespace, read-only", async () => {
|
|
73
|
+
const orch = makeOrchestratorStub();
|
|
74
|
+
const service = new EngramAccessService(orch);
|
|
75
|
+
|
|
76
|
+
const resolved = await resolver(service)({
|
|
77
|
+
sessionKey: "sess-1",
|
|
78
|
+
authenticatedPrincipal: "alice",
|
|
79
|
+
projectTag: "Blend/Supply",
|
|
80
|
+
content: "x",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
assert.equal(resolved, projectNamespaceFor("Blend/Supply"));
|
|
84
|
+
assert.notEqual(resolved, "default", "project context must change the namespace");
|
|
85
|
+
// Read-only: resolving must NOT persist coding context on the session.
|
|
86
|
+
assert.equal(
|
|
87
|
+
orch.getCodingContextForSession("sess-1"),
|
|
88
|
+
null,
|
|
89
|
+
"resolver must not mutate session coding context (peek/dryRun safety)",
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("#1434 a sessionless write with projectTag stays on the base namespace (recall symmetry)", async () => {
|
|
94
|
+
// Without a sessionKey the recall path can't attach or look up coding context
|
|
95
|
+
// (maybeAttachCodingContext / applyCodingNamespaceOverlay both no-op), so a
|
|
96
|
+
// sessionless recall searches the base namespace. A sessionless write must
|
|
97
|
+
// therefore also stay on the base — else the store would be hidden from the
|
|
98
|
+
// same client's recall (Codex review).
|
|
99
|
+
const orch = makeOrchestratorStub();
|
|
100
|
+
const service = new EngramAccessService(orch);
|
|
101
|
+
const resolved = await resolver(service)({
|
|
102
|
+
authenticatedPrincipal: "alice",
|
|
103
|
+
projectTag: "Blend/Supply",
|
|
104
|
+
content: "x",
|
|
105
|
+
});
|
|
106
|
+
assert.equal(resolved, "default");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("#1434 an existing session coding context scopes the write (recall-then-store flow)", async () => {
|
|
110
|
+
const orch = makeOrchestratorStub();
|
|
111
|
+
orch.setCodingContextForSession("sess-ctx", {
|
|
112
|
+
projectId: projectTagProjectId("Blend/Supply"),
|
|
113
|
+
branch: null,
|
|
114
|
+
rootPath: projectTagProjectId("Blend/Supply"),
|
|
115
|
+
defaultBranch: null,
|
|
116
|
+
});
|
|
117
|
+
const service = new EngramAccessService(orch);
|
|
118
|
+
|
|
119
|
+
const resolved = await resolver(service)({
|
|
120
|
+
sessionKey: "sess-ctx",
|
|
121
|
+
authenticatedPrincipal: "alice",
|
|
122
|
+
content: "x",
|
|
123
|
+
});
|
|
124
|
+
assert.equal(resolved, projectNamespaceFor("Blend/Supply"));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("#1434 an existing session binding wins over per-call projectTag (recall symmetry)", async () => {
|
|
128
|
+
// Session is bound to project A; this write also passes per-call projectTag B.
|
|
129
|
+
// The write MUST resolve to A — the same project the session's recall searches
|
|
130
|
+
// (recall is session-first: maybeAttachCodingContext returns early when a
|
|
131
|
+
// context is already attached). A per-call-wins write would land in B, which
|
|
132
|
+
// that session's recall never searches, so the memory would be undiscoverable.
|
|
133
|
+
const orch = makeOrchestratorStub();
|
|
134
|
+
orch.setCodingContextForSession("sess-reuse", {
|
|
135
|
+
projectId: projectTagProjectId("Project/A"),
|
|
136
|
+
branch: null,
|
|
137
|
+
rootPath: projectTagProjectId("Project/A"),
|
|
138
|
+
defaultBranch: null,
|
|
139
|
+
});
|
|
140
|
+
const service = new EngramAccessService(orch);
|
|
141
|
+
const resolved = await resolver(service)({
|
|
142
|
+
sessionKey: "sess-reuse",
|
|
143
|
+
authenticatedPrincipal: "alice",
|
|
144
|
+
projectTag: "Project/B",
|
|
145
|
+
content: "x",
|
|
146
|
+
});
|
|
147
|
+
assert.equal(resolved, projectNamespaceFor("Project/A"));
|
|
148
|
+
assert.notEqual(resolved, projectNamespaceFor("Project/B"));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("#1434 explicit namespace wins and bypasses coding overlay", async () => {
|
|
152
|
+
const orch = makeOrchestratorStub();
|
|
153
|
+
const service = new EngramAccessService(orch);
|
|
154
|
+
const resolved = await resolver(service)({
|
|
155
|
+
sessionKey: "sess-2",
|
|
156
|
+
authenticatedPrincipal: "alice",
|
|
157
|
+
namespace: "default",
|
|
158
|
+
projectTag: "Blend/Supply",
|
|
159
|
+
content: "x",
|
|
160
|
+
});
|
|
161
|
+
assert.equal(resolved, "default");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("#1434 unqualified write (self policy) stays on config.defaultNamespace", async () => {
|
|
165
|
+
// Even when principal "alice" has a self policy, an UNQUALIFIED write (no
|
|
166
|
+
// coding overlay) stays on config.defaultNamespace — exactly the pre-#1434
|
|
167
|
+
// behavior. #1434 only re-scopes project-identified writes; it must not
|
|
168
|
+
// silently move plain unqualified writes to a principal self namespace (Codex
|
|
169
|
+
// review). The symmetry fix applies to the coding-overlay path only.
|
|
170
|
+
const orch = makeOrchestratorStub({
|
|
171
|
+
namespacePolicies: [
|
|
172
|
+
{ name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
173
|
+
],
|
|
174
|
+
} as Partial<PluginConfig>);
|
|
175
|
+
const service = new EngramAccessService(orch);
|
|
176
|
+
const resolved = await resolver(service)({
|
|
177
|
+
sessionKey: "sess-3",
|
|
178
|
+
authenticatedPrincipal: "alice",
|
|
179
|
+
content: "x",
|
|
180
|
+
});
|
|
181
|
+
assert.equal(resolved, "default");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("#1434 unqualified write with no principal policy stays on the default namespace", async () => {
|
|
185
|
+
// No policy named after the principal => base is defaultNamespace, so behavior
|
|
186
|
+
// is unchanged for the common deployment.
|
|
187
|
+
const orch = makeOrchestratorStub();
|
|
188
|
+
const service = new EngramAccessService(orch);
|
|
189
|
+
const resolved = await resolver(service)({
|
|
190
|
+
sessionKey: "sess-3b",
|
|
191
|
+
authenticatedPrincipal: "alice",
|
|
192
|
+
content: "x",
|
|
193
|
+
});
|
|
194
|
+
assert.equal(resolved, "default");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("#1434 project write overlays onto the principal self base (recall symmetry)", async () => {
|
|
198
|
+
// With a self policy, a project-scoped write overlays onto the principal self
|
|
199
|
+
// base (defaultNamespaceForPrincipal) — the SAME base recall/observe/buffer
|
|
200
|
+
// overlay onto — so the store is discoverable by that principal's
|
|
201
|
+
// project-scoped recall (Cursor review / rule 42).
|
|
202
|
+
const orch = makeOrchestratorStub({
|
|
203
|
+
namespacePolicies: [
|
|
204
|
+
{ name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
205
|
+
],
|
|
206
|
+
} as Partial<PluginConfig>);
|
|
207
|
+
const service = new EngramAccessService(orch);
|
|
208
|
+
const resolved = await resolver(service)({
|
|
209
|
+
sessionKey: "sess-3c",
|
|
210
|
+
authenticatedPrincipal: "alice",
|
|
211
|
+
projectTag: "Blend/Supply",
|
|
212
|
+
content: "x",
|
|
213
|
+
});
|
|
214
|
+
assert.equal(
|
|
215
|
+
resolved,
|
|
216
|
+
combineNamespaces("alice", projectNamespaceName(projectTagProjectId("Blend/Supply"))),
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("#1434 an explicit coding-overlay namespace string is NOT a writable target", async () => {
|
|
221
|
+
// Project scoping is requested via cwd/projectTag, never by naming the derived
|
|
222
|
+
// overlay namespace. A caller naming an overlay-shaped namespace directly is
|
|
223
|
+
// authorized strictly through canWriteNamespace and rejected, so the persist
|
|
224
|
+
// allow-list can never be bypassed by guessing an overlay name.
|
|
225
|
+
const orch = makeOrchestratorStub();
|
|
226
|
+
const service = new EngramAccessService(orch);
|
|
227
|
+
await assert.rejects(
|
|
228
|
+
resolver(service)({
|
|
229
|
+
sessionKey: "sess-explicit-overlay",
|
|
230
|
+
authenticatedPrincipal: "alice",
|
|
231
|
+
namespace: projectNamespaceFor("Blend/Supply"), // "default-project-…"
|
|
232
|
+
content: "x",
|
|
233
|
+
}),
|
|
234
|
+
/not writable/,
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("#1434 a prefix-colliding principal namespace cannot be written cross-tenant (Codex P1)", async () => {
|
|
239
|
+
// Policies for both `alice` and `alice-project-team`. An authenticated `alice`
|
|
240
|
+
// must NOT be able to write `alice-project-team-project-foo` (the OTHER
|
|
241
|
+
// principal's project-scoped namespace) by exploiting a shared `alice-project-`
|
|
242
|
+
// prefix. Strict canWriteNamespace authorization rejects it.
|
|
243
|
+
const orch = makeOrchestratorStub({
|
|
244
|
+
namespacePolicies: [
|
|
245
|
+
{ name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
246
|
+
{
|
|
247
|
+
name: "alice-project-team",
|
|
248
|
+
readPrincipals: ["teamuser"],
|
|
249
|
+
writePrincipals: ["teamuser"],
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
} as Partial<PluginConfig>);
|
|
253
|
+
const service = new EngramAccessService(orch);
|
|
254
|
+
await assert.rejects(
|
|
255
|
+
resolver(service)({
|
|
256
|
+
sessionKey: "sess-collide",
|
|
257
|
+
authenticatedPrincipal: "alice",
|
|
258
|
+
namespace: "alice-project-team-project-foo",
|
|
259
|
+
content: "x",
|
|
260
|
+
}),
|
|
261
|
+
/not writable/,
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("#1434 a derived overlay base the principal cannot write is rejected (Codex P1)", async () => {
|
|
266
|
+
// The principal has a self policy but NO write access to the configured
|
|
267
|
+
// default namespace. An explicit `default-project-foo` must be rejected —
|
|
268
|
+
// overlay namespaces are never accepted as caller strings, and the base must
|
|
269
|
+
// pass canWriteNamespace.
|
|
270
|
+
const orch = makeOrchestratorStub({
|
|
271
|
+
defaultNamespace: "default",
|
|
272
|
+
namespacePolicies: [
|
|
273
|
+
{ name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
274
|
+
{ name: "default", readPrincipals: ["admin"], writePrincipals: ["admin"] },
|
|
275
|
+
],
|
|
276
|
+
} as Partial<PluginConfig>);
|
|
277
|
+
const service = new EngramAccessService(orch);
|
|
278
|
+
await assert.rejects(
|
|
279
|
+
resolver(service)({
|
|
280
|
+
sessionKey: "sess-base-noauth",
|
|
281
|
+
authenticatedPrincipal: "alice",
|
|
282
|
+
namespace: "default-project-foo",
|
|
283
|
+
content: "x",
|
|
284
|
+
}),
|
|
285
|
+
/not writable/,
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("#1434 a forged cross-principal namespace cannot widen access", async () => {
|
|
290
|
+
// A caller naming a namespace that is not writable for this principal is
|
|
291
|
+
// rejected by canWriteNamespace — it can't escalate to another principal's
|
|
292
|
+
// namespace.
|
|
293
|
+
const orch = makeOrchestratorStub();
|
|
294
|
+
const service = new EngramAccessService(orch);
|
|
295
|
+
await assert.rejects(
|
|
296
|
+
resolver(service)({
|
|
297
|
+
sessionKey: "sess-forge",
|
|
298
|
+
authenticatedPrincipal: "alice",
|
|
299
|
+
namespace: "victim-secret",
|
|
300
|
+
content: "x",
|
|
301
|
+
}),
|
|
302
|
+
/not writable/,
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("#1434 namespaces disabled: cwd/projectTag are a no-op (common single-tenant MCP case)", async () => {
|
|
307
|
+
const orch = makeOrchestratorStub({ namespacesEnabled: false } as Partial<PluginConfig>);
|
|
308
|
+
const service = new EngramAccessService(orch);
|
|
309
|
+
const resolved = await resolver(service)({
|
|
310
|
+
sessionKey: "sess-4",
|
|
311
|
+
projectTag: "Blend/Supply",
|
|
312
|
+
content: "x",
|
|
313
|
+
});
|
|
314
|
+
assert.equal(resolved, "default");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
function makeAttachOrchestrator() {
|
|
318
|
+
const contexts = new Map<string, CodingContext>();
|
|
319
|
+
const getStorageCalls: Array<string | undefined> = [];
|
|
320
|
+
const orch = {
|
|
321
|
+
config: {
|
|
322
|
+
namespacesEnabled: true,
|
|
323
|
+
defaultNamespace: "default",
|
|
324
|
+
sharedNamespace: "shared",
|
|
325
|
+
namespacePolicies: [],
|
|
326
|
+
codingMode: { projectScope: true },
|
|
327
|
+
memoryDir: "/synthetic/remnic-coding-write-attach",
|
|
328
|
+
recallCrossNamespaceBudgetEnabled: false,
|
|
329
|
+
recallCrossNamespaceBudgetWindowMs: 60_000,
|
|
330
|
+
recallCrossNamespaceBudgetSoftLimit: 10,
|
|
331
|
+
recallCrossNamespaceBudgetHardLimit: 30,
|
|
332
|
+
},
|
|
333
|
+
getCodingContextForSession: (sk: string) => contexts.get(sk) ?? null,
|
|
334
|
+
setCodingContextForSession: (sk: string, ctx: CodingContext) => {
|
|
335
|
+
contexts.set(sk, ctx);
|
|
336
|
+
},
|
|
337
|
+
getStorage: async (ns?: string) => {
|
|
338
|
+
getStorageCalls.push(ns);
|
|
339
|
+
return {
|
|
340
|
+
readAllMemories: async () => [],
|
|
341
|
+
writeMemory: async () => "mem-1",
|
|
342
|
+
appendMemoryLifecycleEvents: async () => {},
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
} as unknown as Orchestrator;
|
|
346
|
+
return { orch, contexts, getStorageCalls };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function storeRequest(
|
|
350
|
+
overrides: Record<string, unknown>,
|
|
351
|
+
): Parameters<EngramAccessService["memoryStore"]>[0] {
|
|
352
|
+
return {
|
|
353
|
+
authenticatedPrincipal: "alice",
|
|
354
|
+
content: "durable project memory",
|
|
355
|
+
category: "fact",
|
|
356
|
+
confidence: 0.9,
|
|
357
|
+
tags: [],
|
|
358
|
+
...overrides,
|
|
359
|
+
} as unknown as Parameters<EngramAccessService["memoryStore"]>[0];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
test("#1434 a real memory_store attaches coding context so a later bare recall on the session is scoped (Cursor review)", async () => {
|
|
363
|
+
// A store with sessionKey + per-call projectTag must seed the session's
|
|
364
|
+
// coding binding (like recall's maybeAttachCodingContext), so a SUBSEQUENT
|
|
365
|
+
// bare recall on the same session — one that omits cwd/projectTag — is scoped
|
|
366
|
+
// to the same project and finds the memory.
|
|
367
|
+
const { orch, contexts, getStorageCalls } = makeAttachOrchestrator();
|
|
368
|
+
const service = new EngramAccessService(orch);
|
|
369
|
+
|
|
370
|
+
const res = await service.memoryStore(
|
|
371
|
+
storeRequest({ sessionKey: "sess-attach", projectTag: "Blend/Supply" }),
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
assert.equal(res.status, "stored");
|
|
375
|
+
assert.equal(res.namespace, projectNamespaceFor("Blend/Supply"));
|
|
376
|
+
// The store attached the coding context the recall path reads.
|
|
377
|
+
assert.equal(
|
|
378
|
+
contexts.get("sess-attach")?.projectId,
|
|
379
|
+
projectTagProjectId("Blend/Supply"),
|
|
380
|
+
);
|
|
381
|
+
assert.ok(
|
|
382
|
+
getStorageCalls.every((ns) => ns === projectNamespaceFor("Blend/Supply")),
|
|
383
|
+
`expected all getStorage calls on the project namespace, got ${JSON.stringify(getStorageCalls)}`,
|
|
384
|
+
);
|
|
385
|
+
// A later BARE resolve (no per-call context) on the same session — what a
|
|
386
|
+
// subsequent recall on this session uses — is now scoped to the same project.
|
|
387
|
+
const bare = await resolver(service)({
|
|
388
|
+
sessionKey: "sess-attach",
|
|
389
|
+
authenticatedPrincipal: "alice",
|
|
390
|
+
content: "y",
|
|
391
|
+
});
|
|
392
|
+
assert.equal(bare, projectNamespaceFor("Blend/Supply"));
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("#1434 an explicit-namespace store does NOT bind the session to a project (Codex review)", async () => {
|
|
396
|
+
// An explicit `namespace` bypasses the coding overlay, so the write must not
|
|
397
|
+
// seed a project binding the session never wrote to — else later bare recalls
|
|
398
|
+
// would search a project namespace with no committed memory.
|
|
399
|
+
const { orch, contexts } = makeAttachOrchestrator();
|
|
400
|
+
const service = new EngramAccessService(orch);
|
|
401
|
+
const res = await service.memoryStore(
|
|
402
|
+
storeRequest({ sessionKey: "sess-explicit", namespace: "default", projectTag: "Blend/Supply" }),
|
|
403
|
+
);
|
|
404
|
+
assert.equal(res.status, "stored");
|
|
405
|
+
assert.equal(res.namespace, "default");
|
|
406
|
+
assert.equal(contexts.get("sess-explicit"), undefined, "explicit-namespace write must not bind the session");
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("#1434 a dryRun store does NOT bind the session to a project (Codex review)", async () => {
|
|
410
|
+
// A dryRun is a read-only preview; it must not mutate session coding context.
|
|
411
|
+
const { orch, contexts } = makeAttachOrchestrator();
|
|
412
|
+
const service = new EngramAccessService(orch);
|
|
413
|
+
const res = await service.memoryStore(
|
|
414
|
+
storeRequest({ sessionKey: "sess-dry", projectTag: "Blend/Supply", dryRun: true }),
|
|
415
|
+
);
|
|
416
|
+
assert.equal(res.status, "validated");
|
|
417
|
+
assert.equal(contexts.get("sess-dry"), undefined, "dryRun must not bind the session");
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// ── Persist layer (#1434 P1/High): a pre-resolved project namespace must reach
|
|
421
|
+
// storage instead of being rejected by the static policy allow-list. ──────────
|
|
422
|
+
|
|
423
|
+
function makePersistOrchestrator() {
|
|
424
|
+
const getStorageCalls: Array<string | undefined> = [];
|
|
425
|
+
const orch = {
|
|
426
|
+
config: {
|
|
427
|
+
namespacesEnabled: true,
|
|
428
|
+
defaultNamespace: "default",
|
|
429
|
+
sharedNamespace: "shared",
|
|
430
|
+
namespacePolicies: [],
|
|
431
|
+
},
|
|
432
|
+
getStorage: async (ns?: string) => {
|
|
433
|
+
getStorageCalls.push(ns);
|
|
434
|
+
return {
|
|
435
|
+
readAllMemories: async () => [],
|
|
436
|
+
writeMemory: async () => "mem-1",
|
|
437
|
+
appendMemoryLifecycleEvents: async () => {},
|
|
438
|
+
};
|
|
439
|
+
},
|
|
440
|
+
} as unknown as Orchestrator;
|
|
441
|
+
return { orch, getStorageCalls };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function candidate(overrides: Partial<ValidExplicitCapture> = {}): ValidExplicitCapture {
|
|
445
|
+
return {
|
|
446
|
+
content: "durable project memory",
|
|
447
|
+
category: "fact",
|
|
448
|
+
confidence: 0.9,
|
|
449
|
+
tags: [],
|
|
450
|
+
namespace: "default-project-tag-abc123",
|
|
451
|
+
...overrides,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
test("#1434 persistExplicitCapture routes a pre-resolved project namespace to storage", async () => {
|
|
456
|
+
const { orch, getStorageCalls } = makePersistOrchestrator();
|
|
457
|
+
const res = await persistExplicitCapture(
|
|
458
|
+
orch,
|
|
459
|
+
candidate({ namespacePreResolved: true }),
|
|
460
|
+
"memory_store",
|
|
461
|
+
);
|
|
462
|
+
assert.equal(res.id, "mem-1");
|
|
463
|
+
// The dynamic project namespace must be used verbatim (dup-check + write),
|
|
464
|
+
// never rewritten or rejected.
|
|
465
|
+
assert.ok(
|
|
466
|
+
getStorageCalls.every((ns) => ns === "default-project-tag-abc123"),
|
|
467
|
+
`expected all getStorage calls on the project namespace, got ${JSON.stringify(getStorageCalls)}`,
|
|
468
|
+
);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("#1434 persistExplicitCapture still rejects an unauthorized namespace when not pre-resolved", async () => {
|
|
472
|
+
const { orch } = makePersistOrchestrator();
|
|
473
|
+
await assert.rejects(
|
|
474
|
+
persistExplicitCapture(orch, candidate(), "memory_store"),
|
|
475
|
+
/unsupported namespace/,
|
|
476
|
+
"the policy allow-list guard must still apply to callers that do not pre-authorize",
|
|
477
|
+
);
|
|
478
|
+
});
|