@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
@@ -4,20 +4,37 @@
4
4
  # The companion to the PreToolUse quote-render-gate: that gate checks the
5
5
  # rendered HTML on the deterministic path (via the render receipt) before the
6
6
  # PDF is printed; the HTML the tool gated cannot yet be a PDF at PreToolUse, so
7
- # the two PDF-only conformance checks run here, once the PDF exists.
7
+ # the PDF-only conformance checks run here, once the PDF exists.
8
8
  #
9
- # For a gated client-quote PDF (memory/users/<phone>/documents/quote-<jobId>.pdf
10
- # or ...-breakdown.pdf) whose render receipt names the source HTML, it asserts:
11
- # 1. the PDF's /URI annotation count >= the source HTML's absolute-link count
9
+ # PAGE CHROME receipt-free and path-agnostic, so it also covers template-seeded
10
+ # documents (budget estimates, valuations, works orders) that never pass through
11
+ # the quote renderer. Each check gates only a document that DECLARES the thing it
12
+ # checks, so a brochure is never touched:
13
+ # 1. a running header on every physical page, when the source declares a text
14
+ # margin box. Chrome decides pagination at print time, so a header placed
15
+ # next to a hand-declared page break misses every bleed page.
16
+ # 2. that header's image no wider than a third of the page, when the source
17
+ # declares an image margin box. Chromium paints a margin box's content:url()
18
+ # at intrinsic pixel size and does NOT scale it to the declared box.
19
+ # Both are offline, and both report before either exits, so one print surfaces
20
+ # both faults rather than the second appearing only after the first is reprinted.
21
+ #
22
+ # LINKS — for a gated client-quote PDF (memory/users/<phone>/documents/
23
+ # quote-<jobId>.pdf or ...-breakdown.pdf) whose render receipt names the source:
24
+ # 3. the PDF's /URI annotation count >= the source HTML's absolute-link count
12
25
  # (a print that dropped links — a rasterised or flattened copy — carries
13
26
  # fewer /URI than the HTML had anchors);
14
- # 2. every embedded https:// anchor in the source HTML answers 200 (a pending
27
+ # 4. every embedded https:// anchor in the source HTML answers 200 (a pending
15
28
  # redeploy that would 404 the Sign-Online link is caught before the PDF is
16
29
  # sent).
17
30
  # The /URI-count check is offline and runs first; the network fetch runs only
18
31
  # when the count check passes, so a link-loss regression never depends on the
19
32
  # network being up.
20
33
  #
34
+ # Every path that inspects nothing emits `op=skip reason=…`: a silent return is
35
+ # indistinguishable from a gate that never ran, which is the state an account
36
+ # with a stale PostToolUse matcher is actually in.
37
+ #
21
38
  # Exit codes: 0 = allow, 2 = block (stderr shown to the agent). Fail OPEN on any
22
39
  # uninspectable call (tty / empty stdin / no python3 / no receipt / PDF not
23
40
  # written): the no-receipt case is already the PreToolUse gate's to block, and
@@ -184,16 +201,73 @@ for p in range(first, pages + 1):
184
201
  missing.append(p)
185
202
 
186
203
  checked = pages - first + 1
204
+ print("LOG|op=pdf-header-conformance file=%s pages=%d pagesChecked=%d pagesHeaderless=%d"
205
+ % (base, pages, checked, len(missing)))
187
206
  if missing:
