@rubytech/create-maxy-code 0.1.608 → 0.1.609

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.
Files changed (241) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.js +86 -0
  3. package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.js.map +1 -1
  4. package/payload/platform/lib/graph-write/dist/index.d.ts +30 -9
  5. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
  6. package/payload/platform/lib/graph-write/dist/index.js +22 -2
  7. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  8. package/payload/platform/lib/graph-write/src/__tests__/account-id-gate.test.ts +99 -1
  9. package/payload/platform/lib/graph-write/src/index.ts +47 -12
  10. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +45 -1
  11. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +8 -0
  12. package/payload/platform/plugins/docs/references/telegram-guide.md +44 -0
  13. package/payload/platform/plugins/memory/mcp/dist/index.js +65 -0
  14. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  15. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/xacct-counters.test.d.ts +2 -0
  16. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/xacct-counters.test.d.ts.map +1 -0
  17. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/xacct-counters.test.js +169 -0
  18. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/xacct-counters.test.js.map +1 -0
  19. package/payload/platform/plugins/memory/mcp/dist/lib/xacct-counters.d.ts +63 -0
  20. package/payload/platform/plugins/memory/mcp/dist/lib/xacct-counters.d.ts.map +1 -0
  21. package/payload/platform/plugins/memory/mcp/dist/lib/xacct-counters.js +114 -0
  22. package/payload/platform/plugins/memory/mcp/dist/lib/xacct-counters.js.map +1 -0
  23. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-cross-account.test.d.ts +2 -0
  24. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-cross-account.test.d.ts.map +1 -0
  25. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-cross-account.test.js +123 -0
  26. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-cross-account.test.js.map +1 -0
  27. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/xacct-write-gate.test.d.ts +2 -0
  28. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/xacct-write-gate.test.d.ts.map +1 -0
  29. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/xacct-write-gate.test.js +110 -0
  30. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/xacct-write-gate.test.js.map +1 -0
  31. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  32. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +7 -0
  33. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  34. package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
  35. package/payload/platform/services/claude-session-manager/dist/claude-trust-preflight.d.ts +43 -0
  36. package/payload/platform/services/claude-session-manager/dist/claude-trust-preflight.d.ts.map +1 -0
  37. package/payload/platform/services/claude-session-manager/dist/claude-trust-preflight.js +88 -0
  38. package/payload/platform/services/claude-session-manager/dist/claude-trust-preflight.js.map +1 -0
  39. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  40. package/payload/platform/services/claude-session-manager/dist/http-server.js +125 -4
  41. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  42. package/payload/platform/services/claude-session-manager/dist/index.js +26 -1
  43. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  44. package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts +1 -1
  45. package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts.map +1 -1
  46. package/payload/platform/services/claude-session-manager/dist/jsonl-path.js +26 -0
  47. package/payload/platform/services/claude-session-manager/dist/jsonl-path.js.map +1 -1
  48. package/payload/platform/services/claude-session-manager/dist/recover-account.d.ts +54 -0
  49. package/payload/platform/services/claude-session-manager/dist/recover-account.d.ts.map +1 -0
  50. package/payload/platform/services/claude-session-manager/dist/recover-account.js +141 -0
  51. package/payload/platform/services/claude-session-manager/dist/recover-account.js.map +1 -0
  52. package/payload/platform/services/claude-session-manager/dist/session-account-census.d.ts +30 -0
  53. package/payload/platform/services/claude-session-manager/dist/session-account-census.d.ts.map +1 -0
  54. package/payload/platform/services/claude-session-manager/dist/session-account-census.js +96 -0
  55. package/payload/platform/services/claude-session-manager/dist/session-account-census.js.map +1 -0
  56. package/payload/platform/services/telegram-channel/dist/instructions.d.ts +9 -0
  57. package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -1
  58. package/payload/platform/services/telegram-channel/dist/instructions.js +15 -0
  59. package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
  60. package/payload/platform/services/telegram-channel/dist/notification.d.ts +6 -4
  61. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  62. package/payload/platform/services/telegram-channel/dist/notification.js +15 -1
  63. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  64. package/payload/server/{chunk-GI5AVCRG.js → chunk-DRDU74ZM.js} +1 -1
  65. package/payload/server/{chunk-MNN2RWJR.js → chunk-IOPUA7QV.js} +1 -1
  66. package/payload/server/{chunk-6AQYMT6C.js → chunk-T6NXRWMG.js} +43 -8
  67. package/payload/server/{manager-VDHT3YYI.js → manager-O5L2SKW7.js} +2 -2
  68. package/payload/server/maxy-edge.js +2 -2
  69. package/payload/server/public/activity.html +6 -6
  70. package/payload/server/public/agents.html +5 -5
  71. package/payload/server/public/assets/{AdminLoginScreens-DuQ3ax5U.js → AdminLoginScreens-BBKEIEjY.js} +1 -1
  72. package/payload/server/public/assets/AdminLoginScreens-BBKEIEjY.js.br +0 -0
  73. package/payload/server/public/assets/AdminLoginScreens-BBKEIEjY.js.gz +0 -0
  74. package/payload/server/public/assets/{AdminShell-1HyATV_t.js → AdminShell-CrCdEMNW.js} +1 -1
  75. package/payload/server/public/assets/AdminShell-CrCdEMNW.js.br +0 -0
  76. package/payload/server/public/assets/AdminShell-CrCdEMNW.js.gz +0 -0
  77. package/payload/server/public/assets/{activity-C2qWlcJ5.js → activity-CbR3vaq1.js} +1 -1
  78. package/payload/server/public/assets/activity-CbR3vaq1.js.br +0 -0
  79. package/payload/server/public/assets/activity-CbR3vaq1.js.gz +0 -0
  80. package/payload/server/public/assets/{admin-DqpfY8-t.js → admin-D8qKnmhE.js} +1 -1
  81. package/payload/server/public/assets/admin-D8qKnmhE.js.br +0 -0
  82. package/payload/server/public/assets/admin-D8qKnmhE.js.gz +0 -0
  83. package/payload/server/public/assets/{agents-DoGVAjIT.js → agents-8gAQjRqI.js} +1 -1
  84. package/payload/server/public/assets/agents-8gAQjRqI.js.br +0 -0
  85. package/payload/server/public/assets/agents-8gAQjRqI.js.gz +0 -0
  86. package/payload/server/public/assets/{browser-DjqS897k.js → browser-jAje5ILy.js} +1 -1
  87. package/payload/server/public/assets/browser-jAje5ILy.js.br +0 -0
  88. package/payload/server/public/assets/browser-jAje5ILy.js.gz +0 -0
  89. package/payload/server/public/assets/{calendar-DJeOm0WS.js → calendar-xEmCJif9.js} +1 -1
  90. package/payload/server/public/assets/calendar-xEmCJif9.js.br +0 -0
  91. package/payload/server/public/assets/calendar-xEmCJif9.js.gz +0 -0
  92. package/payload/server/public/assets/chat-Ct5RDmv6.js +1 -0
  93. package/payload/server/public/assets/chat-Ct5RDmv6.js.br +0 -0
  94. package/payload/server/public/assets/chat-Ct5RDmv6.js.gz +0 -0
  95. package/payload/server/public/assets/{chevron-left-D9z1aK3p.js → chevron-left-DV8gIVC9.js} +1 -1
  96. package/payload/server/public/assets/chevron-left-DV8gIVC9.js.br +0 -0
  97. package/payload/server/public/assets/clock-PnR4Tlg9.js +1 -0
  98. package/payload/server/public/assets/clock-PnR4Tlg9.js.br +0 -0
  99. package/payload/server/public/assets/{copy-p9ZM9fnn.js → copy-b6MpzRz9.js} +1 -1
  100. package/payload/server/public/assets/copy-b6MpzRz9.js.br +0 -0
  101. package/payload/server/public/assets/copy-b6MpzRz9.js.gz +0 -0
  102. package/payload/server/public/assets/data-BxFc9wPl.js +1 -0
  103. package/payload/server/public/assets/data-BxFc9wPl.js.br +2 -0
  104. package/payload/server/public/assets/data-BxFc9wPl.js.gz +0 -0
  105. package/payload/server/public/assets/{field-WRk67lvr.js → field-CiIsn-47.js} +1 -1
  106. package/payload/server/public/assets/field-CiIsn-47.js.br +0 -0
  107. package/payload/server/public/assets/field-CiIsn-47.js.gz +0 -0
  108. package/payload/server/public/assets/{file-text-BJDCb334.js → file-text-Cy-IjCf8.js} +1 -1
  109. package/payload/server/public/assets/file-text-Cy-IjCf8.js.br +0 -0
  110. package/payload/server/public/assets/file-text-Cy-IjCf8.js.gz +0 -0
  111. package/payload/server/public/assets/{graph-CAU2xh2Z.js → graph-BdC2IGOT.js} +1 -1
  112. package/payload/server/public/assets/graph-BdC2IGOT.js.br +0 -0
  113. package/payload/server/public/assets/graph-BdC2IGOT.js.gz +0 -0
  114. package/payload/server/public/assets/{graph-labels-CEDmX7qW.js → graph-labels-3bstML8n.js} +1 -1
  115. package/payload/server/public/assets/graph-labels-3bstML8n.js.br +0 -0
  116. package/payload/server/public/assets/graph-labels-3bstML8n.js.gz +0 -0
  117. package/payload/server/public/assets/{jsx-runtime-hPFGo4rC.css → jsx-runtime-JxxB_2S6.css} +1 -1
  118. package/payload/server/public/assets/jsx-runtime-JxxB_2S6.css.br +0 -0
  119. package/payload/server/public/assets/{jsx-runtime-hPFGo4rC.css.gz → jsx-runtime-JxxB_2S6.css.gz} +0 -0
  120. package/payload/server/public/assets/{operator-1WFztEQA.js → operator-D7L2SA16.js} +1 -1
  121. package/payload/server/public/assets/operator-D7L2SA16.js.br +0 -0
  122. package/payload/server/public/assets/operator-D7L2SA16.js.gz +0 -0
  123. package/payload/server/public/assets/{page-B72XigcU.js → page-DspGbDgK.js} +1 -1
  124. package/payload/server/public/assets/page-DspGbDgK.js.br +0 -0
  125. package/payload/server/public/assets/page-DspGbDgK.js.gz +0 -0
  126. package/payload/server/public/assets/{page-nT8l_cX1.js → page-ObXxk8ol.js} +1 -1
  127. package/payload/server/public/assets/page-ObXxk8ol.js.br +0 -0
  128. package/payload/server/public/assets/page-ObXxk8ol.js.gz +0 -0
  129. package/payload/server/public/assets/play-BL4ztD3m.js +1 -0
  130. package/payload/server/public/assets/play-BL4ztD3m.js.br +0 -0
  131. package/payload/server/public/assets/play-BL4ztD3m.js.gz +0 -0
  132. package/payload/server/public/assets/{public-DAea6w7w.js → public-CEXKl8wg.js} +1 -1
  133. package/payload/server/public/assets/public-CEXKl8wg.js.br +0 -0
  134. package/payload/server/public/assets/public-CEXKl8wg.js.gz +0 -0
  135. package/payload/server/public/assets/{rotate-ccw-CqlRlGsY.js → rotate-ccw-ckXDebK2.js} +1 -1
  136. package/payload/server/public/assets/rotate-ccw-ckXDebK2.js.br +0 -0
  137. package/payload/server/public/assets/rotate-ccw-ckXDebK2.js.gz +0 -0
  138. package/payload/server/public/assets/{routines-vy5xsyqx.js → routines-Dwc2meN2.js} +1 -1
  139. package/payload/server/public/assets/routines-Dwc2meN2.js.br +0 -0
  140. package/payload/server/public/assets/routines-Dwc2meN2.js.gz +0 -0
  141. package/payload/server/public/assets/{skills-X7FTilVy.js → skills-DdHKHl4Z.js} +1 -1
  142. package/payload/server/public/assets/skills-DdHKHl4Z.js.br +0 -0
  143. package/payload/server/public/assets/skills-DdHKHl4Z.js.gz +0 -0
  144. package/payload/server/public/assets/{tasks-BXx_jk_p.js → tasks-C_yWRvOG.js} +1 -1
  145. package/payload/server/public/assets/tasks-C_yWRvOG.js.br +0 -0
  146. package/payload/server/public/assets/tasks-C_yWRvOG.js.gz +0 -0
  147. package/payload/server/public/assets/{triangle-alert-CuWcGaRo.js → triangle-alert-C-7A00P0.js} +1 -1
  148. package/payload/server/public/assets/triangle-alert-C-7A00P0.js.br +0 -0
  149. package/payload/server/public/assets/triangle-alert-C-7A00P0.js.gz +0 -0
  150. package/payload/server/public/assets/{useCopyFeedback-DLSKs6r2.js → useCopyFeedback-CWvSi-Qe.js} +1 -1
  151. package/payload/server/public/assets/useCopyFeedback-CWvSi-Qe.js.br +2 -0
  152. package/payload/server/public/assets/useCopyFeedback-CWvSi-Qe.js.gz +0 -0
  153. package/payload/server/public/assets/{useMediaQuery-C_1CV_iW.js → useMediaQuery-CLVRXJMp.js} +1 -1
  154. package/payload/server/public/assets/useMediaQuery-CLVRXJMp.js.br +0 -0
  155. package/payload/server/public/assets/useMediaQuery-CLVRXJMp.js.gz +0 -0
  156. package/payload/server/public/assets/{useVoiceRecorder-DBzM_HIx.js → useVoiceRecorder-BbJad9CQ.js} +1 -1
  157. package/payload/server/public/assets/useVoiceRecorder-BbJad9CQ.js.br +0 -0
  158. package/payload/server/public/assets/useVoiceRecorder-BbJad9CQ.js.gz +0 -0
  159. package/payload/server/public/assets/{wrench-nDMQ6x-6.js → wrench-Cl6TUm2T.js} +1 -1
  160. package/payload/server/public/assets/wrench-Cl6TUm2T.js.br +0 -0
  161. package/payload/server/public/assets/wrench-Cl6TUm2T.js.gz +0 -0
  162. package/payload/server/public/browser.html +5 -5
  163. package/payload/server/public/calendar.html +8 -8
  164. package/payload/server/public/chat.html +14 -14
  165. package/payload/server/public/data.html +12 -12
  166. package/payload/server/public/field.html +3 -3
  167. package/payload/server/public/graph.html +10 -10
  168. package/payload/server/public/index.html +14 -14
  169. package/payload/server/public/operator.html +15 -15
  170. package/payload/server/public/public.html +14 -14
  171. package/payload/server/public/routines.html +8 -8
  172. package/payload/server/public/skills.html +6 -6
  173. package/payload/server/public/tasks.html +7 -7
  174. package/payload/server/server.js +511 -283
  175. package/payload/server/public/assets/AdminLoginScreens-DuQ3ax5U.js.br +0 -0
  176. package/payload/server/public/assets/AdminLoginScreens-DuQ3ax5U.js.gz +0 -0
  177. package/payload/server/public/assets/AdminShell-1HyATV_t.js.br +0 -0
  178. package/payload/server/public/assets/AdminShell-1HyATV_t.js.gz +0 -0
  179. package/payload/server/public/assets/activity-C2qWlcJ5.js.br +0 -0
  180. package/payload/server/public/assets/activity-C2qWlcJ5.js.gz +0 -0
  181. package/payload/server/public/assets/admin-DqpfY8-t.js.br +0 -0
  182. package/payload/server/public/assets/admin-DqpfY8-t.js.gz +0 -0
  183. package/payload/server/public/assets/agents-DoGVAjIT.js.br +0 -0
  184. package/payload/server/public/assets/agents-DoGVAjIT.js.gz +0 -0
  185. package/payload/server/public/assets/browser-DjqS897k.js.br +0 -0
  186. package/payload/server/public/assets/browser-DjqS897k.js.gz +0 -0
  187. package/payload/server/public/assets/calendar-DJeOm0WS.js.br +0 -0
  188. package/payload/server/public/assets/calendar-DJeOm0WS.js.gz +0 -0
  189. package/payload/server/public/assets/chat-BrRE0Dck.js +0 -1
  190. package/payload/server/public/assets/chat-BrRE0Dck.js.br +0 -0
  191. package/payload/server/public/assets/chat-BrRE0Dck.js.gz +0 -0
  192. package/payload/server/public/assets/chevron-left-D9z1aK3p.js.br +0 -0
  193. package/payload/server/public/assets/clock-BqHWMICF.js +0 -1
  194. package/payload/server/public/assets/clock-BqHWMICF.js.br +0 -0
  195. package/payload/server/public/assets/clock-BqHWMICF.js.gz +0 -0
  196. package/payload/server/public/assets/copy-p9ZM9fnn.js.br +0 -0
  197. package/payload/server/public/assets/copy-p9ZM9fnn.js.gz +0 -0
  198. package/payload/server/public/assets/data-CPsqW0qW.js +0 -1
  199. package/payload/server/public/assets/data-CPsqW0qW.js.br +0 -0
  200. package/payload/server/public/assets/data-CPsqW0qW.js.gz +0 -0
  201. package/payload/server/public/assets/field-WRk67lvr.js.br +0 -0
  202. package/payload/server/public/assets/field-WRk67lvr.js.gz +0 -0
  203. package/payload/server/public/assets/file-text-BJDCb334.js.br +0 -0
  204. package/payload/server/public/assets/file-text-BJDCb334.js.gz +0 -0
  205. package/payload/server/public/assets/graph-CAU2xh2Z.js.br +0 -0
  206. package/payload/server/public/assets/graph-CAU2xh2Z.js.gz +0 -0
  207. package/payload/server/public/assets/graph-labels-CEDmX7qW.js.br +0 -0
  208. package/payload/server/public/assets/graph-labels-CEDmX7qW.js.gz +0 -0
  209. package/payload/server/public/assets/jsx-runtime-hPFGo4rC.css.br +0 -0
  210. package/payload/server/public/assets/operator-1WFztEQA.js.br +0 -0
  211. package/payload/server/public/assets/operator-1WFztEQA.js.gz +0 -0
  212. package/payload/server/public/assets/page-B72XigcU.js.br +0 -0
  213. package/payload/server/public/assets/page-B72XigcU.js.gz +0 -0
  214. package/payload/server/public/assets/page-nT8l_cX1.js.br +0 -0
  215. package/payload/server/public/assets/page-nT8l_cX1.js.gz +0 -0
  216. package/payload/server/public/assets/play-CYqOCqm6.js +0 -1
  217. package/payload/server/public/assets/play-CYqOCqm6.js.br +0 -0
  218. package/payload/server/public/assets/play-CYqOCqm6.js.gz +0 -0
  219. package/payload/server/public/assets/public-DAea6w7w.js.br +0 -0
  220. package/payload/server/public/assets/public-DAea6w7w.js.gz +0 -0
  221. package/payload/server/public/assets/rotate-ccw-CqlRlGsY.js.br +0 -0
  222. package/payload/server/public/assets/rotate-ccw-CqlRlGsY.js.gz +0 -0
  223. package/payload/server/public/assets/routines-vy5xsyqx.js.br +0 -0
  224. package/payload/server/public/assets/routines-vy5xsyqx.js.gz +0 -0
  225. package/payload/server/public/assets/skills-X7FTilVy.js.br +0 -0
  226. package/payload/server/public/assets/skills-X7FTilVy.js.gz +0 -0
  227. package/payload/server/public/assets/tasks-BXx_jk_p.js.br +0 -0
  228. package/payload/server/public/assets/tasks-BXx_jk_p.js.gz +0 -0
  229. package/payload/server/public/assets/triangle-alert-CuWcGaRo.js.br +0 -0
  230. package/payload/server/public/assets/triangle-alert-CuWcGaRo.js.gz +0 -0
  231. package/payload/server/public/assets/useCopyFeedback-DLSKs6r2.js.br +0 -0
  232. package/payload/server/public/assets/useCopyFeedback-DLSKs6r2.js.gz +0 -0
  233. package/payload/server/public/assets/useMediaQuery-C_1CV_iW.js.br +0 -0
  234. package/payload/server/public/assets/useMediaQuery-C_1CV_iW.js.gz +0 -0
  235. package/payload/server/public/assets/useVoiceRecorder-DBzM_HIx.js.br +0 -0
  236. package/payload/server/public/assets/useVoiceRecorder-DBzM_HIx.js.gz +0 -0
  237. package/payload/server/public/assets/wrench-nDMQ6x-6.js.br +0 -0
  238. package/payload/server/public/assets/wrench-nDMQ6x-6.js.gz +0 -0
  239. /package/payload/server/public/assets/{jsx-runtime-CvVXOszc.js → jsx-runtime-BCAZhYqz.js} +0 -0
  240. /package/payload/server/public/assets/{jsx-runtime-CvVXOszc.js.br → jsx-runtime-BCAZhYqz.js.br} +0 -0
  241. /package/payload/server/public/assets/{jsx-runtime-CvVXOszc.js.gz → jsx-runtime-BCAZhYqz.js.gz} +0 -0
