@rubytech/create-maxy-code 0.1.572 → 0.1.574

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 (423) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +2 -2
  3. package/payload/platform/lib/admins-write/__tests__/index.test.ts +97 -0
  4. package/payload/platform/lib/admins-write/dist/index.d.ts +12 -0
  5. package/payload/platform/lib/admins-write/dist/index.d.ts.map +1 -1
  6. package/payload/platform/lib/admins-write/dist/index.js +27 -5
  7. package/payload/platform/lib/admins-write/dist/index.js.map +1 -1
  8. package/payload/platform/lib/admins-write/src/index.ts +35 -5
  9. package/payload/platform/lib/admins-write/vitest.config.ts +14 -0
  10. package/payload/platform/lib/dispatch-read/dist/session.d.ts.map +1 -1
  11. package/payload/platform/lib/dispatch-read/dist/session.js +19 -4
  12. package/payload/platform/lib/dispatch-read/dist/session.js.map +1 -1
  13. package/payload/platform/lib/dispatch-read/src/__tests__/session.test.ts +91 -0
  14. package/payload/platform/lib/dispatch-read/src/session.ts +25 -6
  15. package/payload/platform/lib/routine-templates/dist/index.d.ts +2 -2
  16. package/payload/platform/lib/routine-templates/dist/index.d.ts.map +1 -1
  17. package/payload/platform/lib/routine-templates/dist/index.js +9 -1
  18. package/payload/platform/lib/routine-templates/dist/index.js.map +1 -1
  19. package/payload/platform/lib/routine-templates/src/index.ts +14 -2
  20. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  21. package/payload/platform/plugins/admin/PLUGIN.md +8 -3
  22. package/payload/platform/plugins/admin/hooks/__tests__/html-structure-gate.test.sh +264 -0
  23. package/payload/platform/plugins/admin/hooks/__tests__/quote-render-pdf-conformance.test.sh +122 -24
  24. package/payload/platform/plugins/admin/hooks/__tests__/specialist-dispatch-gate.test.sh +158 -0
  25. package/payload/platform/plugins/admin/hooks/html-structure-gate.sh +165 -0
  26. package/payload/platform/plugins/admin/hooks/quote-render-pdf-conformance.sh +111 -19
  27. package/payload/platform/plugins/admin/hooks/specialist-dispatch-gate.sh +159 -0
  28. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.d.ts +27 -0
  29. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.d.ts.map +1 -0
  30. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.js +29 -0
  31. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.js.map +1 -0
  32. package/payload/platform/plugins/admin/mcp/dist/binding-validate.d.ts +11 -0
  33. package/payload/platform/plugins/admin/mcp/dist/binding-validate.d.ts.map +1 -0
  34. package/payload/platform/plugins/admin/mcp/dist/binding-validate.js +20 -0
  35. package/payload/platform/plugins/admin/mcp/dist/binding-validate.js.map +1 -0
  36. package/payload/platform/plugins/admin/mcp/dist/index.js +137 -2
  37. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  38. package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +32 -2
  39. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +192 -90
  40. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +18 -0
  41. package/payload/platform/plugins/docs/references/admin-ui.md +71 -31
  42. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +50 -57
  43. package/payload/platform/plugins/docs/references/internals.md +6 -0
  44. package/payload/platform/plugins/docs/references/outlook-guide.md +31 -1
  45. package/payload/platform/plugins/docs/references/telegram-guide.md +23 -0
  46. package/payload/platform/plugins/docs/references/troubleshooting.md +10 -0
  47. package/payload/platform/plugins/email/PLUGIN.md +1 -1
  48. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js +36 -5
  49. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js.map +1 -1
  50. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +10 -5
  51. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
  52. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +12 -6
  53. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  54. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +18 -8
  55. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  56. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.d.ts.map +1 -1
  57. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js +41 -10
  58. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js.map +1 -1
  59. package/payload/platform/plugins/email/references/email-reference.md +12 -0
  60. package/payload/platform/plugins/outlook/PLUGIN.md +3 -1
  61. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.d.ts +2 -0
  62. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.d.ts.map +1 -0
  63. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.js +177 -0
  64. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.js.map +1 -0
  65. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.d.ts +2 -0
  66. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.d.ts.map +1 -0
  67. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.js +130 -0
  68. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.js.map +1 -0
  69. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.d.ts +2 -0
  70. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.d.ts.map +1 -0
  71. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.js +162 -0
  72. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.js.map +1 -0
  73. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.d.ts +2 -0
  74. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.d.ts.map +1 -0
  75. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.js +168 -0
  76. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.js.map +1 -0
  77. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.d.ts +64 -0
  78. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.d.ts.map +1 -0
  79. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.js +110 -0
  80. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.js.map +1 -0
  81. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.d.ts +119 -0
  82. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.d.ts.map +1 -0
  83. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.js +0 -0
  84. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.js.map +1 -0
  85. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.d.ts +8 -0
  86. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.d.ts.map +1 -0
  87. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.js +247 -0
  88. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.js.map +1 -0
  89. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.d.ts.map +1 -1
  90. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.js +10 -0
  91. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.js.map +1 -1
  92. package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts.map +1 -1
  93. package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js +21 -0
  94. package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js.map +1 -1
  95. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts +16 -0
  96. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts.map +1 -1
  97. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js +5 -1
  98. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js.map +1 -1
  99. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +4 -0
  100. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -1
  101. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +2 -1
  102. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -1
  103. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.d.ts.map +1 -1
  104. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js +24 -0
  105. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js.map +1 -1
  106. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts.map +1 -1
  107. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js +2 -1
  108. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js.map +1 -1
  109. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.d.ts.map +1 -1
  110. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js +29 -0
  111. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js.map +1 -1
  112. package/payload/platform/plugins/scheduling/PLUGIN.md +4 -0
  113. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.d.ts +2 -0
  114. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.d.ts.map +1 -0
  115. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.js +21 -0
  116. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.js.map +1 -0
  117. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.d.ts +13 -0
  118. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.d.ts.map +1 -0
  119. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.js +10 -0
  120. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.js.map +1 -0
  121. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +54 -0
  122. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  123. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js +78 -5
  124. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js.map +1 -1
  125. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +133 -2
  126. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
  127. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +11 -0
  128. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  129. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +30 -1
  130. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  131. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  132. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +8 -0
  133. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  134. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
  135. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +37 -6
  136. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
  137. package/payload/platform/plugins/telegram/.claude-plugin/plugin.json +1 -1
  138. package/payload/platform/plugins/telegram/PLUGIN.md +14 -2
  139. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.d.ts +2 -0
  140. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.d.ts.map +1 -0
  141. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.js +77 -0
  142. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.js.map +1 -0
  143. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.d.ts +2 -0
  144. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.d.ts.map +1 -0
  145. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js +49 -0
  146. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js.map +1 -0
  147. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.d.ts +2 -0
  148. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.d.ts.map +1 -0
  149. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.js +25 -0
  150. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-methods.test.js.map +1 -0
  151. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.d.ts +2 -0
  152. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.d.ts.map +1 -0
  153. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.js +115 -0
  154. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-tool.test.js.map +1 -0
  155. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.d.ts +2 -0
  156. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.d.ts.map +1 -0
  157. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.js +83 -0
  158. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-api.test.js.map +1 -0
  159. package/payload/platform/plugins/telegram/mcp/dist/index.js +42 -0
  160. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  161. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.d.ts +32 -0
  162. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.d.ts.map +1 -0
  163. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js +59 -0
  164. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js.map +1 -0
  165. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts +18 -0
  166. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts.map +1 -0
  167. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js +35 -0
  168. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js.map +1 -0
  169. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.d.ts +38 -0
  170. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.d.ts.map +1 -0
  171. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.js +21 -0
  172. package/payload/platform/plugins/telegram/mcp/dist/lib/api-methods.js.map +1 -0
  173. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.d.ts +24 -0
  174. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.d.ts.map +1 -0
  175. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.js +57 -0
  176. package/payload/platform/plugins/telegram/mcp/dist/lib/bot-api.js.map +1 -0
  177. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.d.ts +16 -0
  178. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.d.ts.map +1 -0
  179. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.js +34 -0
  180. package/payload/platform/plugins/telegram/mcp/dist/tools/api-census.js.map +1 -0
  181. package/payload/platform/plugins/telegram/mcp/dist/tools/api.d.ts +26 -0
  182. package/payload/platform/plugins/telegram/mcp/dist/tools/api.d.ts.map +1 -0
  183. package/payload/platform/plugins/telegram/mcp/dist/tools/api.js +80 -0
  184. package/payload/platform/plugins/telegram/mcp/dist/tools/api.js.map +1 -0
  185. package/payload/platform/plugins/telegram/skills/build/SKILL.md +110 -0
  186. package/payload/platform/plugins/telegram/skills/build/references/methods.md +219 -0
  187. package/payload/platform/scripts/check-no-esm-require.mjs +4 -0
  188. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.d.ts +32 -0
  189. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.d.ts.map +1 -0
  190. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.js +144 -0
  191. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.js.map +1 -0
  192. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  193. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +3 -0
  194. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  195. package/payload/platform/services/claude-session-manager/dist/html-structure-census.d.ts +44 -0
  196. package/payload/platform/services/claude-session-manager/dist/html-structure-census.d.ts.map +1 -0
  197. package/payload/platform/services/claude-session-manager/dist/html-structure-census.js +185 -0
  198. package/payload/platform/services/claude-session-manager/dist/html-structure-census.js.map +1 -0
  199. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  200. package/payload/platform/services/claude-session-manager/dist/http-server.js +32 -5
  201. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  202. package/payload/platform/services/claude-session-manager/dist/index.js +125 -0
  203. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  204. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +12 -1
  205. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  206. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +13 -1
  207. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  208. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.d.ts.map +1 -1
  209. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js +7 -0
  210. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js.map +1 -1
  211. package/payload/platform/services/claude-session-manager/dist/quote-print-census.d.ts +42 -0
  212. package/payload/platform/services/claude-session-manager/dist/quote-print-census.d.ts.map +1 -0
  213. package/payload/platform/services/claude-session-manager/dist/quote-print-census.js +254 -0
  214. package/payload/platform/services/claude-session-manager/dist/quote-print-census.js.map +1 -0
  215. package/payload/platform/services/claude-session-manager/dist/specialist-census.d.ts +92 -0
  216. package/payload/platform/services/claude-session-manager/dist/specialist-census.d.ts.map +1 -0
  217. package/payload/platform/services/claude-session-manager/dist/specialist-census.js +227 -0
  218. package/payload/platform/services/claude-session-manager/dist/specialist-census.js.map +1 -0
  219. package/payload/platform/services/telegram-channel/dist/jsonl.d.ts +8 -0
  220. package/payload/platform/services/telegram-channel/dist/jsonl.d.ts.map +1 -0
  221. package/payload/platform/services/telegram-channel/dist/jsonl.js +42 -0
  222. package/payload/platform/services/telegram-channel/dist/jsonl.js.map +1 -0
  223. package/payload/platform/services/telegram-channel/dist/notification.d.ts +0 -31
  224. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  225. package/payload/platform/services/telegram-channel/dist/notification.js +4 -33
  226. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  227. package/payload/platform/services/telegram-channel/dist/server.d.ts +0 -5
  228. package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
  229. package/payload/platform/services/telegram-channel/dist/server.js +7 -101
  230. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  231. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +0 -66
  232. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  233. package/payload/platform/services/whatsapp-channel/dist/notification.js +2 -64
  234. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  235. package/payload/platform/services/whatsapp-channel/dist/server.d.ts +0 -12
  236. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  237. package/payload/platform/services/whatsapp-channel/dist/server.js +15 -268
  238. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  239. package/payload/platform/templates/account-settings.json +8 -4
  240. package/payload/server/public/activity.html +6 -6
  241. package/payload/server/public/agents.html +5 -5
  242. package/payload/server/public/assets/{AdminLoginScreens-CMdj1W5p.js → AdminLoginScreens-Dp6k1XgD.js} +1 -1
  243. package/payload/server/public/assets/AdminLoginScreens-Dp6k1XgD.js.br +0 -0
  244. package/payload/server/public/assets/AdminLoginScreens-Dp6k1XgD.js.gz +0 -0
  245. package/payload/server/public/assets/{AdminShell-CZcaW3BQ.js → AdminShell-D-wB90c3.js} +1 -1
  246. package/payload/server/public/assets/AdminShell-D-wB90c3.js.br +0 -0
  247. package/payload/server/public/assets/AdminShell-D-wB90c3.js.gz +0 -0
  248. package/payload/server/public/assets/{activity-CAhibgb-.js → activity-BJxhblz-.js} +1 -1
  249. package/payload/server/public/assets/activity-BJxhblz-.js.br +0 -0
  250. package/payload/server/public/assets/activity-BJxhblz-.js.gz +0 -0
  251. package/payload/server/public/assets/{admin-xEXv3w9e.js → admin-DzEi8shs.js} +1 -1
  252. package/payload/server/public/assets/admin-DzEi8shs.js.br +0 -0
  253. package/payload/server/public/assets/admin-DzEi8shs.js.gz +0 -0
  254. package/payload/server/public/assets/{agents-CiAmzTaT.js → agents-DTL8kjfD.js} +1 -1
  255. package/payload/server/public/assets/agents-DTL8kjfD.js.br +0 -0
  256. package/payload/server/public/assets/agents-DTL8kjfD.js.gz +0 -0
  257. package/payload/server/public/assets/{browser-D36BiYhS.js → browser-UZp75zhx.js} +1 -1
  258. package/payload/server/public/assets/browser-UZp75zhx.js.br +0 -0
  259. package/payload/server/public/assets/browser-UZp75zhx.js.gz +0 -0
  260. package/payload/server/public/assets/{calendar-CJAM-iXn.js → calendar-Dfo0O2K_.js} +1 -1
  261. package/payload/server/public/assets/calendar-Dfo0O2K_.js.br +0 -0
  262. package/payload/server/public/assets/calendar-Dfo0O2K_.js.gz +0 -0
  263. package/payload/server/public/assets/chat-ByJAvEWZ.js +1 -0
  264. package/payload/server/public/assets/chat-ByJAvEWZ.js.br +0 -0
  265. package/payload/server/public/assets/chat-ByJAvEWZ.js.gz +0 -0
  266. package/payload/server/public/assets/chevron-left-iG7sYNm7.js +1 -0
  267. package/payload/server/public/assets/chevron-left-iG7sYNm7.js.br +0 -0
  268. package/payload/server/public/assets/clock-xSs0UF10.js +1 -0
  269. package/payload/server/public/assets/clock-xSs0UF10.js.br +0 -0
  270. package/payload/server/public/assets/clock-xSs0UF10.js.gz +0 -0
  271. package/payload/server/public/assets/{copy-axBe92iY.js → copy-CGI2jioK.js} +1 -1
  272. package/payload/server/public/assets/copy-CGI2jioK.js.br +0 -0
  273. package/payload/server/public/assets/copy-CGI2jioK.js.gz +0 -0
  274. package/payload/server/public/assets/data-dvwK4BcP.js +1 -0
  275. package/payload/server/public/assets/data-dvwK4BcP.js.br +0 -0
  276. package/payload/server/public/assets/data-dvwK4BcP.js.gz +0 -0
  277. package/payload/server/public/assets/{dispatch-D_i2ko7K.js → dispatch-Dx9-ty1K.js} +1 -1
  278. package/payload/server/public/assets/dispatch-Dx9-ty1K.js.br +0 -0
  279. package/payload/server/public/assets/dispatch-Dx9-ty1K.js.gz +0 -0
  280. package/payload/server/public/assets/{field-DFgkqq1P.js → field-D-P6CsWA.js} +1 -1
  281. package/payload/server/public/assets/field-D-P6CsWA.js.br +0 -0
  282. package/payload/server/public/assets/field-D-P6CsWA.js.gz +0 -0
  283. package/payload/server/public/assets/{file-text-CEFoq0Je.js → file-text-RWintp9q.js} +1 -1
  284. package/payload/server/public/assets/file-text-RWintp9q.js.br +0 -0
  285. package/payload/server/public/assets/file-text-RWintp9q.js.gz +0 -0
  286. package/payload/server/public/assets/{graph-BrpbilNr.js → graph-CpBlfYrJ.js} +1 -1
  287. package/payload/server/public/assets/graph-CpBlfYrJ.js.br +0 -0
  288. package/payload/server/public/assets/graph-CpBlfYrJ.js.gz +0 -0
  289. package/payload/server/public/assets/{graph-labels-BtmcWzo7.js → graph-labels-BOMyPppX.js} +1 -1
  290. package/payload/server/public/assets/graph-labels-BOMyPppX.js.br +0 -0
  291. package/payload/server/public/assets/graph-labels-BOMyPppX.js.gz +0 -0
  292. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css +1 -0
  293. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css.br +0 -0
  294. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css.gz +0 -0
  295. package/payload/server/public/assets/{operator-Dl7dJt1G.js → operator-DFgW6GiH.js} +1 -1
  296. package/payload/server/public/assets/operator-DFgW6GiH.js.br +0 -0
  297. package/payload/server/public/assets/operator-DFgW6GiH.js.gz +0 -0
  298. package/payload/server/public/assets/{page-nx31Ij0T.js → page-BRShHSH0.js} +1 -1
  299. package/payload/server/public/assets/page-BRShHSH0.js.br +0 -0
  300. package/payload/server/public/assets/page-BRShHSH0.js.gz +0 -0
  301. package/payload/server/public/assets/{page-zC66xzNT.js → page-BbBCu9iP.js} +5 -5
  302. package/payload/server/public/assets/page-BbBCu9iP.js.br +0 -0
  303. package/payload/server/public/assets/page-BbBCu9iP.js.gz +0 -0
  304. package/payload/server/public/assets/play-BJxUaf6-.js +1 -0
  305. package/payload/server/public/assets/play-BJxUaf6-.js.br +0 -0
  306. package/payload/server/public/assets/play-BJxUaf6-.js.gz +0 -0
  307. package/payload/server/public/assets/{public-DEKnH5Q0.js → public-DkObqkzL.js} +1 -1
  308. package/payload/server/public/assets/public-DkObqkzL.js.br +0 -0
  309. package/payload/server/public/assets/public-DkObqkzL.js.gz +0 -0
  310. package/payload/server/public/assets/{rotate-ccw-CBex8gon.js → rotate-ccw-DgMO69nh.js} +1 -1
  311. package/payload/server/public/assets/rotate-ccw-DgMO69nh.js.br +0 -0
  312. package/payload/server/public/assets/rotate-ccw-DgMO69nh.js.gz +0 -0
  313. package/payload/server/public/assets/routines-D9kvg_Xu.js +2 -0
  314. package/payload/server/public/assets/routines-D9kvg_Xu.js.br +0 -0
  315. package/payload/server/public/assets/routines-D9kvg_Xu.js.gz +0 -0
  316. package/payload/server/public/assets/{skills-BmxjPOmq.js → skills-l5I9HZ3D.js} +1 -1
  317. package/payload/server/public/assets/skills-l5I9HZ3D.js.br +0 -0
  318. package/payload/server/public/assets/skills-l5I9HZ3D.js.gz +0 -0
  319. package/payload/server/public/assets/{tasks-BTFYO9vT.js → tasks-C2qJMv6i.js} +1 -1
  320. package/payload/server/public/assets/tasks-C2qJMv6i.js.br +0 -0
  321. package/payload/server/public/assets/tasks-C2qJMv6i.js.gz +0 -0
  322. package/payload/server/public/assets/{triangle-alert-px1YRJJ7.js → triangle-alert-BnC67uSs.js} +1 -1
  323. package/payload/server/public/assets/triangle-alert-BnC67uSs.js.br +0 -0
  324. package/payload/server/public/assets/triangle-alert-BnC67uSs.js.gz +0 -0
  325. package/payload/server/public/assets/{useCopyFeedback-nMXwRcDX.js → useCopyFeedback-1RmgkTFh.js} +1 -1
  326. package/payload/server/public/assets/useCopyFeedback-1RmgkTFh.js.br +0 -0
  327. package/payload/server/public/assets/useCopyFeedback-1RmgkTFh.js.gz +0 -0
  328. package/payload/server/public/assets/{useMediaQuery-DjbOTk2X.js → useMediaQuery-BQslddHQ.js} +1 -1
  329. package/payload/server/public/assets/useMediaQuery-BQslddHQ.js.br +0 -0
  330. package/payload/server/public/assets/useMediaQuery-BQslddHQ.js.gz +0 -0
  331. package/payload/server/public/assets/{useVoiceRecorder-Cx7AOmib.js → useVoiceRecorder-BeZNQEC4.js} +1 -1
  332. package/payload/server/public/assets/useVoiceRecorder-BeZNQEC4.js.br +0 -0
  333. package/payload/server/public/assets/useVoiceRecorder-BeZNQEC4.js.gz +0 -0
  334. package/payload/server/public/assets/{wrench-Cexvlx6J.js → wrench-D4ufQG6C.js} +1 -1
  335. package/payload/server/public/assets/wrench-D4ufQG6C.js.br +0 -0
  336. package/payload/server/public/assets/wrench-D4ufQG6C.js.gz +0 -0
  337. package/payload/server/public/browser.html +5 -5
  338. package/payload/server/public/calendar.html +8 -8
  339. package/payload/server/public/chat.html +14 -14
  340. package/payload/server/public/data.html +12 -12
  341. package/payload/server/public/dispatch.html +5 -5
  342. package/payload/server/public/field.html +3 -3
  343. package/payload/server/public/graph.html +10 -10
  344. package/payload/server/public/index.html +14 -14
  345. package/payload/server/public/operator.html +16 -16
  346. package/payload/server/public/public.html +14 -14
  347. package/payload/server/public/routines.html +8 -8
  348. package/payload/server/public/skills.html +6 -6
  349. package/payload/server/public/tasks.html +7 -7
  350. package/payload/server/server.js +1088 -694
  351. package/payload/server/public/assets/AdminLoginScreens-CMdj1W5p.js.br +0 -0
  352. package/payload/server/public/assets/AdminLoginScreens-CMdj1W5p.js.gz +0 -0
  353. package/payload/server/public/assets/AdminShell-CZcaW3BQ.js.br +0 -0
  354. package/payload/server/public/assets/AdminShell-CZcaW3BQ.js.gz +0 -0
  355. package/payload/server/public/assets/activity-CAhibgb-.js.br +0 -0
  356. package/payload/server/public/assets/activity-CAhibgb-.js.gz +0 -0
  357. package/payload/server/public/assets/admin-xEXv3w9e.js.br +0 -0
  358. package/payload/server/public/assets/admin-xEXv3w9e.js.gz +0 -0
  359. package/payload/server/public/assets/agents-CiAmzTaT.js.br +0 -0
  360. package/payload/server/public/assets/agents-CiAmzTaT.js.gz +0 -0
  361. package/payload/server/public/assets/browser-D36BiYhS.js.br +0 -0
  362. package/payload/server/public/assets/browser-D36BiYhS.js.gz +0 -0
  363. package/payload/server/public/assets/calendar-CJAM-iXn.js.br +0 -0
  364. package/payload/server/public/assets/calendar-CJAM-iXn.js.gz +0 -0
  365. package/payload/server/public/assets/chat-DhZSvtZK.js +0 -1
  366. package/payload/server/public/assets/chat-DhZSvtZK.js.br +0 -0
  367. package/payload/server/public/assets/chat-DhZSvtZK.js.gz +0 -0
  368. package/payload/server/public/assets/chevron-left-CXEcNlc3.js +0 -1
  369. package/payload/server/public/assets/chevron-left-CXEcNlc3.js.br +0 -0
  370. package/payload/server/public/assets/clock-DdxIJwTr.js +0 -1
  371. package/payload/server/public/assets/clock-DdxIJwTr.js.br +0 -0
  372. package/payload/server/public/assets/clock-DdxIJwTr.js.gz +0 -0
  373. package/payload/server/public/assets/copy-axBe92iY.js.br +0 -0
  374. package/payload/server/public/assets/copy-axBe92iY.js.gz +0 -0
  375. package/payload/server/public/assets/data-DUvkI-zq.js +0 -1
  376. package/payload/server/public/assets/data-DUvkI-zq.js.br +0 -0
  377. package/payload/server/public/assets/data-DUvkI-zq.js.gz +0 -0
  378. package/payload/server/public/assets/dispatch-D_i2ko7K.js.br +0 -0
  379. package/payload/server/public/assets/dispatch-D_i2ko7K.js.gz +0 -0
  380. package/payload/server/public/assets/field-DFgkqq1P.js.br +0 -0
  381. package/payload/server/public/assets/field-DFgkqq1P.js.gz +0 -0
  382. package/payload/server/public/assets/file-text-CEFoq0Je.js.br +0 -0
  383. package/payload/server/public/assets/file-text-CEFoq0Je.js.gz +0 -0
  384. package/payload/server/public/assets/graph-BrpbilNr.js.br +0 -0
  385. package/payload/server/public/assets/graph-BrpbilNr.js.gz +0 -0
  386. package/payload/server/public/assets/graph-labels-BtmcWzo7.js.br +0 -0
  387. package/payload/server/public/assets/graph-labels-BtmcWzo7.js.gz +0 -0
  388. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css +0 -1
  389. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css.br +0 -0
  390. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css.gz +0 -0
  391. package/payload/server/public/assets/operator-Dl7dJt1G.js.br +0 -3
  392. package/payload/server/public/assets/operator-Dl7dJt1G.js.gz +0 -0
  393. package/payload/server/public/assets/page-nx31Ij0T.js.br +0 -0
  394. package/payload/server/public/assets/page-nx31Ij0T.js.gz +0 -0
  395. package/payload/server/public/assets/page-zC66xzNT.js.br +0 -0
  396. package/payload/server/public/assets/page-zC66xzNT.js.gz +0 -0
  397. package/payload/server/public/assets/play-MbU8O2iC.js +0 -1
  398. package/payload/server/public/assets/play-MbU8O2iC.js.br +0 -0
  399. package/payload/server/public/assets/play-MbU8O2iC.js.gz +0 -0
  400. package/payload/server/public/assets/public-DEKnH5Q0.js.br +0 -0
  401. package/payload/server/public/assets/public-DEKnH5Q0.js.gz +0 -0
  402. package/payload/server/public/assets/rotate-ccw-CBex8gon.js.br +0 -0
  403. package/payload/server/public/assets/rotate-ccw-CBex8gon.js.gz +0 -0
  404. package/payload/server/public/assets/routines-BqCmbvFJ.js +0 -2
  405. package/payload/server/public/assets/routines-BqCmbvFJ.js.br +0 -0
  406. package/payload/server/public/assets/routines-BqCmbvFJ.js.gz +0 -0
  407. package/payload/server/public/assets/skills-BmxjPOmq.js.br +0 -0
  408. package/payload/server/public/assets/skills-BmxjPOmq.js.gz +0 -0
  409. package/payload/server/public/assets/tasks-BTFYO9vT.js.br +0 -0
  410. package/payload/server/public/assets/tasks-BTFYO9vT.js.gz +0 -0
  411. package/payload/server/public/assets/triangle-alert-px1YRJJ7.js.br +0 -0
  412. package/payload/server/public/assets/triangle-alert-px1YRJJ7.js.gz +0 -0
  413. package/payload/server/public/assets/useCopyFeedback-nMXwRcDX.js.br +0 -0
  414. package/payload/server/public/assets/useCopyFeedback-nMXwRcDX.js.gz +0 -0
  415. package/payload/server/public/assets/useMediaQuery-DjbOTk2X.js.br +0 -0
  416. package/payload/server/public/assets/useMediaQuery-DjbOTk2X.js.gz +0 -0
  417. package/payload/server/public/assets/useVoiceRecorder-Cx7AOmib.js.br +0 -0
  418. package/payload/server/public/assets/useVoiceRecorder-Cx7AOmib.js.gz +0 -0
  419. package/payload/server/public/assets/wrench-Cexvlx6J.js.br +0 -0
  420. package/payload/server/public/assets/wrench-Cexvlx6J.js.gz +0 -0
  421. /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js → jsx-runtime-5lmxFBIw.js} +0 -0
  422. /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js.br → jsx-runtime-5lmxFBIw.js.br} +0 -0
  423. /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js.gz → jsx-runtime-5lmxFBIw.js.gz} +0 -0
