@rubytech/create-sitedesk-code 0.1.509 → 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.
- package/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-1902-account-owned-entry-declarations.md +555 -0
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-2023-sidebar-sessions-async-reads.md +394 -0
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-1902-account-owned-entry-declarations-design.md +232 -0
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-2014-ui-suite-nondeterminism-design.md +169 -0
- package/payload/platform/plugins/admin/PLUGIN.md +1 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +2 -2
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
- package/payload/platform/plugins/business-assistant/PLUGIN.md +1 -0
- package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/schema-exposed-dirs.test.ts +21 -0
- package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
- package/payload/platform/plugins/scheduling/PLUGIN.md +1 -0
- package/payload/platform/scripts/__tests__/account-schema-owned-dirs.test.sh +112 -4
- package/payload/platform/scripts/lib/account-schema-owned-dirs.py +61 -32
- package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js +17 -30
- package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/config.d.ts +50 -4
- package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/config.js +58 -15
- package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
- package/payload/server/maxy-edge.js +20 -10
- package/payload/server/public/assets/AdminLoginScreens-CWMpccrR.css.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-CWMpccrR.css.gz +0 -0
- package/payload/server/public/assets/AdminLoginScreens-bksN0XPe.js +1 -0
- package/payload/server/public/assets/AdminLoginScreens-bksN0XPe.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-bksN0XPe.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-BMPEWNZk.js.br +0 -0
- package/payload/server/public/assets/AdminShell-BMPEWNZk.js.gz +0 -0
- package/payload/server/public/assets/_baseFor-Be3EKK3K.js.br +0 -0
- package/payload/server/public/assets/_baseFor-Be3EKK3K.js.gz +0 -0
- package/payload/server/public/assets/activity-ByWYF5dC.js.br +0 -0
- package/payload/server/public/assets/activity-ByWYF5dC.js.gz +0 -0
- package/payload/server/public/assets/{admin-CnUWbKcx.js → admin-DGr1HuPg.js} +1 -1
- package/payload/server/public/assets/admin-DGr1HuPg.js.br +0 -0
- package/payload/server/public/assets/admin-DGr1HuPg.js.gz +0 -0
- package/payload/server/public/assets/agents-C0rTfrYd.js.br +0 -0
- package/payload/server/public/assets/agents-C0rTfrYd.js.gz +0 -0
- package/payload/server/public/assets/arc-DaI5MLTq.js.br +0 -0
- package/payload/server/public/assets/arc-DaI5MLTq.js.gz +0 -0
- package/payload/server/public/assets/architecture-YZFGNWBL-DWZIX5aV.js.br +0 -0
- package/payload/server/public/assets/architectureDiagram-Q4EWVU46-CBGVUEN9.js.br +0 -0
- package/payload/server/public/assets/architectureDiagram-Q4EWVU46-CBGVUEN9.js.gz +0 -0
- package/payload/server/public/assets/array-X0JlPOfd.js.br +0 -0
- package/payload/server/public/assets/blockDiagram-DXYQGD6D-DS6dS4GZ.js.br +0 -0
- package/payload/server/public/assets/blockDiagram-DXYQGD6D-DS6dS4GZ.js.gz +0 -0
- package/payload/server/public/assets/browser-4tTaem8N.js.br +0 -0
- package/payload/server/public/assets/browser-4tTaem8N.js.gz +0 -0
- package/payload/server/public/assets/c4Diagram-AHTNJAMY-hBSBUZPt.js.br +0 -0
- package/payload/server/public/assets/c4Diagram-AHTNJAMY-hBSBUZPt.js.gz +0 -0
- package/payload/server/public/assets/calendar-BEjnkKG1.js.br +0 -0
- package/payload/server/public/assets/calendar-BEjnkKG1.js.gz +0 -0
- package/payload/server/public/assets/channel-BEokjAeE.js.br +0 -0
- package/payload/server/public/assets/chat-DBgzjDIE.js.br +0 -0
- package/payload/server/public/assets/chat-DBgzjDIE.js.gz +0 -0
- package/payload/server/public/assets/chevron-right-CQfPGsFb.js.br +0 -0
- package/payload/server/public/assets/chunk-2KRD3SAO-B6koUoB7.js.br +0 -0
- package/payload/server/public/assets/chunk-2KRD3SAO-B6koUoB7.js.gz +0 -0
- package/payload/server/public/assets/chunk-336JU56O-BF6BRh8u.js.br +0 -0
- package/payload/server/public/assets/chunk-336JU56O-BF6BRh8u.js.gz +0 -0
- package/payload/server/public/assets/chunk-426QAEUC-pbQ4PWBU.js.br +0 -0
- package/payload/server/public/assets/chunk-426QAEUC-pbQ4PWBU.js.gz +0 -0
- package/payload/server/public/assets/chunk-4BX2VUAB-7sO95xR2.js.br +0 -0
- package/payload/server/public/assets/chunk-4BX2VUAB-7sO95xR2.js.gz +0 -0
- package/payload/server/public/assets/chunk-4TB4RGXK-GCTndJYx.js.br +0 -0
- package/payload/server/public/assets/chunk-4TB4RGXK-GCTndJYx.js.gz +0 -0
- package/payload/server/public/assets/chunk-55IACEB6-ggIJf_TC.js.br +0 -0
- package/payload/server/public/assets/chunk-55IACEB6-ggIJf_TC.js.gz +0 -0
- package/payload/server/public/assets/chunk-5FUZZQ4R-DeGmkimt.js.br +0 -0
- package/payload/server/public/assets/chunk-5FUZZQ4R-DeGmkimt.js.gz +0 -0
- package/payload/server/public/assets/chunk-5PVQY5BW-aFIQKk_U.js.br +0 -0
- package/payload/server/public/assets/chunk-5PVQY5BW-aFIQKk_U.js.gz +0 -0
- package/payload/server/public/assets/chunk-67CJDMHE-LS8rUbii.js.br +2 -0
- package/payload/server/public/assets/chunk-67CJDMHE-LS8rUbii.js.gz +0 -0
- package/payload/server/public/assets/chunk-7N4EOEYR-JKOVngS0.js.br +2 -0
- package/payload/server/public/assets/chunk-7N4EOEYR-JKOVngS0.js.gz +0 -0
- package/payload/server/public/assets/chunk-AA7GKIK3-CoXnTVK6.js.br +0 -0
- package/payload/server/public/assets/chunk-AA7GKIK3-CoXnTVK6.js.gz +0 -0
- package/payload/server/public/assets/chunk-BSJP7CBP-CcsqObFg.js.br +0 -0
- package/payload/server/public/assets/chunk-BSJP7CBP-CcsqObFg.js.gz +0 -0
- package/payload/server/public/assets/chunk-CIAEETIT-CpsybR7q.js.br +0 -0
- package/payload/server/public/assets/chunk-CIAEETIT-CpsybR7q.js.gz +0 -0
- package/payload/server/public/assets/chunk-CltuBf4Z.js.br +0 -0
- package/payload/server/public/assets/chunk-CltuBf4Z.js.gz +0 -0
- package/payload/server/public/assets/chunk-EDXVE4YY-BZ2I9PSq.js.br +0 -0
- package/payload/server/public/assets/chunk-EDXVE4YY-BZ2I9PSq.js.gz +0 -0
- package/payload/server/public/assets/chunk-ENJZ2VHE-DbTwt6OZ.js.br +0 -0
- package/payload/server/public/assets/chunk-ENJZ2VHE-DbTwt6OZ.js.gz +0 -0
- package/payload/server/public/assets/chunk-FMBD7UC4-D27VnSCa.js.br +0 -0
- package/payload/server/public/assets/chunk-FMBD7UC4-D27VnSCa.js.gz +0 -0
- package/payload/server/public/assets/chunk-FOC6F5B3-C0tU2UFl.js.br +0 -0
- package/payload/server/public/assets/chunk-FOC6F5B3-C0tU2UFl.js.gz +0 -0
- package/payload/server/public/assets/chunk-ICPOFSXX-UPPu2OIq.js.br +0 -0
- package/payload/server/public/assets/chunk-ICPOFSXX-UPPu2OIq.js.gz +0 -0
- package/payload/server/public/assets/chunk-K5T4RW27-DIKxqyYw.js.br +0 -0
- package/payload/server/public/assets/chunk-K5T4RW27-DIKxqyYw.js.gz +0 -0
- package/payload/server/public/assets/chunk-KGLVRYIC-CiL4YVLo.js.br +0 -0
- package/payload/server/public/assets/chunk-KGLVRYIC-CiL4YVLo.js.gz +0 -0
- package/payload/server/public/assets/chunk-LIHQZDEY-ejKezVlD.js.br +0 -0
- package/payload/server/public/assets/chunk-LIHQZDEY-ejKezVlD.js.gz +0 -0
- package/payload/server/public/assets/chunk-ORNJ4GCN-Cc0vqc-D.js.br +0 -0
- package/payload/server/public/assets/chunk-ORNJ4GCN-Cc0vqc-D.js.gz +0 -0
- package/payload/server/public/assets/chunk-OYMX7WX6-Drxn1qG0.js.br +0 -0
- package/payload/server/public/assets/chunk-OYMX7WX6-Drxn1qG0.js.gz +0 -0
- package/payload/server/public/assets/chunk-QZHKN3VN-Dhlh7Pkk.js.br +0 -0
- package/payload/server/public/assets/chunk-QZHKN3VN-Dhlh7Pkk.js.gz +0 -0
- package/payload/server/public/assets/chunk-U2HBQHQK-DnbQIkRP.js.br +0 -0
- package/payload/server/public/assets/chunk-U2HBQHQK-DnbQIkRP.js.gz +0 -0
- package/payload/server/public/assets/chunk-X2U36JSP-CauTSVWg.js.br +0 -0
- package/payload/server/public/assets/chunk-X2U36JSP-CauTSVWg.js.gz +0 -0
- package/payload/server/public/assets/chunk-XPW4576I-DZqqB9Zi.js.br +0 -0
- package/payload/server/public/assets/chunk-XPW4576I-DZqqB9Zi.js.gz +0 -0
- package/payload/server/public/assets/chunk-YZCP3GAM-BGHo4Li-.js.br +0 -0
- package/payload/server/public/assets/chunk-YZCP3GAM-BGHo4Li-.js.gz +0 -0
- package/payload/server/public/assets/chunk-ZZ45TVLE-DWGJKPd7.js.br +0 -0
- package/payload/server/public/assets/chunk-ZZ45TVLE-DWGJKPd7.js.gz +0 -0
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-D2z_ZV1x.js.br +0 -0
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-D2z_ZV1x.js.gz +0 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BJwJoNEK.js.br +0 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BJwJoNEK.js.gz +0 -0
- package/payload/server/public/assets/clock-Dn6FHB51.js.br +0 -0
- package/payload/server/public/assets/clock-Dn6FHB51.js.gz +0 -0
- package/payload/server/public/assets/clone-Bn7XXVhr.js.br +0 -0
- package/payload/server/public/assets/cose-bilkent-S5V4N54A-D-95BzXL.js.br +0 -0
- package/payload/server/public/assets/cose-bilkent-S5V4N54A-D-95BzXL.js.gz +0 -0
- package/payload/server/public/assets/cytoscape.esm-CI2IWMLB.js.br +0 -0
- package/payload/server/public/assets/cytoscape.esm-CI2IWMLB.js.gz +0 -0
- package/payload/server/public/assets/dagre-BZYNBHvT.js.br +0 -0
- package/payload/server/public/assets/dagre-BZYNBHvT.js.gz +0 -0
- package/payload/server/public/assets/dagre-KV5264BT-DyU4HQLQ.js.br +0 -0
- package/payload/server/public/assets/dagre-KV5264BT-DyU4HQLQ.js.gz +0 -0
- package/payload/server/public/assets/data-D_e6Vdfd.js.br +0 -0
- package/payload/server/public/assets/data-D_e6Vdfd.js.gz +0 -0
- package/payload/server/public/assets/defaultLocale-B6RGN4id.js.br +0 -0
- package/payload/server/public/assets/defaultLocale-B6RGN4id.js.gz +0 -0
- package/payload/server/public/assets/diagram-5BDNPKRD-CCttJ9XA.js.br +0 -0
- package/payload/server/public/assets/diagram-5BDNPKRD-CCttJ9XA.js.gz +0 -0
- package/payload/server/public/assets/diagram-G4DWMVQ6-DvkpgSLO.js.br +0 -0
- package/payload/server/public/assets/diagram-G4DWMVQ6-DvkpgSLO.js.gz +0 -0
- package/payload/server/public/assets/diagram-MMDJMWI5-DHmW5Y9k.js.br +0 -0
- package/payload/server/public/assets/diagram-MMDJMWI5-DHmW5Y9k.js.gz +0 -0
- package/payload/server/public/assets/diagram-TYMM5635-C6T4hwzO.js.br +0 -0
- package/payload/server/public/assets/diagram-TYMM5635-C6T4hwzO.js.gz +0 -0
- package/payload/server/public/assets/dist-QvbmK1IP.js.br +0 -0
- package/payload/server/public/assets/dist-QvbmK1IP.js.gz +0 -0
- package/payload/server/public/assets/erDiagram-SMLLAGMA-5v_o7s3T.js.br +0 -0
- package/payload/server/public/assets/erDiagram-SMLLAGMA-5v_o7s3T.js.gz +0 -0
- package/payload/server/public/assets/file-text-DMKw3nIk.js.br +0 -0
- package/payload/server/public/assets/file-text-DMKw3nIk.js.gz +0 -0
- package/payload/server/public/assets/flatten-BtIc9jX7.js.br +0 -0
- package/payload/server/public/assets/flatten-BtIc9jX7.js.gz +0 -0
- package/payload/server/public/assets/flowDiagram-DWJPFMVM-3UhCDsjt.js.br +0 -0
- package/payload/server/public/assets/flowDiagram-DWJPFMVM-3UhCDsjt.js.gz +0 -0
- package/payload/server/public/assets/ganttDiagram-T4ZO3ILL-F2zgHIIQ.js.br +0 -0
- package/payload/server/public/assets/ganttDiagram-T4ZO3ILL-F2zgHIIQ.js.gz +0 -0
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-C-FvNTHZ.js.br +0 -0
- package/payload/server/public/assets/gitGraphDiagram-UUTBAWPF-oQc1N3dT.js.br +0 -0
- package/payload/server/public/assets/gitGraphDiagram-UUTBAWPF-oQc1N3dT.js.gz +0 -0
- package/payload/server/public/assets/graph-C3vW8lvw.js.br +0 -0
- package/payload/server/public/assets/graph-C3vW8lvw.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-BZgd0L6n.js.br +0 -0
- package/payload/server/public/assets/graph-labels-BZgd0L6n.js.gz +0 -0
- package/payload/server/public/assets/graphlib-DY4-AT2Q.js.br +0 -0
- package/payload/server/public/assets/graphlib-DY4-AT2Q.js.gz +0 -0
- package/payload/server/public/assets/info-OMHHGYJF-BjLPgHAC.js.br +0 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-CXNKLTui.js.br +0 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-CXNKLTui.js.gz +0 -0
- package/payload/server/public/assets/init-BmUWJJHz.js.br +0 -0
- package/payload/server/public/assets/init-BmUWJJHz.js.gz +0 -0
- package/payload/server/public/assets/isEmpty-Bu4fZ8Kf.js.br +0 -0
- package/payload/server/public/assets/isEmpty-Bu4fZ8Kf.js.gz +0 -0
- package/payload/server/public/assets/ishikawaDiagram-UXIWVN3A-wJhSOkuw.js.br +0 -0
- package/payload/server/public/assets/ishikawaDiagram-UXIWVN3A-wJhSOkuw.js.gz +0 -0
- package/payload/server/public/assets/journeyDiagram-VCZTEJTY-DgadC6Pz.js.br +0 -0
- package/payload/server/public/assets/journeyDiagram-VCZTEJTY-DgadC6Pz.js.gz +0 -0
- package/payload/server/public/assets/kanban-definition-6JOO6SKY-Bs4nQqXI.js.br +0 -0
- package/payload/server/public/assets/kanban-definition-6JOO6SKY-Bs4nQqXI.js.gz +0 -0
- package/payload/server/public/assets/katex-BjZuzSQn.js.br +0 -0
- package/payload/server/public/assets/katex-BjZuzSQn.js.gz +0 -0
- package/payload/server/public/assets/line-Bnmxy1hO.js.br +0 -0
- package/payload/server/public/assets/line-Bnmxy1hO.js.gz +0 -0
- package/payload/server/public/assets/linear-RvwZxFzh.js.br +0 -0
- package/payload/server/public/assets/linear-RvwZxFzh.js.gz +0 -0
- package/payload/server/public/assets/maximize-2-la3IBA3M.js.br +0 -0
- package/payload/server/public/assets/maximize-2-la3IBA3M.js.gz +0 -0
- package/payload/server/public/assets/mermaid-parser.core-DeNV8RtA.js.br +0 -0
- package/payload/server/public/assets/mermaid-parser.core-DeNV8RtA.js.gz +0 -0
- package/payload/server/public/assets/mermaid.core-BU9IiSCP.js.br +0 -0
- package/payload/server/public/assets/mermaid.core-BU9IiSCP.js.gz +0 -0
- package/payload/server/public/assets/mindmap-definition-QFDTVHPH-D1PMUxxm.js.br +0 -0
- package/payload/server/public/assets/mindmap-definition-QFDTVHPH-D1PMUxxm.js.gz +0 -0
- package/payload/server/public/assets/{operator-RRRdqoRQ.js → operator-D9NgVL9_.js} +1 -1
- package/payload/server/public/assets/operator-D9NgVL9_.js.br +0 -0
- package/payload/server/public/assets/operator-D9NgVL9_.js.gz +0 -0
- package/payload/server/public/assets/ordinal-C2TEWKNu.js.br +0 -0
- package/payload/server/public/assets/ordinal-C2TEWKNu.js.gz +0 -0
- package/payload/server/public/assets/packet-4T2RLAQJ-BBAZASfb.js.br +0 -0
- package/payload/server/public/assets/page-BWFHRIAH.js.br +0 -0
- package/payload/server/public/assets/page-BWFHRIAH.js.gz +0 -0
- package/payload/server/public/assets/page-DC11gesX.js.br +0 -0
- package/payload/server/public/assets/page-DC11gesX.js.gz +0 -0
- package/payload/server/public/assets/path-CoPyR7c2.js.br +0 -0
- package/payload/server/public/assets/path-CoPyR7c2.js.gz +0 -0
- package/payload/server/public/assets/pdf-render-C257cidb.js.br +0 -0
- package/payload/server/public/assets/pdf-render-C257cidb.js.gz +0 -0
- package/payload/server/public/assets/pdf.worker.min-yatZIOMy.mjs.br +0 -0
- package/payload/server/public/assets/pdf.worker.min-yatZIOMy.mjs.gz +0 -0
- package/payload/server/public/assets/pie-ZZUOXDRM-CCJUnMeG.js.br +0 -0
- package/payload/server/public/assets/pieDiagram-DEJITSTG-BzdNzoKA.js.br +0 -0
- package/payload/server/public/assets/pieDiagram-DEJITSTG-BzdNzoKA.js.gz +0 -0
- package/payload/server/public/assets/preload-helper-BQrMkyGX.js.br +0 -0
- package/payload/server/public/assets/preload-helper-BQrMkyGX.js.gz +0 -0
- package/payload/server/public/assets/public-DQbbLHQN.js.br +0 -0
- package/payload/server/public/assets/public-DQbbLHQN.js.gz +0 -0
- package/payload/server/public/assets/quadrantDiagram-34T5L4WZ-oFr1MJRO.js.br +0 -0
- package/payload/server/public/assets/quadrantDiagram-34T5L4WZ-oFr1MJRO.js.gz +0 -0
- package/payload/server/public/assets/radar-PYXPWWZC-BsRU8Aep.js.br +0 -0
- package/payload/server/public/assets/reduce-BGpN5lgn.js.br +0 -0
- package/payload/server/public/assets/reduce-BGpN5lgn.js.gz +0 -0
- package/payload/server/public/assets/requirementDiagram-MS252O5E-C9QBQxDh.js.br +0 -0
- package/payload/server/public/assets/requirementDiagram-MS252O5E-C9QBQxDh.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-OQD5si8N.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-OQD5si8N.js.gz +0 -0
- package/payload/server/public/assets/rough.esm-jSt7UMyi.js.br +0 -0
- package/payload/server/public/assets/rough.esm-jSt7UMyi.js.gz +0 -0
- package/payload/server/public/assets/routines-asrBGZUR.js.br +0 -0
- package/payload/server/public/assets/routines-asrBGZUR.js.gz +0 -0
- package/payload/server/public/assets/sankeyDiagram-XADWPNL6-c_y3bvVv.js.br +0 -0
- package/payload/server/public/assets/sankeyDiagram-XADWPNL6-c_y3bvVv.js.gz +0 -0
- package/payload/server/public/assets/sequenceDiagram-FGHM5R23-YwlCBPHE.js.br +0 -0
- package/payload/server/public/assets/sequenceDiagram-FGHM5R23-YwlCBPHE.js.gz +0 -0
- package/payload/server/public/assets/skills-D4ECwoxa.js.br +0 -0
- package/payload/server/public/assets/skills-D4ECwoxa.js.gz +0 -0
- package/payload/server/public/assets/src-C2WwTWSY.js.br +0 -0
- package/payload/server/public/assets/src-C2WwTWSY.js.gz +0 -0
- package/payload/server/public/assets/stateDiagram-FHFEXIEX-BFE3e-FM.js.br +0 -0
- package/payload/server/public/assets/stateDiagram-FHFEXIEX-BFE3e-FM.js.gz +0 -0
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CpjB4ykI.js.br +4 -0
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CpjB4ykI.js.gz +0 -0
- package/payload/server/public/assets/tasks-U3QdbUsO.js.br +0 -0
- package/payload/server/public/assets/tasks-U3QdbUsO.js.gz +0 -0
- package/payload/server/public/assets/time-entry-format-BgdTKgYr.js.br +0 -0
- package/payload/server/public/assets/time-entry-format-BgdTKgYr.js.gz +0 -0
- package/payload/server/public/assets/timeline-definition-GMOUNBTQ-azqhmRwZ.js.br +0 -0
- package/payload/server/public/assets/timeline-definition-GMOUNBTQ-azqhmRwZ.js.gz +0 -0
- package/payload/server/public/assets/treeView-SZITEDCU-BDzhKA6v.js.br +0 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-epHpdeWa.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-Ck_3VhT9.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-Ck_3VhT9.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-BdUwyNpa.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-BdUwyNpa.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CJKjEPNN.js.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CJKjEPNN.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-DOWQXDT8.css.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-DOWQXDT8.css.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-ChnBrzbZ.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-ChnBrzbZ.js.gz +0 -0
- package/payload/server/public/assets/vennDiagram-DHZGUBPP-Da2RfFs8.js.br +0 -0
- package/payload/server/public/assets/vennDiagram-DHZGUBPP-Da2RfFs8.js.gz +0 -0
- package/payload/server/public/assets/wardley-RL74JXVD-DNSZCrxV.js.br +0 -0
- package/payload/server/public/assets/wardleyDiagram-NUSXRM2D-HdrdBYES.js.br +0 -0
- package/payload/server/public/assets/wardleyDiagram-NUSXRM2D-HdrdBYES.js.gz +0 -0
- package/payload/server/public/assets/wrench-Cpee3C3J.js.br +2 -0
- package/payload/server/public/assets/wrench-Cpee3C3J.js.gz +0 -0
- package/payload/server/public/assets/xychartDiagram-5P7HB3ND-DMk6rTcQ.js.br +0 -0
- package/payload/server/public/assets/xychartDiagram-5P7HB3ND-DMk6rTcQ.js.gz +0 -0
- package/payload/server/public/index.html +2 -2
- package/payload/server/public/operator.html +2 -2
- package/payload/server/server.js +524 -503
- package/payload/server/public/assets/AdminLoginScreens-8Jk7hnWH.js +0 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Task 2014 design — cut the jsdom the `platform/ui` suite does not use
|
|
2
|
+
|
|
3
|
+
Date: 2026-07-26
|
|
4
|
+
Task: `.tasks/pending/2014-platform-ui-suite-is-nondeterministic-under-parallel-load.md`
|
|
5
|
+
Supersedes brief: 1765, folded into 2014 on 2026-07-26
|
|
6
|
+
|
|
7
|
+
## The measurement this design rests on
|
|
8
|
+
|
|
9
|
+
All figures from the dev Mac, Node 22.22.0, vitest 4.1.2, worktree
|
|
10
|
+
`task-2014-ui-suite-nondeterminism`, 2026-07-26.
|
|
11
|
+
|
|
12
|
+
### Most apparent worktree flakiness is missing build output, not flakiness
|
|
13
|
+
|
|
14
|
+
`platform/node_modules` and `platform/lib/*/dist` are gitignored build output. A
|
|
15
|
+
fresh worktree has neither. Four full-suite runs, each after adding one more
|
|
16
|
+
piece of provisioning:
|
|
17
|
+
|
|
18
|
+
| Run | Worktree state | Failed | Duration |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| 1 | `platform/ui` deps only | 63 files / 32 tests | 435s |
|
|
21
|
+
| 2 | + 3 libs built | 5 files / 8 tests | 180s |
|
|
22
|
+
| 3 | + `platform` deps | 5 files / 3 tests | 111s |
|
|
23
|
+
| 4 | + all 24 libs built | 0 | 143s |
|
|
24
|
+
|
|
25
|
+
Runs 1 and 2 failed on `Cannot find module .../dist/index.js` and
|
|
26
|
+
`Failed to resolve import "neo4j-driver"`. Those are absent artifacts, not
|
|
27
|
+
timing. Only run 3's three failures are the defect this task describes, and all
|
|
28
|
+
three are in the brief's population: both `/login/start` sub-account cases in
|
|
29
|
+
`whatsapp-self-or-house-scope.test.ts` at 5013ms and 515ms, and
|
|
30
|
+
`whatsapp-conversation-graph-state-projection.test.ts` "sub-account all-senders
|
|
31
|
+
mode returns only its bound thread" at 5017ms. 5000ms is the vitest default
|
|
32
|
+
`testTimeout`.
|
|
33
|
+
|
|
34
|
+
### Environment setup costs six times what the tests cost
|
|
35
|
+
|
|
36
|
+
Run 4, fully green:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
tests 267s
|
|
40
|
+
environment 1571s
|
|
41
|
+
import 257s
|
|
42
|
+
setup 196s
|
|
43
|
+
transform 42s
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`vitest.config.ts` sets `environment: 'jsdom'` globally. 530 test files pay it.
|
|
47
|
+
|
|
48
|
+
### Roughly half the suite boots a DOM it never touches
|
|
49
|
+
|
|
50
|
+
266 `.test.ts` files do not declare `@vitest-environment node`. Both runs
|
|
51
|
+
back to back, same machine load:
|
|
52
|
+
|
|
53
|
+
| | Duration | environment CPU |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| as-is (jsdom) | 103.0s | 1317.6s |
|
|
56
|
+
| `--environment node` | 30.9s | 0.074s |
|
|
57
|
+
|
|
58
|
+
Three consecutive node-environment passes over those 266 files agreed on 17
|
|
59
|
+
`.ts` files that genuinely need a DOM. A file that appeared in only one pass
|
|
60
|
+
(`route-cache-watchers.test.ts`) is the known flake, not a DOM dependency. A
|
|
61
|
+
`.tsx` file in the failure list arrived because vitest CLI arguments are
|
|
62
|
+
substring filters rather than exact paths.
|
|
63
|
+
|
|
64
|
+
So the suite's problem is not 4757 tests. The tests are 267s of CPU. It is that
|
|
65
|
+
about half the files run a browser DOM environment for nothing, and that is the
|
|
66
|
+
CPU starving the workers the named tests race against.
|
|
67
|
+
|
|
68
|
+
### Machine context
|
|
69
|
+
|
|
70
|
+
The dev Mac ran these measurements at load average 100 to 350 with 60 `claude`
|
|
71
|
+
processes across six peer worktree sessions. Self-contention from 20 vitest
|
|
72
|
+
forks is not the only contender for CPU, and any verification has to record the
|
|
73
|
+
load it ran under or a red run cannot be attributed.
|
|
74
|
+
|
|
75
|
+
## Design
|
|
76
|
+
|
|
77
|
+
### 1. Route the test environment by file extension
|
|
78
|
+
|
|
79
|
+
`platform/ui/vitest.config.ts` replaces its single global `environment: 'jsdom'`
|
|
80
|
+
with two projects:
|
|
81
|
+
|
|
82
|
+
- `dom`: `**/*.{test,spec}.?(c|m)[jt]sx`, environment `jsdom`.
|
|
83
|
+
- `node`: `**/*.{test,spec}.?(c|m)[jt]s` plus `../scripts/__tests__/**`,
|
|
84
|
+
environment `node`.
|
|
85
|
+
|
|
86
|
+
The existing `resolve.dedupe`, `setupFiles`, `globals`, and `exclude` settings
|
|
87
|
+
carry into both projects unchanged.
|
|
88
|
+
|
|
89
|
+
The rule is structural, not a maintained list: a test that renders JSX gets a
|
|
90
|
+
DOM, and anything else opts in explicitly. No `.tsx` file is edited.
|
|
91
|
+
|
|
92
|
+
### 2. Seventeen `.ts` files opt back into jsdom
|
|
93
|
+
|
|
94
|
+
Each gets `// @vitest-environment jsdom` with a short reason. Verified on vitest
|
|
95
|
+
4.1.2 that a per-file docblock overrides a project's `environment` setting.
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
app/__tests__/spinning-class-global.test.ts
|
|
99
|
+
app/__tests__/wa-viewer-width.test.ts
|
|
100
|
+
app/graph/__tests__/side-panel-helpers.test.ts
|
|
101
|
+
app/hooks/__tests__/useCopyFeedback.test.ts
|
|
102
|
+
app/hooks/__tests__/useMediaQuery.test.ts
|
|
103
|
+
app/hooks/__tests__/useVoiceRecorder.deviceId.test.ts
|
|
104
|
+
app/hooks/__tests__/useVoiceRecorder.mic-result.test.ts
|
|
105
|
+
app/lib/__tests__/admin-auth-login-remote-auth.test.ts
|
|
106
|
+
app/lib/__tests__/switch-account-scope.test.ts
|
|
107
|
+
app/lib/__tests__/use-admin-auth-claude-connected.test.ts
|
|
108
|
+
app/lib/__tests__/use-admin-auth-heartbeat-remote-auth.test.ts
|
|
109
|
+
app/lib/__tests__/useAdminFetch.test.ts
|
|
110
|
+
app/lib/__tests__/useSubAccountSwitcher.test.ts
|
|
111
|
+
app/public/__tests__/extract-seed-question.test.ts
|
|
112
|
+
app/whatsapp/__tests__/useTranscriptStream-activity.test.ts
|
|
113
|
+
app/whatsapp/__tests__/useTranscriptStream-endpoint.test.ts
|
|
114
|
+
app/whatsapp/__tests__/useTranscriptStream.test.ts
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
A `.ts` test that needs a DOM and lacks the docblock fails loudly on
|
|
118
|
+
`document is not defined`. It does not pass silently.
|
|
119
|
+
|
|
120
|
+
### 3. A prebuild gate so the waste cannot creep back
|
|
121
|
+
|
|
122
|
+
New `platform/ui/scripts/check-jsdom-budget.mjs`, joining the 24 checks already
|
|
123
|
+
in the `prebuild` script. It counts the files that will boot jsdom, which is
|
|
124
|
+
every `.test.tsx`/`.spec.tsx` plus every `.ts` test declaring the jsdom
|
|
125
|
+
docblock, and fails the build when that count exceeds a ceiling written in the
|
|
126
|
+
script. Adding a jsdom file becomes a deliberate one-line bump with a reason,
|
|
127
|
+
not an accident. The failure message names the files above the ceiling and the
|
|
128
|
+
CPU cost per jsdom file measured here, so a reader knows what the number buys.
|
|
129
|
+
|
|
130
|
+
### 4. The 141 existing `@vitest-environment node` docblocks stay
|
|
131
|
+
|
|
132
|
+
They remain accurate and are now redundant. Removing them is 141 files of churn
|
|
133
|
+
for no behaviour change.
|
|
134
|
+
|
|
135
|
+
## What this does not claim
|
|
136
|
+
|
|
137
|
+
Removing the environment waste reduces the contention the named tests race
|
|
138
|
+
against. It is not proven, before measurement, that the ten named tests stop
|
|
139
|
+
failing. If group A's long-press tests still race React Testing Library's
|
|
140
|
+
1000ms `findBy` default once contention drops, that is a second and separate
|
|
141
|
+
lever, and it gets measured and brought back rather than stacked on top as a
|
|
142
|
+
speculative second fix.
|
|
143
|
+
|
|
144
|
+
## Verification
|
|
145
|
+
|
|
146
|
+
**Group A and the suite.** Ten consecutive `npm test` runs in `platform/ui` on
|
|
147
|
+
the merged branch, `uptime` captured before and after each run, exit code and
|
|
148
|
+
any `×` lines collected to one file. Success is ten `exit=0` and zero `×`
|
|
149
|
+
matches across the collection, reported alongside the load each ran under. A red
|
|
150
|
+
run with its load recorded is attributable; a red run without is not.
|
|
151
|
+
|
|
152
|
+
**Group B.** Ten runs do not test a one-in-twenty failure rate. Instead measure
|
|
153
|
+
the wall-clock cost of the four group B seed calls under full-suite parallel
|
|
154
|
+
load, before and after the change, and state the headroom inside the 5000ms
|
|
155
|
+
default. The measurement is the evidence, not the run count.
|
|
156
|
+
|
|
157
|
+
**The jsdom cut itself.** The suite must still report 530 files and 4757 tests.
|
|
158
|
+
A file silently dropping out of the run because a glob missed it would look like
|
|
159
|
+
a pass.
|
|
160
|
+
|
|
161
|
+
## Out of scope
|
|
162
|
+
|
|
163
|
+
- The `platform/lib` build artifacts that made runs 1 to 3 look flaky. That is
|
|
164
|
+
worktree provisioning friction and gets its own task file.
|
|
165
|
+
- A blanket `testTimeout` bump, excluded by the brief.
|
|
166
|
+
- Deleting, skipping, or loosening the four group B tests, excluded by the
|
|
167
|
+
brief: they are the only place the cross-account 403 ordering is asserted.
|
|
168
|
+
- Removing the 141 redundant node docblocks.
|
|
169
|
+
- Any product change to the whatsapp routes, the data browser, or graph-write.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: admin
|
|
3
3
|
surface: platform
|
|
4
4
|
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_create requires the client business owner's real name (ownerName), refusing a blank or the placeholder "Owner" and seeding it onto the sub-account's owner AdminUser and owner Person, and requires the owner's IANA timezone (timezone) — refusing a blank or a non-IANA value — seeding it onto the owner UserProfile marked owner-set so scheduling reads the owner's real zone from the start rather than the box's server default, while a managing house admin (its userId) is optional — when supplied it is validated as a live house admin and recorded as the sub-account's managingAdminUserId, and when omitted the sub-account is unmanaged and its passive-intake tasks route to the install's primary admin; account_reassign_admin repoints a client sub-account's managing admin to another live house admin (the repair path for a dangling managingAdminUserId, or to record one on an unmanaged account); account_roster is the canonical per-account identity view — one row per sub-account giving business name, role, the managing house admin resolved to a name with status (assigned/absent/dangling), the business owner from the graph with status (named/nameless), and the WhatsApp account-manager binding if any, each field carrying what it means and does not (managing admin is the responsible house team member not the owner; account manager is WhatsApp routing only with no admin rights), so the agent never stitches admin-list + account.json + whatsapp-config; account_set_owner_name names (or corrects) a client sub-account's business owner by writing the owned Person — the canonical name source — refusing a blank or the placeholder "Owner", the guarded repair for a nameless owner (the standing identity-audit op=nameless-owned-person detector); account_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add (refuses on a client sub-account, where admin access is already install-wide so a per-operator seat grants nothing — the sub-account's one identity, its business owner, is set at account creation; the house account, where operators hold PIN logins and admins[] rows, is unchanged), admin-remove (refuses to remove a house admin who still manages one or more client sub-accounts, naming them so the operator reassigns each with account_reassign_admin first — never silently orphans them), admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, cpu-triage-start and cpu-triage-read (the two-call device CPU diagnostic — start spawns a detached sampling run across at least one full 120s reconcile cycle and returns a run handle immediately, read returns running/complete/failed with the per-core distribution; no admin tool blocks through a sampling window, and no spot reading is obtainable), plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), skill-search (free-text intent → skill resolver: ranks every plugin skill's SKILL.md name+description against a query and returns the top owners with their canonical skills/<slug>/ path, so intent→skill mapping survives the available-skills menu leaving context after a compaction), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the SiteDesk platform."
|
|
5
|
+
account-owned-files: [{"file": "webchat-channel-bindings.json", "description": "Routes this account's inbound admin webchat messages. Written by the session manager's webchat-channel-store."}, {"file": "wa-channel-bindings.json", "description": "Routes this account's inbound WhatsApp messages. Written by the session manager's wa-channel-store, which runs on every brand whether or not the whatsapp plugin ships."}, {"file": "telegram-channel-bindings.json", "description": "Routes this account's inbound Telegram messages. Written by the session manager's telegram-channel-store, which runs on every brand whether or not the telegram plugin ships."}, {"file": "canonical-webchat-session.json", "description": "Pins the account's canonical webchat session. Written by the admin UI server."}, {"file": "session-titles.json", "description": "Operator renames of sessions. Written by the session manager's title stores."}, {"file": "agents-disabled.json", "description": "Names the agents the operator switched off. Written by the admin agents route; provision reads it to re-apply the disables and never creates it."}]
|
|
5
6
|
tools:
|
|
6
7
|
- name: system-status
|
|
7
8
|
publicAllowlist: false
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what SiteDesk ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:86e768fb27dbcb38fc304ace12dbca3462943fcb58435cea6925ebf2469a0647
|
|
5
5
|
brand: sitedesk-code
|
|
6
6
|
product-name: SiteDesk
|
|
7
7
|
---
|
|
@@ -5451,7 +5451,7 @@ If the initial Cloudflare login fails during setup, SiteDesk will fall back to a
|
|
|
5451
5451
|
|
|
5452
5452
|
`maxy-edge.service` (always-on front door) classifies upstream errors and serves a brand-aware response. There are two distinct user-visible shapes; the right one depends on what failed.
|
|
5453
5453
|
|
|
5454
|
-
**Branded holding page (brand logo + "Starting") for ~10 s during an upgrade — this is expected and self-healing.** The edge process binds the public port immediately, but `maxy.service` (the upstream UI) takes ~10 s after restart to apply the neo4j schema and mount its 11 routes. Any browser navigation that lands during that window gets a self-contained HTML holding page that polls `/api/health` and reloads automatically once the upstream binds. The page renders the brand logo (inlined as a base64 data URI at edge boot from `<install>/server/public/brand/<assets.logo>`) and the brand display/body fonts (loaded from fonts.googleapis.com) — both paths bypass the unavailable upstream so the page never makes a same-origin asset fetch. When `brand.logoContainsName` is true the logo replaces the productName text; otherwise the page falls back to "SiteDesk is starting". No operator action required. The diagnostic line in `~/.maxy/logs/edge.log` is `[edge] upstream http error path=… err=connect ECONNREFUSED 127.0.0.1:<UPSTREAM_PORT> err-class=econnrefused-coldstart upstream=…` and disappears as soon as upstream binds. Boot-time confirmation that the logo resolved: `[edge] brand=<name> holding-logo=inlined assets-dir=<path>` — `holding-logo=missing` means the logo file wasn't found at `assets-dir`, the page degrades to text-only.
|
|
5454
|
+
**Branded holding page (brand logo + "Starting") for ~10 s during an upgrade — this is expected and self-healing.** The edge process binds the public port immediately, but `maxy.service` (the upstream UI) takes ~10 s after restart to apply the neo4j schema and mount its 11 routes. Any browser navigation that lands during that window gets a self-contained HTML holding page that polls `/api/health` and reloads automatically once the upstream binds. The page renders the brand logo (inlined as a base64 data URI at edge boot from `<install>/server/public/brand/<assets.logo>`) and the brand display/body fonts (loaded from fonts.googleapis.com) — both paths bypass the unavailable upstream so the page never makes a same-origin asset fetch. When `brand.logoContainsName` is true the logo replaces the productName text; otherwise the page falls back to "SiteDesk is starting". No operator action required. The diagnostic line in `~/.maxy/logs/edge.log` is `[edge] ts=<ISO-8601> upstream http error path=… err=connect ECONNREFUSED 127.0.0.1:<UPSTREAM_PORT> err-class=econnrefused-coldstart upstream=…` and disappears as soon as upstream binds. `ts=` is what lets a run of these be read as an outage with a start, an end and a duration, and cross-read against `journalctl --user -u <brand>-code.service`; `path=` carries the endpoint only, never the query string, because this line fires on paths whose query can hold a download signature, an OAuth code, or a customer phone number. Boot-time confirmation that the logo resolved: `[edge] brand=<name> holding-logo=inlined assets-dir=<path>` — `holding-logo=missing` means the logo file wasn't found at `assets-dir`, the page degrades to text-only.
|
|
5455
5455
|
|
|
5456
5456
|
**Branded plain-text 502 ("Bad Gateway (SiteDesk unavailable)") — real upstream failure, not cold-start.** Any error class other than `ECONNREFUSED` (timeouts, resets, host-unreachable) returns the existing 502 path. The diagnostic line carries `err-class=other`. Read the log with `tail -200 ~/.maxy/logs/edge.log | rg 'err-class=other'` and check `~/.maxy/logs/server.log` for upstream stack traces — the upstream itself is the source.
|
|
5457
5457
|
|
|
@@ -9,6 +9,12 @@ Invoked by the admin agent directly.
|
|
|
9
9
|
|
|
10
10
|
This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
|
|
11
11
|
|
|
12
|
+
## 2026-07-26 (0.1.510)
|
|
13
|
+
|
|
14
|
+
- The admin screens load faster: page assets are now sent pre-compressed, and the sessions list reads from disk without holding up everything else on the server.
|
|
15
|
+
- Fixed several admin screens that could answer for the wrong account when an install has more than one, including the calendar booking link, the logs search, and the chat settings.
|
|
16
|
+
- When an account folder cannot be found, the error now says which condition it actually hit instead of blaming a missing file.
|
|
17
|
+
|
|
12
18
|
## 2026-07-26 (0.1.509)
|
|
13
19
|
|
|
14
20
|
- The Agents page now names the tool that set an agent's risk level, and shows it by its short name instead of the full internal path.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: business-assistant
|
|
3
3
|
description: "Business assistant for small businesses. Handles customer enquiries, appointment booking, quote formatting, invoice generation, and daily briefings. Responds instantly, triages by urgency, and never lets a message fall through the cracks."
|
|
4
|
+
account-owned-dirs: [{"dir": "e-sign", "description": "One folder per signed document, each holding that document's render-once base.pdf, its base.sha256, and the per-signer stamped copies. Written by the e-sign skill; the layout is skill-owned and recreated on the next document."}]
|
|
4
5
|
metadata: {"platform":{"always":true,"embed":["admin"],"pluginKey":"business-assistant","optional":true,"recommended":true}}
|
|
5
6
|
---
|
|
6
7
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cloudflare
|
|
3
3
|
description: Cloudflare operations — tunnel setup/reset, DNS, Pages hosting, D1 data capture, and dashboard guidance. Zero agent-facing MCP tools; every operation is the agent invoking `cloudflared` or `wrangler` directly via Bash, calling the Cloudflare API with a reused per-scope narrow token, or quoting a dashboard click-path the operator performs themselves.
|
|
4
|
+
account-owned-files: [{"file": "data-portal.json", "description": "The account's data-portal folder index, written by bin/portal-index-push.mjs on every push."}]
|
|
4
5
|
tools: []
|
|
5
6
|
mcp-manifest: skip
|
|
6
7
|
---
|
|
@@ -68,6 +68,27 @@ describe('resolveExposedDirs', () => {
|
|
|
68
68
|
}
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
it('does not read a declared file as a plugin-owned dir', () => {
|
|
72
|
+
// Task 1902 — the plugin-owned region also carries file lines now. This is
|
|
73
|
+
// the client-facing copy of the parser, kept separately from
|
|
74
|
+
// platform/lib/account-schema-regions, so it needs its own case: a file must
|
|
75
|
+
// never enter pluginOwned, and asking to expose one must not expose it. The
|
|
76
|
+
// trailing slash inside the backticks is the discriminator.
|
|
77
|
+
const withFile = [
|
|
78
|
+
'<!-- plugin-owned-dirs:start -->',
|
|
79
|
+
'## Plugin-owned top-level entries',
|
|
80
|
+
'',
|
|
81
|
+
'- `sites/` — Owned by the cloudflare plugin.',
|
|
82
|
+
'- `data-portal.json` — the account data-portal index.',
|
|
83
|
+
'<!-- plugin-owned-dirs:end -->',
|
|
84
|
+
].join('\n')
|
|
85
|
+
const r = resolveExposedDirs(schema({ ontology: ONTOLOGY, pluginOwned: withFile }), [
|
|
86
|
+
'data-portal.json',
|
|
87
|
+
])
|
|
88
|
+
expect(r.pluginOwned).toEqual(['sites'])
|
|
89
|
+
expect(r.exposed).not.toContain('data-portal.json')
|
|
90
|
+
})
|
|
91
|
+
|
|
71
92
|
it('reports a plugin-owned collision and keeps the dir out of exposed', () => {
|
|
72
93
|
const collide = ONTOLOGY.replace('`quotes/`', '`sites/`').replace('Quotation', 'Site')
|
|
73
94
|
const r = resolveExposedDirs(schema({ ontology: collide, pluginOwned: PLUGIN_OWNED }))
|
|
@@ -283,7 +283,7 @@ If the initial Cloudflare login fails during setup, {{productName}} will fall ba
|
|
|
283
283
|
|
|
284
284
|
`maxy-edge.service` (always-on front door) classifies upstream errors and serves a brand-aware response. There are two distinct user-visible shapes; the right one depends on what failed.
|
|
285
285
|
|
|
286
|
-
**Branded holding page (brand logo + "Starting") for ~10 s during an upgrade — this is expected and self-healing.** The edge process binds the public port immediately, but `maxy.service` (the upstream UI) takes ~10 s after restart to apply the neo4j schema and mount its 11 routes. Any browser navigation that lands during that window gets a self-contained HTML holding page that polls `/api/health` and reloads automatically once the upstream binds. The page renders the brand logo (inlined as a base64 data URI at edge boot from `<install>/server/public/brand/<assets.logo>`) and the brand display/body fonts (loaded from fonts.googleapis.com) — both paths bypass the unavailable upstream so the page never makes a same-origin asset fetch. When `brand.logoContainsName` is true the logo replaces the productName text; otherwise the page falls back to "{{productName}} is starting". No operator action required. The diagnostic line in `~/.maxy/logs/edge.log` is `[edge] upstream http error path=… err=connect ECONNREFUSED 127.0.0.1:<UPSTREAM_PORT> err-class=econnrefused-coldstart upstream=…` and disappears as soon as upstream binds. Boot-time confirmation that the logo resolved: `[edge] brand=<name> holding-logo=inlined assets-dir=<path>` — `holding-logo=missing` means the logo file wasn't found at `assets-dir`, the page degrades to text-only.
|
|
286
|
+
**Branded holding page (brand logo + "Starting") for ~10 s during an upgrade — this is expected and self-healing.** The edge process binds the public port immediately, but `maxy.service` (the upstream UI) takes ~10 s after restart to apply the neo4j schema and mount its 11 routes. Any browser navigation that lands during that window gets a self-contained HTML holding page that polls `/api/health` and reloads automatically once the upstream binds. The page renders the brand logo (inlined as a base64 data URI at edge boot from `<install>/server/public/brand/<assets.logo>`) and the brand display/body fonts (loaded from fonts.googleapis.com) — both paths bypass the unavailable upstream so the page never makes a same-origin asset fetch. When `brand.logoContainsName` is true the logo replaces the productName text; otherwise the page falls back to "{{productName}} is starting". No operator action required. The diagnostic line in `~/.maxy/logs/edge.log` is `[edge] ts=<ISO-8601> upstream http error path=… err=connect ECONNREFUSED 127.0.0.1:<UPSTREAM_PORT> err-class=econnrefused-coldstart upstream=…` and disappears as soon as upstream binds. `ts=` is what lets a run of these be read as an outage with a start, an end and a duration, and cross-read against `journalctl --user -u <brand>-code.service`; `path=` carries the endpoint only, never the query string, because this line fires on paths whose query can hold a download signature, an OAuth code, or a customer phone number. Boot-time confirmation that the logo resolved: `[edge] brand=<name> holding-logo=inlined assets-dir=<path>` — `holding-logo=missing` means the logo file wasn't found at `assets-dir`, the page degrades to text-only.
|
|
287
287
|
|
|
288
288
|
**Branded plain-text 502 ("Bad Gateway ({{productName}} unavailable)") — real upstream failure, not cold-start.** Any error class other than `ECONNREFUSED` (timeouts, resets, host-unreachable) returns the existing 502 path. The diagnostic line carries `err-class=other`. Read the log with `tail -200 ~/.maxy/logs/edge.log | rg 'err-class=other'` and check `~/.maxy/logs/server.log` for upstream stack traces — the upstream itself is the source.
|
|
289
289
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: scheduling
|
|
3
3
|
description: "Calendar and scheduling — create events, manage appointments, set up recurring triggers."
|
|
4
|
+
account-owned-files: [{"file": "calendar-availability.json", "description": "The booking page's availability config, written by the publish-availability script and read by the live booking page."}]
|
|
4
5
|
tools:
|
|
5
6
|
- name: schedule-event
|
|
6
7
|
publicAllowlist: false
|
|
@@ -10,8 +10,10 @@ REAL_PROJECT_DIR="$(cd "$PLAT_SCRIPTS/.." && pwd)"
|
|
|
10
10
|
REAL_TEMPLATE="$REAL_PROJECT_DIR/templates/account-schema/SCHEMA.md"
|
|
11
11
|
REAL_PREMIUM="$(cd "$REAL_PROJECT_DIR/.." && pwd)/premium-plugins"
|
|
12
12
|
PASS=0; FAIL=0; FAILED=()
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
# `--` so a pattern that starts with a hyphen (a region line: "- `x.json` — …")
|
|
14
|
+
# is read as the pattern, not as a grep option.
|
|
15
|
+
assert_grep() { if printf '%s' "$2" | grep -qF -- "$1"; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); FAILED+=("$3: expected to find [$1]"); fi; }
|
|
16
|
+
assert_nogrep() { if printf '%s' "$2" | grep -qF -- "$1"; then FAIL=$((FAIL+1)); FAILED+=("$3: expected NOT to find [$1]"); else PASS=$((PASS+1)); fi; }
|
|
15
17
|
assert_eq() { if [ "$1" = "$2" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); FAILED+=("$3: expected [$2] got [$1]"); fi; }
|
|
16
18
|
assert_dir() { if [ -d "$1" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); FAILED+=("$2: expected directory [$1]"); fi; }
|
|
17
19
|
assert_nodir() { if [ -d "$1" ]; then FAIL=$((FAIL+1)); FAILED+=("$2: expected NO directory [$1]"); else PASS=$((PASS+1)); fi; }
|
|
@@ -57,7 +59,7 @@ assert_grep "op=seed-owned-dirs" "$OUT" "sd-seed-log"
|
|
|
57
59
|
assert_grep "added=quoting" "$OUT" "sd-added-quoting"
|
|
58
60
|
ALLOWED="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_SD/SCHEMA.md")"
|
|
59
61
|
assert_grep "quoting" "$ALLOWED" "sd-allowlist-has-quoting"
|
|
60
|
-
assert_grep "Plugin-owned top-level
|
|
62
|
+
assert_grep "Plugin-owned top-level entries" "$(cat "$A_SD/SCHEMA.md")" "sd-descriptive-section"
|
|
61
63
|
assert_grep "plugin-owned-dirs:start" "$(cat "$A_SD/SCHEMA.md")" "sd-marker"
|
|
62
64
|
|
|
63
65
|
# --- idempotency: second merge is byte-identical ---
|
|
@@ -73,7 +75,7 @@ export PROJECT_DIR="$T_MX/platform"
|
|
|
73
75
|
merge_owned_dirs_into_schema "$A_MX" >/dev/null
|
|
74
76
|
ALLOWED_MX="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_MX/SCHEMA.md")"
|
|
75
77
|
assert_nogrep "quoting" "$ALLOWED_MX" "mx-no-quoting"
|
|
76
|
-
assert_nogrep "Plugin-owned top-level
|
|
78
|
+
assert_nogrep "Plugin-owned top-level entries" "$(cat "$A_MX/SCHEMA.md")" "mx-no-section"
|
|
77
79
|
|
|
78
80
|
# --- platform plugins declare pages (storage-broker) + archive (memory) ---
|
|
79
81
|
# Task 1891: these two plugin-owned root dirs must land in the allowed block so
|
|
@@ -287,5 +289,111 @@ B_P="$(shasum "$A_P/SCHEMA.md" | awk '{print $1}')"
|
|
|
287
289
|
merge_owned_dirs_into_schema "$A_P" >/dev/null
|
|
288
290
|
assert_eq "$(shasum "$A_P/SCHEMA.md" | awk '{print $1}')" "$B_P" "prune-idempotent"
|
|
289
291
|
|
|
292
|
+
# --- account-owned-files: a declared file joins the same allowed block ---
|
|
293
|
+
# The block is a flat list of names and already carries SCHEMA.md and
|
|
294
|
+
# account.json, so a file needs no second surface. A second plugin declares a
|
|
295
|
+
# malformed value: its key is skipped and the valid declaration still merges.
|
|
296
|
+
TF="$(mktemp -d)"
|
|
297
|
+
mkdir -p "$TF/platform/plugins/filer" "$TF/platform/plugins/broken" \
|
|
298
|
+
"$TF/platform/config" "$TF/platform/templates/account-schema"
|
|
299
|
+
cp "$REAL_TEMPLATE" "$TF/platform/templates/account-schema/SCHEMA.md"
|
|
300
|
+
printf '{"installDir":"files-brand","shipsPremiumBundles":[]}\n' > "$TF/platform/config/brand.json"
|
|
301
|
+
printf -- '---\nname: filer\naccount-owned-files: [{"file": "x.json", "description": "Filer index."}]\n---\n' \
|
|
302
|
+
> "$TF/platform/plugins/filer/PLUGIN.md"
|
|
303
|
+
printf -- '---\nname: broken\naccount-owned-files: [{"file": "y.json"\n---\n' \
|
|
304
|
+
> "$TF/platform/plugins/broken/PLUGIN.md"
|
|
305
|
+
A_TF="$(mktemp -d)"; cp "$TF/platform/templates/account-schema/SCHEMA.md" "$A_TF/SCHEMA.md"
|
|
306
|
+
export PROJECT_DIR="$TF/platform"
|
|
307
|
+
OUT_TF="$(merge_owned_dirs_into_schema "$A_TF")"
|
|
308
|
+
ALLOWED_TF="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_TF/SCHEMA.md")"
|
|
309
|
+
assert_grep "x.json" "$ALLOWED_TF" "files-allowlist-has-x"
|
|
310
|
+
assert_nogrep "y.json" "$ALLOWED_TF" "files-malformed-skipped"
|
|
311
|
+
assert_grep "added=x.json" "$OUT_TF" "files-added-logged"
|
|
312
|
+
# A file renders without the trailing slash a dir gets; three parsers read that
|
|
313
|
+
# slash to tell a bucket from anything else.
|
|
314
|
+
assert_grep '- `x.json` — Filer index.' "$(cat "$A_TF/SCHEMA.md")" "files-region-no-slash"
|
|
315
|
+
assert_nogrep '- `x.json/`' "$(cat "$A_TF/SCHEMA.md")" "files-region-not-a-dir"
|
|
316
|
+
RC_TF="$(PROJECT_DIR="$PROJECT_DIR" python3 "$PLAT_SCRIPTS/lib/account-schema-owned-dirs.py" reconcile "$A_TF")"
|
|
317
|
+
assert_grep "ownedFile=x.json present=true" "$RC_TF" "files-reconcile-line"
|
|
318
|
+
B_TF="$(shasum "$A_TF/SCHEMA.md" | awk '{print $1}')"
|
|
319
|
+
merge_owned_dirs_into_schema "$A_TF" >/dev/null
|
|
320
|
+
assert_eq "$(shasum "$A_TF/SCHEMA.md" | awk '{print $1}')" "$B_TF" "files-idempotent"
|
|
321
|
+
|
|
322
|
+
# A dir declaration keeps its slash and its ownedDir= reconcile line.
|
|
323
|
+
TD="$(mktemp -d)"
|
|
324
|
+
mkdir -p "$TD/platform/plugins/dirred" "$TD/platform/config" "$TD/platform/templates/account-schema"
|
|
325
|
+
cp "$REAL_TEMPLATE" "$TD/platform/templates/account-schema/SCHEMA.md"
|
|
326
|
+
printf '{"installDir":"dirs-brand","shipsPremiumBundles":[]}\n' > "$TD/platform/config/brand.json"
|
|
327
|
+
printf -- '---\nname: dirred\naccount-owned-dirs: [{"dir": "widgets", "description": "Widget workspace."}]\n---\n' \
|
|
328
|
+
> "$TD/platform/plugins/dirred/PLUGIN.md"
|
|
329
|
+
A_TD="$(mktemp -d)"; cp "$TD/platform/templates/account-schema/SCHEMA.md" "$A_TD/SCHEMA.md"
|
|
330
|
+
export PROJECT_DIR="$TD/platform"
|
|
331
|
+
merge_owned_dirs_into_schema "$A_TD" >/dev/null
|
|
332
|
+
assert_grep '- `widgets/` — Widget workspace.' "$(cat "$A_TD/SCHEMA.md")" "dirs-region-keeps-slash"
|
|
333
|
+
RC_TD="$(PROJECT_DIR="$PROJECT_DIR" python3 "$PLAT_SCRIPTS/lib/account-schema-owned-dirs.py" reconcile "$A_TD")"
|
|
334
|
+
assert_grep "ownedDir=widgets present=true" "$RC_TD" "dirs-reconcile-line"
|
|
335
|
+
|
|
336
|
+
# --- a malformed ELEMENT skips its whole key, not just that element ---
|
|
337
|
+
# json.loads succeeds and the first entry is well-formed, so an append-as-you-go
|
|
338
|
+
# parse would merge a.json and silently drop c.json. A half-merged declaration is
|
|
339
|
+
# worse than none: the dropped name reports as a stray forever with no line
|
|
340
|
+
# anywhere saying its declaration failed.
|
|
341
|
+
TP="$(mktemp -d)"
|
|
342
|
+
mkdir -p "$TP/platform/plugins/partial" "$TP/platform/plugins/good" \
|
|
343
|
+
"$TP/platform/config" "$TP/platform/templates/account-schema"
|
|
344
|
+
cp "$REAL_TEMPLATE" "$TP/platform/templates/account-schema/SCHEMA.md"
|
|
345
|
+
printf '{"installDir":"partial-brand","shipsPremiumBundles":[]}\n' > "$TP/platform/config/brand.json"
|
|
346
|
+
printf -- '---\nname: partial\naccount-owned-files: [{"file": "a.json"}, "b.json", {"file": "c.json"}]\n---\n' \
|
|
347
|
+
> "$TP/platform/plugins/partial/PLUGIN.md"
|
|
348
|
+
printf -- '---\nname: good\naccount-owned-files: [{"file": "d.json", "description": "Good."}]\n---\n' \
|
|
349
|
+
> "$TP/platform/plugins/good/PLUGIN.md"
|
|
350
|
+
A_TP="$(mktemp -d)"; cp "$TP/platform/templates/account-schema/SCHEMA.md" "$A_TP/SCHEMA.md"
|
|
351
|
+
export PROJECT_DIR="$TP/platform"
|
|
352
|
+
merge_owned_dirs_into_schema "$A_TP" >/dev/null
|
|
353
|
+
ALLOWED_TP="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_TP/SCHEMA.md")"
|
|
354
|
+
assert_nogrep "a.json" "$ALLOWED_TP" "partial-first-entry-skipped"
|
|
355
|
+
assert_nogrep "c.json" "$ALLOWED_TP" "partial-later-entry-skipped"
|
|
356
|
+
assert_grep "d.json" "$ALLOWED_TP" "partial-sibling-plugin-still-merges"
|
|
357
|
+
|
|
358
|
+
# --- a name carrying a path separator is refused ---
|
|
359
|
+
# The trailing slash inside the backticks is what tells a bucket from a file in
|
|
360
|
+
# the descriptive region, and the fence entry must match a readdir name. A
|
|
361
|
+
# declared "foo/" would render as a bucket line and never match anything.
|
|
362
|
+
TS_="$(mktemp -d)"
|
|
363
|
+
mkdir -p "$TS_/platform/plugins/slashed" "$TS_/platform/config" "$TS_/platform/templates/account-schema"
|
|
364
|
+
cp "$REAL_TEMPLATE" "$TS_/platform/templates/account-schema/SCHEMA.md"
|
|
365
|
+
printf '{"installDir":"slash-brand","shipsPremiumBundles":[]}\n' > "$TS_/platform/config/brand.json"
|
|
366
|
+
printf -- '---\nname: slashed\naccount-owned-files: [{"file": "foo/", "description": "Bad."}]\naccount-owned-dirs: [{"dir": "bar/baz", "description": "Bad."}]\n---\n' \
|
|
367
|
+
> "$TS_/platform/plugins/slashed/PLUGIN.md"
|
|
368
|
+
A_TS="$(mktemp -d)"; cp "$TS_/platform/templates/account-schema/SCHEMA.md" "$A_TS/SCHEMA.md"
|
|
369
|
+
export PROJECT_DIR="$TS_/platform"
|
|
370
|
+
merge_owned_dirs_into_schema "$A_TS" >/dev/null
|
|
371
|
+
assert_nogrep "foo/" "$(cat "$A_TS/SCHEMA.md")" "slash-file-refused"
|
|
372
|
+
assert_nogrep "bar/baz" "$(cat "$A_TS/SCHEMA.md")" "slash-dir-refused"
|
|
373
|
+
|
|
374
|
+
# --- shipped declarations reach an account's allowed block ---
|
|
375
|
+
# Symlink the real plugin dirs so the case exercises the shipped PLUGIN.md
|
|
376
|
+
# frontmatter, the same way the Task 1891 pages/archive case does.
|
|
377
|
+
T_SHIP="$(make_tree '[]')"
|
|
378
|
+
for _p in business-assistant cloudflare scheduling admin; do
|
|
379
|
+
ln -s "$REAL_PROJECT_DIR/plugins/$_p" "$T_SHIP/platform/plugins/$_p"
|
|
380
|
+
done
|
|
381
|
+
A_SHIP="$(make_account "$T_SHIP")"
|
|
382
|
+
export PROJECT_DIR="$T_SHIP/platform"
|
|
383
|
+
merge_owned_dirs_into_schema "$A_SHIP" >/dev/null
|
|
384
|
+
ALLOWED_SHIP="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_SHIP/SCHEMA.md")"
|
|
385
|
+
# The four channel/session files are written by the session manager and the UI
|
|
386
|
+
# server on every brand, so they are declared on admin, which no brand excludes.
|
|
387
|
+
# realagent-code and property-administrators exclude the telegram plugin from
|
|
388
|
+
# their payload (brands/*/brand.json) while still constructing the telegram store
|
|
389
|
+
# (services/claude-session-manager/src/index.ts), so a declaration on the telegram
|
|
390
|
+
# plugin would leave those two brands reporting a permanent false stray. This
|
|
391
|
+
# tree deliberately omits whatsapp and telegram to prove the reach.
|
|
392
|
+
for _n in e-sign data-portal.json calendar-availability.json wa-channel-bindings.json \
|
|
393
|
+
telegram-channel-bindings.json webchat-channel-bindings.json \
|
|
394
|
+
canonical-webchat-session.json session-titles.json agents-disabled.json; do
|
|
395
|
+
assert_grep "$_n" "$ALLOWED_SHIP" "ship-allow-$_n"
|
|
396
|
+
done
|
|
397
|
+
|
|
290
398
|
echo "PASS=$PASS FAIL=$FAIL"
|
|
291
399
|
if [ "$FAIL" -ne 0 ]; then printf '%s\n' "${FAILED[@]}"; exit 1; fi
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Resolve plugin-declared account-owned top-level
|
|
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
|
|
5
|
+
A plugin declares owned top-level account entries in its PLUGIN.md frontmatter,
|
|
6
|
+
by kind:
|
|
6
7
|
|
|
7
|
-
account-owned-dirs:
|
|
8
|
+
account-owned-dirs: [{"dir": "quoting", "description": "..."}]
|
|
9
|
+
account-owned-files: [{"file": "data-portal.json", "description": "..."}]
|
|
8
10
|
|
|
9
|
-
The owned-
|
|
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, [ {
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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-
|
|
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["
|
|
190
|
+
if e["name"] in seen:
|
|
169
191
|
continue
|
|
170
|
-
seen[e["
|
|
171
|
-
|
|
172
|
-
|
|
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["
|
|
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["
|
|
323
|
-
insert.append(e["
|
|
324
|
-
added.append(e["
|
|
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
|
|
340
|
-
"A plugin
|
|
341
|
-
"structure
|
|
342
|
-
"write
|
|
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
|
-
|
|
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["
|
|
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["
|
|
391
|
-
|
|
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:
|
package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account-dir-schema-reconcile.d.ts","sourceRoot":"","sources":["../src/account-dir-schema-reconcile.ts"],"names":[],"mappings":"
|
|
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"}
|