@saluzi/saluzi-edu 0.7.4 → 0.7.6
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/cli.js +93 -61
- package/dist/rcs-schema.sql +42 -0
- package/dist/rcs-web/assets/{AdminPanel-B9YWaqlO.js → AdminPanel-D0i_28O2.js} +1 -1
- package/dist/rcs-web/assets/{ArtifactView-B8lAeS5B.js → ArtifactView-Dd6d3O3z.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/ArtifactsGallery-CNd9-Awp.js → dist/rcs-web/assets/ArtifactsGallery-BTVS7sar.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/Claim-Dq08pl4W.js → dist/rcs-web/assets/Claim-CkuDG_ay.js} +1 -1
- package/dist/rcs-web/assets/{Dashboard-JZcrtByh.js → Dashboard-BBZHnmut.js} +2 -2
- package/dist/rcs-web/assets/{Join-DgvRnpyb.js → Join-DbBj-5mj.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/Login-CZbz6Wms.js → dist/rcs-web/assets/Login-Csvt3wvz.js} +1 -1
- package/dist/rcs-web/assets/MemoriesGallery-C5cT73hA.js +1 -0
- package/dist/rcs-web/assets/{Register-DD7uiwEj.js → Register-Cmp7ogoN.js} +1 -1
- package/dist/rcs-web/assets/{RegisterEmpty-BrBfeQXp.js → RegisterEmpty-BoIy42g_.js} +2 -2
- package/dist/rcs-web/assets/SessionDetail-CocGah-8.js +83 -0
- package/dist/rcs-web/assets/{SessionVisibilityBadge-kSo9esvT.js → SessionVisibilityBadge-C873QvnC.js} +2 -2
- package/dist/rcs-web/assets/{Setup-Ca30VUSF.js → Setup-DOU0qTNX.js} +1 -1
- package/dist/rcs-web/assets/{ShareLanding-C_vlxFAM.js → ShareLanding-DMR9eh0F.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/TeamDetail-8T_kKUcX.js → dist/rcs-web/assets/TeamDetail-MPfqesJf.js} +2 -2
- package/dist/rcs-web/assets/{TeamList-DRXGQCJi.js → TeamList-CgWVmdPU.js} +1 -1
- package/dist/rcs-web/assets/{TeamSettings-Byhon5C1.js → TeamSettings-V3L0H8Nb.js} +1 -1
- package/dist/rcs-web/assets/{UserSearchInput-wixyd_8w.js → UserSearchInput-xqtDX6Bz.js} +2 -2
- package/dist/rcs-web/assets/{UserSettings-Ds2retFk.js → UserSettings-Cfqs_5af.js} +1 -1
- package/dist/rcs-web/assets/{arc-CThS8KNL.js → arc-Bl97za3P.js} +1 -1
- package/dist/rcs-web/assets/{architectureDiagram-3BPJPVTR-Bko99SuX.js → architectureDiagram-3BPJPVTR-B8IQfxi6.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-CqCAgp0i.js → dist/rcs-web/assets/blockDiagram-GPEHLZMM-DNfBHM8J.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-C1pGDG-L.js → dist/rcs-web/assets/c4Diagram-AAUBKEIU-CO4HDUF2.js} +1 -1
- package/dist/rcs-web/assets/channel-DcTKV8fL.js +1 -0
- package/dist/rcs-web/assets/{chunk-2J33WTMH-Bz0kJwEY.js → chunk-2J33WTMH-4CBnqzb3.js} +1 -1
- package/dist/rcs-web/assets/{chunk-4BX2VUAB-DYnlidI7.js → chunk-4BX2VUAB-ClbIGQlY.js} +1 -1
- package/dist/rcs-web/assets/{chunk-55IACEB6-cMV4HRvm.js → chunk-55IACEB6-BnrTfmFM.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-Bgz9JRYO.js → dist/rcs-web/assets/chunk-727SXJPM-C17NZOQL.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-DIFGJlEF.js → dist/rcs-web/assets/chunk-AQP2D5EJ-CljirtnS.js} +1 -1
- package/dist/rcs-web/assets/{chunk-FMBD7UC4-BUC7O7T0.js → chunk-FMBD7UC4-Bb20ipZI.js} +1 -1
- package/dist/rcs-web/assets/{chunk-ND2GUHAM-kGqgVnBZ.js → chunk-ND2GUHAM-L3q9_xAn.js} +1 -1
- package/dist/rcs-web/assets/{chunk-QZHKN3VN-B01O0qXh.js → chunk-QZHKN3VN-FyYNxzak.js} +1 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-5DsQRSDS.js +1 -0
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-5DsQRSDS.js +1 -0
- package/dist/rcs-web/assets/{code-block-IT6T5CEO-CscK0pCh.js → code-block-IT6T5CEO-CQVTEZwg.js} +1 -1
- package/dist/rcs-web/assets/{copy-CvmA5P_Q.js → copy-CFZBXuu9.js} +1 -1
- package/dist/rcs-web/assets/{cose-bilkent-S5V4N54A-BsBsVccW.js → cose-bilkent-S5V4N54A-Cs2MVxlS.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-Bn4B33S-.js → dist/rcs-web/assets/dagre-BM42HDAG-CWZcIIf5.js} +1 -1
- package/dist/rcs-web/assets/{diagram-2AECGRRQ-DAuAIPR6.js → diagram-2AECGRRQ-DpumSk2d.js} +1 -1
- package/dist/rcs-web/assets/{diagram-5GNKFQAL-w3k2b4gC.js → diagram-5GNKFQAL-DcF2qwdh.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/diagram-KO2AKTUF-CMLav2KT.js → dist/rcs-web/assets/diagram-KO2AKTUF-BGnqGSPz.js} +1 -1
- package/dist/rcs-web/assets/{diagram-LMA3HP47-D14sXWdh.js → diagram-LMA3HP47-k-UxYOZ9.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-BexFwBQa.js → dist/rcs-web/assets/diagram-OG6HWLK6-BMPzmD6S.js} +1 -1
- package/dist/rcs-web/assets/{dialog-B4hKrd7w.js → dialog-DKk230y5.js} +1 -1
- package/dist/rcs-web/assets/{erDiagram-TEJ5UH35-BA8ANmoV.js → erDiagram-TEJ5UH35-BvmA6W9Z.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-dTQIvKsj.js → dist/rcs-web/assets/flowDiagram-I6XJVG4X-ptAnksiF.js} +1 -1
- package/dist/rcs-web/assets/{ganttDiagram-6RSMTGT7-8SDHYxXc.js → ganttDiagram-6RSMTGT7-Bhgrzcs3.js} +1 -1
- package/dist/rcs-web/assets/{gitGraphDiagram-PVQCEYII-Bu7LyvTG.js → gitGraphDiagram-PVQCEYII-B8-vcbF-.js} +1 -1
- package/dist/rcs-web/assets/{infoDiagram-5YYISTIA-BuQuDvuX.js → infoDiagram-5YYISTIA-DaW0unc2.js} +1 -1
- package/dist/rcs-web/assets/{ishikawaDiagram-YF4QCWOH-QjafGk8V.js → ishikawaDiagram-YF4QCWOH-4oYGuWP6.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-_FOXjXDF.js → dist/rcs-web/assets/journeyDiagram-JHISSGLW-B9YXpw0W.js} +1 -1
- package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-CbqJqyY6.js → kanban-definition-UN3LZRKU-Cglus7wI.js} +1 -1
- package/dist/rcs-web/assets/{linear-H6F41eb2.js → linear-CRTJd3_G.js} +1 -1
- package/dist/rcs-web/assets/{main-BopVSHhX.js → main-BKbA2ddo.js} +65 -65
- package/dist/rcs-web/assets/main-CP31AzxJ.css +1 -0
- package/{packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-5FH_DIq9.js → dist/rcs-web/assets/mermaid-VLURNSYL-D0JeVL0B.js} +4 -4
- package/dist/rcs-web/assets/{mermaid.core-C004NitB.js → mermaid.core-CobUk_K5.js} +5 -5
- package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-utqEJ2a8.js → mindmap-definition-RKZ34NQL-B1s-et_p.js} +1 -1
- package/dist/rcs-web/assets/{notifications-DOBs9HVf.js → notifications-BrgRJuzQ.js} +1 -1
- package/dist/rcs-web/assets/{pieDiagram-4H26LBE5-OlbTwDZQ.js → pieDiagram-4H26LBE5-PcBs9RT_.js} +1 -1
- package/dist/rcs-web/assets/{quadrantDiagram-W4KKPZXB-BOe2DhrU.js → quadrantDiagram-W4KKPZXB-B2GxITjv.js} +1 -1
- package/dist/rcs-web/assets/{refresh-cw-BSwpDl-7.js → refresh-cw-BwmFF3kw.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/requirementDiagram-4Y6WPE33-s6V1yWUG.js → dist/rcs-web/assets/requirementDiagram-4Y6WPE33-BfyZiasB.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-CdJD78Mx.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-Br7tRtmq.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-DvGV6Dhl.js → dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-CN1NSfHS.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-Bj-_Ulcn.js → dist/rcs-web/assets/stateDiagram-AJRCARHV-C4S8hKUx.js} +1 -1
- package/dist/rcs-web/assets/{stateDiagram-v2-BHNVJYJU-BLGWynk-.js → stateDiagram-v2-BHNVJYJU-CbEMz-jf.js} +1 -1
- package/dist/rcs-web/assets/{timeline-definition-PNZ67QCA-B6VAH6Ka.js → timeline-definition-PNZ67QCA-mTiK0HwD.js} +1 -1
- package/dist/rcs-web/assets/{vennDiagram-CIIHVFJN-DUwaLI6R.js → vennDiagram-CIIHVFJN-DCe95URb.js} +1 -1
- package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-CBetJore.js → wardleyDiagram-YWT4CUSO-Cct1r0Ax.js} +1 -1
- package/dist/rcs-web/assets/{workerType-BF4aY_R_.js → workerType-DW9l12WG.js} +1 -1
- package/dist/rcs-web/assets/{xychartDiagram-2RQKCTM6-BBnx6V66.js → xychartDiagram-2RQKCTM6-CVc0eQYa.js} +1 -1
- package/dist/rcs-web/index.html +2 -2
- package/package.json +1 -1
- package/packages/remote-control-server/src/__tests__/memories.test.ts +654 -0
- package/packages/remote-control-server/src/db/rcs-schema.sql +42 -0
- package/packages/remote-control-server/src/registerRoutes.ts +6 -0
- package/packages/remote-control-server/src/routes/v1/memories.ts +132 -0
- package/packages/remote-control-server/src/routes/web/memories.ts +321 -0
- package/packages/remote-control-server/src/services/memories.ts +747 -0
- package/packages/remote-control-server/web/dist/assets/{AdminPanel-B9YWaqlO.js → AdminPanel-D0i_28O2.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ArtifactView-B8lAeS5B.js → ArtifactView-Dd6d3O3z.js} +1 -1
- package/{dist/rcs-web/assets/ArtifactsGallery-CNd9-Awp.js → packages/remote-control-server/web/dist/assets/ArtifactsGallery-BTVS7sar.js} +1 -1
- package/{dist/rcs-web/assets/Claim-Dq08pl4W.js → packages/remote-control-server/web/dist/assets/Claim-CkuDG_ay.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{Dashboard-JZcrtByh.js → Dashboard-BBZHnmut.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{Join-DgvRnpyb.js → Join-DbBj-5mj.js} +1 -1
- package/{dist/rcs-web/assets/Login-CZbz6Wms.js → packages/remote-control-server/web/dist/assets/Login-Csvt3wvz.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/MemoriesGallery-C5cT73hA.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{Register-DD7uiwEj.js → Register-Cmp7ogoN.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{RegisterEmpty-BrBfeQXp.js → RegisterEmpty-BoIy42g_.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/SessionDetail-CocGah-8.js +83 -0
- package/packages/remote-control-server/web/dist/assets/{SessionVisibilityBadge-kSo9esvT.js → SessionVisibilityBadge-C873QvnC.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{Setup-Ca30VUSF.js → Setup-DOU0qTNX.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ShareLanding-C_vlxFAM.js → ShareLanding-DMR9eh0F.js} +1 -1
- package/{dist/rcs-web/assets/TeamDetail-8T_kKUcX.js → packages/remote-control-server/web/dist/assets/TeamDetail-MPfqesJf.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{TeamList-DRXGQCJi.js → TeamList-CgWVmdPU.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{TeamSettings-Byhon5C1.js → TeamSettings-V3L0H8Nb.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{UserSearchInput-wixyd_8w.js → UserSearchInput-xqtDX6Bz.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{UserSettings-Ds2retFk.js → UserSettings-Cfqs_5af.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{arc-CThS8KNL.js → arc-Bl97za3P.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{architectureDiagram-3BPJPVTR-Bko99SuX.js → architectureDiagram-3BPJPVTR-B8IQfxi6.js} +1 -1
- package/{dist/rcs-web/assets/blockDiagram-GPEHLZMM-CqCAgp0i.js → packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-DNfBHM8J.js} +1 -1
- package/{dist/rcs-web/assets/c4Diagram-AAUBKEIU-C1pGDG-L.js → packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-CO4HDUF2.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/channel-DcTKV8fL.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-Bz0kJwEY.js → chunk-2J33WTMH-4CBnqzb3.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-DYnlidI7.js → chunk-4BX2VUAB-ClbIGQlY.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-cMV4HRvm.js → chunk-55IACEB6-BnrTfmFM.js} +1 -1
- package/{dist/rcs-web/assets/chunk-727SXJPM-Bgz9JRYO.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-C17NZOQL.js} +1 -1
- package/{dist/rcs-web/assets/chunk-AQP2D5EJ-DIFGJlEF.js → packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-CljirtnS.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-BUC7O7T0.js → chunk-FMBD7UC4-Bb20ipZI.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-kGqgVnBZ.js → chunk-ND2GUHAM-L3q9_xAn.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-B01O0qXh.js → chunk-QZHKN3VN-FyYNxzak.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-5DsQRSDS.js +1 -0
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-5DsQRSDS.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{code-block-IT6T5CEO-CscK0pCh.js → code-block-IT6T5CEO-CQVTEZwg.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{copy-CvmA5P_Q.js → copy-CFZBXuu9.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{cose-bilkent-S5V4N54A-BsBsVccW.js → cose-bilkent-S5V4N54A-Cs2MVxlS.js} +1 -1
- package/{dist/rcs-web/assets/dagre-BM42HDAG-Bn4B33S-.js → packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-CWZcIIf5.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-2AECGRRQ-DAuAIPR6.js → diagram-2AECGRRQ-DpumSk2d.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-5GNKFQAL-w3k2b4gC.js → diagram-5GNKFQAL-DcF2qwdh.js} +1 -1
- package/{dist/rcs-web/assets/diagram-KO2AKTUF-CMLav2KT.js → packages/remote-control-server/web/dist/assets/diagram-KO2AKTUF-BGnqGSPz.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-LMA3HP47-D14sXWdh.js → diagram-LMA3HP47-k-UxYOZ9.js} +1 -1
- package/{dist/rcs-web/assets/diagram-OG6HWLK6-BexFwBQa.js → packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-BMPzmD6S.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{dialog-B4hKrd7w.js → dialog-DKk230y5.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{erDiagram-TEJ5UH35-BA8ANmoV.js → erDiagram-TEJ5UH35-BvmA6W9Z.js} +1 -1
- package/{dist/rcs-web/assets/flowDiagram-I6XJVG4X-dTQIvKsj.js → packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-ptAnksiF.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ganttDiagram-6RSMTGT7-8SDHYxXc.js → ganttDiagram-6RSMTGT7-Bhgrzcs3.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{gitGraphDiagram-PVQCEYII-Bu7LyvTG.js → gitGraphDiagram-PVQCEYII-B8-vcbF-.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{infoDiagram-5YYISTIA-BuQuDvuX.js → infoDiagram-5YYISTIA-DaW0unc2.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ishikawaDiagram-YF4QCWOH-QjafGk8V.js → ishikawaDiagram-YF4QCWOH-4oYGuWP6.js} +1 -1
- package/{dist/rcs-web/assets/journeyDiagram-JHISSGLW-_FOXjXDF.js → packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-B9YXpw0W.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-CbqJqyY6.js → kanban-definition-UN3LZRKU-Cglus7wI.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{linear-H6F41eb2.js → linear-CRTJd3_G.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{main-BopVSHhX.js → main-BKbA2ddo.js} +65 -65
- package/packages/remote-control-server/web/dist/assets/main-CP31AzxJ.css +1 -0
- package/{dist/rcs-web/assets/mermaid-VLURNSYL-5FH_DIq9.js → packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-D0JeVL0B.js} +4 -4
- package/packages/remote-control-server/web/dist/assets/{mermaid.core-C004NitB.js → mermaid.core-CobUk_K5.js} +5 -5
- package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-utqEJ2a8.js → mindmap-definition-RKZ34NQL-B1s-et_p.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{notifications-DOBs9HVf.js → notifications-BrgRJuzQ.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{pieDiagram-4H26LBE5-OlbTwDZQ.js → pieDiagram-4H26LBE5-PcBs9RT_.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{quadrantDiagram-W4KKPZXB-BOe2DhrU.js → quadrantDiagram-W4KKPZXB-B2GxITjv.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{refresh-cw-BSwpDl-7.js → refresh-cw-BwmFF3kw.js} +1 -1
- package/{dist/rcs-web/assets/requirementDiagram-4Y6WPE33-s6V1yWUG.js → packages/remote-control-server/web/dist/assets/requirementDiagram-4Y6WPE33-BfyZiasB.js} +1 -1
- package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-CdJD78Mx.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-Br7tRtmq.js} +1 -1
- package/{dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-DvGV6Dhl.js → packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-CN1NSfHS.js} +1 -1
- package/{dist/rcs-web/assets/stateDiagram-AJRCARHV-Bj-_Ulcn.js → packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-C4S8hKUx.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{stateDiagram-v2-BHNVJYJU-BLGWynk-.js → stateDiagram-v2-BHNVJYJU-CbEMz-jf.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{timeline-definition-PNZ67QCA-B6VAH6Ka.js → timeline-definition-PNZ67QCA-mTiK0HwD.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{vennDiagram-CIIHVFJN-DUwaLI6R.js → vennDiagram-CIIHVFJN-DCe95URb.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-CBetJore.js → wardleyDiagram-YWT4CUSO-Cct1r0Ax.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{workerType-BF4aY_R_.js → workerType-DW9l12WG.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{xychartDiagram-2RQKCTM6-BBnx6V66.js → xychartDiagram-2RQKCTM6-CVc0eQYa.js} +1 -1
- package/packages/remote-control-server/web/dist/index.html +2 -2
- package/dist/rcs-web/assets/SessionDetail-BbnQBjP1.js +0 -78
- package/dist/rcs-web/assets/channel-D6RbJTfV.js +0 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-BlCPk6aR.js +0 -1
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-BlCPk6aR.js +0 -1
- package/dist/rcs-web/assets/main-C0n90aVY.css +0 -1
- package/packages/remote-control-server/web/dist/assets/SessionDetail-BbnQBjP1.js +0 -78
- package/packages/remote-control-server/web/dist/assets/channel-D6RbJTfV.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-BlCPk6aR.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-BlCPk6aR.js +0 -1
- package/packages/remote-control-server/web/dist/assets/main-C0n90aVY.css +0 -1
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, mock } from 'bun:test'
|
|
2
|
+
import { mockConfigModule } from './helpers/mock-config'
|
|
3
|
+
|
|
4
|
+
// Mock config (apiKeys: ['test-api-key'])
|
|
5
|
+
mock.module('../config', () => mockConfigModule({}))
|
|
6
|
+
|
|
7
|
+
import { Hono } from 'hono'
|
|
8
|
+
import v1Memories from '../routes/v1/memories'
|
|
9
|
+
import webMemories from '../routes/web/memories'
|
|
10
|
+
import { getDb } from '../db/sqlite'
|
|
11
|
+
import { freshDb } from './helpers/db'
|
|
12
|
+
import { createUserDirectly, type TestUser } from './helpers/fixtures'
|
|
13
|
+
import { storeCreateSession, storeBindSession, storeReset } from '../store'
|
|
14
|
+
import { issueSessionToken } from '../auth/session'
|
|
15
|
+
import {
|
|
16
|
+
MemoryError,
|
|
17
|
+
archiveMemory,
|
|
18
|
+
buildMemoryPack,
|
|
19
|
+
canManageMemory,
|
|
20
|
+
createMemory,
|
|
21
|
+
getMemoryRow,
|
|
22
|
+
isMemoryVisibleToUser,
|
|
23
|
+
listMemoriesForUser,
|
|
24
|
+
promoteMemoryToTeam,
|
|
25
|
+
syncMemories,
|
|
26
|
+
updateMemory,
|
|
27
|
+
} from '../services/memories'
|
|
28
|
+
|
|
29
|
+
// Fresh DB per file so schema additions (memories tables) are guaranteed.
|
|
30
|
+
freshDb()
|
|
31
|
+
|
|
32
|
+
function createApp() {
|
|
33
|
+
const app = new Hono()
|
|
34
|
+
app.route('/v1', v1Memories)
|
|
35
|
+
app.route('/web', webMemories)
|
|
36
|
+
return app
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const AUTH_HEADERS = {
|
|
40
|
+
Authorization: 'Bearer test-api-key',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createTeam(name: string, createdBy: string): string {
|
|
44
|
+
const db = getDb()
|
|
45
|
+
const id = `team_${name}`
|
|
46
|
+
const now = new Date().toISOString()
|
|
47
|
+
db.query(
|
|
48
|
+
`INSERT INTO teams (id, name, slug, created_by, created_at, updated_at)
|
|
49
|
+
VALUES ($id, $name, $slug, $createdBy, $now, $now)`,
|
|
50
|
+
).run({
|
|
51
|
+
$id: id,
|
|
52
|
+
$name: name,
|
|
53
|
+
$slug: `slug-${name}`,
|
|
54
|
+
$createdBy: createdBy,
|
|
55
|
+
$now: now,
|
|
56
|
+
})
|
|
57
|
+
// Mirror the production create-team route: creator becomes owner member.
|
|
58
|
+
db.query(
|
|
59
|
+
`INSERT INTO team_members (team_id, user_id, role, added_at, added_by)
|
|
60
|
+
VALUES ($teamId, $userId, 'owner', $now, $addedBy)`,
|
|
61
|
+
).run({ $teamId: id, $userId: createdBy, $addedBy: createdBy, $now: now })
|
|
62
|
+
return id
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function addTeamMember(
|
|
66
|
+
teamId: string,
|
|
67
|
+
userId: string,
|
|
68
|
+
role: 'owner' | 'admin' | 'member',
|
|
69
|
+
): void {
|
|
70
|
+
const db = getDb()
|
|
71
|
+
db.query(
|
|
72
|
+
`INSERT INTO team_members (team_id, user_id, role, added_at, added_by)
|
|
73
|
+
VALUES ($teamId, $userId, $role, $now, $addedBy)`,
|
|
74
|
+
).run({
|
|
75
|
+
$teamId: teamId,
|
|
76
|
+
$userId: userId,
|
|
77
|
+
$role: role,
|
|
78
|
+
$now: new Date().toISOString(),
|
|
79
|
+
$addedBy: userId,
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function createSessionFor(ownerId: string, teamId?: string): string {
|
|
84
|
+
const session = storeCreateSession({})
|
|
85
|
+
storeBindSession(session.id, ownerId, 'user')
|
|
86
|
+
if (teamId) storeBindSession(session.id, teamId, 'team')
|
|
87
|
+
return session.id
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const MEMORY_FILE = `---
|
|
91
|
+
name: Release process
|
|
92
|
+
description: Ship via bun run precheck then tag
|
|
93
|
+
type: project
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
Always run bun run precheck before tagging.
|
|
97
|
+
`
|
|
98
|
+
|
|
99
|
+
describe('memories service', () => {
|
|
100
|
+
beforeEach(() => {
|
|
101
|
+
const db = getDb()
|
|
102
|
+
db.query('DELETE FROM memories').run()
|
|
103
|
+
db.query('DELETE FROM memories_fts').run()
|
|
104
|
+
db.query('DELETE FROM team_members').run()
|
|
105
|
+
db.query('DELETE FROM teams').run()
|
|
106
|
+
storeReset()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('createMemory personal stores owner and CHECK constraint rejects missing owner', () => {
|
|
110
|
+
const db = getDb()
|
|
111
|
+
const user = createUserDirectly(db, { username: 'alice' })
|
|
112
|
+
const row = createMemory({
|
|
113
|
+
scopeKind: 'personal',
|
|
114
|
+
ownerUserId: user.id,
|
|
115
|
+
name: 'Prefers Chinese',
|
|
116
|
+
description: 'Answers in Chinese',
|
|
117
|
+
type: 'user',
|
|
118
|
+
content: 'User prefers Chinese responses.',
|
|
119
|
+
})
|
|
120
|
+
expect(row.scope_kind).toBe('personal')
|
|
121
|
+
expect(row.owner_user_id).toBe(user.id)
|
|
122
|
+
expect(row.team_id).toBeNull()
|
|
123
|
+
expect(row.status).toBe('active')
|
|
124
|
+
|
|
125
|
+
// Missing owner on personal violates the CHECK constraint.
|
|
126
|
+
let caught: unknown = null
|
|
127
|
+
try {
|
|
128
|
+
createMemory({
|
|
129
|
+
scopeKind: 'personal',
|
|
130
|
+
name: 'x',
|
|
131
|
+
type: 'user',
|
|
132
|
+
content: 'x',
|
|
133
|
+
})
|
|
134
|
+
} catch (err) {
|
|
135
|
+
caught = err
|
|
136
|
+
}
|
|
137
|
+
expect(caught).toBeInstanceOf(MemoryError)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('updateMemory bumps version and archiveMemory soft-deletes', () => {
|
|
141
|
+
const db = getDb()
|
|
142
|
+
const user = createUserDirectly(db, { username: 'bob' })
|
|
143
|
+
const row = createMemory({
|
|
144
|
+
scopeKind: 'personal',
|
|
145
|
+
ownerUserId: user.id,
|
|
146
|
+
name: 'v1',
|
|
147
|
+
type: 'user',
|
|
148
|
+
content: 'first',
|
|
149
|
+
})
|
|
150
|
+
const updated = updateMemory(row.id, { content: 'second' })
|
|
151
|
+
expect(updated?.version).toBe(2)
|
|
152
|
+
expect(updated?.content).toBe('second')
|
|
153
|
+
|
|
154
|
+
expect(archiveMemory(row.id)).toBe(true)
|
|
155
|
+
expect(getMemoryRow(row.id)).toBeNull()
|
|
156
|
+
expect(archiveMemory(row.id)).toBe(false)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test('visibility: personal is owner-only (admin listings excluded), team needs membership', () => {
|
|
160
|
+
const db = getDb()
|
|
161
|
+
const owner = createUserDirectly(db, { username: 'owner' })
|
|
162
|
+
const stranger = createUserDirectly(db, { username: 'stranger' })
|
|
163
|
+
const admin = createUserDirectly(db, { username: 'root', role: 'admin' })
|
|
164
|
+
const member = createUserDirectly(db, { username: 'mate' })
|
|
165
|
+
|
|
166
|
+
const personal = createMemory({
|
|
167
|
+
scopeKind: 'personal',
|
|
168
|
+
ownerUserId: owner.id,
|
|
169
|
+
name: 'secret',
|
|
170
|
+
type: 'user',
|
|
171
|
+
content: 'private note',
|
|
172
|
+
})
|
|
173
|
+
expect(isMemoryVisibleToUser(personal.id, owner.id)).toBe(true)
|
|
174
|
+
expect(isMemoryVisibleToUser(personal.id, stranger.id)).toBe(false)
|
|
175
|
+
// Strict private: even a system admin cannot browse another user's
|
|
176
|
+
// personal memory.
|
|
177
|
+
expect(isMemoryVisibleToUser(personal.id, admin.id, true)).toBe(false)
|
|
178
|
+
|
|
179
|
+
const teamId = createTeam('alpha', owner.id)
|
|
180
|
+
addTeamMember(teamId, member.id, 'member')
|
|
181
|
+
const team = createMemory({
|
|
182
|
+
scopeKind: 'team',
|
|
183
|
+
teamId,
|
|
184
|
+
name: 'shared',
|
|
185
|
+
type: 'project',
|
|
186
|
+
content: 'team convention',
|
|
187
|
+
})
|
|
188
|
+
expect(isMemoryVisibleToUser(team.id, member.id)).toBe(true)
|
|
189
|
+
expect(isMemoryVisibleToUser(team.id, stranger.id)).toBe(false)
|
|
190
|
+
expect(isMemoryVisibleToUser(team.id, admin.id, true)).toBe(true)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test('manage: creator + team owner/admin + system admin', () => {
|
|
194
|
+
const db = getDb()
|
|
195
|
+
const owner = createUserDirectly(db, { username: 't-owner' })
|
|
196
|
+
const creator = createUserDirectly(db, { username: 't-creator' })
|
|
197
|
+
const plainMember = createUserDirectly(db, { username: 't-member' })
|
|
198
|
+
const teamAdmin = createUserDirectly(db, { username: 't-admin' })
|
|
199
|
+
const sysAdmin = createUserDirectly(db, { username: 'sys', role: 'admin' })
|
|
200
|
+
|
|
201
|
+
const teamId = createTeam('beta', owner.id)
|
|
202
|
+
for (const [u, r] of [
|
|
203
|
+
[creator, 'member'],
|
|
204
|
+
[plainMember, 'member'],
|
|
205
|
+
[teamAdmin, 'admin'],
|
|
206
|
+
] as const) {
|
|
207
|
+
addTeamMember(teamId, u.id, r)
|
|
208
|
+
}
|
|
209
|
+
const row = createMemory({
|
|
210
|
+
scopeKind: 'team',
|
|
211
|
+
teamId,
|
|
212
|
+
name: 'convention',
|
|
213
|
+
type: 'project',
|
|
214
|
+
content: 'x',
|
|
215
|
+
createdBy: creator.id,
|
|
216
|
+
})
|
|
217
|
+
expect(canManageMemory(row.id, creator.id)).toBe(true)
|
|
218
|
+
expect(canManageMemory(row.id, plainMember.id)).toBe(false)
|
|
219
|
+
expect(canManageMemory(row.id, teamAdmin.id)).toBe(true)
|
|
220
|
+
expect(canManageMemory(row.id, owner.id)).toBe(true)
|
|
221
|
+
expect(canManageMemory(row.id, sysAdmin.id, true)).toBe(true)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('listMemoriesForUser: personal scope, team scope, session filter; admin never sees others personal', () => {
|
|
225
|
+
const db = getDb()
|
|
226
|
+
const alice = createUserDirectly(db, { username: 'alice2' })
|
|
227
|
+
const bob = createUserDirectly(db, { username: 'bob2' })
|
|
228
|
+
const admin = createUserDirectly(db, { username: 'root2', role: 'admin' })
|
|
229
|
+
const teamId = createTeam('gamma', alice.id)
|
|
230
|
+
addTeamMember(teamId, bob.id, 'member')
|
|
231
|
+
|
|
232
|
+
const p1 = createMemory({
|
|
233
|
+
scopeKind: 'personal',
|
|
234
|
+
ownerUserId: alice.id,
|
|
235
|
+
name: 'alice note',
|
|
236
|
+
type: 'user',
|
|
237
|
+
content: 'x',
|
|
238
|
+
})
|
|
239
|
+
createMemory({
|
|
240
|
+
scopeKind: 'personal',
|
|
241
|
+
ownerUserId: bob.id,
|
|
242
|
+
name: 'bob note',
|
|
243
|
+
type: 'user',
|
|
244
|
+
content: 'x',
|
|
245
|
+
})
|
|
246
|
+
const t1 = createMemory({
|
|
247
|
+
scopeKind: 'team',
|
|
248
|
+
teamId,
|
|
249
|
+
name: 'team note',
|
|
250
|
+
type: 'project',
|
|
251
|
+
content: 'x',
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const alicePersonal = listMemoriesForUser(alice.id, { personalScope: true })
|
|
255
|
+
expect(alicePersonal.map(r => r.id)).toEqual([p1.id])
|
|
256
|
+
|
|
257
|
+
const aliceTeam = listMemoriesForUser(alice.id, { teamId })
|
|
258
|
+
expect(aliceTeam.map(r => r.id)).toEqual([t1.id])
|
|
259
|
+
|
|
260
|
+
// Admin across-team listing sees team rows but never others' personal.
|
|
261
|
+
const adminAll = listMemoriesForUser(admin.id, {}, true)
|
|
262
|
+
expect(adminAll.map(r => r.id)).toEqual([t1.id])
|
|
263
|
+
|
|
264
|
+
// Session provenance filter.
|
|
265
|
+
const sessionId = createSessionFor(alice.id)
|
|
266
|
+
createMemory({
|
|
267
|
+
scopeKind: 'personal',
|
|
268
|
+
ownerUserId: alice.id,
|
|
269
|
+
name: 'from session',
|
|
270
|
+
type: 'project',
|
|
271
|
+
content: 'x',
|
|
272
|
+
sourceSessionId: sessionId,
|
|
273
|
+
})
|
|
274
|
+
const fromSession = listMemoriesForUser(alice.id, { sessionId })
|
|
275
|
+
expect(fromSession).toHaveLength(1)
|
|
276
|
+
expect(fromSession[0].name).toBe('from session')
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
test('promoteMemoryToTeam: owner-only, membership-checked, migrates scope', () => {
|
|
280
|
+
const db = getDb()
|
|
281
|
+
const owner = createUserDirectly(db, { username: 'promo' })
|
|
282
|
+
const stranger = createUserDirectly(db, { username: 'promo2' })
|
|
283
|
+
const teamId = createTeam('delta', owner.id)
|
|
284
|
+
const otherTeam = createTeam('epsilon', stranger.id)
|
|
285
|
+
|
|
286
|
+
const row = createMemory({
|
|
287
|
+
scopeKind: 'personal',
|
|
288
|
+
ownerUserId: owner.id,
|
|
289
|
+
name: 'worth sharing',
|
|
290
|
+
type: 'project',
|
|
291
|
+
content: 'x',
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
// Stranger cannot promote someone else's memory.
|
|
295
|
+
expect(() => promoteMemoryToTeam(row.id, stranger.id, teamId)).toThrow(
|
|
296
|
+
MemoryError,
|
|
297
|
+
)
|
|
298
|
+
// Owner cannot promote into a team they don't belong to.
|
|
299
|
+
expect(() => promoteMemoryToTeam(row.id, owner.id, otherTeam)).toThrow(
|
|
300
|
+
MemoryError,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
const promoted = promoteMemoryToTeam(row.id, owner.id, teamId)
|
|
304
|
+
expect(promoted.scope_kind).toBe('team')
|
|
305
|
+
expect(promoted.team_id).toBe(teamId)
|
|
306
|
+
expect(promoted.owner_user_id).toBeNull()
|
|
307
|
+
expect(promoted.version).toBe(2)
|
|
308
|
+
// Second promote is rejected — already team scope.
|
|
309
|
+
expect(() => promoteMemoryToTeam(row.id, owner.id, teamId)).toThrow(
|
|
310
|
+
MemoryError,
|
|
311
|
+
)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
test('buildMemoryPack: personal + team layout with MEMORY.md index', () => {
|
|
315
|
+
const db = getDb()
|
|
316
|
+
const alice = createUserDirectly(db, { username: 'pack' })
|
|
317
|
+
const teamId = createTeam('zeta', alice.id)
|
|
318
|
+
const sessionId = createSessionFor(alice.id, teamId)
|
|
319
|
+
|
|
320
|
+
createMemory({
|
|
321
|
+
scopeKind: 'personal',
|
|
322
|
+
ownerUserId: alice.id,
|
|
323
|
+
name: 'My style',
|
|
324
|
+
description: 'Concise answers',
|
|
325
|
+
type: 'user',
|
|
326
|
+
content: 'Prefer concise answers.',
|
|
327
|
+
})
|
|
328
|
+
createMemory({
|
|
329
|
+
scopeKind: 'team',
|
|
330
|
+
teamId,
|
|
331
|
+
name: 'Team convention',
|
|
332
|
+
description: 'Ship on Fridays',
|
|
333
|
+
type: 'project',
|
|
334
|
+
content: 'Ship on Fridays.',
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
const pack = buildMemoryPack(sessionId)
|
|
338
|
+
const paths = Object.keys(pack.files)
|
|
339
|
+
expect(paths).toContain('MEMORY.md')
|
|
340
|
+
expect(paths.some(p => p.startsWith('team/') && p.endsWith('.md'))).toBe(
|
|
341
|
+
true,
|
|
342
|
+
)
|
|
343
|
+
expect(paths.some(p => !p.includes('/') && p.endsWith('.md'))).toBe(true)
|
|
344
|
+
|
|
345
|
+
const index = pack.files['MEMORY.md'] ?? ''
|
|
346
|
+
expect(index).toContain('[My style]')
|
|
347
|
+
expect(index).toContain('[Team convention]')
|
|
348
|
+
expect(index).toContain('(team/')
|
|
349
|
+
|
|
350
|
+
// Personal memory file carries the memdir frontmatter shape.
|
|
351
|
+
const personalFile = paths.find(p => !p.includes('/') && p !== 'MEMORY.md')
|
|
352
|
+
expect(pack.files[personalFile!]).toContain('type: user')
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
test('buildMemoryPack: no user owner → empty pack; no team owner → no team files', () => {
|
|
356
|
+
const db = getDb()
|
|
357
|
+
const alice = createUserDirectly(db, { username: 'pack2' })
|
|
358
|
+
const sessionId = createSessionFor(alice.id)
|
|
359
|
+
createMemory({
|
|
360
|
+
scopeKind: 'team',
|
|
361
|
+
teamId: createTeam('eta', alice.id),
|
|
362
|
+
name: 'orphan team memory',
|
|
363
|
+
type: 'project',
|
|
364
|
+
content: 'x',
|
|
365
|
+
})
|
|
366
|
+
// Session has a user owner but no team owner → personal memories only.
|
|
367
|
+
createMemory({
|
|
368
|
+
scopeKind: 'personal',
|
|
369
|
+
ownerUserId: alice.id,
|
|
370
|
+
name: 'mine',
|
|
371
|
+
type: 'user',
|
|
372
|
+
content: 'x',
|
|
373
|
+
})
|
|
374
|
+
const pack = buildMemoryPack(sessionId)
|
|
375
|
+
expect(Object.keys(pack.files).some(p => p.startsWith('team/'))).toBe(false)
|
|
376
|
+
|
|
377
|
+
// Session with no user owner at all → empty pack.
|
|
378
|
+
const teamOnly = storeCreateSession({})
|
|
379
|
+
storeBindSession(teamOnly.id, 'team_nope', 'team')
|
|
380
|
+
expect(buildMemoryPack(teamOnly.id)).toEqual({ files: {} })
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
test('syncMemories: identity from session, working layer skipped, dedup by name', () => {
|
|
384
|
+
const db = getDb()
|
|
385
|
+
const alice = createUserDirectly(db, { username: 'sync' })
|
|
386
|
+
const sessionId = createSessionFor(alice.id)
|
|
387
|
+
|
|
388
|
+
const result = syncMemories(sessionId, [
|
|
389
|
+
// Valid entry — new memory.
|
|
390
|
+
{ rel_path: 'project_release-process.md', content: MEMORY_FILE },
|
|
391
|
+
// Working-layer scratch — skipped.
|
|
392
|
+
{
|
|
393
|
+
rel_path: '.working/turn_summary/x.md',
|
|
394
|
+
content: MEMORY_FILE,
|
|
395
|
+
},
|
|
396
|
+
// Index file — skipped.
|
|
397
|
+
{ rel_path: 'MEMORY.md', content: '# index' },
|
|
398
|
+
// Bad frontmatter (missing type) — skipped.
|
|
399
|
+
{ rel_path: 'junk.md', content: 'no frontmatter at all' },
|
|
400
|
+
])
|
|
401
|
+
expect(result.created).toBe(1)
|
|
402
|
+
expect(result.skipped).toBe(3)
|
|
403
|
+
|
|
404
|
+
const rows = listMemoriesForUser(alice.id, { sessionId })
|
|
405
|
+
expect(rows).toHaveLength(1)
|
|
406
|
+
expect(rows[0].name).toBe('Release process')
|
|
407
|
+
expect(rows[0].scope_kind).toBe('personal')
|
|
408
|
+
expect(rows[0].owner_user_id).toBe(alice.id)
|
|
409
|
+
|
|
410
|
+
// Same name again → dedup update, not a second row.
|
|
411
|
+
const second = syncMemories(sessionId, [
|
|
412
|
+
{ rel_path: 'project_release-process.md', content: MEMORY_FILE },
|
|
413
|
+
])
|
|
414
|
+
expect(second.created).toBe(0)
|
|
415
|
+
expect(second.updated).toBe(1)
|
|
416
|
+
const rowsAfter = listMemoriesForUser(alice.id, { sessionId })
|
|
417
|
+
expect(rowsAfter).toHaveLength(1)
|
|
418
|
+
expect(rowsAfter[0].version).toBe(2)
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
test('syncMemories: injected team/ files are skipped, not forked as personal copies', () => {
|
|
422
|
+
const db = getDb()
|
|
423
|
+
const alice = createUserDirectly(db, { username: 'sync-team' })
|
|
424
|
+
const teamId = createTeam('sync-team', alice.id)
|
|
425
|
+
const sessionId = createSessionFor(alice.id, teamId)
|
|
426
|
+
// Team memory that was materialized into the pack's team/ subtree.
|
|
427
|
+
const teamRow = createMemory({
|
|
428
|
+
scopeKind: 'team',
|
|
429
|
+
teamId,
|
|
430
|
+
name: 'Team convention',
|
|
431
|
+
type: 'project',
|
|
432
|
+
content: 'v1',
|
|
433
|
+
createdBy: alice.id,
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
const result = syncMemories(sessionId, [
|
|
437
|
+
{
|
|
438
|
+
rel_path: 'team/project_team-convention.md',
|
|
439
|
+
content: `---\nname: Team convention\ntype: project\n---\nv2 edited by child\n`,
|
|
440
|
+
},
|
|
441
|
+
])
|
|
442
|
+
expect(result.created).toBe(0)
|
|
443
|
+
expect(result.updated).toBe(0)
|
|
444
|
+
expect(result.skipped).toBe(1)
|
|
445
|
+
|
|
446
|
+
// The team row is untouched — no silent personal fork either.
|
|
447
|
+
const row = getMemoryRow(teamRow.id)
|
|
448
|
+
expect(row?.content).toBe('v1')
|
|
449
|
+
expect(row?.version).toBe(1)
|
|
450
|
+
expect(listMemoriesForUser(alice.id, { sessionId })).toHaveLength(0)
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
test('syncMemories: session without user owner throws no_owner', () => {
|
|
454
|
+
const db = getDb()
|
|
455
|
+
const teamOnly = storeCreateSession({})
|
|
456
|
+
storeBindSession(teamOnly.id, 'team_x', 'team')
|
|
457
|
+
let caught: unknown = null
|
|
458
|
+
try {
|
|
459
|
+
syncMemories(teamOnly.id, [{ rel_path: 'a.md', content: MEMORY_FILE }])
|
|
460
|
+
} catch (err) {
|
|
461
|
+
caught = err
|
|
462
|
+
}
|
|
463
|
+
expect(caught).toBeInstanceOf(MemoryError)
|
|
464
|
+
expect((caught as MemoryError).code).toBe('no_owner')
|
|
465
|
+
})
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
describe('memories web routes', () => {
|
|
469
|
+
beforeEach(() => {
|
|
470
|
+
const db = getDb()
|
|
471
|
+
db.query('DELETE FROM memories').run()
|
|
472
|
+
db.query('DELETE FROM memories_fts').run()
|
|
473
|
+
db.query('DELETE FROM team_members').run()
|
|
474
|
+
db.query('DELETE FROM teams').run()
|
|
475
|
+
storeReset()
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
test('CRUD round-trip with permission checks', async () => {
|
|
479
|
+
const db = getDb()
|
|
480
|
+
const app = createApp()
|
|
481
|
+
const alice = createUserDirectly(db, { username: 'web-alice' })
|
|
482
|
+
const bob = createUserDirectly(db, { username: 'web-bob' })
|
|
483
|
+
const token = `Bearer ${issueToken(alice.id)}`
|
|
484
|
+
|
|
485
|
+
// Create personal.
|
|
486
|
+
let res = await app.request('/web/memories', {
|
|
487
|
+
method: 'POST',
|
|
488
|
+
headers: { Authorization: token, 'content-type': 'application/json' },
|
|
489
|
+
body: JSON.stringify({
|
|
490
|
+
name: 'Web note',
|
|
491
|
+
description: 'from web',
|
|
492
|
+
type: 'project',
|
|
493
|
+
content: 'created from web UI',
|
|
494
|
+
}),
|
|
495
|
+
})
|
|
496
|
+
expect(res.status).toBe(201)
|
|
497
|
+
const created = (await res.json()) as { id: string; can_manage: boolean }
|
|
498
|
+
expect(created.can_manage).toBe(true)
|
|
499
|
+
|
|
500
|
+
// Another user cannot see it (404, existence-hiding).
|
|
501
|
+
res = await app.request(`/web/memories/${created.id}`, {
|
|
502
|
+
headers: { Authorization: `Bearer ${issueToken(bob.id)}` },
|
|
503
|
+
})
|
|
504
|
+
expect(res.status).toBe(404)
|
|
505
|
+
|
|
506
|
+
// Owner edits it.
|
|
507
|
+
res = await app.request(`/web/memories/${created.id}`, {
|
|
508
|
+
method: 'PATCH',
|
|
509
|
+
headers: { Authorization: token, 'content-type': 'application/json' },
|
|
510
|
+
body: JSON.stringify({ content: 'edited' }),
|
|
511
|
+
})
|
|
512
|
+
expect(res.status).toBe(200)
|
|
513
|
+
|
|
514
|
+
// Owner deletes it.
|
|
515
|
+
res = await app.request(`/web/memories/${created.id}`, {
|
|
516
|
+
method: 'DELETE',
|
|
517
|
+
headers: { Authorization: token },
|
|
518
|
+
})
|
|
519
|
+
expect(res.status).toBe(200)
|
|
520
|
+
|
|
521
|
+
// Now missing.
|
|
522
|
+
res = await app.request(`/web/memories/${created.id}`, {
|
|
523
|
+
headers: { Authorization: token },
|
|
524
|
+
})
|
|
525
|
+
expect(res.status).toBe(404)
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
test('team memory creation requires membership; promote route migrates scope', async () => {
|
|
529
|
+
const db = getDb()
|
|
530
|
+
const app = createApp()
|
|
531
|
+
const alice = createUserDirectly(db, { username: 'web-t-alice' })
|
|
532
|
+
const mallory = createUserDirectly(db, { username: 'web-mallory' })
|
|
533
|
+
const teamId = createTeam('web-team', alice.id)
|
|
534
|
+
const aliceToken = `Bearer ${issueToken(alice.id)}`
|
|
535
|
+
|
|
536
|
+
// Non-member cannot create team memory.
|
|
537
|
+
let res = await app.request('/web/memories', {
|
|
538
|
+
method: 'POST',
|
|
539
|
+
headers: {
|
|
540
|
+
Authorization: `Bearer ${issueToken(mallory.id)}`,
|
|
541
|
+
'content-type': 'application/json',
|
|
542
|
+
},
|
|
543
|
+
body: JSON.stringify({
|
|
544
|
+
name: 'sneaky',
|
|
545
|
+
type: 'project',
|
|
546
|
+
content: 'x',
|
|
547
|
+
team_id: teamId,
|
|
548
|
+
}),
|
|
549
|
+
})
|
|
550
|
+
expect(res.status).toBe(403)
|
|
551
|
+
|
|
552
|
+
// Alice creates personal, then promotes via the route.
|
|
553
|
+
res = await app.request('/web/memories', {
|
|
554
|
+
method: 'POST',
|
|
555
|
+
headers: {
|
|
556
|
+
Authorization: aliceToken,
|
|
557
|
+
'content-type': 'application/json',
|
|
558
|
+
},
|
|
559
|
+
body: JSON.stringify({
|
|
560
|
+
name: 'promote me',
|
|
561
|
+
type: 'project',
|
|
562
|
+
content: 'x',
|
|
563
|
+
}),
|
|
564
|
+
})
|
|
565
|
+
const created = (await res.json()) as { id: string }
|
|
566
|
+
|
|
567
|
+
res = await app.request(`/web/memories/${created.id}/promote`, {
|
|
568
|
+
method: 'POST',
|
|
569
|
+
headers: {
|
|
570
|
+
Authorization: aliceToken,
|
|
571
|
+
'content-type': 'application/json',
|
|
572
|
+
},
|
|
573
|
+
body: JSON.stringify({ team_id: teamId }),
|
|
574
|
+
})
|
|
575
|
+
expect(res.status).toBe(200)
|
|
576
|
+
const promoted = (await res.json()) as {
|
|
577
|
+
scope_kind: string
|
|
578
|
+
team_id: string
|
|
579
|
+
}
|
|
580
|
+
expect(promoted.scope_kind).toBe('team')
|
|
581
|
+
expect(promoted.team_id).toBe(teamId)
|
|
582
|
+
})
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
describe('memories v1 routes', () => {
|
|
586
|
+
beforeEach(() => {
|
|
587
|
+
const db = getDb()
|
|
588
|
+
db.query('DELETE FROM memories').run()
|
|
589
|
+
db.query('DELETE FROM memories_fts').run()
|
|
590
|
+
db.query('DELETE FROM team_members').run()
|
|
591
|
+
db.query('DELETE FROM teams').run()
|
|
592
|
+
storeReset()
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
test('pack requires worker auth and returns files for owned session', async () => {
|
|
596
|
+
const db = getDb()
|
|
597
|
+
const app = createApp()
|
|
598
|
+
const alice = createUserDirectly(db, { username: 'v1-alice' })
|
|
599
|
+
const sessionId = createSessionFor(alice.id)
|
|
600
|
+
createMemory({
|
|
601
|
+
scopeKind: 'personal',
|
|
602
|
+
ownerUserId: alice.id,
|
|
603
|
+
name: 'injected',
|
|
604
|
+
type: 'user',
|
|
605
|
+
content: 'should appear in pack',
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
// No auth → 401.
|
|
609
|
+
let res = await app.request(`/v1/memories/pack?session_id=${sessionId}`)
|
|
610
|
+
expect(res.status).toBe(401)
|
|
611
|
+
|
|
612
|
+
// Worker auth → pack with the memory file.
|
|
613
|
+
res = await app.request(`/v1/memories/pack?session_id=${sessionId}`, {
|
|
614
|
+
headers: AUTH_HEADERS,
|
|
615
|
+
})
|
|
616
|
+
expect(res.status).toBe(200)
|
|
617
|
+
const pack = (await res.json()) as { files: Record<string, string> }
|
|
618
|
+
expect(Object.keys(pack.files)).toContain('MEMORY.md')
|
|
619
|
+
|
|
620
|
+
// Unknown session → empty pack (spawn proceeds without injection).
|
|
621
|
+
res = await app.request('/v1/memories/pack?session_id=session_missing', {
|
|
622
|
+
headers: AUTH_HEADERS,
|
|
623
|
+
})
|
|
624
|
+
expect(res.status).toBe(200)
|
|
625
|
+
expect((await res.json()).files).toEqual({})
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
test('sync ingests entries attributed to the session owner', async () => {
|
|
629
|
+
const db = getDb()
|
|
630
|
+
const app = createApp()
|
|
631
|
+
const alice = createUserDirectly(db, { username: 'v1-sync' })
|
|
632
|
+
const sessionId = createSessionFor(alice.id)
|
|
633
|
+
|
|
634
|
+
const res = await app.request('/v1/memories/sync', {
|
|
635
|
+
method: 'POST',
|
|
636
|
+
headers: { ...AUTH_HEADERS, 'content-type': 'application/json' },
|
|
637
|
+
body: JSON.stringify({
|
|
638
|
+
session_id: sessionId,
|
|
639
|
+
files: [{ rel_path: 'project_x.md', content: MEMORY_FILE }],
|
|
640
|
+
}),
|
|
641
|
+
})
|
|
642
|
+
expect(res.status).toBe(200)
|
|
643
|
+
const result = (await res.json()) as { created: number }
|
|
644
|
+
expect(result.created).toBe(1)
|
|
645
|
+
|
|
646
|
+
const rows = listMemoriesForUser(alice.id, { sessionId })
|
|
647
|
+
expect(rows).toHaveLength(1)
|
|
648
|
+
})
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
function issueToken(userId: string): string {
|
|
652
|
+
// SQLite-backed token, same shape as the artifacts tests.
|
|
653
|
+
return issueSessionToken(userId, getDb()).accessToken
|
|
654
|
+
}
|
|
@@ -165,6 +165,48 @@ CREATE VIRTUAL TABLE IF NOT EXISTS session_events_fts USING fts5(
|
|
|
165
165
|
session_id UNINDEXED,
|
|
166
166
|
event_id UNINDEXED
|
|
167
167
|
);
|
|
168
|
+
-- Memories — durable team/personal memory entries (agent memory system).
|
|
169
|
+
-- `scope_kind` discriminates the two first-class scopes; the CHECK forces
|
|
170
|
+
-- exactly one owner column: personal rows carry owner_user_id, team rows
|
|
171
|
+
-- carry team_id. `source_session_id` keeps the provenance link back to the
|
|
172
|
+
-- session that produced the memory (agent write-back). `archived` is a
|
|
173
|
+
-- soft-delete terminal state that readers treat as missing.
|
|
174
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
175
|
+
id TEXT PRIMARY KEY,
|
|
176
|
+
scope_kind TEXT NOT NULL CHECK (scope_kind IN ('personal', 'team')),
|
|
177
|
+
owner_user_id TEXT,
|
|
178
|
+
team_id TEXT,
|
|
179
|
+
name TEXT NOT NULL,
|
|
180
|
+
description TEXT,
|
|
181
|
+
type TEXT NOT NULL CHECK (type IN ('user', 'feedback', 'project', 'reference')),
|
|
182
|
+
layer TEXT NOT NULL DEFAULT 'semantic' CHECK (layer IN ('episodic', 'semantic', 'procedural')),
|
|
183
|
+
content TEXT NOT NULL,
|
|
184
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'superseded', 'archived')),
|
|
185
|
+
source_session_id TEXT,
|
|
186
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
187
|
+
created_by TEXT,
|
|
188
|
+
created_at TEXT NOT NULL,
|
|
189
|
+
updated_at TEXT NOT NULL,
|
|
190
|
+
CHECK (
|
|
191
|
+
(scope_kind = 'personal' AND owner_user_id IS NOT NULL AND team_id IS NULL)
|
|
192
|
+
OR (scope_kind = 'team' AND team_id IS NOT NULL AND owner_user_id IS NULL)
|
|
193
|
+
)
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
CREATE INDEX IF NOT EXISTS idx_memories_personal ON memories(owner_user_id, status, updated_at);
|
|
197
|
+
CREATE INDEX IF NOT EXISTS idx_memories_team ON memories(team_id, status, updated_at);
|
|
198
|
+
CREATE INDEX IF NOT EXISTS idx_memories_session ON memories(source_session_id);
|
|
199
|
+
|
|
200
|
+
-- Full-text search over memories (name/description/content), synced on
|
|
201
|
+
-- write in services/memories.ts — same contentless-free pattern as
|
|
202
|
+
-- session_events_fts (regular fts5 with an UNINDEXED join key).
|
|
203
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
204
|
+
name,
|
|
205
|
+
description,
|
|
206
|
+
content,
|
|
207
|
+
memory_id UNINDEXED
|
|
208
|
+
);
|
|
209
|
+
|
|
168
210
|
CREATE INDEX IF NOT EXISTS idx_artifacts_session ON artifacts(session_id);
|
|
169
211
|
CREATE INDEX IF NOT EXISTS idx_artifacts_team ON artifacts(team_id);
|
|
170
212
|
CREATE INDEX IF NOT EXISTS idx_artifacts_expires ON artifacts(expires_at);
|