188
- print("LOG|op=pdf-header-conformance file=%s pages=%d pagesChecked=%d pagesHeaderless=%d"
189
- % (base, pages, checked, len(missing)))
190
- out("HBLOCK|%s|pagesHeaderless=%d pages=%s"
191
- % (base, len(missing), ",".join(str(x) for x in missing)))
192
- out("LOG|op=pdf-header-conformance file=%s pages=%d pagesChecked=%d pagesHeaderless=0"
193
- % (base, pages, checked))
207
+ print("HBLOCK|%s|pagesHeaderless=%d pages=%s"
208
+ % (base, len(missing), ",".join(str(x) for x in missing)))
209
+
210
+ # --- running-image geometry -------------------------------------------------
211
+ # Chromium paints a page-margin box's content:url() at INTRINSIC pixel size (one
212
+ # image px to one CSS px at 96 px/in) and does NOT scale it to the box's declared
213
+ # width/height, so a full-resolution logo covers most of the page on every page
214
+ # the box paints. Measured 2026-08-05 on a real print: a 700x252 asset in a
215
+ # declared 34mm box rendered 525.0 pt wide on a 594.96 pt A4 page, 88%.
216
+ #
217
+ # The assertion is scoped to the RUNNING image — the object present on every
218
+ # checked page, which is what a running header is by construction — never to
219
+ # every image. A content photo legitimately runs wider: measured across three
220
+ # real quotes, running headers occupy 9.0% to 18.9% of page width while
221
+ # single-page content photos occupy 38.7% to 77.2%, so a whole-page threshold
222
+ # blocks legitimate documents while the recurrence-scoped one separates the
223
+ # correct case from the defect by more than four to one.
224
+ #
225
+ # This runs after the header verdict rather than instead of it, so one print
226
+ # reports a headerless page AND an oversized logo together. Both are offline, so
227
+ # the file's offline-before-network ordering still holds.
228
+ if re.search(r'@top-(?:left|center|right)\s*\{[^}]*?content\s*:\s*url\(', doc, re.S) and checked >= 2:
229
+ try:
230
+ listing = subprocess.run(["pdfimages", "-list", pdf],
231
+ capture_output=True, text=True, timeout=30).stdout
232
+ except Exception:
233
+ listing = ""
234
+ if not listing:
235
+ out("LOG|op=pdf-image-geometry file=%s pagesChecked=0 reason=no-poppler" % base)
236
+ # Columns, zero-based: 0 page, 3 width, 10 object, 12 x-ppi. A row whose
237
+ # object or ppi is not numeric (an inline image) is skipped rather than
238
+ # guessed at.
239
+ per = {}
240
+ for row in listing.splitlines()[2:]:
241
+ f = row.split()
242
+ if len(f) < 13:
243
+ continue
244
+ try:
245
+ pg, wpx, obj, ppi = int(f[0]), int(f[3]), f[10], float(f[12])
246
+ except ValueError:
247
+ continue
248
+ if pg < first or pg > pages or ppi <= 0:
249
+ continue
250
+ seen = per.setdefault(obj, {})
251
+ seen[pg] = max(seen.get(pg, 0.0), wpx / ppi * 72.0)
252
+ limit = pw / 3.0
253
+ running = {o: max(v.values()) for o, v in per.items() if len(v) == checked}
254
+ widest = max(running.values()) if running else 0.0
255
+ over = sorted(w for w in running.values() if w > limit)
256
+ # Logged whether or not it fails: this is the line that decides the verdict.
257
+ print("LOG|op=pdf-image-geometry file=%s pagesChecked=%d runningImages=%d "
258
+ "widestPt=%.1f fracPage=%.1f%% limitPt=%.1f"
259
+ % (base, checked, len(running), widest, widest / pw * 100.0, limit))
260
+ if over:
261
+ print("GBLOCK|%s|widestPt=%.1f fracPage=%.1f%% limitPt=%.1f"
262
+ % (base, over[-1], over[-1] / pw * 100.0, limit))
263
+ sys.exit(0)
194
264
  PY
195
265
  )
196
266
 
267
+ # Both page-chrome faults are collected before exiting, so a document with a
268
+ # headerless page AND an oversized running logo reports both on one print rather
269
+ # than surfacing the second only after the first is fixed and reprinted.
270
+ BLOCKED=0
197
271
  while IFS= read -r hline; do
198
272
  case "$hline" in
199
273
  LOG\|*) emit_log "${hline#LOG|}" ;;
