@rubytech/create-sitedesk-code 0.1.508 → 0.1.510

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 (326) hide show
  1. package/dist/index.js +53 -0
  2. package/package.json +1 -1
  3. package/payload/platform/docs/superpowers/plans/2026-07-26-task-1902-account-owned-entry-declarations.md +555 -0
  4. package/payload/platform/docs/superpowers/plans/2026-07-26-task-1926-portal-push-concurrency-guard.md +482 -0
  5. package/payload/platform/docs/superpowers/plans/2026-07-26-task-2023-sidebar-sessions-async-reads.md +394 -0
  6. package/payload/platform/docs/superpowers/specs/2026-07-26-task-1902-account-owned-entry-declarations-design.md +232 -0
  7. package/payload/platform/docs/superpowers/specs/2026-07-26-task-1926-portal-push-concurrency-guard-design.md +200 -0
  8. package/payload/platform/docs/superpowers/specs/2026-07-26-task-2014-ui-suite-nondeterminism-design.md +169 -0
  9. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  10. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +12 -7
  11. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +12 -0
  12. package/payload/platform/plugins/business-assistant/PLUGIN.md +1 -0
  13. package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -0
  14. package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +68 -28
  15. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +271 -29
  16. package/payload/platform/plugins/cloudflare/mcp/__tests__/schema-exposed-dirs.test.ts +21 -0
  17. package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +5 -4
  18. package/payload/platform/plugins/docs/references/admin-ui.md +10 -5
  19. package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
  20. package/payload/platform/plugins/scheduling/PLUGIN.md +1 -0
  21. package/payload/platform/scripts/__tests__/account-schema-owned-dirs.test.sh +112 -4
  22. package/payload/platform/scripts/__tests__/check-brand-cache.test.sh +231 -0
  23. package/payload/platform/scripts/check-brand-cache.sh +138 -0
  24. package/payload/platform/scripts/lib/account-schema-owned-dirs.py +61 -32
  25. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts.map +1 -1
  26. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js +17 -30
  27. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js.map +1 -1
  28. package/payload/platform/services/claude-session-manager/dist/config.d.ts +50 -4
  29. package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
  30. package/payload/platform/services/claude-session-manager/dist/config.js +58 -15
  31. package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
  32. package/payload/server/maxy-edge.js +26 -11
  33. package/payload/server/public/activity.html +5 -5
  34. package/payload/server/public/agents.html +4 -4
  35. package/payload/server/public/assets/AdminLoginScreens-CWMpccrR.css.br +0 -0
  36. package/payload/server/public/assets/AdminLoginScreens-CWMpccrR.css.gz +0 -0
  37. package/payload/server/public/assets/AdminLoginScreens-bksN0XPe.js +1 -0
  38. package/payload/server/public/assets/AdminLoginScreens-bksN0XPe.js.br +0 -0
  39. package/payload/server/public/assets/AdminLoginScreens-bksN0XPe.js.gz +0 -0
  40. package/payload/server/public/assets/{AdminShell-HeELCCjJ.js → AdminShell-BMPEWNZk.js} +1 -1
  41. package/payload/server/public/assets/AdminShell-BMPEWNZk.js.br +0 -0
  42. package/payload/server/public/assets/AdminShell-BMPEWNZk.js.gz +0 -0
  43. package/payload/server/public/assets/_baseFor-Be3EKK3K.js.br +0 -0
  44. package/payload/server/public/assets/_baseFor-Be3EKK3K.js.gz +0 -0
  45. package/payload/server/public/assets/{activity-BEIDAxDA.js → activity-ByWYF5dC.js} +1 -1
  46. package/payload/server/public/assets/activity-ByWYF5dC.js.br +0 -0
  47. package/payload/server/public/assets/activity-ByWYF5dC.js.gz +0 -0
  48. package/payload/server/public/assets/admin-DGr1HuPg.js +1 -0
  49. package/payload/server/public/assets/admin-DGr1HuPg.js.br +0 -0
  50. package/payload/server/public/assets/admin-DGr1HuPg.js.gz +0 -0
  51. package/payload/server/public/assets/agents-C0rTfrYd.js +1 -0
  52. package/payload/server/public/assets/agents-C0rTfrYd.js.br +0 -0
  53. package/payload/server/public/assets/agents-C0rTfrYd.js.gz +0 -0
  54. package/payload/server/public/assets/arc-DaI5MLTq.js.br +0 -0
  55. package/payload/server/public/assets/arc-DaI5MLTq.js.gz +0 -0
  56. package/payload/server/public/assets/architecture-YZFGNWBL-DWZIX5aV.js.br +0 -0
  57. package/payload/server/public/assets/architectureDiagram-Q4EWVU46-CBGVUEN9.js.br +0 -0
  58. package/payload/server/public/assets/architectureDiagram-Q4EWVU46-CBGVUEN9.js.gz +0 -0
  59. package/payload/server/public/assets/array-X0JlPOfd.js.br +0 -0
  60. package/payload/server/public/assets/blockDiagram-DXYQGD6D-DS6dS4GZ.js.br +0 -0
  61. package/payload/server/public/assets/blockDiagram-DXYQGD6D-DS6dS4GZ.js.gz +0 -0
  62. package/payload/server/public/assets/{browser-GkVTGqlb.js → browser-4tTaem8N.js} +1 -1
  63. package/payload/server/public/assets/browser-4tTaem8N.js.br +0 -0
  64. package/payload/server/public/assets/browser-4tTaem8N.js.gz +0 -0
  65. package/payload/server/public/assets/c4Diagram-AHTNJAMY-hBSBUZPt.js.br +0 -0
  66. package/payload/server/public/assets/c4Diagram-AHTNJAMY-hBSBUZPt.js.gz +0 -0
  67. package/payload/server/public/assets/{calendar-CSqJeFEg.js → calendar-BEjnkKG1.js} +1 -1
  68. package/payload/server/public/assets/calendar-BEjnkKG1.js.br +0 -0
  69. package/payload/server/public/assets/calendar-BEjnkKG1.js.gz +0 -0
  70. package/payload/server/public/assets/channel-BEokjAeE.js.br +0 -0
  71. package/payload/server/public/assets/chat-DBgzjDIE.js +1 -0
  72. package/payload/server/public/assets/chat-DBgzjDIE.js.br +0 -0
  73. package/payload/server/public/assets/chat-DBgzjDIE.js.gz +0 -0
  74. package/payload/server/public/assets/chevron-left-DqYv3oFh.js +1 -0
  75. package/payload/server/public/assets/chevron-right-CQfPGsFb.js +1 -0
  76. package/payload/server/public/assets/chevron-right-CQfPGsFb.js.br +0 -0
  77. package/payload/server/public/assets/chunk-2KRD3SAO-B6koUoB7.js.br +0 -0
  78. package/payload/server/public/assets/chunk-2KRD3SAO-B6koUoB7.js.gz +0 -0
  79. package/payload/server/public/assets/chunk-336JU56O-BF6BRh8u.js.br +0 -0
  80. package/payload/server/public/assets/chunk-336JU56O-BF6BRh8u.js.gz +0 -0
  81. package/payload/server/public/assets/chunk-426QAEUC-pbQ4PWBU.js.br +0 -0
  82. package/payload/server/public/assets/chunk-426QAEUC-pbQ4PWBU.js.gz +0 -0
  83. package/payload/server/public/assets/chunk-4BX2VUAB-7sO95xR2.js.br +0 -0
  84. package/payload/server/public/assets/chunk-4BX2VUAB-7sO95xR2.js.gz +0 -0
  85. package/payload/server/public/assets/chunk-4TB4RGXK-GCTndJYx.js.br +0 -0
  86. package/payload/server/public/assets/chunk-4TB4RGXK-GCTndJYx.js.gz +0 -0
  87. package/payload/server/public/assets/chunk-55IACEB6-ggIJf_TC.js.br +0 -0
  88. package/payload/server/public/assets/chunk-55IACEB6-ggIJf_TC.js.gz +0 -0
  89. package/payload/server/public/assets/chunk-5FUZZQ4R-DeGmkimt.js.br +0 -0
  90. package/payload/server/public/assets/chunk-5FUZZQ4R-DeGmkimt.js.gz +0 -0
  91. package/payload/server/public/assets/chunk-5PVQY5BW-aFIQKk_U.js.br +0 -0
  92. package/payload/server/public/assets/chunk-5PVQY5BW-aFIQKk_U.js.gz +0 -0
  93. package/payload/server/public/assets/chunk-67CJDMHE-LS8rUbii.js.br +2 -0
  94. package/payload/server/public/assets/chunk-67CJDMHE-LS8rUbii.js.gz +0 -0
  95. package/payload/server/public/assets/chunk-7N4EOEYR-JKOVngS0.js.br +2 -0
  96. package/payload/server/public/assets/chunk-7N4EOEYR-JKOVngS0.js.gz +0 -0
  97. package/payload/server/public/assets/chunk-AA7GKIK3-CoXnTVK6.js.br +0 -0
  98. package/payload/server/public/assets/chunk-AA7GKIK3-CoXnTVK6.js.gz +0 -0
  99. package/payload/server/public/assets/chunk-BSJP7CBP-CcsqObFg.js.br +0 -0
  100. package/payload/server/public/assets/chunk-BSJP7CBP-CcsqObFg.js.gz +0 -0
  101. package/payload/server/public/assets/chunk-CIAEETIT-CpsybR7q.js.br +0 -0
  102. package/payload/server/public/assets/chunk-CIAEETIT-CpsybR7q.js.gz +0 -0
  103. package/payload/server/public/assets/chunk-CltuBf4Z.js.br +0 -0
  104. package/payload/server/public/assets/chunk-CltuBf4Z.js.gz +0 -0
  105. package/payload/server/public/assets/chunk-EDXVE4YY-BZ2I9PSq.js.br +0 -0
  106. package/payload/server/public/assets/chunk-EDXVE4YY-BZ2I9PSq.js.gz +0 -0
  107. package/payload/server/public/assets/chunk-ENJZ2VHE-DbTwt6OZ.js.br +0 -0
  108. package/payload/server/public/assets/chunk-ENJZ2VHE-DbTwt6OZ.js.gz +0 -0
  109. package/payload/server/public/assets/chunk-FMBD7UC4-D27VnSCa.js.br +0 -0
  110. package/payload/server/public/assets/chunk-FMBD7UC4-D27VnSCa.js.gz +0 -0
  111. package/payload/server/public/assets/chunk-FOC6F5B3-C0tU2UFl.js.br +0 -0
  112. package/payload/server/public/assets/chunk-FOC6F5B3-C0tU2UFl.js.gz +0 -0
  113. package/payload/server/public/assets/chunk-ICPOFSXX-UPPu2OIq.js.br +0 -0
  114. package/payload/server/public/assets/chunk-ICPOFSXX-UPPu2OIq.js.gz +0 -0
  115. package/payload/server/public/assets/chunk-K5T4RW27-DIKxqyYw.js.br +0 -0
  116. package/payload/server/public/assets/chunk-K5T4RW27-DIKxqyYw.js.gz +0 -0
  117. package/payload/server/public/assets/chunk-KGLVRYIC-CiL4YVLo.js.br +0 -0
  118. package/payload/server/public/assets/chunk-KGLVRYIC-CiL4YVLo.js.gz +0 -0
  119. package/payload/server/public/assets/chunk-LIHQZDEY-ejKezVlD.js.br +0 -0
  120. package/payload/server/public/assets/chunk-LIHQZDEY-ejKezVlD.js.gz +0 -0
  121. package/payload/server/public/assets/chunk-ORNJ4GCN-Cc0vqc-D.js.br +0 -0
  122. package/payload/server/public/assets/chunk-ORNJ4GCN-Cc0vqc-D.js.gz +0 -0
  123. package/payload/server/public/assets/chunk-OYMX7WX6-Drxn1qG0.js.br +0 -0
  124. package/payload/server/public/assets/chunk-OYMX7WX6-Drxn1qG0.js.gz +0 -0
  125. package/payload/server/public/assets/chunk-QZHKN3VN-Dhlh7Pkk.js.br +0 -0
  126. package/payload/server/public/assets/chunk-QZHKN3VN-Dhlh7Pkk.js.gz +0 -0
  127. package/payload/server/public/assets/chunk-U2HBQHQK-DnbQIkRP.js.br +0 -0
  128. package/payload/server/public/assets/chunk-U2HBQHQK-DnbQIkRP.js.gz +0 -0
  129. package/payload/server/public/assets/chunk-X2U36JSP-CauTSVWg.js.br +0 -0
  130. package/payload/server/public/assets/chunk-X2U36JSP-CauTSVWg.js.gz +0 -0
  131. package/payload/server/public/assets/chunk-XPW4576I-DZqqB9Zi.js.br +0 -0
  132. package/payload/server/public/assets/chunk-XPW4576I-DZqqB9Zi.js.gz +0 -0
  133. package/payload/server/public/assets/chunk-YZCP3GAM-BGHo4Li-.js.br +0 -0
  134. package/payload/server/public/assets/chunk-YZCP3GAM-BGHo4Li-.js.gz +0 -0
  135. package/payload/server/public/assets/chunk-ZZ45TVLE-DWGJKPd7.js.br +0 -0
  136. package/payload/server/public/assets/chunk-ZZ45TVLE-DWGJKPd7.js.gz +0 -0
  137. package/payload/server/public/assets/classDiagram-6PBFFD2Q-D2z_ZV1x.js.br +0 -0
  138. package/payload/server/public/assets/classDiagram-6PBFFD2Q-D2z_ZV1x.js.gz +0 -0
  139. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BJwJoNEK.js.br +0 -0
  140. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BJwJoNEK.js.gz +0 -0
  141. package/payload/server/public/assets/clock-Dn6FHB51.js +1 -0
  142. package/payload/server/public/assets/clock-Dn6FHB51.js.br +0 -0
  143. package/payload/server/public/assets/clock-Dn6FHB51.js.gz +0 -0
  144. package/payload/server/public/assets/clone-Bn7XXVhr.js.br +0 -0
  145. package/payload/server/public/assets/cose-bilkent-S5V4N54A-D-95BzXL.js.br +0 -0
  146. package/payload/server/public/assets/cose-bilkent-S5V4N54A-D-95BzXL.js.gz +0 -0
  147. package/payload/server/public/assets/cytoscape.esm-CI2IWMLB.js.br +0 -0
  148. package/payload/server/public/assets/cytoscape.esm-CI2IWMLB.js.gz +0 -0
  149. package/payload/server/public/assets/dagre-BZYNBHvT.js.br +0 -0
  150. package/payload/server/public/assets/dagre-BZYNBHvT.js.gz +0 -0
  151. package/payload/server/public/assets/dagre-KV5264BT-DyU4HQLQ.js.br +0 -0
  152. package/payload/server/public/assets/dagre-KV5264BT-DyU4HQLQ.js.gz +0 -0
  153. package/payload/server/public/assets/data-D_e6Vdfd.js +1 -0
  154. package/payload/server/public/assets/data-D_e6Vdfd.js.br +0 -0
  155. package/payload/server/public/assets/data-D_e6Vdfd.js.gz +0 -0
  156. package/payload/server/public/assets/defaultLocale-B6RGN4id.js.br +0 -0
  157. package/payload/server/public/assets/defaultLocale-B6RGN4id.js.gz +0 -0
  158. package/payload/server/public/assets/diagram-5BDNPKRD-CCttJ9XA.js.br +0 -0
  159. package/payload/server/public/assets/diagram-5BDNPKRD-CCttJ9XA.js.gz +0 -0
  160. package/payload/server/public/assets/diagram-G4DWMVQ6-DvkpgSLO.js.br +0 -0
  161. package/payload/server/public/assets/diagram-G4DWMVQ6-DvkpgSLO.js.gz +0 -0
  162. package/payload/server/public/assets/diagram-MMDJMWI5-DHmW5Y9k.js.br +0 -0
  163. package/payload/server/public/assets/diagram-MMDJMWI5-DHmW5Y9k.js.gz +0 -0
  164. package/payload/server/public/assets/diagram-TYMM5635-C6T4hwzO.js.br +0 -0
  165. package/payload/server/public/assets/diagram-TYMM5635-C6T4hwzO.js.gz +0 -0
  166. package/payload/server/public/assets/dist-QvbmK1IP.js.br +0 -0
  167. package/payload/server/public/assets/dist-QvbmK1IP.js.gz +0 -0
  168. package/payload/server/public/assets/erDiagram-SMLLAGMA-5v_o7s3T.js.br +0 -0
  169. package/payload/server/public/assets/erDiagram-SMLLAGMA-5v_o7s3T.js.gz +0 -0
  170. package/payload/server/public/assets/{file-text-DiscM2pP.js → file-text-DMKw3nIk.js} +1 -1
  171. package/payload/server/public/assets/file-text-DMKw3nIk.js.br +0 -0
  172. package/payload/server/public/assets/file-text-DMKw3nIk.js.gz +0 -0
  173. package/payload/server/public/assets/flatten-BtIc9jX7.js.br +0 -0
  174. package/payload/server/public/assets/flatten-BtIc9jX7.js.gz +0 -0
  175. package/payload/server/public/assets/flowDiagram-DWJPFMVM-3UhCDsjt.js.br +0 -0
  176. package/payload/server/public/assets/flowDiagram-DWJPFMVM-3UhCDsjt.js.gz +0 -0
  177. package/payload/server/public/assets/ganttDiagram-T4ZO3ILL-F2zgHIIQ.js.br +0 -0
  178. package/payload/server/public/assets/ganttDiagram-T4ZO3ILL-F2zgHIIQ.js.gz +0 -0
  179. package/payload/server/public/assets/gitGraph-7Q5UKJZL-C-FvNTHZ.js.br +0 -0
  180. package/payload/server/public/assets/gitGraphDiagram-UUTBAWPF-oQc1N3dT.js.br +0 -0
  181. package/payload/server/public/assets/gitGraphDiagram-UUTBAWPF-oQc1N3dT.js.gz +0 -0
  182. package/payload/server/public/assets/{graph-CRP5DM8C.js → graph-C3vW8lvw.js} +1 -1
  183. package/payload/server/public/assets/graph-C3vW8lvw.js.br +0 -0
  184. package/payload/server/public/assets/graph-C3vW8lvw.js.gz +0 -0
  185. package/payload/server/public/assets/{graph-labels-Du-6KoBE.js → graph-labels-BZgd0L6n.js} +1 -1
  186. package/payload/server/public/assets/graph-labels-BZgd0L6n.js.br +0 -0
  187. package/payload/server/public/assets/graph-labels-BZgd0L6n.js.gz +0 -0
  188. package/payload/server/public/assets/graphlib-DY4-AT2Q.js.br +0 -0
  189. package/payload/server/public/assets/graphlib-DY4-AT2Q.js.gz +0 -0
  190. package/payload/server/public/assets/info-OMHHGYJF-BjLPgHAC.js.br +0 -0
  191. package/payload/server/public/assets/infoDiagram-42DDH7IO-CXNKLTui.js.br +0 -0
  192. package/payload/server/public/assets/infoDiagram-42DDH7IO-CXNKLTui.js.gz +0 -0
  193. package/payload/server/public/assets/init-BmUWJJHz.js.br +0 -0
  194. package/payload/server/public/assets/init-BmUWJJHz.js.gz +0 -0
  195. package/payload/server/public/assets/isEmpty-Bu4fZ8Kf.js.br +0 -0
  196. package/payload/server/public/assets/isEmpty-Bu4fZ8Kf.js.gz +0 -0
  197. package/payload/server/public/assets/ishikawaDiagram-UXIWVN3A-wJhSOkuw.js.br +0 -0
  198. package/payload/server/public/assets/ishikawaDiagram-UXIWVN3A-wJhSOkuw.js.gz +0 -0
  199. package/payload/server/public/assets/journeyDiagram-VCZTEJTY-DgadC6Pz.js.br +0 -0
  200. package/payload/server/public/assets/journeyDiagram-VCZTEJTY-DgadC6Pz.js.gz +0 -0
  201. package/payload/server/public/assets/kanban-definition-6JOO6SKY-Bs4nQqXI.js.br +0 -0
  202. package/payload/server/public/assets/kanban-definition-6JOO6SKY-Bs4nQqXI.js.gz +0 -0
  203. package/payload/server/public/assets/katex-BjZuzSQn.js.br +0 -0
  204. package/payload/server/public/assets/katex-BjZuzSQn.js.gz +0 -0
  205. package/payload/server/public/assets/line-Bnmxy1hO.js.br +0 -0
  206. package/payload/server/public/assets/line-Bnmxy1hO.js.gz +0 -0
  207. package/payload/server/public/assets/linear-RvwZxFzh.js.br +0 -0
  208. package/payload/server/public/assets/linear-RvwZxFzh.js.gz +0 -0
  209. package/payload/server/public/assets/{maximize-2-D-H_uIV-.js → maximize-2-la3IBA3M.js} +1 -1
  210. package/payload/server/public/assets/maximize-2-la3IBA3M.js.br +0 -0
  211. package/payload/server/public/assets/maximize-2-la3IBA3M.js.gz +0 -0
  212. package/payload/server/public/assets/mermaid-parser.core-DeNV8RtA.js.br +0 -0
  213. package/payload/server/public/assets/mermaid-parser.core-DeNV8RtA.js.gz +0 -0
  214. package/payload/server/public/assets/mermaid.core-BU9IiSCP.js.br +0 -0
  215. package/payload/server/public/assets/mermaid.core-BU9IiSCP.js.gz +0 -0
  216. package/payload/server/public/assets/mindmap-definition-QFDTVHPH-D1PMUxxm.js.br +0 -0
  217. package/payload/server/public/assets/mindmap-definition-QFDTVHPH-D1PMUxxm.js.gz +0 -0
  218. package/payload/server/public/assets/{operator-D5rA76YY.js → operator-D9NgVL9_.js} +1 -1
  219. package/payload/server/public/assets/operator-D9NgVL9_.js.br +0 -0
  220. package/payload/server/public/assets/operator-D9NgVL9_.js.gz +0 -0
  221. package/payload/server/public/assets/ordinal-C2TEWKNu.js.br +0 -0
  222. package/payload/server/public/assets/ordinal-C2TEWKNu.js.gz +0 -0
  223. package/payload/server/public/assets/packet-4T2RLAQJ-BBAZASfb.js.br +0 -0
  224. package/payload/server/public/assets/{page-CHK1415C.js → page-BWFHRIAH.js} +1 -1
  225. package/payload/server/public/assets/page-BWFHRIAH.js.br +0 -0
  226. package/payload/server/public/assets/page-BWFHRIAH.js.gz +0 -0
  227. package/payload/server/public/assets/{page-DZE3H_n4.js → page-DC11gesX.js} +1 -1
  228. package/payload/server/public/assets/page-DC11gesX.js.br +0 -0
  229. package/payload/server/public/assets/page-DC11gesX.js.gz +0 -0
  230. package/payload/server/public/assets/path-CoPyR7c2.js.br +0 -0
  231. package/payload/server/public/assets/path-CoPyR7c2.js.gz +0 -0
  232. package/payload/server/public/assets/pdf-render-C257cidb.js.br +0 -0
  233. package/payload/server/public/assets/pdf-render-C257cidb.js.gz +0 -0
  234. package/payload/server/public/assets/pdf.worker.min-yatZIOMy.mjs.br +0 -0
  235. package/payload/server/public/assets/pdf.worker.min-yatZIOMy.mjs.gz +0 -0
  236. package/payload/server/public/assets/pie-ZZUOXDRM-CCJUnMeG.js.br +0 -0
  237. package/payload/server/public/assets/pieDiagram-DEJITSTG-BzdNzoKA.js.br +0 -0
  238. package/payload/server/public/assets/pieDiagram-DEJITSTG-BzdNzoKA.js.gz +0 -0
  239. package/payload/server/public/assets/preload-helper-BQrMkyGX.js.br +0 -0
  240. package/payload/server/public/assets/preload-helper-BQrMkyGX.js.gz +0 -0
  241. package/payload/server/public/assets/{public-DFHXVkdD.js → public-DQbbLHQN.js} +1 -1
  242. package/payload/server/public/assets/public-DQbbLHQN.js.br +0 -0
  243. package/payload/server/public/assets/public-DQbbLHQN.js.gz +0 -0
  244. package/payload/server/public/assets/quadrantDiagram-34T5L4WZ-oFr1MJRO.js.br +0 -0
  245. package/payload/server/public/assets/quadrantDiagram-34T5L4WZ-oFr1MJRO.js.gz +0 -0
  246. package/payload/server/public/assets/radar-PYXPWWZC-BsRU8Aep.js.br +0 -0
  247. package/payload/server/public/assets/reduce-BGpN5lgn.js.br +0 -0
  248. package/payload/server/public/assets/reduce-BGpN5lgn.js.gz +0 -0
  249. package/payload/server/public/assets/requirementDiagram-MS252O5E-C9QBQxDh.js.br +0 -0
  250. package/payload/server/public/assets/requirementDiagram-MS252O5E-C9QBQxDh.js.gz +0 -0
  251. package/payload/server/public/assets/{rotate-ccw-CesEJsuO.js → rotate-ccw-OQD5si8N.js} +1 -1
  252. package/payload/server/public/assets/rotate-ccw-OQD5si8N.js.br +0 -0
  253. package/payload/server/public/assets/rotate-ccw-OQD5si8N.js.gz +0 -0
  254. package/payload/server/public/assets/rough.esm-jSt7UMyi.js.br +0 -0
  255. package/payload/server/public/assets/rough.esm-jSt7UMyi.js.gz +0 -0
  256. package/payload/server/public/assets/{routines-DYMYY_r8.js → routines-asrBGZUR.js} +1 -1
  257. package/payload/server/public/assets/routines-asrBGZUR.js.br +0 -0
  258. package/payload/server/public/assets/routines-asrBGZUR.js.gz +0 -0
  259. package/payload/server/public/assets/sankeyDiagram-XADWPNL6-c_y3bvVv.js.br +0 -0
  260. package/payload/server/public/assets/sankeyDiagram-XADWPNL6-c_y3bvVv.js.gz +0 -0
  261. package/payload/server/public/assets/sequenceDiagram-FGHM5R23-YwlCBPHE.js.br +0 -0
  262. package/payload/server/public/assets/sequenceDiagram-FGHM5R23-YwlCBPHE.js.gz +0 -0
  263. package/payload/server/public/assets/{skills-67jtJ16D.js → skills-D4ECwoxa.js} +1 -1
  264. package/payload/server/public/assets/skills-D4ECwoxa.js.br +0 -0
  265. package/payload/server/public/assets/skills-D4ECwoxa.js.gz +0 -0
  266. package/payload/server/public/assets/src-C2WwTWSY.js.br +0 -0
  267. package/payload/server/public/assets/src-C2WwTWSY.js.gz +0 -0
  268. package/payload/server/public/assets/stateDiagram-FHFEXIEX-BFE3e-FM.js.br +0 -0
  269. package/payload/server/public/assets/stateDiagram-FHFEXIEX-BFE3e-FM.js.gz +0 -0
  270. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CpjB4ykI.js.br +4 -0
  271. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CpjB4ykI.js.gz +0 -0
  272. package/payload/server/public/assets/{tasks-D2C2a-uk.js → tasks-U3QdbUsO.js} +1 -1
  273. package/payload/server/public/assets/tasks-U3QdbUsO.js.br +0 -0
  274. package/payload/server/public/assets/tasks-U3QdbUsO.js.gz +0 -0
  275. package/payload/server/public/assets/{time-entry-format-BixTGQAd.js → time-entry-format-BgdTKgYr.js} +1 -1
  276. package/payload/server/public/assets/time-entry-format-BgdTKgYr.js.br +0 -0
  277. package/payload/server/public/assets/time-entry-format-BgdTKgYr.js.gz +0 -0
  278. package/payload/server/public/assets/timeline-definition-GMOUNBTQ-azqhmRwZ.js.br +0 -0
  279. package/payload/server/public/assets/timeline-definition-GMOUNBTQ-azqhmRwZ.js.gz +0 -0
  280. package/payload/server/public/assets/treeView-SZITEDCU-BDzhKA6v.js.br +0 -0
  281. package/payload/server/public/assets/treemap-W4RFUUIX-epHpdeWa.js.br +0 -0
  282. package/payload/server/public/assets/{triangle-alert-Co9RWb9q.js → triangle-alert-Ck_3VhT9.js} +1 -1
  283. package/payload/server/public/assets/triangle-alert-Ck_3VhT9.js.br +0 -0
  284. package/payload/server/public/assets/triangle-alert-Ck_3VhT9.js.gz +0 -0
  285. package/payload/server/public/assets/{useCopyFeedback-7zwf9_w4.js → useCopyFeedback-BdUwyNpa.js} +1 -1
  286. package/payload/server/public/assets/useCopyFeedback-BdUwyNpa.js.br +0 -0
  287. package/payload/server/public/assets/useCopyFeedback-BdUwyNpa.js.gz +0 -0
  288. package/payload/server/public/assets/useSubAccountSwitcher-CJKjEPNN.js.br +0 -0
  289. package/payload/server/public/assets/useSubAccountSwitcher-CJKjEPNN.js.gz +0 -0
  290. package/payload/server/public/assets/{useSubAccountSwitcher-DE4v5_Fz.css → useSubAccountSwitcher-DOWQXDT8.css} +1 -1
  291. package/payload/server/public/assets/useSubAccountSwitcher-DOWQXDT8.css.br +0 -0
  292. package/payload/server/public/assets/useSubAccountSwitcher-DOWQXDT8.css.gz +0 -0
  293. package/payload/server/public/assets/{useVoiceRecorder-CZDDiwMD.js → useVoiceRecorder-ChnBrzbZ.js} +1 -1
  294. package/payload/server/public/assets/useVoiceRecorder-ChnBrzbZ.js.br +0 -0
  295. package/payload/server/public/assets/useVoiceRecorder-ChnBrzbZ.js.gz +0 -0
  296. package/payload/server/public/assets/vennDiagram-DHZGUBPP-Da2RfFs8.js.br +0 -0
  297. package/payload/server/public/assets/vennDiagram-DHZGUBPP-Da2RfFs8.js.gz +0 -0
  298. package/payload/server/public/assets/wardley-RL74JXVD-DNSZCrxV.js.br +0 -0
  299. package/payload/server/public/assets/wardleyDiagram-NUSXRM2D-HdrdBYES.js.br +0 -0
  300. package/payload/server/public/assets/wardleyDiagram-NUSXRM2D-HdrdBYES.js.gz +0 -0
  301. package/payload/server/public/assets/{wrench-CjQqdj4P.js → wrench-Cpee3C3J.js} +1 -1
  302. package/payload/server/public/assets/wrench-Cpee3C3J.js.br +2 -0
  303. package/payload/server/public/assets/wrench-Cpee3C3J.js.gz +0 -0
  304. package/payload/server/public/assets/xychartDiagram-5P7HB3ND-DMk6rTcQ.js.br +0 -0
  305. package/payload/server/public/assets/xychartDiagram-5P7HB3ND-DMk6rTcQ.js.gz +0 -0
  306. package/payload/server/public/browser.html +4 -4
  307. package/payload/server/public/calendar.html +7 -7
  308. package/payload/server/public/chat.html +13 -13
  309. package/payload/server/public/data.html +11 -11
  310. package/payload/server/public/graph.html +9 -9
  311. package/payload/server/public/index.html +14 -14
  312. package/payload/server/public/operator.html +14 -14
  313. package/payload/server/public/public.html +13 -13
  314. package/payload/server/public/routines.html +6 -6
  315. package/payload/server/public/skills.html +5 -5
  316. package/payload/server/public/tasks.html +6 -6
  317. package/payload/server/server.js +817 -715
  318. package/payload/server/public/assets/AdminLoginScreens-DKeCt1uf.js +0 -1
  319. package/payload/server/public/assets/admin-C0-WU1sf.js +0 -1
  320. package/payload/server/public/assets/agents-DjUvMFTj.js +0 -1
  321. package/payload/server/public/assets/chat-C0Ps84U5.js +0 -1
  322. package/payload/server/public/assets/chevron-left-DQ2uNlu9.js +0 -1
  323. package/payload/server/public/assets/chevron-right-C4QCiRGD.js +0 -1
  324. package/payload/server/public/assets/clock-BEeQ7JxX.js +0 -1
  325. package/payload/server/public/assets/data-CGuNfym7.js +0 -1
  326. /package/payload/server/public/assets/{useSubAccountSwitcher-Bg72hdQ8.js → useSubAccountSwitcher-CJKjEPNN.js} +0 -0
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env bash
2
+ # Tests for check-brand-cache.sh (Task 2019). Fixture-driven: builds a throwaway
3
+ # install dir + config dir per case and stubs the fetch command, because the real
4
+ # one crosses a Cloudflare edge that no test can hold still. sha256 and the
5
+ # ingress parse are the real code paths — those are what this asserts.
6
+ set -uo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ CHECK="$SCRIPT_DIR/../check-brand-cache.sh"
10
+
11
+ PASS=0
12
+ FAIL=0
13
+
14
+ run_case() {
15
+ local name="$1"; shift
16
+ echo ""
17
+ echo "=== CASE: $name ==="
18
+ if "$@"; then
19
+ PASS=$((PASS + 1)); echo "PASS: $name"
20
+ else
21
+ FAIL=$((FAIL + 1)); echo "FAIL: $name"
22
+ fi
23
+ }
24
+
25
+ # Build install dir with two brand files and brand-defaults.css. Echoes its path.
26
+ mk_install() {
27
+ local d
28
+ d="$(mktemp -d "${TMPDIR:-/tmp}/brand-cache-install.XXXXXX")"
29
+ mkdir -p "$d/server/public/brand"
30
+ printf 'lime-logo-bytes' > "$d/server/public/brand/logo.png"
31
+ printf 'lime-icon-bytes' > "$d/server/public/brand/icon.png"
32
+ printf ':root{--brand:#c3f53c}' > "$d/server/public/brand-defaults.css"
33
+ echo "$d"
34
+ }
35
+
36
+ # Build a config dir whose cloudflared/config.yml has the given ingress hosts.
37
+ mk_config_with_hosts() {
38
+ local d; d="$(mktemp -d "${TMPDIR:-/tmp}/brand-cache-config.XXXXXX")"
39
+ mkdir -p "$d/cloudflared"
40
+ { echo "ingress:"; for h in "$@"; do echo " - hostname: $h"; done; echo " - service: http_status:404"; } \
41
+ > "$d/cloudflared/config.yml"
42
+ echo "$d"
43
+ }
44
+
45
+ mk_config_no_tunnel() {
46
+ local d; d="$(mktemp -d "${TMPDIR:-/tmp}/brand-cache-config.XXXXXX")"
47
+ echo "$d"
48
+ }
49
+
50
+ # A config dir with no cloudflared ingress but an alias-domains.json, the
51
+ # second source resolvePublicHostname reads.
52
+ mk_config_alias_only() {
53
+ local d; d="$(mktemp -d "${TMPDIR:-/tmp}/brand-cache-config.XXXXXX")"
54
+ printf '["public.realagent.app"]' > "$d/alias-domains.json"
55
+ echo "$d"
56
+ }
57
+
58
+ # Stub curl. $1 = dir whose files it serves as the "edge", keyed by basename.
59
+ # Writes headers to the -D path and the body to the -o path, exit 0.
60
+ mk_curl_stub() {
61
+ local serve_dir="$1" stub
62
+ stub="$(mktemp "${TMPDIR:-/tmp}/brand-cache-curl.XXXXXX")"
63
+ cat > "$stub" <<STUB
64
+ #!/usr/bin/env bash
65
+ hdr=""; out=""; url=""
66
+ while [ \$# -gt 0 ]; do
67
+ case "\$1" in
68
+ -D) hdr="\$2"; shift 2 ;;
69
+ -o) out="\$2"; shift 2 ;;
70
+ -*) shift ;;
71
+ *) url="\$1"; shift ;;
72
+ esac
73
+ done
74
+ name="\${url##*/}"
75
+ printf 'HTTP/2 200\r\ncf-cache-status: HIT\r\nage: 5352\r\n\r\n' > "\$hdr"
76
+ cat "$serve_dir/\$name" > "\$out"
77
+ STUB
78
+ chmod +x "$stub"
79
+ echo "$stub"
80
+ }
81
+
82
+ case_clean_install_reports_clean() {
83
+ local i c stub edge out rc
84
+ i="$(mk_install)"; c="$(mk_config_with_hosts operations.realagent.network)"
85
+ # Edge serves exactly what is on disk.
86
+ edge="$(mktemp -d "${TMPDIR:-/tmp}/brand-cache-edge.XXXXXX")"
87
+ cp "$i/server/public/brand/"* "$edge/"
88
+ cp "$i/server/public/brand-defaults.css" "$edge/"
89
+ stub="$(mk_curl_stub "$edge")"
90
+ out="$(BRAND_CACHE_CURL="$stub" "$CHECK" --install-dir "$i" --config-dir "$c" 2>/dev/null)"
91
+ rc=$?
92
+ [ "$rc" -eq 0 ] || { echo " expected exit 0, got $rc"; rm -rf "$i" "$c" "$edge" "$stub"; return 1; }
93
+ # files=2 pinned as an integer: a \d+ match also accepts files=0, and a
94
+ # hostname resolving to nothing would then read as a pass.
95
+ echo "$out" | grep -qx "\[brand-cache\] clean host=operations.realagent.network files=2" \
96
+ || { echo " no clean line with files=2; got: $out"; rm -rf "$i" "$c" "$edge" "$stub"; return 1; }
97
+ rm -rf "$i" "$c" "$edge" "$stub"; return 0
98
+ }
99
+
100
+ case_stale_edge_reports_stale_on_stderr() {
101
+ local i c stub edge err rc
102
+ i="$(mk_install)"; c="$(mk_config_with_hosts operations.realagent.network)"
103
+ edge="$(mktemp -d "${TMPDIR:-/tmp}/brand-cache-edge.XXXXXX")"
104
+ cp "$i/server/public/brand/"* "$edge/"
105
+ cp "$i/server/public/brand-defaults.css" "$edge/"
106
+ # The measured symptom: the edge holds the retired mark, disk holds the new one.
107
+ printf 'black-square-bytes' > "$edge/logo.png"
108
+ stub="$(mk_curl_stub "$edge")"
109
+ err="$(BRAND_CACHE_CURL="$stub" "$CHECK" --install-dir "$i" --config-dir "$c" 2>&1 >/dev/null)"
110
+ rc=$?
111
+ [ "$rc" -eq 1 ] || { echo " expected exit 1, got $rc"; rm -rf "$i" "$c" "$edge" "$stub"; return 1; }
112
+ echo "$err" | grep -qE "^\[brand-cache\] STALE host=operations\.realagent\.network path=/brand/logo\.png edge=[0-9a-f]{7} disk=[0-9a-f]{7} cf-cache-status=HIT age=5352$" \
113
+ || { echo " no STALE line for /brand/logo.png; got: $err"; rm -rf "$i" "$c" "$edge" "$stub"; return 1; }
114
+ rm -rf "$i" "$c" "$edge" "$stub"; return 0
115
+ }
116
+
117
+ case_no_tunnel_reports_skipped() {
118
+ local i c out rc
119
+ i="$(mk_install)"; c="$(mk_config_no_tunnel)"
120
+ out="$("$CHECK" --install-dir "$i" --config-dir "$c" 2>/dev/null)"
121
+ rc=$?
122
+ [ "$rc" -eq 0 ] || { echo " expected exit 0, got $rc"; rm -rf "$i" "$c"; return 1; }
123
+ echo "$out" | grep -qx "\[brand-cache\] skipped host=none reason=no-tunnel-ingress" \
124
+ || { echo " no skipped line; got: $out"; rm -rf "$i" "$c"; return 1; }
125
+ rm -rf "$i" "$c"; return 0
126
+ }
127
+
128
+ case_localhost_only_ingress_is_no_tunnel() {
129
+ local i c out rc
130
+ i="$(mk_install)"; c="$(mk_config_with_hosts localhost muvin.local)"
131
+ out="$("$CHECK" --install-dir "$i" --config-dir "$c" 2>/dev/null)"
132
+ rc=$?
133
+ [ "$rc" -eq 0 ] || { echo " expected exit 0, got $rc"; rm -rf "$i" "$c"; return 1; }
134
+ echo "$out" | grep -qx "\[brand-cache\] skipped host=none reason=no-tunnel-ingress" \
135
+ || { echo " localhost/.local counted as ingress; got: $out"; rm -rf "$i" "$c"; return 1; }
136
+ rm -rf "$i" "$c"; return 0
137
+ }
138
+
139
+ case_unfetchable_asset_is_not_silently_clean() {
140
+ local i c stub err rc
141
+ i="$(mk_install)"; c="$(mk_config_with_hosts operations.realagent.network)"
142
+ # Stub that answers 502 with an empty body for every path.
143
+ stub="$(mktemp "${TMPDIR:-/tmp}/brand-cache-curl.XXXXXX")"
144
+ cat > "$stub" <<'STUB'
145
+ #!/usr/bin/env bash
146
+ hdr=""; out=""
147
+ while [ $# -gt 0 ]; do
148
+ case "$1" in
149
+ -D) hdr="$2"; shift 2 ;;
150
+ -o) out="$2"; shift 2 ;;
151
+ -*) shift ;;
152
+ *) shift ;;
153
+ esac
154
+ done
155
+ printf 'HTTP/2 502\r\n\r\n' > "$hdr"
156
+ : > "$out"
157
+ STUB
158
+ chmod +x "$stub"
159
+ err="$(BRAND_CACHE_CURL="$stub" "$CHECK" --install-dir "$i" --config-dir "$c" 2>&1 >/dev/null)"
160
+ rc=$?
161
+ [ "$rc" -eq 1 ] || { echo " expected exit 1, got $rc"; rm -rf "$i" "$c" "$stub"; return 1; }
162
+ echo "$err" | grep -qE "^\[brand-cache\] UNREACHABLE host=operations\.realagent\.network path=/brand/logo\.png http=502$" \
163
+ || { echo " no UNREACHABLE line; got: $err"; rm -rf "$i" "$c" "$stub"; return 1; }
164
+ rm -rf "$i" "$c" "$stub"; return 0
165
+ }
166
+
167
+ case_no_assets_is_a_named_skip_not_a_clean() {
168
+ local i c out rc
169
+ i="$(mktemp -d "${TMPDIR:-/tmp}/brand-cache-install.XXXXXX")"; mkdir -p "$i/server/public"
170
+ c="$(mk_config_with_hosts operations.realagent.network)"
171
+ # bash 3.2 (macOS) errors on "${PATHS[@]}" for an empty array under `set -u`,
172
+ # so this also pins that the loop is never entered with nothing to compare.
173
+ out="$(/bin/bash "$CHECK" --install-dir "$i" --config-dir "$c" 2>&1)"
174
+ rc=$?
175
+ [ "$rc" -eq 0 ] || { echo " expected exit 0, got $rc; got: $out"; rm -rf "$i" "$c"; return 1; }
176
+ echo "$out" | grep -qx "\[brand-cache\] skipped host=operations.realagent.network reason=no-brand-assets" \
177
+ || { echo " no named skip for an empty asset list; got: $out"; rm -rf "$i" "$c"; return 1; }
178
+ # files=0 must never be emitted: the task's own pinning exists so an empty
179
+ # comparison cannot read as a pass.
180
+ echo "$out" | grep -q "files=0" \
181
+ && { echo " emitted files=0, which reads as a pass"; rm -rf "$i" "$c"; return 1; }
182
+ rm -rf "$i" "$c"; return 0
183
+ }
184
+
185
+ case_alias_only_install_is_checked_not_skipped() {
186
+ local i c stub edge out rc
187
+ i="$(mk_install)"; c="$(mk_config_alias_only)"
188
+ edge="$(mktemp -d "${TMPDIR:-/tmp}/brand-cache-edge.XXXXXX")"
189
+ cp "$i/server/public/brand/"* "$edge/"
190
+ stub="$(mk_curl_stub "$edge")"
191
+ out="$(BRAND_CACHE_CURL="$stub" "$CHECK" --install-dir "$i" --config-dir "$c" 2>/dev/null)"
192
+ rc=$?
193
+ [ "$rc" -eq 0 ] || { echo " expected exit 0, got $rc"; rm -rf "$i" "$c" "$edge" "$stub"; return 1; }
194
+ # An install with an alias edge and no ingress row does have an edge that can
195
+ # diverge, so reporting `skipped` there would read as nothing-to-check on the
196
+ # one state the skip wording promises it is not.
197
+ echo "$out" | grep -qx "\[brand-cache\] clean host=public.realagent.app files=2" \
198
+ || { echo " alias-only install was not checked; got: $out"; rm -rf "$i" "$c" "$edge" "$stub"; return 1; }
199
+ rm -rf "$i" "$c" "$edge" "$stub"; return 0
200
+ }
201
+
202
+ case_brand_defaults_css_is_not_fetched() {
203
+ local i c stub edge out rc
204
+ i="$(mk_install)"; c="$(mk_config_with_hosts admin.realagent.network public.realagent.network)"
205
+ edge="$(mktemp -d "${TMPDIR:-/tmp}/brand-cache-edge.XXXXXX")"
206
+ cp "$i/server/public/brand/"* "$edge/"
207
+ # No brand-defaults.css at the edge. The gate at server/index.ts:1311-1322
208
+ # exempts /brand/ but not /brand-defaults.css, so through an admin-gated host
209
+ # that path answers with a login page at status 200 — hashing that against
210
+ # the stylesheet would be a permanent false STALE with no cache involved.
211
+ stub="$(mk_curl_stub "$edge")"
212
+ out="$(BRAND_CACHE_CURL="$stub" "$CHECK" --install-dir "$i" --config-dir "$c" 2>/dev/null)"
213
+ rc=$?
214
+ [ "$rc" -eq 0 ] || { echo " expected exit 0, got $rc"; rm -rf "$i" "$c" "$edge" "$stub"; return 1; }
215
+ echo "$out" | grep -qx "\[brand-cache\] clean host=admin.realagent.network files=2" \
216
+ || { echo " expected only the two /brand/ files; got: $out"; rm -rf "$i" "$c" "$edge" "$stub"; return 1; }
217
+ rm -rf "$i" "$c" "$edge" "$stub"; return 0
218
+ }
219
+
220
+ run_case "clean install reports clean with a pinned file count" case_clean_install_reports_clean
221
+ run_case "stale edge reports STALE on stderr and exits 1" case_stale_edge_reports_stale_on_stderr
222
+ run_case "no tunnel ingress reports skipped and exits 0" case_no_tunnel_reports_skipped
223
+ run_case "localhost/.local-only ingress counts as no tunnel" case_localhost_only_ingress_is_no_tunnel
224
+ run_case "an unfetchable asset is reported, never counted clean" case_unfetchable_asset_is_not_silently_clean
225
+ run_case "an empty asset list is a named skip, never files=0" case_no_assets_is_a_named_skip_not_a_clean
226
+ run_case "an alias-only install is checked, not skipped" case_alias_only_install_is_checked_not_skipped
227
+ run_case "brand-defaults.css is not fetched through a gated host" case_brand_defaults_css_is_not_fetched
228
+
229
+ echo ""
230
+ echo "PASS=$PASS FAIL=$FAIL"
231
+ [ "$FAIL" -eq 0 ]
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env bash
2
+ # check-brand-cache.sh — reconcile the bytes the public hostname returns
3
+ # against the bytes on disk (Task 2019).
4
+ #
5
+ # The failure this exists for emits no event at either end. The installer
6
+ # writes the new brand bytes and logs a successful upgrade; the origin serves
7
+ # them correctly to anyone asking it directly; Cloudflare's edge serves the
8
+ # pre-upgrade ones and reports `HIT` to nobody looking. There is no action to
9
+ # log and re-running the upgrade does not surface it, so only a reconciliation
10
+ # of served bytes against on-disk bytes reveals the state. That is the
11
+ # condition behind the 2026-07-26 report, where the regenerated Real Agent logo
12
+ # was correct on disk and invisible through the hostname for four hours.
13
+ #
14
+ # $ check-brand-cache.sh --install-dir <dir> --config-dir <dir>
15
+ #
16
+ # stdout, exit 0: [brand-cache] clean host=<h> files=<n>
17
+ # stdout, exit 0: [brand-cache] skipped host=none reason=no-tunnel-ingress
18
+ # stderr, exit 1: [brand-cache] STALE host=<h> path=<p> edge=<sha7> disk=<sha7> cf-cache-status=<s> age=<n>
19
+ # stderr, exit 1: [brand-cache] UNREACHABLE host=<h> path=<p> http=<code>
20
+ #
21
+ # No ingress hostname and no alias domain means no edge and therefore no
22
+ # possible divergence, so that is a skip, not a pass — a caller must never read
23
+ # the two as the same.
24
+ #
25
+ # Scope: the files under server/public/brand/ only. See the PATHS block below.
26
+ #
27
+ # BRAND_CACHE_CURL overrides the fetch command (tests stub it).
28
+ set -uo pipefail
29
+
30
+ INSTALL_DIR=""
31
+ CONFIG_DIR=""
32
+ while [ $# -gt 0 ]; do
33
+ case "$1" in
34
+ --install-dir) INSTALL_DIR="$2"; shift 2 ;;
35
+ --config-dir) CONFIG_DIR="$2"; shift 2 ;;
36
+ *) echo "[brand-cache] unknown argument: $1" >&2; exit 2 ;;
37
+ esac
38
+ done
39
+ if [ -z "$INSTALL_DIR" ] || [ -z "$CONFIG_DIR" ]; then
40
+ echo "[brand-cache] usage: check-brand-cache.sh --install-dir <dir> --config-dir <dir>" >&2
41
+ exit 2
42
+ fi
43
+
44
+ CURL="${BRAND_CACHE_CURL:-curl}"
45
+
46
+ # sha256sum is GNU; darwin ships shasum. Both print "<digest> <path>".
47
+ sha256() {
48
+ if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}'
49
+ else shasum -a 256 "$1" | awk '{print $1}'; fi
50
+ }
51
+
52
+ # The hostname to probe, read from the same two sources and with the same
53
+ # exclusions as platform/plugins/admin/mcp/src/lib/public-hostname.ts:
54
+ # cloudflared/config.yml first, then alias-domains.json. First non-excluded
55
+ # wins within each source.
56
+ #
57
+ # No apex tiebreak, and no attempt to pick the *public* host over the admin
58
+ # one: only /brand/ assets are fetched, and the admin gate exempts /brand/ on
59
+ # every host class (server/index.ts:1311-1322), so any ingress host serves them
60
+ # identically. The alias source is read because an install whose edge is an
61
+ # alias domain with no ingress row does have an edge that can diverge, and
62
+ # reporting `skipped` there would read as nothing-to-check.
63
+ resolve_host() {
64
+ local cfg="$CONFIG_DIR/cloudflared/config.yml" alias="$CONFIG_DIR/alias-domains.json" h
65
+ if [ -f "$cfg" ]; then
66
+ h="$(sed -n 's/^[[:space:]]*-[[:space:]]*hostname:[[:space:]]*\([^[:space:]]*\).*/\1/p' "$cfg" \
67
+ | grep -v '^localhost$' | grep -v '\.local$' | head -1)"
68
+ if [ -n "$h" ]; then echo "$h"; return 0; fi
69
+ fi
70
+ [ -f "$alias" ] || return 0
71
+ tr ',' '\n' < "$alias" | sed -n 's/.*"\([^"]*\)".*/\1/p' \
72
+ | grep -v '^localhost$' | grep -v '\.local$' | head -1
73
+ }
74
+
75
+ HOST="$(resolve_host)"
76
+ if [ -z "$HOST" ]; then
77
+ echo "[brand-cache] skipped host=none reason=no-tunnel-ingress"
78
+ exit 0
79
+ fi
80
+
81
+ # /brand/ only. /brand-defaults.css is NOT exempt from the admin gate
82
+ # (server/index.ts:1311-1322 lists /favicon.ico, /assets/, /brand/,
83
+ # /api/calendar/free-busy and the PWA paths, and nothing else), so through an
84
+ # admin-gated or operator host it answers with a login page at status 200
85
+ # (index.ts:1379). Hashing that against the stylesheet on disk would be a
86
+ # permanent STALE with no cache involved, which would both bury a real
87
+ # divergence and make the clean line unreachable. Covering it, and the page
88
+ # shells, needs a resolved public hostname — Task 2026.
89
+ PUBLIC_DIR="$INSTALL_DIR/server/public"
90
+ PATHS=()
91
+ if [ -d "$PUBLIC_DIR/brand" ]; then
92
+ while IFS= read -r f; do PATHS+=("/brand/$(basename "$f")"); done \
93
+ < <(find "$PUBLIC_DIR/brand" -maxdepth 1 -type f | sort)
94
+ fi
95
+
96
+ # Nothing to compare is a skip with its own reason, not a clean run. Emitting
97
+ # `files=0` here would read as a pass on an install whose payload never
98
+ # deployed, and that is the dead signal the file count is pinned against.
99
+ # Also the guard for bash 3.2 (darwin), where "${PATHS[@]}" on an empty array
100
+ # is an unbound-variable error under `set -u`.
101
+ if [ "${#PATHS[@]}" -eq 0 ]; then
102
+ echo "[brand-cache] skipped host=$HOST reason=no-brand-assets"
103
+ exit 0
104
+ fi
105
+
106
+ TMP="$(mktemp -d "${TMPDIR:-/tmp}/brand-cache.XXXXXX")"
107
+ trap 'rm -rf "$TMP"' EXIT
108
+
109
+ FAILED=0
110
+ CHECKED=0
111
+ for p in "${PATHS[@]}"; do
112
+ body="$TMP/body"; hdr="$TMP/hdr"
113
+ "$CURL" -sS -L -D "$hdr" -o "$body" "https://$HOST$p" >/dev/null 2>&1
114
+ code="$(awk 'tolower($0) ~ /^http\// {c=$2} END{print c+0}' "$hdr" 2>/dev/null)"
115
+ if [ "$code" != "200" ]; then
116
+ echo "[brand-cache] UNREACHABLE host=$HOST path=$p http=$code" >&2
117
+ FAILED=1
118
+ continue
119
+ fi
120
+ edge="$(sha256 "$body")"
121
+ disk="$(sha256 "$PUBLIC_DIR$p")"
122
+ if [ "$edge" != "$disk" ]; then
123
+ # tail -1, not head -1: -L means $hdr can hold several header blocks, and
124
+ # the cache state that matters is the one on the response the bytes came
125
+ # from. head -1 would name a redirect hop's cache state instead, which is
126
+ # the field an operator reads to tell this fault from an origin fault.
127
+ status="$(awk 'tolower($1) == "cf-cache-status:" {print $2}' "$hdr" | tr -d '\r' | tail -1)"
128
+ age="$(awk 'tolower($1) == "age:" {print $2}' "$hdr" | tr -d '\r' | tail -1)"
129
+ echo "[brand-cache] STALE host=$HOST path=$p edge=${edge:0:7} disk=${disk:0:7} cf-cache-status=${status:-none} age=${age:-0}" >&2
130
+ FAILED=1
131
+ continue
132
+ fi
133
+ CHECKED=$((CHECKED + 1))
134
+ done
135
+
136
+ [ "$FAILED" -eq 1 ] && exit 1
137
+ echo "[brand-cache] clean host=$HOST files=$CHECKED"
138
+ exit 0
@@ -1,16 +1,23 @@
1
1
  #!/usr/bin/env python3