@@ -29,6 +29,14 @@
29
29
  # P. a block names the headerless page numbers
30
30
  # Q. a running footer repeating the ref does NOT count as a header
31
31
  # R. @page :first declared before @page still yields the marker
32
+ #
33
+ # Running-image geometry (only a document declaring an image margin box):
34
+ # S. running image within a third of the page width → ALLOWED
35
+ # T. running image wider than a third → BLOCKED
36
+ # U. an oversized image on ONE page only (content photo) → ALLOWED
37
+ # W. source declares no image margin box → ALLOWED (never gated)
38
+ # V. pdfimages unusable → ALLOWED, and the skip says reason=no-poppler
39
+ # X. the link stage's non-gated return names its reason rather than going quiet
32
40
 
33
41
  set -u
34
42
 
@@ -59,39 +67,66 @@ mk_job() { # jobId, html_body, pdf_uri_repeat
59
67
  > "$DOCS/quote-$job.pdf"
60
68
  }
61
69
 
62
- # A minimal, uncompressed, multi-page PDF. Each page argument is
63
- # "<top line>|<bottom line>"; the bottom line is optional and stands in for a
64
- # running footer. The /URI stubs above are not readable by pdftotext, and the
65
- # header check needs real per-page text at real positions, so the header
66
- # fixtures need a genuine PDF. Pure stdlib no third-party generator, no
67
- # network, deterministic byte-for-byte.
68
- mk_pdf() { # out_path, then one "top|bottom" argument per page
70
+ # A minimal, multi-page PDF. Each page argument is "<top>|<bottom>|<images>";
71
+ # the bottom line is optional and stands in for a running footer, and images is
72
+ # an optional ";"-separated list of "<pixel width>,<pixel height>,<rendered pt>".
73
+ # The /URI stubs above are not readable by pdftotext, and the header check needs
74
+ # real per-page text at real positions, so the header fixtures need a genuine
75
+ # PDF. Pure stdlib — no third-party generator, no network, deterministic
76
+ # byte-for-byte.
77
+ #
78
+ # An image is drawn through a transform matrix, so its rendered size on the page
79
+ # is independent of its pixel size: that is what makes `pdfimages -list` report a
80
+ # controlled x-ppi, and rendered width = pixel width / x-ppi * 72 is exactly what
81
+ # the geometry check asserts. The image object is created once per (width,height)
82
+ # and referenced from every page carrying it, which is what a running header is
83
+ # in a real print and what makes "present on every checked page" testable.
84
+ mk_pdf() { # out_path, then one "top|bottom|W,H,pt;W,H,pt" argument per page
69
85
  local out="$1"; shift
70
86
  python3 - "$out" "$@" <<'PY'
71
- import sys
72
- out, texts = sys.argv[1], sys.argv[2:]
87
+ import sys, zlib
88
+ out, specs = sys.argv[1], sys.argv[2:]
73
89
  objs = []
74
- kids = " ".join("%d 0 R" % (4 + 2 * i) for i in range(len(texts)))
75
- objs.append("<< /Type /Catalog /Pages 2 0 R >>")
76
- objs.append("<< /Type /Pages /Kids [%s] /Count %d >>" % (kids, len(texts)))
77
- objs.append("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
78
- for i, t in enumerate(texts):
79
- top, _, bottom = t.partition("|")
90
+ def add(b):
91
+ objs.append(b if isinstance(b, bytes) else b.encode("latin-1"))
92
+ return len(objs)
93
+ add("<< /Type /Catalog /Pages 2 0 R >>")
94
+ add("PLACEHOLDER") # 2: /Pages, patched once the kid object numbers are known
95
+ add("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
96
+ kids, shared = [], {}
97
+ for spec in specs:
98
+ top, bottom, imgs = (spec.split("|") + ["", ""])[:3]
80
99
  body = "BT /F1 11 Tf 72 760 Td (%s) Tj ET" % top
81
100
  if bottom:
82
101
  body += " BT /F1 8 Tf 72 40 Td (%s) Tj ET" % bottom
83
- objs.append("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] "
84
- "/Resources << /Font << /F1 3 0 R >> >> /Contents %d 0 R >>" % (5 + 2 * i))
85
- objs.append("<< /Length %d >>\nstream\n%s\nendstream" % (len(body), body))
86
- pdf, offsets = "%PDF-1.4\n", []
102
+ res, xo = "/Font << /F1 3 0 R >>", []
103
+ for k, one in enumerate(x for x in imgs.split(";") if x):
104
+ ws, hs, pts = one.split(",")
105
+ w, h, pt = int(ws), int(hs), float(pts)
106
+ if (w, h) not in shared:
107
+ comp = zlib.compress(bytes([200, 30, 30]) * (w * h))
108
+ shared[(w, h)] = add(
109
+ b"<< /Type /XObject /Subtype /Image /Width %d /Height %d /ColorSpace /DeviceRGB "
110
+ b"/BitsPerComponent 8 /Filter /FlateDecode /Length %d >>\nstream\n"
111
+ % (w, h, len(comp)) + comp + b"\nendstream")
112
+ xo.append("/Im%d %d 0 R" % (k, shared[(w, h)]))
113
+ body += " q %.3f 0 0 %.3f 40 %d cm /Im%d Do Q" % (pt, pt * h / w, 700 - k * 120, k)
114
+ if xo:
115
+ res += " /XObject << %s >>" % " ".join(xo)
116
+ c = add("<< /Length %d >>\nstream\n%s\nendstream" % (len(body), body))
117
+ kids.append(add("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] "
118
+ "/Resources << %s >> /Contents %d 0 R >>" % (res, c)))
119
+ objs[1] = ("<< /Type /Pages /Kids [%s] /Count %d >>"
120
+ % (" ".join("%d 0 R" % k for k in kids), len(kids))).encode("latin-1")
121
+ pdf, offsets = b"%PDF-1.4\n", []
87
122
  for i, o in enumerate(objs, start=1):
88
123
  offsets.append(len(pdf))
89
- pdf += "%d 0 obj\n%s\nendobj\n" % (i, o)
124
+ pdf += b"%d 0 obj\n" % i + o + b"\nendobj\n"
90
125
  start = len(pdf)
91
- pdf += "xref\n0 %d\n0000000000 65535 f \n" % (len(objs) + 1)
92
- pdf += "".join("%010d 00000 n \n" % off for off in offsets)
93
- pdf += "trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n" % (len(objs) + 1, start)
94
- open(out, "wb").write(pdf.encode("latin-1"))
126
+ pdf += b"xref\n0 %d\n0000000000 65535 f \n" % (len(objs) + 1)
127
+ pdf += b"".join(b"%010d 00000 n \n" % off for off in offsets)
128
+ pdf += b"trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n" % (len(objs) + 1, start)
129
+ open(out, "wb").write(pdf)
95
130
  PY
96
131
  }
97
132
 
@@ -192,6 +227,69 @@ run_case "Q: footer repeats the ref, page 3 headerless → BLOCKED" \
192
227
  run_case "R: @page :first declared first, page 3 headerless → BLOCKED" \
193
228
  "$(envelope "$BROWSER" "$DOCS/hdr-first-declared-first.pdf")" 2
194
229
 
230
+ # ---- running-image geometry ----
231
+ # Chromium paints a page-margin box's content:url() at intrinsic pixel size and
232
+ # does not scale it to the declared box, so a full-resolution logo covers most of
233
+ # the page on every page the box paints. The assertion is scoped to the RUNNING
234
+ # image — the object present on every checked page — because a content photo
235
+ # legitimately runs wider: measured on real quotes, running headers 9.0-18.9% of
236
+ # page width against content photos at 38.7-77.2% and the defect at 88.2%.
237
+ mk_doc_img() { # basename, css, then one "top|bottom|W,H,pt;…" per page
238
+ local base="$1" css="$2"; shift 2
239
+ printf '<html><head>%s</head><body>doc</body></html>' "$css" > "$DOCS/$base.html"
240
+ mk_pdf "$DOCS/$base.pdf" "$@"
241
+ }
242
+
243
+ # S: 125px at 96ppi = 93.8pt = 15.8% of the 595pt page. The corrected print.
244
+ mk_doc_img "geo-ok" "$HDR_CSS" \
245
+ "cover masthead" "Quote 2468/1 two||125,45,93.8" "Quote 2468/1 three||125,45,93.8"
246
+ # T: 700px at 96ppi = 525.0pt = 88.2%. The measured defect.
247
+ mk_doc_img "geo-wide" "$HDR_CSS" \
248
+ "cover masthead" "Quote 2468/1 two||700,252,525" "Quote 2468/1 three||700,252,525"
249
+ # U: compliant running header plus a one-page 1300px photo at 230pt = 38.7%.
250
+ # A whole-page third-of-width rule blocks this document, and it is legitimate.
251
+ mk_doc_img "geo-photo" "$HDR_CSS" \
252
+ "cover masthead" "Quote 2468/1 two||125,45,93.8;1300,1300,230" "Quote 2468/1 three||125,45,93.8"
253
+ # W: no image margin box declared, so geometry never applies however wide.
254
+ mk_doc_img "geo-nobox" "$NOHDR_CSS" \
255
+ "page one" "page two||700,252,525" "page three||700,252,525"
256
+
257
+ run_case "S: running image within a third of page width → ALLOWED" \
258
+ "$(envelope "$BROWSER" "$DOCS/geo-ok.pdf")" 0
259
+ run_case "T: running image over a third of page width → BLOCKED" \
260
+ "$(envelope "$BROWSER" "$DOCS/geo-wide.pdf")" 2
261
+ run_case "U: an oversized photo on one page only → ALLOWED" \
262
+ "$(envelope "$BROWSER" "$DOCS/geo-photo.pdf")" 0
263
+ run_case "W: no image margin box declared → ALLOWED" \
264
+ "$(envelope "$BROWSER" "$DOCS/geo-nobox.pdf")" 0
265
+
266
+ # V: `pdfimages` alone is unusable. Shadowing pdfinfo too would make the header
267
+ # stage take its own pre-existing no-poppler exit and the geometry stage would
268
+ # never run, so the case would pass without testing anything new. The stub is
269
+ # EXECUTABLE and prints nothing: a non-executable file is skipped by the PATH
270
+ # search rather than failing, so the real binary would still be found. An absent
271
+ # binary and an unusable one converge on the same empty listing.
272
+ STUB="$ACCT/nopdfimages"
273
+ mkdir -p "$STUB"
274
+ printf '#!/bin/sh\nexit 127\n' > "$STUB/pdfimages"
275
+ chmod +x "$STUB/pdfimages"
276
+ V_OUT=$( cd "$ACCT" && PATH="$STUB:$PATH" bash -c 'printf "%s" "$1" | bash "$2" 2>&1 >/dev/null' \
277
+ _ "$(envelope "$BROWSER" "$DOCS/geo-ok.pdf")" "$HOOK" )
278
+ if printf '%s' "$V_OUT" | grep -q 'op=pdf-image-geometry .*reason=no-poppler'; then
279
+ echo "PASS: V: absent pdfimages emits reason=no-poppler"; PASS=$((PASS + 1))
280
+ else
281
+ echo "FAIL: V: geometry no-poppler skip absent (got: $V_OUT)" >&2; FAIL=$((FAIL + 1))
282
+ fi
283
+
284
+ # X: the link stage returns without inspecting anything on a non-quote path. It
285
+ # did so silently, which is indistinguishable from a gate that never ran at all.
286
+ X_OUT=$( cd "$ACCT" && printf '%s' "$(envelope "$BROWSER" "$DOCS/brochure.pdf")" | bash "$HOOK" 2>&1 >/dev/null )
287
+ if printf '%s' "$X_OUT" | grep -q 'op=skip reason=not-gated'; then
288
+ echo "PASS: X: link stage skip names its reason"; PASS=$((PASS + 1))
289
+ else
290
+ echo "FAIL: X: link skip reason absent (got: $X_OUT)" >&2; FAIL=$((FAIL + 1))
291
+ fi
292
+
195
293
  # O: a clean run is distinguishable from a check that never ran.
196
294
  STDERR=$( cd "$ACCT" && printf '%s' "$(envelope "$BROWSER" "$DOCS/hdr-ok.pdf")" | bash "$HOOK" 2>&1 >/dev/null )
197
295
  if printf '%s' "$STDERR" | grep -q 'op=pdf-header-conformance .*pagesChecked=2 pagesHeaderless=0'; then
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env bash
2
+ # Regression test for specialist-dispatch-gate.sh.
3
+ #
4
+ # An account's own specialists own their tools: the admin does not do their work
5
+ # by hand. The gate refuses a main-conversation call to a tool one of the
6
+ # account's authored cards declares, and names the specialist to dispatch.
7
+ #
8
+ # Two narrowings are load-bearing and both are asserted here. Only cards the
9
+ # ACCOUNT authored count — provisioning copies every shipped template in, and one
10
+ # of them declares roughly 130 tools including the whole email surface, so
11
+ # reading every card would refuse the admin nearly its entire seat. And native
12
+ # tools are never gated, because every card declares some of them.
13
+ #
14
+ # Caller identity is `agent_type`, which Claude Code sets when a hook fires
15
+ # inside a subagent and leaves empty in the main conversation.
16
+ #
17
+ # Covers:
18
+ # A. main conversation, authored tool → BLOCKED, names the specialist
19
+ # B. same call inside a subagent → ALLOWED (never filters a subagent)
20
+ # C. a native tool the card also declares → ALLOWED
21
+ # D. a tool only a shipped template declares → ALLOWED
22
+ # E. a tool only a premium bundle declares → ALLOWED
23
+ # F. an account with no authored cards → ALLOWED
24
+ # G. empty stdin → ALLOWED
25
+ # H. unparseable envelope → ALLOWED
26
+ # I. an unreadable specialists dir → ALLOWED, op=degraded on stderr
27
+ # J. a card under the ACCOUNT's own plugin root → BLOCKED (still the account's own)
28
+ # K. the refuse line carries the account and the tool
29
+ # L. the native set matches CC_NATIVE_TOOLS in the session manager
30
+
31
+ set -uo pipefail
32
+ HOOKS_DIR="$(cd "$(dirname "$0")/.." && pwd)"
33
+ GATE="$HOOKS_DIR/specialist-dispatch-gate.sh"
34
+ DRIFT_SOURCE="$(cd "$HOOKS_DIR/../../../services/claude-session-manager/src" && pwd)/specialist-drift.ts"
35
+
36
+ PASS=0
37
+ FAIL=0
38
+ ok() { PASS=$((PASS + 1)); echo " ok $1"; }
39
+ bad() { FAIL=$((FAIL + 1)); echo " FAIL $1"; }
40
+
41
+ ROOT="$(mktemp -d)"
42
+ trap 'chmod -R u+rwX "$ROOT" 2>/dev/null; rm -rf "$ROOT"' EXIT
43
+
44
+ ACCT="$ROOT/2078cb54aaaa"
45
+ PLATFORM="$ROOT/platform"
46
+ PREMIUM="$ROOT/premium-plugins"
47
+ mkdir -p "$ACCT/specialists/agents" \
48
+ "$PLATFORM/templates/specialists/agents" \
49
+ "$PLATFORM/plugins" \
50
+ "$PREMIUM/sitedesk/agents"
51
+
52
+ card() { # dir file name tools
53
+ mkdir -p "$1"
54
+ printf -- '---\nname: %s\ndescription: "d"\ntools: %s\n---\nbody\n' "$3" "$4" > "$1/$2"
55
+ }
56
+
57
+ # The account's own clerk: no copy in any shipped root.
58
+ card "$ACCT/specialists/agents" "glsmith--email-clerk.md" "email-clerk" \
59
+ "Read, Bash, mcp__plugin_email_email__email-draft"
60
+ # A shipped template, copied into the account by provisioning.
61
+ card "$ACCT/specialists/agents" "personal-assistant.md" "personal-assistant" \
62
+ "mcp__plugin_whatsapp_whatsapp__whatsapp-send-admin"
63
+ card "$PLATFORM/templates/specialists/agents" "personal-assistant.md" "personal-assistant" \
64
+ "mcp__plugin_whatsapp_whatsapp__whatsapp-send-admin"
65
+ # A premium bundle card, also copied in.
66
+ card "$ACCT/specialists/agents" "sitedesk--quoter.md" "quoter" \
67
+ "mcp__plugin_sitedesk-job_sitedesk-job__quote-render"
68
+ card "$PREMIUM/sitedesk/agents" "sitedesk--quoter.md" "quoter" \
69
+ "mcp__plugin_sitedesk-job_sitedesk-job__quote-render"
70
+ # An authored card whose durable copy lives under the ACCOUNT's own plugin root.
71
+ card "$ACCT/specialists/agents" "glsmith--order-clerk.md" "order-clerk" \
72
+ "mcp__plugin_work_work__work-create"
73
+ card "$ACCT/plugins/glsmith/agents" "glsmith--order-clerk.md" "order-clerk" \
74
+ "mcp__plugin_work_work__work-create"
75
+
76
+ envelope() { printf '{"tool_name":"%s","agent_type":%s,"tool_input":{}}' "$1" "$2"; }
77
+
78
+ # fire <cwd> <json> — sets RC and ERR
79
+ fire() {
80
+ ERR="$(cd "$1" && printf '%s' "$2" | PLATFORM_ROOT="$PLATFORM" bash "$GATE" 2>&1 >/dev/null)"
81
+ RC=$?
82
+ }
83
+
84
+ # A
85
+ fire "$ACCT" "$(envelope mcp__plugin_email_email__email-draft '""')"
86
+ if [ "$RC" = 2 ] && printf '%s' "$ERR" | grep -q 'email-clerk'; then
87
+ ok "A blocks the main conversation and names the specialist"
88
+ else bad "A rc=$RC err=$ERR"; fi
89
+
90
+ # B
91
+ fire "$ACCT" "$(envelope mcp__plugin_email_email__email-draft '"specialists:email-clerk"')"
92
+ if [ "$RC" = 0 ]; then ok "B allows the same call inside a subagent"; else bad "B rc=$RC err=$ERR"; fi
93
+
94
+ # C
95
+ fire "$ACCT" "$(envelope Bash '""')"
96
+ if [ "$RC" = 0 ]; then ok "C allows a native tool the card declares"; else bad "C rc=$RC err=$ERR"; fi
97
+
98
+ # D
99
+ fire "$ACCT" "$(envelope mcp__plugin_whatsapp_whatsapp__whatsapp-send-admin '""')"
100
+ if [ "$RC" = 0 ]; then ok "D allows a tool only a shipped template declares"; else bad "D rc=$RC err=$ERR"; fi
101
+
102
+ # E
103
+ fire "$ACCT" "$(envelope mcp__plugin_sitedesk-job_sitedesk-job__quote-render '""')"
104
+ if [ "$RC" = 0 ]; then ok "E allows a tool only a premium bundle declares"; else bad "E rc=$RC err=$ERR"; fi
105
+
106
+ # F
107
+ EMPTY="$ROOT/empty"
108
+ mkdir -p "$EMPTY/specialists/agents"
109
+ fire "$EMPTY" "$(envelope mcp__plugin_email_email__email-draft '""')"
110
+ if [ "$RC" = 0 ]; then ok "F allows on an account with no authored cards"; else bad "F rc=$RC err=$ERR"; fi
111
+
112
+ # G
113
+ fire "$ACCT" ""
114
+ if [ "$RC" = 0 ]; then ok "G allows on empty stdin"; else bad "G rc=$RC err=$ERR"; fi
115
+
116
+ # H
117
+ fire "$ACCT" '{not json'
118
+ if [ "$RC" = 0 ]; then ok "H allows on an unparseable envelope"; else bad "H rc=$RC err=$ERR"; fi
119
+
120
+ # I — failing open is fine; failing open SILENTLY is the fault.
121
+ BROKEN="$ROOT/brokenacct"
122
+ mkdir -p "$BROKEN/specialists/agents"
123
+ chmod 000 "$BROKEN/specialists/agents"
124
+ fire "$BROKEN" "$(envelope mcp__plugin_email_email__email-draft '""')"
125
+ chmod 755 "$BROKEN/specialists/agents"
126
+ if [ "$RC" = 0 ] && printf '%s' "$ERR" | grep -q 'op=degraded'; then
127
+ ok "I fails open and says so"
128
+ else bad "I rc=$RC err=$ERR"; fi
129
+
130
+ # J
131
+ fire "$ACCT" "$(envelope mcp__plugin_work_work__work-create '""')"
132
+ if [ "$RC" = 2 ] && printf '%s' "$ERR" | grep -q 'order-clerk'; then
133
+ ok "J blocks a card whose durable copy is under the account plugin root"
134
+ else bad "J rc=$RC err=$ERR"; fi
135
+
136
+ # K
137
+ fire "$ACCT" "$(envelope mcp__plugin_email_email__email-draft '""')"
138
+ if printf '%s' "$ERR" | grep -q 'op=refuse account=2078cb54 tool=mcp__plugin_email_email__email-draft specialist=email-clerk'; then
139
+ ok "K the refuse line carries the account, the tool and the specialist"
140
+ else bad "K err=$ERR"; fi
141
+
142
+ # L — the hook cannot import the manager's TypeScript, so it mirrors
143
+ # CC_NATIVE_TOOLS as a python literal. A mirror that drifts silently un-gates or
144
+ # over-gates, so the two sets are compared rather than trusted.
145
+ TS_SET="$(sed -n '/export const CC_NATIVE_TOOLS/,/^\])/p' "$DRIFT_SOURCE" \
146
+ | grep -oE "'[A-Za-z]+'" | tr -d "'" | sort -u)"
147
+ SH_SET="$(sed -n '/^NATIVE = {/,/^}/p' "$GATE" \
148
+ | grep -oE '"[A-Za-z]+"' | tr -d '"' | sort -u)"
149
+ if [ "$TS_SET" = "$SH_SET" ]; then
150
+ ok "L the native set matches CC_NATIVE_TOOLS"
151
+ else
152
+ bad "L native sets differ:"
153
+ diff <(printf '%s\n' "$TS_SET") <(printf '%s\n' "$SH_SET") | sed 's/^/ /'
154
+ fi
155
+
156
+ echo "──────── specialist-dispatch-gate summary ────────"
157
+ echo "pass=$PASS fail=$FAIL"
158
+ [ "$FAIL" -eq 0 ]
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env bash
2
+ # html-structure-gate — PreToolUse gate on Write / Edit / MultiEdit.
3
+ #
4
+ # Refuses a write that would produce an HTML document with a <style> element
5
+ # opened inside another one, or with more openers than closers.
6
+ #
7
+ # The fault is not cosmetic. HTML parses style content as raw text, so the FIRST
8
+ # </style> closes the outer element: everything after it becomes body content and
9
+ # renders as visible text, and the rule immediately after the stray opener is
10
+ # eaten as part of an invalid selector. One fault, two symptoms — a leaked
11
+ # stylesheet above the header and an unstyled box further down the same page.
12
+ #
13
+ # The detector has three rules, and the same three are implemented in the
14
+ # session-manager census that walks what is already on disk:
15
+ # 1. HTML comments are stripped first, each replaced by its own newlines, so a
16
+ # <style> inside <!-- … --> is not scanned and line numbers do not shift.
17
+ # That comment case is real: two account templates carry one and are clean,
18
+ # and a comment-blind detector reports both.
19
+ # 2. <style…> and </style…> are walked case-insensitively, tracking depth. An
20
+ # opener seen at depth > 0 is the fault.
21
+ # 3. At end of input, openers must equal closers; an unmatched opener is
22
+ # reported at its own line.
23
+ #
24
+ # It walks the document the call would PRODUCE, not the fragment it carries. For
25
+ # Write that is `content`; for Edit and MultiEdit the on-disk file is read and
26
+ # the replacements applied in memory first. Walking a fragment cannot work: rule
27
+ # 3 would refuse every partial edit, and a nested opener whose outer <style> is
28
+ # already on disk would pass.
29
+ #
30
+ # Scope is the whole account with an exclusion list, never a bucket list: every
31
+ # .html/.htm resolving inside the account dir except under node_modules/,
32
+ # .wrangler/, .tool/ and .trash/. A bucket list is what let the live document
33
+ # through — it sat under sites/<project>/public/, which no quote bucket covers.
34
+ # The account dir is this hook's cwd, as in fs-schema-guard.sh.
35
+ #
36
+ # Exit codes: 0 = allow, 2 = block (stderr shown to the agent). Fail OPEN on an
37
+ # uninspectable envelope — tty, empty stdin, unreadable target, unparseable edit.
38
+ # A gate that bricks writes on a parse failure is worse than the fault it catches.
39
+ #
40
+ # The observability line is a direct append to the resolved server.log, not an
41
+ # echo: a hook's stderr reaches no sink on an allow, so a stderr-only line would
42
+ # be an audit nothing reads. No task numbers or internal refs in operator text.
43
+
44
+ set -uo pipefail
45
+
46
+ # No envelope (tty or empty stdin) → allow: cannot inspect, must not brick writes.
47
+ if [ -t 0 ]; then exit 0; fi
48
+ INPUT=$(cat)
49
+ [ -z "$INPUT" ] && exit 0
50
+
51
+ VERDICT=$(INPUT="$INPUT" ACCT="$PWD" python3 - <<'PY' 2>/dev/null || true
52
+ import os, re, json, sys
53
+
54
+ COMMENT = re.compile(r"<!--.*?-->", re.S)
55
+ CSS_COMMENT = re.compile(r"/\*.*?\*/", re.S)
56
+ TAG = re.compile(r"<\s*(/)?\s*style\b[^>]*>", re.I)
57
+ EXCLUDED = ("node_modules", ".wrangler", ".tool", ".trash")
58
+
59
+
60
+ def fault_line(s):
61
+ """Line of the first nested or unmatched <style> opener, else None."""
62
+ t = COMMENT.sub(lambda m: "\n" * m.group(0).count("\n"), s)
63
+ spans = [(m.start(), m.end()) for m in CSS_COMMENT.finditer(t)]
64
+ depth = 0
65
+ open_line = 0
66
+ for m in TAG.finditer(t):
67
+ line = t.count("\n", 0, m.start()) + 1
68
+ if m.group(1):
69
+ if depth > 0:
70
+ depth -= 1
71
+ elif depth > 0:
72
+ # A CSS comment protects an opener but never a closer. Style content
73
+ # is raw text to the HTML parser, so </style> terminates the element
74
+ # wherever it appears; an opener is never a parse event there, only
75
+ # text the CSS parser reads as an invalid selector — and inside a
76
+ # comment it is discarded, so nothing is eaten.
77
+ if any(a <= m.start() < b for a, b in spans):
78
+ continue
79
+ return line
80
+ else:
81
+ depth = 1
82
+ open_line = line
83
+ return open_line if depth > 0 else None
84
+
85
+
86
+ try:
87
+ d = json.loads(os.environ["INPUT"])
88
+ except Exception:
89
+ sys.exit(0)
90
+ if (d.get("tool_name", "") or "") not in ("Write", "Edit", "MultiEdit"):
91
+ sys.exit(0)
92
+ tool = d["tool_name"]
93
+ ti = d.get("tool_input", {}) or {}
94
+ target = ti.get("file_path") or ""
95
+ if not target:
96
+ sys.exit(0)
97
+
98
+ acct = os.path.realpath(os.environ["ACCT"])
99
+ ab = os.path.realpath(target if os.path.isabs(target) else os.path.join(acct, target))
100
+ if not ab.startswith(acct + os.sep):
101
+ sys.exit(0)
102
+ rel = os.path.relpath(ab, acct)
103
+ if os.path.splitext(rel)[1].lower() not in (".html", ".htm"):
104
+ sys.exit(0)
105
+ if any(p in EXCLUDED for p in rel.split(os.sep)):
106
+ sys.exit(0)
107
+
108
+ if tool == "Write":
109
+ produced = ti.get("content")
110
+ if not isinstance(produced, str):
111
+ sys.exit(0)
112
+ else:
113
+ try:
114
+ with open(ab, encoding="utf-8", errors="replace") as fh:
115
+ produced = fh.read()
116
+ except Exception:
117
+ sys.exit(0)
118
+ edits = ti.get("edits") if tool == "MultiEdit" else [ti]
119
+ if not isinstance(edits, list):
120
+ sys.exit(0)
121
+ for e in edits:
122
+ if not isinstance(e, dict):
123
+ sys.exit(0)
124
+ old, new = e.get("old_string"), e.get("new_string")
125
+ if not isinstance(old, str) or not isinstance(new, str):
126
+ sys.exit(0)
127
+ produced = produced.replace(old, new) if e.get("replace_all") else produced.replace(old, new, 1)
128
+
129
+ line = fault_line(produced)
130
+ if line is None:
131
+ sys.exit(0)
132
+ print(line)
133
+ print(json.dumps(rel))
134
+ PY
135
+ )
136
+
137
+ [ -z "$VERDICT" ] && exit 0
138
+ LINE=$(printf '%s' "$VERDICT" | sed -n 1p)
139
+ QREL=$(printf '%s' "$VERDICT" | sed -n 2p)
140
+ [ -z "$LINE" ] && exit 0
141
+
142
+ # Sink ladder, as quote-render-pdf-conformance.sh: LOG_DIR is stamped on the
143
+ # rc-spawn path, CLAUDE_SESSION_MANAGER_PERSIST_DIR is on the systemd unit and
144
+ # resolves to the same directory, the account's own logs dir is the last resort.
145
+ #
146
+ # There is deliberately no hard-coded brand fallback. Four brands are co-resident
147
+ # on one box, each with its own ~/.<brand>/logs, so a literal would file this
148
+ # account's path in a SIBLING brand's server.log. When no rung resolves, the line
149
+ # is dropped: the refusal still blocks and still reaches the agent on stderr.
150
+ LOGD=""
151
+ if [ -n "${LOG_DIR:-}" ] && [ -d "${LOG_DIR}" ]; then
152
+ LOGD="$LOG_DIR"
153
+ elif [ -n "${CLAUDE_SESSION_MANAGER_PERSIST_DIR:-}" ] && [ -d "${CLAUDE_SESSION_MANAGER_PERSIST_DIR}/logs" ]; then
154
+ LOGD="$CLAUDE_SESSION_MANAGER_PERSIST_DIR/logs"
155
+ elif [ -d "$PWD/logs" ]; then
156
+ LOGD="$PWD/logs"
157
+ fi
158
+ if [ -n "$LOGD" ]; then
159
+ ID8=$(basename "$PWD" | cut -c1-8)
160
+ printf '[html-structure-gate] op=refuse account=%s file=%s line=%s\n' \
161
+ "$ID8" "$QREL" "$LINE" >>"$LOGD/server.log" 2>/dev/null || true
162
+ fi
163
+
164
+ echo "Blocked: this document opens a <style> element inside another one (line $LINE). The browser closes the outer element at the first </style>, so the rest of the stylesheet renders as visible text on the page and the rule after the stray opener is discarded. Put the block in the body as its own <style> element, or merge its rules into the one already open." >&2
165
+ exit 2