@@ -0,0 +1,169 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
2
+ import { noteXacctWriteCall, noteXacctWriteOutcome, readXacctWindow, __resetXacctWindow, formatWriteOutcome, formatXacctCensus, writeOutcomeReason, startXacctCensus, } from "../xacct-counters.js";
3
+ import { INVALID_ACCOUNT_ID_REASON } from "../../../../../../lib/graph-write/dist/index.js";
4
+ // Task 2731 — the two signals that make an allow-then-refuse pair visible.
5
+ //
6
+ // Both halves of the defect were already logged before this task, in one file,
7
+ // and nothing reconciled them: `[xacct] op=call … decision=allow` from the
8
+ // resolver, `[graph-write] reject reason=invalid-account-id` from the gate. The
9
+ // write-outcome line pairs them on one row. The census answers the other
10
+ // question a per-call line cannot: whether the feature is being exercised at
11
+ // all, because a capability nobody invokes reads identically to one that works.
12
+ beforeEach(() => {
13
+ __resetXacctWindow();
14
+ });
15
+ describe("cross-account write counters", () => {
16
+ it("starts empty", () => {
17
+ expect(readXacctWindow()).toEqual({
18
+ writeCalls: 0,
19
+ allowed: 0,
20
+ gatePassed: 0,
21
+ gateRejected: 0,
22
+ });
23
+ });
24
+ it("counts an allowed call and its passing write", () => {
25
+ noteXacctWriteCall(true);
26
+ noteXacctWriteOutcome("passed");
27
+ expect(readXacctWindow()).toEqual({
28
+ writeCalls: 1,
29
+ allowed: 1,
30
+ gatePassed: 1,
31
+ gateRejected: 0,
32
+ });
33
+ });
34
+ it("counts a denied call as a call but never as allowed", () => {
35
+ noteXacctWriteCall(false);
36
+ expect(readXacctWindow()).toEqual({
37
+ writeCalls: 1,
38
+ allowed: 0,
39
+ gatePassed: 0,
40
+ gateRejected: 0,
41
+ });
42
+ });
43
+ it("counts a gate rejection separately from a pass", () => {
44
+ noteXacctWriteCall(true);
45
+ noteXacctWriteOutcome("rejected");
46
+ expect(readXacctWindow()).toEqual({
47
+ writeCalls: 1,
48
+ allowed: 1,
49
+ gatePassed: 0,
50
+ gateRejected: 1,
51
+ });
52
+ });
53
+ });
54
+ describe("write-outcome line", () => {
55
+ it("renders the pass case with reason=none", () => {
56
+ expect(formatWriteOutcome({
57
+ tool: "memory-write",
58
+ caller: "20028fb5-db26-4de7-bff6-c5ec2d4fc766",
59
+ target: "8a160746-1111-2222-3333-444444444444",
60
+ gate: "passed",
61
+ reason: null,
62
+ })).toBe("[xacct] op=write-outcome tool=memory-write caller=20028fb5 target=8a160746 gate=passed reason=none\n");
63
+ });
64
+ it("renders the defect signature: an allowed call the gate refused", () => {
65
+ expect(formatWriteOutcome({
66
+ tool: "memory-write",
67
+ caller: "20028fb5-db26-4de7-bff6-c5ec2d4fc766",
68
+ target: "8a160746-1111-2222-3333-444444444444",
69
+ gate: "rejected",
70
+ reason: "invalid-account-id",
71
+ })).toBe("[xacct] op=write-outcome tool=memory-write caller=20028fb5 target=8a160746 gate=rejected reason=invalid-account-id\n");
72
+ });
73
+ it("names a non-gate failure without claiming the gate refused it", () => {
74
+ const line = formatWriteOutcome({
75
+ tool: "memory-write",
76
+ caller: "20028fb5-db26-4de7-bff6-c5ec2d4fc766",
77
+ target: "8a160746-1111-2222-3333-444444444444",
78
+ gate: "passed",
79
+ reason: "schema-validation",
80
+ });
81
+ expect(line).toContain("gate=passed");
82
+ expect(line).toContain("reason=schema-validation");
83
+ });
84
+ it("collapses whitespace in a reason so one failure cannot become two fields", () => {
85
+ const line = formatWriteOutcome({
86
+ tool: "memory-write",
87
+ caller: "20028fb5-db26-4de7-bff6-c5ec2d4fc766",
88
+ target: "8a160746-1111-2222-3333-444444444444",
89
+ gate: "passed",
90
+ reason: "write blocked (system-managed-key)",
91
+ });
92
+ // [xacct] op= tool= caller= target= gate= reason= — seven, always.
93
+ expect(line.trimEnd().split(" ")).toHaveLength(7);
94
+ expect(line).toContain("reason=write-blocked-(system-managed-key)");
95
+ });
96
+ });
97
+ describe("reason vocabulary", () => {
98
+ it("names a gate rejection with the code an operator greps", () => {
99
+ expect(writeOutcomeReason("rejected", true)).toBe("invalid-account-id");
100
+ });
101
+ it("uses the chokepoint's own constant, so the two cannot drift", () => {
102
+ expect(writeOutcomeReason("rejected", true)).toBe(INVALID_ACCOUNT_ID_REASON);
103
+ });
104
+ it("names a non-gate failure without claiming the gate refused it", () => {
105
+ expect(writeOutcomeReason("passed", true)).toBe("write-failed");
106
+ });
107
+ it("is null when the write succeeded", () => {
108
+ expect(writeOutcomeReason("passed", false)).toBeNull();
109
+ });
110
+ it("never yields an error message, whatever the failure", () => {
111
+ // The whole point: `reason` is a name. A message would carry spaces,
112
+ // punctuation and an account slice into a field an operator greps by
113
+ // equality, and match nothing.
114
+ for (const gate of ["passed", "rejected"]) {
115
+ const reason = writeOutcomeReason(gate, true);
116
+ expect(reason).toMatch(/^[a-z-]+$/);
117
+ }
118
+ });
119
+ });
120
+ describe("census line", () => {
121
+ it("renders every counter for the window", () => {
122
+ expect(formatXacctCensus(1, {
123
+ writeCalls: 13,
124
+ allowed: 13,
125
+ gatePassed: 0,
126
+ gateRejected: 13,
127
+ })).toBe("[xacct-census] window=1 writeCalls=13 allowed=13 gatePassed=0 gateRejected=13\n");
128
+ });
129
+ it("emits a zero window rather than staying silent", () => {
130
+ // A quiet period and a broken feature must be distinguishable. Suppressing
131
+ // the zero line would render them identically.
132
+ expect(formatXacctCensus(1, readXacctWindow())).toBe("[xacct-census] window=1 writeCalls=0 allowed=0 gatePassed=0 gateRejected=0\n");
133
+ });
134
+ });
135
+ describe("census timer", () => {
136
+ afterEach(() => {
137
+ vi.useRealTimers();
138
+ });
139
+ it("emits once per window and resets the counters", () => {
140
+ vi.useFakeTimers();
141
+ const emit = vi.fn();
142
+ const handle = startXacctCensus({ intervalMs: 1000, windowHours: 1, emit });
143
+ try {
144
+ noteXacctWriteCall(true);
145
+ noteXacctWriteOutcome("rejected");
146
+ vi.advanceTimersByTime(1000);
147
+ expect(emit).toHaveBeenCalledTimes(1);
148
+ expect(emit.mock.calls[0][0]).toBe("[xacct-census] window=1 writeCalls=1 allowed=1 gatePassed=0 gateRejected=1\n");
149
+ vi.advanceTimersByTime(1000);
150
+ expect(emit.mock.calls[1][0]).toBe("[xacct-census] window=1 writeCalls=0 allowed=0 gatePassed=0 gateRejected=0\n");
151
+ }
152
+ finally {
153
+ clearInterval(handle);
154
+ }
155
+ });
156
+ it("does not hold the MCP child open", () => {
157
+ // The memory plugin is a stdio child of a session. A referenced timer keeps
158
+ // the event loop alive, so the child would outlive the session that spawned
159
+ // it. Real timers here on purpose: a faked handle has no ref to inspect.
160
+ const handle = startXacctCensus({ intervalMs: 1000, windowHours: 1, emit: vi.fn() });
161
+ try {
162
+ expect(handle.hasRef()).toBe(false);
163
+ }
164
+ finally {
165
+ clearInterval(handle);
166
+ }
167
+ });
168
+ });
169
+ //# sourceMappingURL=xacct-counters.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xacct-counters.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/xacct-counters.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ export interface XacctWindow {
2
+ /** Write-tool calls that carried a `targetAccountId`. */
3
+ writeCalls: number;
4
+ /** Of those, the ones `resolveToolAccount` authorised. */
5
+ allowed: number;
6
+ /** Of those allowed, the ones that got past the graph-write account gate. */
7
+ gatePassed: number;
8
+ /** Of those allowed, the ones the account gate refused. Healthy = 0. */
9
+ gateRejected: number;
10
+ }
11
+ /** Record a cross-account write-tool call and whether the resolver allowed it. */
12
+ export declare function noteXacctWriteCall(allowed: boolean): void;
13
+ /** Record what the graph-write account gate did with an allowed call. */
14
+ export declare function noteXacctWriteOutcome(gate: "passed" | "rejected"): void;
15
+ export declare function readXacctWindow(): XacctWindow;
16
+ /** Test seam. Production resets happen only on a census emit. */
17
+ export declare function __resetXacctWindow(): void;
18
+ export interface WriteOutcome {
19
+ tool: string;
20
+ /** The calling session's own account. */
21
+ caller: string;
22
+ /** The authorised target account this write was resolved to. */
23
+ target: string;
24
+ /**
25
+ * `rejected` iff the graph-write ACCOUNT gate refused the write. A write that
26
+ * failed for any other reason got past that gate, so it reads `passed` with
27
+ * the failure named in `reason` — conflating the two would hide the one
28
+ * pairing this line exists to catch.
29
+ */
30
+ gate: "passed" | "rejected";
31
+ /** `null` when the write succeeded. */
32
+ reason: string | null;
33
+ }
34
+ /**
35
+ * The three names `reason` may take, and nothing else. An operator greps
36
+ * `reason=invalid-account-id`; an error message pasted into the field would
37
+ * carry its own punctuation and account slice and match nothing. The detail of a
38
+ * non-gate failure is already on the `[mcp:memory] … result=error` line for the
39
+ * same call, so nothing is lost by naming it `write-failed` here.
40
+ */
41
+ export declare function writeOutcomeReason(gate: "passed" | "rejected", failed: boolean): string | null;
42
+ /**
43
+ * The paired line. `caller` and `target` are sliced to 8 characters to match the
44
+ * `[xacct] op=call` line a reader joins this against.
45
+ */
46
+ export declare function formatWriteOutcome(o: WriteOutcome): string;
47
+ export declare function formatXacctCensus(windowHours: number, w: XacctWindow): string;
48
+ export interface CensusOptions {
49
+ intervalMs?: number;
50
+ windowHours?: number;
51
+ emit?: (line: string) => void;
52
+ }
53
+ /**
54
+ * Arm the standing census. The returned handle is unref'd: this runs inside a
55
+ * stdio child of a session, and a referenced timer would keep that child alive
56
+ * after the session it serves has gone.
57
+ *
58
+ * A window with no cross-account calls still emits. Suppressing the zero would
59
+ * make a quiet period and a dead feature look identical, which is the failure
60
+ * this census exists to catch.
61
+ */
62
+ export declare function startXacctCensus(opts?: CensusOptions): NodeJS.Timeout;
63
+ //# sourceMappingURL=xacct-counters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xacct-counters.d.ts","sourceRoot":"","sources":["../../src/lib/xacct-counters.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAC;CACtB;AASD,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAGzD;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,IAAI,CAGvE;AAED,wBAAgB,eAAe,IAAI,WAAW,CAE7C;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC;AAWD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,uCAAuC;IACvC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,GAAG,UAAU,EAC3B,MAAM,EAAE,OAAO,GACd,MAAM,GAAG,IAAI,CAMf;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAM1D;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,GAAG,MAAM,CAK7E;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAID;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,aAAkB,GAAG,MAAM,CAAC,OAAO,CAUzE"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Cross-account write observability (Task 2731).
3
+ *
4
+ * Task 1455 authorises a house-scoped session to write into a sub-account
5
+ * subgraph; the graph-write account gate then refused that same write because
6
+ * the stamped account was not the process's own. Both halves were logged, in
7
+ * one file, and nothing reconciled them — so the feature was broken on every
8
+ * account and every install for as long as it existed, while the tool
9
+ * descriptions advertised it.
10
+ *
11
+ * Two signals close that. `formatWriteOutcome` pairs the resolver's decision
12
+ * with the write's fate on one row, so `decision=allow` followed by
13
+ * `gate=rejected` is a single greppable shape rather than two lines a reader
14
+ * has to join. `formatXacctCensus` answers the question a per-call line cannot:
15
+ * whether the capability is being exercised at all, because a feature nobody
16
+ * invokes reads identically to one that works.
17
+ *
18
+ * The counters live here, in the memory MCP child, because that is the process
19
+ * that observes both halves. A census keyed on grepping `server.log` would go
20
+ * blind exactly when the install is busiest — `dispatch-tg-census.ts` documents
21
+ * why. The cost of counting in the child is that the window is per-child: a
22
+ * short session emits no census at all.
23
+ *
24
+ * Only cross-account calls are counted, so `writeCalls=0` means precisely "no
25
+ * session targeted another account this window", not "no writes happened".
26
+ */
27
+ import { INVALID_ACCOUNT_ID_REASON } from "../../../../../lib/graph-write/dist/index.js";
28
+ const window = {
29
+ writeCalls: 0,
30
+ allowed: 0,
31
+ gatePassed: 0,
32
+ gateRejected: 0,
33
+ };
34
+ /** Record a cross-account write-tool call and whether the resolver allowed it. */
35
+ export function noteXacctWriteCall(allowed) {
36
+ window.writeCalls++;
37
+ if (allowed)
38
+ window.allowed++;
39
+ }
40
+ /** Record what the graph-write account gate did with an allowed call. */
41
+ export function noteXacctWriteOutcome(gate) {
42
+ if (gate === "passed")
43
+ window.gatePassed++;
44
+ else
45
+ window.gateRejected++;
46
+ }
47
+ export function readXacctWindow() {
48
+ return { ...window };
49
+ }
50
+ /** Test seam. Production resets happen only on a census emit. */
51
+ export function __resetXacctWindow() {
52
+ window.writeCalls = 0;
53
+ window.allowed = 0;
54
+ window.gatePassed = 0;
55
+ window.gateRejected = 0;
56
+ }
57
+ /**
58
+ * A log field must never split into two. A reason sourced from an error message
59
+ * can carry spaces, which would shift every field after it and make the line
60
+ * unparseable, so whitespace collapses to a single dash.
61
+ */
62
+ function field(value) {
63
+ return value.trim().replace(/\s+/g, "-");
64
+ }
65
+ /**
66
+ * The three names `reason` may take, and nothing else. An operator greps
67
+ * `reason=invalid-account-id`; an error message pasted into the field would
68
+ * carry its own punctuation and account slice and match nothing. The detail of a
69
+ * non-gate failure is already on the `[mcp:memory] … result=error` line for the
70
+ * same call, so nothing is lost by naming it `write-failed` here.
71
+ */
72
+ export function writeOutcomeReason(gate, failed) {
73
+ if (!failed)
74
+ return null;
75
+ // Imported, never spelled out again: the operator's grep and the chokepoint's
76
+ // own reason code must be the same string, and a second literal would let
77
+ // them drift silently.
78
+ return gate === "rejected" ? INVALID_ACCOUNT_ID_REASON : "write-failed";
79
+ }
80
+ /**
81
+ * The paired line. `caller` and `target` are sliced to 8 characters to match the
82
+ * `[xacct] op=call` line a reader joins this against.
83
+ */
84
+ export function formatWriteOutcome(o) {
85
+ return (`[xacct] op=write-outcome tool=${field(o.tool)} caller=${o.caller.slice(0, 8)} ` +
86
+ `target=${o.target.slice(0, 8)} gate=${o.gate} ` +
87
+ `reason=${o.reason === null ? "none" : field(o.reason)}\n`);
88
+ }
89
+ export function formatXacctCensus(windowHours, w) {
90
+ return (`[xacct-census] window=${windowHours} writeCalls=${w.writeCalls} ` +
91
+ `allowed=${w.allowed} gatePassed=${w.gatePassed} gateRejected=${w.gateRejected}\n`);
92
+ }
93
+ const HOUR_MS = 60 * 60 * 1000;
94
+ /**
95
+ * Arm the standing census. The returned handle is unref'd: this runs inside a
96
+ * stdio child of a session, and a referenced timer would keep that child alive
97
+ * after the session it serves has gone.
98
+ *
99
+ * A window with no cross-account calls still emits. Suppressing the zero would
100
+ * make a quiet period and a dead feature look identical, which is the failure
101
+ * this census exists to catch.
102
+ */
103
+ export function startXacctCensus(opts = {}) {
104
+ const intervalMs = opts.intervalMs ?? HOUR_MS;
105
+ const windowHours = opts.windowHours ?? intervalMs / HOUR_MS;
106
+ const emit = opts.emit ?? ((line) => void process.stderr.write(line));
107
+ const handle = setInterval(() => {
108
+ emit(formatXacctCensus(windowHours, readXacctWindow()));
109
+ __resetXacctWindow();
110
+ }, intervalMs);
111
+ handle.unref();
112
+ return handle;
113
+ }
114
+ //# sourceMappingURL=xacct-counters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xacct-counters.js","sourceRoot":"","sources":["../../src/lib/xacct-counters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=memory-write-cross-account.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-write-cross-account.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/memory-write-cross-account.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,123 @@
1
+ // Task 2731 — a house-scoped session writing into an authorised sub-account.
2
+ //
3
+ // The graph-write account gate runs for real here. Only Neo4j and the embedder
4
+ // are stubbed, so the test exercises the same chain the defect lived on:
5
+ // `memoryWrite` receives the account `resolveToolAccount` authorised, stamps it
6
+ // on the node, and hands it to the chokepoint as `expectedAccountId`. Before
7
+ // this task the last step was missing, the chokepoint fell back to
8
+ // `process.env.ACCOUNT_ID` (the house account), and every cross-account write
9
+ // was allowed by the resolver and then refused by the chokepoint.
10
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
11
+ const HOUSE_ACCOUNT = "20028fb5-db26-4de7-bff6-c5ec2d4fc766";
12
+ const SUB_ACCOUNT = "8a160746-1111-2222-3333-444444444444";
13
+ const TARGET_NODE = "target-element-id";
14
+ let createdProps = null;
15
+ function rec(map) {
16
+ return { get: (k) => map[k] };
17
+ }
18
+ /** Resolves the edge-target pre-check, the node CREATE, and the edge CREATE. */
19
+ function makeStubSession() {
20
+ const tx = {
21
+ run: vi.fn(async (cypher, params = {}) => {
22
+ if (cypher.includes("RETURN elementId(t) AS id, labels(t) AS labels")) {
23
+ const ids = params.ids ?? [];
24
+ return { records: ids.filter((id) => id === TARGET_NODE).map((id) => rec({ id, labels: ["Person"] })) };
25
+ }
26
+ if (cypher.includes("CREATE (n:")) {
27
+ createdProps = params.props;
28
+ return { records: [rec({ nodeId: "new-node-id", nodeLabels: ["Person"] })] };
29
+ }
30
+ return { summary: { counters: { updates: () => ({ relationshipsCreated: 1 }) } } };
31
+ }),
32
+ };
33
+ return {
34
+ run: vi.fn(async () => ({ records: [] })),
35
+ close: vi.fn(async () => undefined),
36
+ executeWrite: async (work) => work(tx),
37
+ };
38
+ }
39
+ vi.mock("../../lib/neo4j.js", () => ({ getSession: () => makeStubSession() }));
40
+ vi.mock("../../lib/embeddings.js", () => ({ embed: async () => [0.1, 0.2, 0.3] }));
41
+ const { memoryWrite } = await import("../memory-write.js");
42
+ const PERSON = new Set(["Person"]);
43
+ // The smallest validator env that lets a `Person` write through: `Person` is
44
+ // recognised, nothing is a synonym, nothing is forbidden, nothing is required.
45
+ // Schema rules are not what this test is about.
46
+ const VALIDATOR = {
47
+ schema: {
48
+ markdownLabels: new Map(),
49
+ wrongToCorrect: new Map(),
50
+ forbiddenByLabel: new Map(),
51
+ documentedCanonicals: [],
52
+ sourceFiles: ["schema-base.md"],
53
+ brandVertical: null,
54
+ relationshipPatterns: [],
55
+ },
56
+ liveSource: {
57
+ liveLabels: () => PERSON,
58
+ declaredLabels: () => PERSON,
59
+ recognisedLabels: () => PERSON,
60
+ },
61
+ };
62
+ const RELATIONSHIPS = [
63
+ { type: "KNOWS", direction: "outgoing", targetNodeId: TARGET_NODE },
64
+ ];
65
+ function write(accountId) {
66
+ return memoryWrite({
67
+ labels: ["Person"],
68
+ properties: { name: "Ada Lovelace" },
69
+ accountId,
70
+ scope: "admin",
71
+ relationships: RELATIONSHIPS,
72
+ createdBy: { agent: "test-agent", session: "s1", tool: "memory-write" },
73
+ validator: VALIDATOR,
74
+ });
75
+ }
76
+ describe("memory-write cross-account gate (Task 2731)", () => {
77
+ let originalAccountId;
78
+ beforeEach(() => {
79
+ createdProps = null;
80
+ originalAccountId = process.env.ACCOUNT_ID;
81
+ // The spawning process is the house account, as it is on every real
82
+ // cross-account call: the sub-account never spawns this child.
83
+ process.env.ACCOUNT_ID = HOUSE_ACCOUNT;
84
+ });
85
+ afterEach(() => {
86
+ if (originalAccountId === undefined)
87
+ delete process.env.ACCOUNT_ID;
88
+ else
89
+ process.env.ACCOUNT_ID = originalAccountId;
90
+ });
91
+ it("writes to an authorised sub-account and stamps that account", async () => {
92
+ const result = await write(SUB_ACCOUNT);
93
+ expect(result.nodeId).toBe("new-node-id");
94
+ expect(createdProps?.accountId).toBe(SUB_ACCOUNT);
95
+ });
96
+ it("still writes own-account when the resolver returned the house account", async () => {
97
+ const result = await write(HOUSE_ACCOUNT);
98
+ expect(result.nodeId).toBe("new-node-id");
99
+ expect(createdProps?.accountId).toBe(HOUSE_ACCOUNT);
100
+ });
101
+ it("ignores an operator-supplied accountId in the properties bag", async () => {
102
+ // `memoryWrite` overwrites `properties.accountId` with the authorised
103
+ // account, so a caller cannot smuggle a different account past the
104
+ // chokepoint by putting it in the bag. This is also why the red-green
105
+ // anchor for the gate itself lives in the graph-write suite
106
+ // (`account-id-gate.test.ts`) rather than here: inside `memoryWrite` the
107
+ // stamped account and `expectedAccountId` are the same variable, and an
108
+ // empty one is refused by schema validation before any write, so the two
109
+ // can never disagree at this layer.
110
+ const result = await memoryWrite({
111
+ labels: ["Person"],
112
+ properties: { name: "Ada Lovelace", accountId: "ffffffff-9999-9999-9999-999999999999" },
113
+ accountId: SUB_ACCOUNT,
114
+ scope: "admin",
115
+ relationships: RELATIONSHIPS,
116
+ createdBy: { agent: "test-agent", session: "s1", tool: "memory-write" },
117
+ validator: VALIDATOR,
118
+ });
119
+ expect(result.nodeId).toBe("new-node-id");
120
+ expect(createdProps?.accountId).toBe(SUB_ACCOUNT);
121
+ });
122
+ });
123
+ //# sourceMappingURL=memory-write-cross-account.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-write-cross-account.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/memory-write-cross-account.test.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,+EAA+E;AAC/E,yEAAyE;AACzE,gFAAgF;AAChF,6EAA6E;AAC7E,mEAAmE;AACnE,8EAA8E;AAC9E,kEAAkE;AAElE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAEzE,MAAM,aAAa,GAAG,sCAAsC,CAAC;AAC7D,MAAM,WAAW,GAAG,sCAAsC,CAAC;AAC3D,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAExC,IAAI,YAAY,GAAmC,IAAI,CAAC;AAExD,SAAS,GAAG,CAAC,GAA4B;IACvC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AACxC,CAAC;AAED,gFAAgF;AAChF,SAAS,eAAe;IACtB,MAAM,EAAE,GAAG;QACT,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,MAAc,EAAE,SAAkC,EAAE,EAAE,EAAE;YACxE,IAAI,MAAM,CAAC,QAAQ,CAAC,gDAAgD,CAAC,EAAE,CAAC;gBACtE,MAAM,GAAG,GAAI,MAAM,CAAC,GAAgB,IAAI,EAAE,CAAC;gBAC3C,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAC1G,CAAC;YACD,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBAClC,YAAY,GAAG,MAAM,CAAC,KAAgC,CAAC;gBACvD,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAC/E,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,oBAAoB,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;QACrF,CAAC,CAAC;KACH,CAAC;IACF,OAAO;QACL,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;QACzC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;QACnC,YAAY,EAAE,KAAK,EAAM,IAAkC,EAAc,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;KACrF,CAAC;AACJ,CAAC;AAED,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,CAAC;AAC/E,EAAE,CAAC,IAAI,CAAC,yBAAyB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAEnF,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAE3D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AACnC,6EAA6E;AAC7E,+EAA+E;AAC/E,gDAAgD;AAChD,MAAM,SAAS,GAAG;IAChB,MAAM,EAAE;QACN,cAAc,EAAE,IAAI,GAAG,EAAE;QACzB,cAAc,EAAE,IAAI,GAAG,EAAE;QACzB,gBAAgB,EAAE,IAAI,GAAG,EAAE;QAC3B,oBAAoB,EAAE,EAAE;QACxB,WAAW,EAAE,CAAC,gBAAgB,CAAC;QAC/B,aAAa,EAAE,IAAI;QACnB,oBAAoB,EAAE,EAAE;KACzB;IACD,UAAU,EAAE;QACV,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM;QACxB,cAAc,EAAE,GAAG,EAAE,CAAC,MAAM;QAC5B,gBAAgB,EAAE,GAAG,EAAE,CAAC,MAAM;KAC/B;CACO,CAAC;AAEX,MAAM,aAAa,GAAG;IACpB,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE;CAC3D,CAAC;AAEX,SAAS,KAAK,CAAC,SAAiB;IAC9B,OAAO,WAAW,CAAC;QACjB,MAAM,EAAE,CAAC,QAAQ,CAAC;QAClB,UAAU,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE;QACpC,SAAS;QACT,KAAK,EAAE,OAAO;QACd,aAAa,EAAE,aAAa;QAC5B,SAAS,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAW;QAChF,SAAS,EAAE,SAAS;KACrB,CAAC,CAAC;AACL,CAAC;AAED,QAAQ,CAAC,6CAA6C,EAAE,GAAG,EAAE;IAC3D,IAAI,iBAAqC,CAAC;IAE1C,UAAU,CAAC,GAAG,EAAE;QACd,YAAY,GAAG,IAAI,CAAC;QACpB,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;QAC3C,oEAAoE;QACpE,+DAA+D;QAC/D,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,aAAa,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,iBAAiB,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;;YAC9D,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,iBAAiB,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;QACrF,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,sEAAsE;QACtE,mEAAmE;QACnE,sEAAsE;QACtE,4DAA4D;QAC5D,yEAAyE;QACzE,wEAAwE;QACxE,yEAAyE;QACzE,oCAAoC;QACpC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC;YAC/B,MAAM,EAAE,CAAC,QAAQ,CAAC;YAClB,UAAU,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,sCAAsC,EAAE;YACvF,SAAS,EAAE,WAAW;YACtB,KAAK,EAAE,OAAO;YACd,aAAa,EAAE,aAAa;YAC5B,SAAS,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAW;YAChF,SAAS,EAAE,SAAS;SACrB,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=xacct-write-gate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xacct-write-gate.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/xacct-write-gate.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,110 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { readdirSync, readFileSync, statSync } from "node:fs";
3
+ import { resolve, dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ // Task 2731 — the audit that stops this defect recurring.
6
+ //
7
+ // Task 1455 lets a house-scoped session resolve a sub-account target, and
8
+ // `resolveToolAccount` returns that authorised account. `writeNodeWithEdges`
9
+ // then compares the stamped `accountId` against `process.env.ACCOUNT_ID` unless
10
+ // the caller supplies `expectedAccountId`. A call site that omits it therefore
11
+ // gets `decision=allow` from the resolver and `reason=invalid-account-id` from
12
+ // the gate, one for one, inside a single process — which is how every
13
+ // cross-account memory write failed from Task 1455 until this fix.
14
+ //
15
+ // The guard is static because the failure is a missing argument, not a wrong
16
+ // value: a behavioural test only sees it on the cross-account path, which is
17
+ // exactly the path nobody exercised for the months the defect lived.
18
+ const SRC = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
19
+ /** Every `.ts` under `src/`, excluding test files and compiled output. */
20
+ function sourceFiles(dir) {
21
+ const out = [];
22
+ for (const entry of readdirSync(dir)) {
23
+ const full = join(dir, entry);
24
+ if (statSync(full).isDirectory()) {
25
+ if (entry === "__tests__" || entry === "dist" || entry === "node_modules")
26
+ continue;
27
+ out.push(...sourceFiles(full));
28
+ continue;
29
+ }
30
+ if (entry.endsWith(".ts") && !entry.endsWith(".test.ts"))
31
+ out.push(full);
32
+ }
33
+ return out;
34
+ }
35
+ /**
36
+ * The argument list of every `writeNodeWithEdges({ ... })` call in `src`, as
37
+ * `{ file, args }`. The call is always written as a single object literal, so
38
+ * the extent is found by counting braces from the opening `{` — no parser, and
39
+ * no line-window heuristic that a longer call would silently outgrow.
40
+ */
41
+ function writeCallSites() {
42
+ const sites = [];
43
+ for (const file of sourceFiles(SRC)) {
44
+ const src = readFileSync(file, "utf8");
45
+ const needle = "writeNodeWithEdges({";
46
+ let from = 0;
47
+ for (;;) {
48
+ const at = src.indexOf(needle, from);
49
+ if (at === -1)
50
+ break;
51
+ let depth = 0;
52
+ let end = at + needle.length - 1;
53
+ for (let i = end; i < src.length; i++) {
54
+ if (src[i] === "{")
55
+ depth++;
56
+ else if (src[i] === "}") {
57
+ depth--;
58
+ if (depth === 0) {
59
+ end = i;
60
+ break;
61
+ }
62
+ }
63
+ }
64
+ sites.push({ file, args: src.slice(at, end + 1) });
65
+ from = at + needle.length;
66
+ }
67
+ }
68
+ return sites;
69
+ }
70
+ /** Every `writeNodeWithEdges(` invocation, whatever the argument's formatting. */
71
+ function totalInvocations() {
72
+ let n = 0;
73
+ for (const file of sourceFiles(SRC)) {
74
+ // The import binding is not an invocation.
75
+ n += (readFileSync(file, "utf8").match(/writeNodeWithEdges\(/g) ?? []).length;
76
+ }
77
+ return n;
78
+ }
79
+ describe("cross-account write gate (Task 2731)", () => {
80
+ it("finds the memory plugin's writeNodeWithEdges call sites", () => {
81
+ // A zero-site pass would make every assertion below vacuous: the guard
82
+ // could not tell "all call sites are correct" from "the call moved and this
83
+ // test stopped measuring anything".
84
+ expect(writeCallSites().length).toBeGreaterThan(0);
85
+ });
86
+ it("matches every invocation, so no call site can slip past unmeasured", () => {
87
+ // The extent scan keys on `writeNodeWithEdges({`. A call formatted with a
88
+ // line break before the brace would not match, and the assertions below
89
+ // would quietly stop covering it — the exact silent-gap failure this guard
90
+ // exists to prevent. Counting invocations independently turns that into a
91
+ // failure instead.
92
+ expect(writeCallSites().length).toBe(totalInvocations());
93
+ });
94
+ it("passes expectedAccountId at every writeNodeWithEdges call site", () => {
95
+ const missing = writeCallSites()
96
+ .filter((s) => !/\bexpectedAccountId\s*:/.test(s.args))
97
+ .map((s) => s.file.slice(SRC.length + 1));
98
+ expect(missing).toEqual([]);
99
+ });
100
+ it("passes the resolved account, never the process env, as expectedAccountId", () => {
101
+ // `process.env.ACCOUNT_ID` is the house account on a cross-account call.
102
+ // Passing it explicitly would restore the exact defect while satisfying the
103
+ // presence check above.
104
+ const wrong = writeCallSites()
105
+ .filter((s) => /expectedAccountId\s*:\s*process\.env/.test(s.args))
106
+ .map((s) => s.file.slice(SRC.length + 1));
107
+ expect(wrong).toEqual([]);
108
+ });
109
+ });
110
+ //# sourceMappingURL=xacct-write-gate.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xacct-write-gate.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/xacct-write-gate.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"memory-write.d.ts","sourceRoot":"","sources":["../../src/tools/memory-write.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAoB9E,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACf,MAAM,8CAA8C,CAAC;AAEtD,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6HAA6H;IAC7H,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,+EAA+E;IAC/E,SAAS,EAAE,SAAS,CAAC;IACrB;;;;;;OAMG;IACH,SAAS,EAAE,YAAY,CAAC;IACxB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AA+CD,wBAAsB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAS3E;AA4XD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EAAE,EAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,MAAM,CAYR"}
1
+ {"version":3,"file":"memory-write.d.ts","sourceRoot":"","sources":["../../src/tools/memory-write.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAoB9E,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACf,MAAM,8CAA8C,CAAC;AAEtD,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6HAA6H;IAC7H,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,+EAA+E;IAC/E,SAAS,EAAE,SAAS,CAAC;IACrB;;;;;;OAMG;IACH,SAAS,EAAE,YAAY,CAAC;IACxB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AA+CD,wBAAsB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAS3E;AAmYD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EAAE,EAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,MAAM,CAYR"}