2
- """Resolve plugin-declared account-owned top-level dirs for the brand and merge
3
- them into an account's SCHEMA.md.
2
+ """Resolve plugin-declared account-owned top-level entries for the brand and
3
+ merge them into an account's SCHEMA.md.
4
4
 
5
- A plugin declares owned top-level account dirs in its PLUGIN.md frontmatter:
5
+ A plugin declares owned top-level account entries in its PLUGIN.md frontmatter,
6
+ by kind:
6
7
 
7
- account-owned-dirs: [{"dir": "quoting", "description": "..."}]
8
+ account-owned-dirs: [{"dir": "quoting", "description": "..."}]
9
+ account-owned-files: [{"file": "data-portal.json", "description": "..."}]
8
10
 
9
- The owned-dir set for a brand is the union declared by every platform plugin
11
+ The owned-entry set for a brand is the union declared by every platform plugin
10
12
  ($PROJECT_DIR/plugins/*/PLUGIN.md) and every premium sub-plugin of a bundle the
11
13
  brand ships ($PREMIUM_ROOT/<bundle>/plugins/*/PLUGIN.md, gated by brand.json
12
14
  shipsPremiumBundles). Reads PROJECT_DIR from the environment.
13
15
 
16
+ A declaration reaches an account only on a brand whose payload carries the
17
+ declaring plugin (brand.json plugins.excluded drops the directory entirely), so
18
+ an entry written by platform code that runs on every brand belongs on a plugin
19
+ no brand excludes, not on the plugin its feature is named after.
20
+
14
21
  The account SCHEMA.md also gains one domain-entity bucket per top-level