@@ -203,32 +277,49 @@ while IFS= read -r hline; do
203
277
  hdetail=${hrest#*|}
204
278
  emit_log "op=bypass file=$hfile reason=pdf-header-conformance detail=$hdetail"
205
279
  echo "Blocked: this printed document has physical pages carrying no running header, so it would go out with unbranded pages ($hdetail). The header must be a property of the page itself, a page margin box, not a header block placed next to a declared page break, because the browser decides the real page boundaries at print time. Fix the document's print CSS, print it again, and check every page." >&2
206
- exit 2
280
+ BLOCKED=1
281
+ ;;
282
+ GBLOCK\|*)
283
+ grest=${hline#GBLOCK|}
284
+ gfile=${grest%%|*}
285
+ gdetail=${grest#*|}
286
+ emit_log "op=bypass file=$gfile reason=pdf-header-image-geometry detail=$gdetail"
287
+ echo "Blocked: the running header image on this printed document covers most of the page width ($gdetail). An image used as a page margin box's content is painted at its own pixel size and is not scaled down to the box you declared, so the image file itself has to be made the size the box expects. Shrink the asset, rebuild the document, print it again, and check a page after the first." >&2
288
+ BLOCKED=1
207
289
  ;;
208
290
  esac
209
291
  done <<< "$HRESULT"
292
+ [ "$BLOCKED" -eq 1 ] && exit 2
210
293
 
211
294
  RESULT=$(INPUT="$INPUT" BASE="$PWD" python3 - <<'PY' 2>/dev/null || true
212
295
  import os, json, re, sys, urllib.request
213
296
 
297
+ # Every return that inspects nothing names its reason. A silent return is
298
+ # indistinguishable from a gate that never ran at all, which is the state a
299
+ # stale PostToolUse matcher leaves an account in; fail-open behaviour itself is
300
+ # unchanged, only its visibility.
301
+ def skip(reason):
302
+ print("SKIP|%s" % reason)
303
+ sys.exit(0)
304
+
214
305
  try:
215
306
  d = json.loads(os.environ["INPUT"])
216
307
  except Exception:
217
- sys.exit(0) # uninspectable envelope -> fail open
308
+ skip("bad-envelope")
218
309
 
219
310
  tool = d.get("tool_name", "") or ""
220
311
  if not tool.endswith("browser-pdf-save"):
221
- sys.exit(0)
312
+ skip("not-browser-tool")
222
313
  path = (d.get("tool_input", {}) or {}).get("path", "") or ""
223
314
  if not path:
224
- sys.exit(0)
315
+ skip("no-path")
225
316
  norm = os.path.normpath(path)
226
317
  m = re.search(r'(?:^|/)memory/users/[^/]+/documents/quote-(.+?)(-breakdown)?\.pdf$', norm)
227
318
  if not m:
228
- sys.exit(0) # not a gated client-quote artefact
319
+ skip("not-gated") # not a gated client-quote artefact
229
320
  jobid = m.group(1)
230
321
  if "/" in jobid or ".." in jobid:
231
- sys.exit(0) # the PreToolUse gate owns bad-jobid; here fail open
322
+ skip("bad-jobid") # the PreToolUse gate owns bad-jobid; here fail open
232
323
  variant = m.group(2) # "-breakdown" or None
233
324
 
234
325
  base = os.environ["BASE"]
@@ -236,12 +327,12 @@ receipt_path = os.path.join(base, "quoting", "jobs", jobid, "render-receipt.json
236
327
  try:
237
328
  receipt = json.load(open(receipt_path))
238
329
  except Exception:
239
- sys.exit(0) # no receipt -> PreToolUse gate already blocks; fail open
330
+ skip("no-receipt") # PreToolUse gate already blocks that; fail open here
240
331
 
241
332
  filed = receipt.get("filed", {}) or {}
242
333
  html_path = filed.get("breakdown" if variant else "quote", "")
243
334
  if not html_path or not os.path.isfile(html_path) or not os.path.isfile(path):
244
- sys.exit(0) # nothing to compare against -> fail open
335
+ skip("no-source") # nothing to compare against -> fail open
245
336
 
246
337
  html = open(html_path, encoding="utf-8", errors="replace").read()
247
338
  # Anchors that become a /URI annotation in the printed PDF: absolute http(s) or
@@ -274,6 +365,7 @@ PY
274
365
  )
275
366
 
276
367
  case "$RESULT" in
368
+ SKIP\|*) emit_log "op=skip reason=${RESULT#SKIP|}" ;;
277
369
  BLOCK\|*)
278
370
  rest=${RESULT#BLOCK|}
279
371
  jobid=${rest%%|*}
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env bash
2
+ # specialist-dispatch-gate — PreToolUse gate on every tool.
3
+ #
4
+ # An account's own specialists own their tools. This gate refuses the main
5
+ # conversation a tool one of the account's authored cards declares, and names the
6
+ # specialist to dispatch instead. It exists because skipped delegation writes no
7
+ # record: an inline draft logs an ordinary success, so nothing in any log
8
+ # separates "the clerk did it" from "the admin did it by hand". On one account
9
+ # four authored clerks owned email, works orders, invoices and quotes while the
10
+ # admin produced all of them inline for three days.
11
+ #
12
+ # Two narrowings, both load-bearing.
13
+ #
14
+ # Card axis — the account's OWN cards only. Provisioning copies every shipped
15
+ # template into each account, and one of them declares roughly 130 tools
16
+ # including the whole email, WhatsApp, calendar and browser surfaces, so reading
17
+ # every card would refuse the admin nearly its entire seat. A card is the
18
+ # account's own when its filename appears in none of the shipped roots. The
19
+ # account's own plugin root is deliberately NOT one of those: a card filed there
20
+ # is that account's durable copy, not something we ship.
21
+ #
22
+ # Tool axis — native tools are never gated. Every card declares some of Bash,
23
+ # Read, Write and Edit, and refusing those would brick the seat. The NATIVE set
24
+ # below mirrors CC_NATIVE_TOOLS in the session manager, which a bash hook cannot
25
+ # import; the gate's own test compares the two sets rather than trusting them to
26
+ # stay equal.
27
+ #
28
+ # Caller identity is `agent_type` from the hook envelope, which Claude Code sets
29
+ # when a hook fires inside a subagent. A non-empty value exits 0 unconditionally:
30
+ # this gate must never filter a subagent's tool surface, which is the failure an
31
+ # inherited CLI deny produced when it stripped the writers from a specialist's
32
+ # own frontmatter. The MAXY_SPECIALIST env is not usable — it is stamped only for
33
+ # a specialist spawned as its own PTY process, and native Agent-tool subagents
34
+ # inherit the admin process env.
35
+ #
36
+ # Exit codes: 0 = allow, 2 = block (stderr shown to the agent). Fail OPEN on an
37
+ # uninspectable envelope, and say so with op=degraded when the card set cannot be
38
+ # read: failing open is fine, failing open silently is the fault. No task numbers
39
+ # or internal refs in any operator-visible string.
40
+
41
+ set -uo pipefail
42
+
43
+ # No envelope (tty or empty stdin) → allow: cannot inspect, must not brick tools.
44
+ if [ -t 0 ]; then exit 0; fi
45
+ INPUT=$(cat)
46
+ [ -z "$INPUT" ] && exit 0
47
+
48
+ # Prints nothing for an out-of-scope call, "block <specialist>" for a refusal, or
49
+ # "degraded <reason>" when the card set could not be read.
50
+ DECISION=$(INPUT="$INPUT" python3 - <<'PY' 2>/dev/null || true
51
+ import os, json, re, sys, glob
52
+
53
+ # Mirrors CC_NATIVE_TOOLS (claude-session-manager/src/specialist-drift.ts). A
54
+ # card that declares these declares them for its own use; they are the admin's
55
+ # too, and gating them would refuse the seat its own disk and shell.
56
+ NATIVE = {
57
+ "Read", "Write", "Edit", "MultiEdit", "Glob", "Grep", "LS", "Bash",
58
+ "BashOutput", "KillShell", "Skill", "WebSearch", "WebFetch", "Task",
59
+ "TodoWrite", "NotebookEdit", "NotebookRead", "AskUserQuestion",
60
+ }
61
+
62
+ try:
63
+ d = json.loads(os.environ["INPUT"])
64
+ except Exception:
65
+ sys.exit(0)
66
+
67
+ tool = (d.get("tool_name", "") or "").strip()
68
+ if not tool or tool in NATIVE:
69
+ sys.exit(0)
70
+
71
+ # A subagent is never filtered. This is the whole reason the gate can be broad:
72
+ # it constrains one seat, not a tool surface.
73
+ if (d.get("agent_type") or "").strip():
74
+ sys.exit(0)
75
+
76
+ agents_dir = os.path.join(os.getcwd(), "specialists", "agents")
77
+ platform_root = os.environ.get("PLATFORM_ROOT", "")
78
+
79
+ shipped = set()
80
+ try:
81
+ if platform_root:
82
+ for f in glob.glob(os.path.join(platform_root, "templates", "specialists", "agents", "*.md")):
83
+ shipped.add(os.path.basename(f))
84
+ for f in glob.glob(os.path.join(platform_root, "plugins", "*", "agents", "*.md")):
85
+ shipped.add(os.path.basename(f))
86
+ premium = os.path.join(os.path.dirname(platform_root), "premium-plugins")
87
+ for f in glob.glob(os.path.join(premium, "*", "agents", "*.md")):
88
+ shipped.add(os.path.basename(f))
89
+ if not os.path.isdir(agents_dir):
90
+ # An account with no specialists directory is normal, not degraded.
91
+ sys.exit(0)
92
+ cards = sorted(os.listdir(agents_dir))
93
+ except Exception as err:
94
+ print("degraded %s" % type(err).__name__)
95
+ sys.exit(0)
96
+
97
+
98
+ def fields(path):
99
+ """(name, tools) from a card's frontmatter, or None when there is none."""
100
+ try:
101
+ with open(path, encoding="utf-8") as fh:
102
+ lines = fh.read().split("\n")
103
+ except Exception:
104
+ return None
105
+ if not lines or lines[0].strip() != "---":
106
+ return None
107
+ name = tools = None
108
+ for line in lines[1:]:
109
+ if line.strip() == "---":
110
+ break
111
+ m = re.match(r'^name:\s*(.+?)\s*$', line)
112
+ if m:
113
+ name = m.group(1).strip().strip('"').strip("'")
114
+ m = re.match(r'^tools:\s*(.+?)\s*$', line)
115
+ if m:
116
+ tools = m.group(1)
117
+ return (name, tools)
118
+
119
+
120
+ for filename in cards:
121
+ if not filename.endswith(".md"):
122
+ continue
123
+ if filename in shipped:
124
+ continue
125
+ got = fields(os.path.join(agents_dir, filename))
126
+ if not got:
127
+ continue
128
+ name, tools = got
129
+ if not name or not tools:
130
+ continue
131
+ # Match the tool itself, never a substring of a longer name.
132
+ for declared in tools.split(","):
133
+ declared = declared.strip()
134
+ if not declared or declared in NATIVE:
135
+ continue
136
+ if declared == tool:
137
+ print("block %s" % name)
138
+ sys.exit(0)
139
+ PY
140
+ )
141
+
142
+ [ -z "$DECISION" ] && exit 0
143
+
144
+ VERB=${DECISION%% *}
145
+ ARG=${DECISION#* }
146
+
147
+ if [ "$VERB" = "degraded" ]; then
148
+ echo "[specialist-gate] op=degraded reason=$ARG" >&2
149
+ exit 0
150
+ fi
151
+
152
+ ACCOUNT_ID8=$(basename "$PWD" | cut -c1-8)
153
+ TOOL=$(printf '%s' "$INPUT" \
154
+ | python3 -c 'import sys,json;print(json.load(sys.stdin).get("tool_name",""))' 2>/dev/null \
155
+ || echo "")
156
+
157
+ echo "[specialist-gate] op=refuse account=$ACCOUNT_ID8 tool=$TOOL specialist=$ARG" >&2
158
+ echo "Blocked: this tool belongs to your $ARG. Dispatch that specialist with the Agent tool and let it do the work — it holds the tool and the standards that go with it." >&2
159
+ exit 2
@@ -0,0 +1,27 @@
1
+ export type AdminBindVerdict = {
2
+ kind: "ok";
3
+ pin: string;
4
+ } | {
5
+ kind: "nothing-to-set";
6
+ } | {
7
+ kind: "bad-phone";
8
+ } | {
9
+ kind: "bad-telegram";
10
+ } | {
11
+ kind: "no-such-user";
12
+ } | {
13
+ kind: "not-an-admin";
14
+ };
15
+ export declare function classifyAdminBind(input: {
16
+ userId: string;
17
+ phone?: string;
18
+ telegram?: string;
19
+ users: ReadonlyArray<{
20
+ userId: string;
21
+ pin: string;
22
+ }>;
23
+ admins: ReadonlyArray<{
24
+ userId: string;
25
+ }>;
26
+ }): AdminBindVerdict;
27
+ //# sourceMappingURL=admin-bind-classify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-bind-classify.d.ts","sourceRoot":"","sources":["../src/admin-bind-classify.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,GAC1B;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC;AAE7B,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,aAAa,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtD,MAAM,EAAE,aAAa,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3C,GAAG,gBAAgB,CAUnB"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Task 2531 — the pure refusal policy for `admin-bind`, split from the IO
3
+ * wrapper for the same reason `resolve-admin-userid.ts` splits its picker: every
4
+ * refusal is then unit-testable without a filesystem, and the policy cannot
5
+ * drift from the tool that enforces it.
6
+ *
7
+ * Never infers a userId. `addAdminPhone` takes an account directory and a phone
8
+ * with no userId, which is exactly why it cannot write a binding: the allowlist
9
+ * knows a number, not a person. Binding runs the other way, so the caller names
10
+ * whom and an unknown userId is refused rather than guessed at.
11
+ */
12
+ import { isE164, isTelegramChatId } from "./binding-validate.js";
13
+ export function classifyAdminBind(input) {
14
+ if (input.phone === undefined && input.telegram === undefined)
15
+ return { kind: "nothing-to-set" };
16
+ if (input.phone !== undefined && !isE164(input.phone))
17
+ return { kind: "bad-phone" };
18
+ if (input.telegram !== undefined && !isTelegramChatId(input.telegram))
19
+ return { kind: "bad-telegram" };
20
+ const row = input.users.find(u => u.userId === input.userId);
21
+ if (!row)
22
+ return { kind: "no-such-user" };
23
+ if (!input.admins.some(a => a.userId === input.userId))
24
+ return { kind: "not-an-admin" };
25
+ // The existing hash, returned so the caller can route through writeAdminEntry
26
+ // (which requires a pin) without rotating the credential it is not touching.
27
+ return { kind: "ok", pin: row.pin };
28
+ }
29
+ //# sourceMappingURL=admin-bind-classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-bind-classify.js","sourceRoot":"","sources":["../src/admin-bind-classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAUjE,MAAM,UAAU,iBAAiB,CAAC,KAMjC;IACC,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;IACjG,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACpF,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IACvG,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7D,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IAC1C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IACxF,8EAA8E;IAC9E,6EAA6E;IAC7E,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Task 2531 — the two channel-binding shapes, in one place.
3
+ *
4
+ * Imported by `index.ts` (admin-add's own validation) and by
5
+ * `admin-bind-classify.ts` (admin-bind's refusal policy), so the tool and the
6
+ * policy cannot disagree about what a valid binding is. Two copies of a
7
+ * validation rule drift the same way two copies of the record type did.
8
+ */
9
+ export declare function isE164(v: string): boolean;
10
+ export declare function isTelegramChatId(v: string): boolean;
11
+ //# sourceMappingURL=binding-validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding-validate.d.ts","sourceRoot":"","sources":["../src/binding-validate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEzC;AAMD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEnD"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Task 2531 — the two channel-binding shapes, in one place.
3
+ *
4
+ * Imported by `index.ts` (admin-add's own validation) and by
5
+ * `admin-bind-classify.ts` (admin-bind's refusal policy), so the tool and the
6
+ * policy cannot disagree about what a valid binding is. Two copies of a
7
+ * validation rule drift the same way two copies of the record type did.
8
+ */
9
+ /** E.164, the same shape `config-persist.ts` enforces for `adminPhones`. */
10
+ const E164 = /^\+[1-9]\d{1,14}$/;
11
+ export function isE164(v) {
12
+ return E164.test(v);
13
+ }
14
+ /** A Telegram chat id is an integer. A group chat's is negative, so the leading
15
+ * minus is legal and refusing it would reject a real binding. */
16
+ const TG_CHAT_ID = /^-?\d+$/;
17
+ export function isTelegramChatId(v) {
18
+ return TG_CHAT_ID.test(v);
19
+ }
20
+ //# sourceMappingURL=binding-validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding-validate.js","sourceRoot":"","sources":["../src/binding-validate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,4EAA4E;AAC5E,MAAM,IAAI,GAAG,mBAAmB,CAAC;AAEjC,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACtB,CAAC;AAED;kEACkE;AAClE,MAAM,UAAU,GAAG,SAAS,CAAC;AAE7B,MAAM,UAAU,gBAAgB,CAAC,CAAS;IACxC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC"}