15
22
  operator-entry node type of the brand's business ontology. That set is derived
16
23
  from the brand vertical's schema reference (brand.json#vertical ->
@@ -102,7 +109,17 @@ def _read_vertical(project_dir):
102
109
 
103
110
 
104
111
  def _parse_plugin_md(path):
105
- """Return (name, [ {dir, description} ]) from a PLUGIN.md, or (None, [])."""
112
+ """Return (name, [ {name, kind, description} ]) from a PLUGIN.md, or (None, []).
113
+ Two frontmatter keys, one list: `account-owned-dirs` yields kind='dir',
114
+ `account-owned-files` yields kind='file'. A key whose value is not a JSON
115
+ array of objects is skipped WHOLE: entries are staged and committed only once
116
+ every element parses, so a bad element cannot half-merge a declaration and
117
+ leave the rest reporting as strays. The other key and every other plugin
118
+ still merge, because one hand-edited manifest must never empty the set. A
119
+ name carrying a path separator is dropped: a fence entry has to match one
120
+ readdir name, and the region tells a bucket from a file by the trailing slash
121
+ it adds itself, so a declared `foo/` would render as a bucket that matches
122
+ nothing."""
106
123
  try:
107
124
  with open(path) as f:
108
125
  text = f.read()
@@ -117,16 +134,21 @@ def _parse_plugin_md(path):
117
134
  if nm:
118
135
  name = nm.group(1).strip().strip('"').strip("'")
119
136
  owned = []
120
- om = re.search(r"^account-owned-dirs:\s*(.+)$", fm, re.MULTILINE)
121
- if om:
137
+ for key, field, kind in (("account-owned-dirs", "dir", "dir"),
138
+ ("account-owned-files", "file", "file")):
139
+ om = re.search(r"^" + key + r":\s*(.+)$", fm, re.MULTILINE)
140
+ if not om:
141
+ continue
142
+ staged = []
122
143
  try:
123
- raw = json.loads(om.group(1).strip())
124
- for e in raw:
125
- d = (e.get("dir") or "").strip()
126
- if d:
127
- owned.append({"dir": d, "description": (e.get("description") or "").strip()})
144
+ for e in json.loads(om.group(1).strip()):
145
+ n = (e.get(field) or "").strip()
146
+ if n and "/" not in n:
147
+ staged.append({"name": n, "kind": kind,
148
+ "description": (e.get("description") or "").strip()})
128
149
  except Exception:
129
- pass
150
+ continue
151
+ owned.extend(staged)
130
152
  return name, owned
131
153
 
132
154
 
@@ -158,18 +180,19 @@ def _plugin_md_paths(project_dir):
158
180
 
159
181
 
160
182
  def resolve(project_dir):
161
- """Union of owned-dir declarations. First declarer of a dir wins its owner
162
- label + description."""
183
+ """Union of owned-entry declarations. First declarer of a name wins its
184
+ owner label + description, whichever kind it declared."""
163
185
  seen = {}
164
186
  order = []
165
187
  for path in _plugin_md_paths(project_dir):
166
188
  name, owned = _parse_plugin_md(path)
167
189
  for e in owned:
168
- if e["dir"] in seen:
190
+ if e["name"] in seen:
169
191
  continue
170
- seen[e["dir"]] = {"dir": e["dir"], "description": e["description"], "plugin": name or "unknown"}
171
- order.append(e["dir"])
172
- return [seen[d] for d in order]
192
+ seen[e["name"]] = {"name": e["name"], "kind": e["kind"],
193
+ "description": e["description"], "plugin": name or "unknown"}
194
+ order.append(e["name"])
195
+ return [seen[n] for n in order]
173
196
 
174
197
 
175
198
  def _top_level_labels(schema_path):
@@ -304,7 +327,7 @@ def merge(project_dir, account_dir):
304
327
  # entity (non-blank Owner) nests under its owner and is not a target,
305
328
  # so a pre-fix account's empty owned bucket prunes while a populated one
306
329
  # is kept-and-logged like any other stale bucket.
307
- target = FIXED_SEED | {e["dir"] for e in owned} | {b["dir"] for b in domain if _is_root(b["owner"])}
330
+ target = FIXED_SEED | {e["name"] for e in owned} | {b["dir"] for b in domain if _is_root(b["owner"])}
308
331
  # Reconcile existing entries: keep in-target; keep populated strays
309
332
  # (logged); drop empty/absent strays (logged).
310
333
  new_entries = []
@@ -319,9 +342,9 @@ def merge(project_dir, account_dir):
319
342
  # Append newly-projected owned then domain dirs not already present.
320
343
  insert = []
321
344
  for e in owned:
322
- if e["dir"] not in present and e["dir"] not in insert:
323
- insert.append(e["dir"])
324
- added.append(e["dir"])
345
+ if e["name"] not in present and e["name"] not in insert:
346
+ insert.append(e["name"])
347
+ added.append(e["name"])
325
348
  for b in domain:
326
349
  if not _is_root(b["owner"]):
327
350
  continue # contained entity: no root bucket, nests under its owner
@@ -336,16 +359,21 @@ def merge(project_dir, account_dir):
336
359
  text = _strip_region(text, MARK_START, MARK_END)
337
360
  text = _strip_region(text, ONT_START, ONT_END)
338
361
  if owned:
339
- region = [MARK_START, "## Plugin-owned top-level workspaces", "",
340
- "A plugin that owns a top-level workspace declares it. The internal",
341
- "structure is owned by that plugin's tools and recreated on the next",
342
- "write. Never reorganise it.", ""]
362
+ region = [MARK_START, "## Plugin-owned top-level entries", "",
363
+ "A plugin declares the account-root entries its feature owns. A",
364
+ "directory's internal structure belongs to that plugin's tools and is",
365
+ "recreated on the next write, so never reorganise it; a declared file",
366
+ "is written whole by its owner. Neither is operator data.", ""]
343
367
  for e in owned:
344
368
  desc = e["description"] or f"Owned by the {e['plugin']} plugin."
345
- region.append(f"- `{e['dir']}/` {desc}")
369
+ # The trailing slash marks a bucket. Three readers anchor on it to
370
+ # tell a directory from a declared file (account-schema-regions,
371
+ # schema-exposed-dirs.mjs), so a file must never gain one.
372
+ head = f"{e['name']}/" if e["kind"] == "dir" else e["name"]
373
+ region.append(f"- `{head}` — {desc}")
346
374
  region.append(MARK_END)
347
375
  text = text.rstrip("\n") + "\n\n" + "\n".join(region) + "\n"
348
- owned_dirs = {e["dir"] for e in owned}
376
+ owned_dirs = {e["name"] for e in owned if e["kind"] == "dir"}
349
377
  # A dir already claimed by a plugin (described in the plugin-owned region)
350
378
  # is not also described here, so a name collision cannot double-describe.
351
379
  described = [b for b in domain if b["dir"] not in owned_dirs]
@@ -387,8 +415,9 @@ def reconcile(project_dir, account_dir):
387
415
  entries, _s, _e = _allowed_block(f.read())
388
416
  present = set(entries or [])
389
417
  for e in owned:
390
- is_present = "true" if e["dir"] in present else "false"
391
- print(f"brand={brand} plugin={e['plugin']} ownedDir={e['dir']} present={is_present}")
418
+ is_present = "true" if e["name"] in present else "false"
419
+ label = "ownedDir" if e["kind"] == "dir" else "ownedFile"
420
+ print(f"brand={brand} plugin={e['plugin']} {label}={e['name']} present={is_present}")
392
421
  vertical = _read_vertical(project_dir)
393
422
  domain = resolve_domain_buckets(project_dir)
394
423
  if vertical and not domain:
@@ -1 +1 @@
1
- {"version":3,"file":"account-dir-schema-reconcile.d.ts","sourceRoot":"","sources":["../src/account-dir-schema-reconcile.ts"],"names":[],"mappings":"AA2FA,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD;;;;;0CAKsC;IACtC,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED;;+DAE+D;AAC/D,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,eAAe,EAClB,WAAW,SAAI,EACf,aAAa,SAAI,EACjB,UAAU,SAAI,GACb,MAAM,CAGR;AAED;;8EAE8E;AAC9E,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAcpE;AA+ED;;;8EAG8E;AAC9E,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAoCpE;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AA0DD;;;;;;;;;;;;;iCAaiC;AACjC,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CA8DtD;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;CACb;AAED;oDACoD;AACpD,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,aAAa,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3C,yBAAyB,CAiB3B"}
1
+ {"version":3,"file":"account-dir-schema-reconcile.d.ts","sourceRoot":"","sources":["../src/account-dir-schema-reconcile.ts"],"names":[],"mappings":"AA8EA,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD;;;;;0CAKsC;IACtC,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED;;+DAE+D;AAC/D,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,eAAe,EAClB,WAAW,SAAI,EACf,aAAa,SAAI,EACjB,UAAU,SAAI,GACb,MAAM,CAGR;AAED;;8EAE8E;AAC9E,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAcpE;AA+ED;;;8EAG8E;AAC9E,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAoCpE;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AA0DD;;;;;;;;;;;;;iCAaiC;AACjC,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CA8DtD;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;CACb;AAED;oDACoD;AACpD,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,aAAa,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3C,yBAAyB,CAiB3B"}
@@ -31,26 +31,24 @@ import { join, relative, sep } from 'node:path';
31
31
  import { parseSchemaRegions, HOME_FIXED } from '../../../lib/account-schema-regions/dist/index.js';
32
32
  const TAG = '[fs-reconcile]';
33
33
  const QUARANTINE_DIR = '.quarantine';
34
- /** Platform control-plane entries the reconcile NEVER counts as strays, regardless
35
- * of what an account's SCHEMA.md allowed set says. provision-account-dir.sh makes
36
- * the account dir a git root (`.git`) and writes its hook-enforcement settings
37
- * into `.claude/`, and the account's identity lives in account.json. SCHEMA.md is
38
- * refreshed on every provision, but an account carrying a stale/older-template/
39
- * hand-edited schema whose allowed block omits one of these must not be reported
40
- * on every run for machinery it is required to have.
34
+ /** The account's own machinery, which the reconcile NEVER counts as a stray
35
+ * whatever that account's SCHEMA.md allowed set says. provision-account-dir.sh
36
+ * makes the account dir a git root (`.git`) and writes its hook-enforcement
37
+ * settings into `.claude/`; the account's identity lives in account.json;
38
+ * AGENTS.md and SCHEMA.md are refreshed on every provision. An account carrying
39
+ * a stale, hand-edited or older-template schema must not be reported every run
40
+ * for machinery it is required to have.
41
41
  *
42
- * The second group is platform-written control-plane FILES the platform reads
43
- * and writes at the account root every run. Unlike the tool-owned buckets, no
44
- * plugin declares them into the SCHEMA.md allowed-top-level block: calendar-
45
- * availability.json is the booking page's availability config (quarantining it
46
- * froze a live booking page, Task 1877 fix); wa/webchat/telegram-channel-
47
- * bindings.json route each channel's inbound; canonical-webchat-session.json
48
- * pins the webchat session; session-titles.json holds operator renames. Each is
49
- * `join(accountDir, <name>)` in platform code, none is operator data.
50
- *
51
- * Since Task 1899 this belt no longer protects anything from being moved,
52
- * because nothing moves. It keeps the account's own machinery out of the stray
53
- * count so the standing op=strays line names only what an operator must act on. */
42
+ * Feature files at the account root are NOT here. Each is declared by the plugin
43
+ * whose feature owns it (Task 1902, `account-owned-files` in PLUGIN.md
44
+ * frontmatter), unioned into the account's allowed block by
45
+ * platform/scripts/lib/account-schema-owned-dirs.py, and read from there. This
46
+ * constant used to hold seven of them and was extended once per casualty
47
+ * (calendar-availability.json was Task 1877, after quarantining it froze a live
48
+ * booking page; agents-disabled.json was Task 1996), which is the growth the
49
+ * declarations replace. setup-account.sh runs the all-accounts merge on every
50
+ * install, so a declaration reaches existing accounts and not only fresh ones,
51
+ * and the reconcile's ownedFile= lines name any account it has not reached. */
54
52
  const PROTECTED_TOP_LEVEL = new Set([
55
53
  '.git',
56
54
  '.claude',
@@ -58,17 +56,6 @@ const PROTECTED_TOP_LEVEL = new Set([
58
56
  'SCHEMA.md',
59
57
  'account.json',
60
58
  'AGENTS.md',
61
- 'calendar-availability.json',
62
- 'wa-channel-bindings.json',
63
- 'webchat-channel-bindings.json',
64
- 'telegram-channel-bindings.json',
65
- 'canonical-webchat-session.json',
66
- 'session-titles.json',
67
- // Task 1996 — names the agents the operator switched off. Platform-written at
68
- // the account root every disable, declared by no plugin, so without this every
69
- // account with a disabled agent reports it as a stray forever and the standing
70
- // line stops naming only what an operator must act on.
71
- 'agents-disabled.json',
72
59
  ]);
73
60
  /** Buckets whose depth fs-schema-guard.sh checks, with the same 3-segment cap.
74
61
  * Every other top-level bucket is tool-owned and unbounded — not walked. This