@rubytech/create-maxy-code 0.1.488 → 0.1.490

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 (282) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-22-task-1899-reconcile-report-only.md +748 -0
  3. package/payload/platform/docs/superpowers/plans/2026-07-22-task-1910-two-way-portal-exchange.md +1716 -0
  4. package/payload/platform/docs/superpowers/specs/2026-07-22-task-1899-reconcile-report-only-design.md +121 -0
  5. package/payload/platform/docs/superpowers/specs/2026-07-22-task-1910-two-way-portal-exchange-design.md +103 -0
  6. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js +51 -0
  7. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js.map +1 -1
  8. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts +2 -2
  9. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts.map +1 -1
  10. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js +20 -4
  11. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js.map +1 -1
  12. package/payload/platform/lib/graph-mcp/src/__tests__/schema-cypher-parser.test.ts +55 -0
  13. package/payload/platform/lib/graph-mcp/src/schema-cypher-parser.ts +19 -4
  14. package/payload/platform/lib/graph-style/dist/index.d.ts +24 -0
  15. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
  16. package/payload/platform/lib/graph-style/dist/index.js +25 -1
  17. package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
  18. package/payload/platform/lib/graph-style/src/__tests__/additional-base-labels.test.ts +22 -0
  19. package/payload/platform/lib/graph-style/src/index.ts +27 -0
  20. package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js +15 -0
  21. package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js.map +1 -1
  22. package/payload/platform/lib/storage-broker/dist/audit.d.ts +34 -1
  23. package/payload/platform/lib/storage-broker/dist/audit.d.ts.map +1 -1
  24. package/payload/platform/lib/storage-broker/dist/audit.js +28 -4
  25. package/payload/platform/lib/storage-broker/dist/audit.js.map +1 -1
  26. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +13 -0
  27. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
  28. package/payload/platform/lib/storage-broker/dist/cf-exec.js +22 -0
  29. package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
  30. package/payload/platform/lib/storage-broker/src/__tests__/audit.test.ts +21 -0
  31. package/payload/platform/lib/storage-broker/src/audit.ts +67 -3
  32. package/payload/platform/lib/storage-broker/src/cf-exec.ts +48 -0
  33. package/payload/platform/neo4j/schema.cypher +105 -0
  34. package/payload/platform/plugins/admin/skills/agent-builder/references/agent-pattern.md +34 -5
  35. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +8 -2
  36. package/payload/platform/plugins/cloudflare/PLUGIN.md +22 -2
  37. package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +161 -41
  38. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-delete-withdraw.test.ts +95 -0
  39. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-directory-listing.test.ts +75 -13
  40. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +216 -16
  41. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +38 -2
  42. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-moved-download.test.ts +83 -0
  43. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-schema-shape.test.ts +48 -0
  44. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +62 -0
  45. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-target.test.ts +103 -0
  46. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-multipart.test.ts +211 -0
  47. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-target.test.ts +168 -0
  48. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-uploads-listing.test.ts +138 -0
  49. package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +179 -0
  50. package/payload/platform/plugins/cloudflare/mcp/__tests__/upload-route.test.ts +14 -13
  51. package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +87 -13
  52. package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +87 -9
  53. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +74 -0
  54. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/target.ts +91 -0
  55. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +23 -0
  56. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/delete.ts +45 -0
  57. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +72 -5
  58. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +80 -13
  59. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/upload.ts +358 -16
  60. package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +12 -1
  61. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +45 -0
  62. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +258 -21
  63. package/payload/platform/plugins/docs/references/calendar-booking.md +5 -1
  64. package/payload/platform/plugins/docs/references/internals.md +2 -0
  65. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js +61 -31
  66. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js.map +1 -1
  67. package/payload/platform/plugins/scheduling/PLUGIN.md +1 -1
  68. package/payload/platform/plugins/scheduling/mcp/dist/index.js +2 -2
  69. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  70. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js +70 -5
  71. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map +1 -1
  72. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js +31 -6
  73. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js.map +1 -1
  74. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +31 -4
  75. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
  76. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +18 -7
  77. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
  78. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts +15 -6
  79. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts.map +1 -1
  80. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js +24 -11
  81. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js.map +1 -1
  82. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +5 -122
  83. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  84. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +84 -5
  85. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  86. package/payload/platform/scripts/__tests__/quarantine-restore.test.sh +143 -0
  87. package/payload/platform/scripts/quarantine-restore.mjs +175 -0
  88. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts +24 -7
  89. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts.map +1 -1
  90. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js +52 -74
  91. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js.map +1 -1
  92. package/payload/platform/services/claude-session-manager/dist/index.js +11 -8
  93. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  94. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +32 -0
  95. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  96. package/payload/platform/services/whatsapp-channel/dist/notification.js +32 -2
  97. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  98. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +6 -1
  99. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  100. package/payload/platform/services/whatsapp-channel/dist/targets.js +6 -0
  101. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  102. package/payload/platform/templates/account-schema/SCHEMA.md +7 -4
  103. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  104. package/payload/platform/templates/agents/passive/IDENTITY.md +6 -0
  105. package/payload/platform/templates/agents/public/IDENTITY.md +6 -0
  106. package/payload/platform/templates/specialists/agents/citation-auditor.md +6 -0
  107. package/payload/platform/templates/specialists/agents/coding-assistant.md +6 -0
  108. package/payload/platform/templates/specialists/agents/compiled-truth-rewriter.md +6 -0
  109. package/payload/platform/templates/specialists/agents/content-producer.md +6 -0
  110. package/payload/platform/templates/specialists/agents/data-manager.md +6 -0
  111. package/payload/platform/templates/specialists/agents/database-operator.md +6 -0
  112. package/payload/platform/templates/specialists/agents/librarian.md +6 -0
  113. package/payload/platform/templates/specialists/agents/personal-assistant.md +6 -0
  114. package/payload/platform/templates/specialists/agents/project-manager.md +6 -0
  115. package/payload/platform/templates/specialists/agents/public-session-reviewer.md +6 -0
  116. package/payload/platform/templates/specialists/agents/research-assistant.md +7 -1
  117. package/payload/platform/templates/specialists/agents/typed-edge-classifier.md +6 -0
  118. package/payload/premium-plugins/writer-craft/agents/writer-craft--manuscript-reviewer.md +6 -0
  119. package/payload/server/{chunk-REWXLAPC.js → chunk-KZFE3MOY.js} +52 -4
  120. package/payload/server/public/activity.html +5 -6
  121. package/payload/server/public/assets/AdminLoginScreens-DgVFsHtd.js +1 -0
  122. package/payload/server/public/assets/AdminShell-BwNqwb4k.js +2 -0
  123. package/payload/server/public/assets/activity-2DL2DwLr.js +1 -0
  124. package/payload/server/public/assets/admin-COe0dT2U.js +1 -0
  125. package/payload/server/public/assets/{arc-D-QzyndF.js → arc-CFMJyMm-.js} +1 -1
  126. package/payload/server/public/assets/architecture-YZFGNWBL-Cypt5qHL.js +1 -0
  127. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-W7f0Us4S.js → architectureDiagram-Q4EWVU46-CiSSgdL8.js} +1 -1
  128. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-CfPS4FXg.js → blockDiagram-DXYQGD6D-_sXtIW5c.js} +1 -1
  129. package/payload/server/public/assets/{bot-DnuyRsLS.js → bot-CpBuDQlF.js} +1 -1
  130. package/payload/server/public/assets/{browser-q4EFqKvR.js → browser-CSApuZrv.js} +1 -1
  131. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Dq60cLVI.js → c4Diagram-AHTNJAMY-Bh7OAMXs.js} +1 -1
  132. package/payload/server/public/assets/{calendar-C-q9kWmA.js → calendar-DlvwZnS7.js} +1 -1
  133. package/payload/server/public/assets/channel-BMyXE4zc.js +1 -0
  134. package/payload/server/public/assets/chat-DWvYv9jq.js +1 -0
  135. package/payload/server/public/assets/chevron-down-Dnuuw6Q2.js +1 -0
  136. package/payload/server/public/assets/chevron-left-CC4zmWOr.js +1 -0
  137. package/payload/server/public/assets/chevron-right-CG_lHTx_.js +1 -0
  138. package/payload/server/public/assets/{chunk-2KRD3SAO-CXDjuDN0.js → chunk-2KRD3SAO-D4fktAwW.js} +1 -1
  139. package/payload/server/public/assets/{chunk-336JU56O-926ox0tj.js → chunk-336JU56O-CcQoKH2G.js} +2 -2
  140. package/payload/server/public/assets/chunk-426QAEUC-Xu6JKP4d.js +1 -0
  141. package/payload/server/public/assets/{chunk-4BX2VUAB-C2nrPCfN.js → chunk-4BX2VUAB-C13Wz3zS.js} +1 -1
  142. package/payload/server/public/assets/{chunk-4TB4RGXK-CuW3FOR6.js → chunk-4TB4RGXK-BL48D7gN.js} +1 -1
  143. package/payload/server/public/assets/{chunk-55IACEB6-B4_-In90.js → chunk-55IACEB6-TmOKdy8a.js} +1 -1
  144. package/payload/server/public/assets/{chunk-5FUZZQ4R-DnYGr9CP.js → chunk-5FUZZQ4R-DZ_jSF6N.js} +1 -1
  145. package/payload/server/public/assets/{chunk-5PVQY5BW-BYCOi9bf.js → chunk-5PVQY5BW-De4G1vfV.js} +1 -1
  146. package/payload/server/public/assets/{chunk-67CJDMHE-CBxsC4JP.js → chunk-67CJDMHE-Cif9Uu5C.js} +1 -1
  147. package/payload/server/public/assets/{chunk-7N4EOEYR-CAACaqFr.js → chunk-7N4EOEYR-WjTHCXnd.js} +1 -1
  148. package/payload/server/public/assets/{chunk-AA7GKIK3-BoamZdS6.js → chunk-AA7GKIK3-CwCfdQmb.js} +1 -1
  149. package/payload/server/public/assets/{chunk-BSJP7CBP-CHk82eFy.js → chunk-BSJP7CBP-9A67yXwT.js} +1 -1
  150. package/payload/server/public/assets/{chunk-CIAEETIT-DdeW9zxl.js → chunk-CIAEETIT-DPMiWSBi.js} +1 -1
  151. package/payload/server/public/assets/{chunk-EDXVE4YY-DJqSRv2w.js → chunk-EDXVE4YY-C2f_Qvx-.js} +1 -1
  152. package/payload/server/public/assets/{chunk-ENJZ2VHE-BRi4BwM5.js → chunk-ENJZ2VHE-B-Q9EbSQ.js} +1 -1
  153. package/payload/server/public/assets/{chunk-FMBD7UC4-OJp2CtUE.js → chunk-FMBD7UC4-Bmt_ZRlV.js} +1 -1
  154. package/payload/server/public/assets/{chunk-FOC6F5B3-BUAQ6spG.js → chunk-FOC6F5B3-DfGJGxpX.js} +1 -1
  155. package/payload/server/public/assets/{chunk-ICPOFSXX-DeRggD-Q.js → chunk-ICPOFSXX-Wyym1Q8K.js} +2 -2
  156. package/payload/server/public/assets/{chunk-K5T4RW27-EHiXZ8Py.js → chunk-K5T4RW27-DMpoaqw2.js} +1 -1
  157. package/payload/server/public/assets/{chunk-KGLVRYIC-tOajPcpn.js → chunk-KGLVRYIC-DYUFgri2.js} +1 -1
  158. package/payload/server/public/assets/{chunk-LIHQZDEY-uwO6oHIU.js → chunk-LIHQZDEY-x3LmeKmx.js} +1 -1
  159. package/payload/server/public/assets/{chunk-ORNJ4GCN-Dpa1_BCA.js → chunk-ORNJ4GCN-BeuNAWkW.js} +1 -1
  160. package/payload/server/public/assets/{chunk-OYMX7WX6-ChdDqaQL.js → chunk-OYMX7WX6-BUWVAfQA.js} +1 -1
  161. package/payload/server/public/assets/chunk-QZHKN3VN-7_kS3RBy.js +1 -0
  162. package/payload/server/public/assets/{chunk-U2HBQHQK-5JgWosPv.js → chunk-U2HBQHQK-DKS5adQo.js} +1 -1
  163. package/payload/server/public/assets/{chunk-X2U36JSP-BGJqysB2.js → chunk-X2U36JSP-CT_UM4IN.js} +1 -1
  164. package/payload/server/public/assets/{chunk-XPW4576I-GERE2ekx.js → chunk-XPW4576I-ByFnN4JO.js} +1 -1
  165. package/payload/server/public/assets/{chunk-YZCP3GAM-BskqX1V5.js → chunk-YZCP3GAM-DCetC20J.js} +1 -1
  166. package/payload/server/public/assets/{chunk-ZZ45TVLE-DG1iY-L9.js → chunk-ZZ45TVLE-Bh1QIv8N.js} +1 -1
  167. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DJqbNcuC.js +1 -0
  168. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CGglqfQA.js +1 -0
  169. package/payload/server/public/assets/clone-BiTMTzp_.js +1 -0
  170. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-C8Rl4ykh.js → cose-bilkent-S5V4N54A-DgLEaApT.js} +1 -1
  171. package/payload/server/public/assets/{dagre-KV5264BT-C0YrJPF3.js → dagre-KV5264BT-BfkUPrEV.js} +1 -1
  172. package/payload/server/public/assets/{dagre-DTlv-FFy.js → dagre-unhMIyVo.js} +1 -1
  173. package/payload/server/public/assets/data-CJbC_cVy.js +1 -0
  174. package/payload/server/public/assets/{diagram-5BDNPKRD-Dfk5oepI.js → diagram-5BDNPKRD-BjjwcTD9.js} +1 -1
  175. package/payload/server/public/assets/{diagram-G4DWMVQ6-QRj55DiB.js → diagram-G4DWMVQ6-BbgWtVSS.js} +1 -1
  176. package/payload/server/public/assets/{diagram-MMDJMWI5-BAPKsohU.js → diagram-MMDJMWI5-DyAnkWGy.js} +1 -1
  177. package/payload/server/public/assets/{diagram-TYMM5635-C3iZXPkC.js → diagram-TYMM5635-SAMMoLxq.js} +1 -1
  178. package/payload/server/public/assets/{erDiagram-SMLLAGMA-DIbI7g_5.js → erDiagram-SMLLAGMA-Cvo-5-fD.js} +1 -1
  179. package/payload/server/public/assets/{flatten-DlAJeN0L.js → flatten-BXzaHgtL.js} +1 -1
  180. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-l3eJJr8R.js → flowDiagram-DWJPFMVM-y3o2Ycld.js} +1 -1
  181. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-aaEzHxIq.js → ganttDiagram-T4ZO3ILL-BEm_CQaK.js} +1 -1
  182. package/payload/server/public/assets/gitGraph-7Q5UKJZL-C_-XjxLd.js +1 -0
  183. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-Di-1eg_m.js → gitGraphDiagram-UUTBAWPF-CqmOxS7F.js} +1 -1
  184. package/payload/server/public/assets/{graph-CNgt7ygi.js → graph-kaxS9a_7.js} +2 -2
  185. package/payload/server/public/assets/graph-labels-GgejRtgV.js +1 -0
  186. package/payload/server/public/assets/{graphlib-D5awC2yp.js → graphlib-_HfbQcBf.js} +1 -1
  187. package/payload/server/public/assets/info-OMHHGYJF-BH8yDCfL.js +1 -0
  188. package/payload/server/public/assets/infoDiagram-42DDH7IO-DyDXyJ6U.js +2 -0
  189. package/payload/server/public/assets/{isEmpty-Dh1dF-tM.js → isEmpty-uB1dEu_Z.js} +1 -1
  190. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CLOd_cNf.js → ishikawaDiagram-UXIWVN3A-C_d0vcZC.js} +1 -1
  191. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-cGyfW6Tg.js → journeyDiagram-VCZTEJTY-DsjRBqlW.js} +1 -1
  192. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-CRTKWPlh.js → kanban-definition-6JOO6SKY-BwtK6LS-.js} +1 -1
  193. package/payload/server/public/assets/{line-B8Brm0TD.js → line-szBCFOLJ.js} +1 -1
  194. package/payload/server/public/assets/{linear-BHnSKXBe.js → linear-X3O7lwK3.js} +1 -1
  195. package/payload/server/public/assets/{maximize-2-k3Fu5hdP.js → maximize-2-CPq3nwZA.js} +1 -1
  196. package/payload/server/public/assets/{mermaid-parser.core-BtNpdQct.js → mermaid-parser.core-BavvMAhU.js} +2 -2
  197. package/payload/server/public/assets/{mermaid.core-BY87anQZ.js → mermaid.core-Br2F9Hdm.js} +3 -3
  198. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-rUSv9qN1.js → mindmap-definition-QFDTVHPH-4qlwXoJ-.js} +1 -1
  199. package/payload/server/public/assets/operator-CldXXilj.js +1 -0
  200. package/payload/server/public/assets/{ordinal-Bnl5GL-x.js → ordinal-CZY9laxg.js} +1 -1
  201. package/payload/server/public/assets/packet-4T2RLAQJ-Bajcns4r.js +1 -0
  202. package/payload/server/public/assets/{page-Bvx0A2jB.js → page-BE6DkJP9.js} +1 -1
  203. package/payload/server/public/assets/{page-DhwPG5Zf.js → page-BIwdWXnv.js} +3 -3
  204. package/payload/server/public/assets/{pdf-render-CrpAi4Ba.js → pdf-render-H884L3EI.js} +1 -1
  205. package/payload/server/public/assets/pie-ZZUOXDRM-D7hjM5w1.js +1 -0
  206. package/payload/server/public/assets/{pieDiagram-DEJITSTG-Cjn9IP-C.js → pieDiagram-DEJITSTG-BbXryIg0.js} +1 -1
  207. package/payload/server/public/assets/{public-BJ5Oo2Fw.js → public-BKQEQGPE.js} +1 -1
  208. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-BkSHkSsn.js → quadrantDiagram-34T5L4WZ-BHLjcvh3.js} +1 -1
  209. package/payload/server/public/assets/radar-PYXPWWZC-B46d5x7a.js +1 -0
  210. package/payload/server/public/assets/{reduce-Bp5oNw1n.js → reduce-BGQzAenn.js} +1 -1
  211. package/payload/server/public/assets/{requirementDiagram-MS252O5E-hLwgyB-F.js → requirementDiagram-MS252O5E-BXaVO0lk.js} +1 -1
  212. package/payload/server/public/assets/{rotate-ccw-D-ov5jM4.js → rotate-ccw-DieZzl10.js} +1 -1
  213. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-D6Dl1RIa.js → sankeyDiagram-XADWPNL6-CmN1s17Q.js} +1 -1
  214. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-j0QBqkbL.js → sequenceDiagram-FGHM5R23-DC08e5NY.js} +1 -1
  215. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-TvEBNsuV.js → stateDiagram-FHFEXIEX-DnoQMuOW.js} +1 -1
  216. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CrstH34p.js +1 -0
  217. package/payload/server/public/assets/{tasks-DHMxTZVD.js → tasks-DYyCGz8W.js} +1 -1
  218. package/payload/server/public/assets/{time-entry-format-EmV5lz6o.js → time-entry-format-CC_9jFuR.js} +1 -1
  219. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BfivOP-j.js → timeline-definition-GMOUNBTQ-eTn3uRaH.js} +1 -1
  220. package/payload/server/public/assets/treeView-SZITEDCU-3XQh5xQ1.js +1 -0
  221. package/payload/server/public/assets/treemap-W4RFUUIX-DwR-_l5z.js +1 -0
  222. package/payload/server/public/assets/{triangle-alert-CivyPfAL.js → triangle-alert-7mQfCQWP.js} +1 -1
  223. package/payload/server/public/assets/{useCopyFeedback-hbnPS-r9.js → useCopyFeedback-os7N0yCx.js} +1 -1
  224. package/payload/server/public/assets/useSubAccountSwitcher-Bgfqig1V.css +1 -0
  225. package/payload/server/public/assets/{useVoiceRecorder-QN9_GqB_.js → useVoiceRecorder-ZFwt5i6d.js} +2 -2
  226. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-Bvfw9aFJ.js → vennDiagram-DHZGUBPP-DNx6o_ZQ.js} +1 -1
  227. package/payload/server/public/assets/wardley-RL74JXVD-DZp9oCGv.js +1 -0
  228. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-C9h04G6b.js → wardleyDiagram-NUSXRM2D-BZOchqF3.js} +1 -1
  229. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-BMvi2m2q.js → xychartDiagram-5P7HB3ND-ZBEGH6ZI.js} +1 -1
  230. package/payload/server/public/browser.html +4 -4
  231. package/payload/server/public/calendar.html +6 -6
  232. package/payload/server/public/chat.html +14 -14
  233. package/payload/server/public/data.html +11 -11
  234. package/payload/server/public/graph.html +10 -11
  235. package/payload/server/public/index.html +16 -17
  236. package/payload/server/public/operator.html +15 -16
  237. package/payload/server/public/public.html +14 -14
  238. package/payload/server/public/tasks.html +6 -6
  239. package/payload/server/server.js +1836 -998
  240. package/payload/server/{src-JJ6PHO5I.js → src-UYDFMWF4.js} +1 -1
  241. package/payload/server/public/assets/AdminLoginScreens-Dic_fPt6.js +0 -1
  242. package/payload/server/public/assets/AdminShell-JN7rZ8aG.js +0 -2
  243. package/payload/server/public/assets/Checkbox-CB74kINA.js +0 -1
  244. package/payload/server/public/assets/activity-DbfpHg3i.js +0 -1
  245. package/payload/server/public/assets/admin-CThbXaYH.js +0 -1
  246. package/payload/server/public/assets/architecture-YZFGNWBL-CGvD4r9F.js +0 -1
  247. package/payload/server/public/assets/channel-Bpg-ADC0.js +0 -1
  248. package/payload/server/public/assets/chat-DcefIV9q.js +0 -1
  249. package/payload/server/public/assets/chevron-down-Blma_CKl.js +0 -1
  250. package/payload/server/public/assets/chevron-left-xeYDxFiC.js +0 -1
  251. package/payload/server/public/assets/chevron-right-BonbtjqJ.js +0 -1
  252. package/payload/server/public/assets/chunk-426QAEUC-1poG7Nd3.js +0 -1
  253. package/payload/server/public/assets/chunk-QZHKN3VN-GN1tIO8J.js +0 -1
  254. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DMGVq8Jv.js +0 -1
  255. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DfddT8UV.js +0 -1
  256. package/payload/server/public/assets/clone-Cc9vFIEk.js +0 -1
  257. package/payload/server/public/assets/data-D5dp6nj6.js +0 -1
  258. package/payload/server/public/assets/gitGraph-7Q5UKJZL-DtjXE0UI.js +0 -1
  259. package/payload/server/public/assets/graph-labels-DA48HrRR.js +0 -1
  260. package/payload/server/public/assets/info-OMHHGYJF-CmArhGYf.js +0 -1
  261. package/payload/server/public/assets/infoDiagram-42DDH7IO-utWz7oZy.js +0 -2
  262. package/payload/server/public/assets/operator-8NVpNLeD.js +0 -1
  263. package/payload/server/public/assets/packet-4T2RLAQJ-BTfK0Zw4.js +0 -1
  264. package/payload/server/public/assets/pie-ZZUOXDRM-BvtPeMQY.js +0 -1
  265. package/payload/server/public/assets/radar-PYXPWWZC-D2UXvQgn.js +0 -1
  266. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Ur4eM6rb.js +0 -1
  267. package/payload/server/public/assets/treeView-SZITEDCU-0dE7FvM2.js +0 -1
  268. package/payload/server/public/assets/treemap-W4RFUUIX-BrwlXpr5.js +0 -1
  269. package/payload/server/public/assets/useSubAccountSwitcher-kwhG-aJj.css +0 -1
  270. package/payload/server/public/assets/wardley-RL74JXVD-CO9HdfJ-.js +0 -1
  271. /package/payload/server/public/assets/{_baseFor-BBkmqPEk.js → _baseFor-CMWvz0uA.js} +0 -0
  272. /package/payload/server/public/assets/{array-Bjtb2C_5.js → array-Cny_NT6T.js} +0 -0
  273. /package/payload/server/public/assets/{cytoscape.esm-B1dQ664k.js → cytoscape.esm-De0CCmB2.js} +0 -0
  274. /package/payload/server/public/assets/{defaultLocale-iwYA7ne8.js → defaultLocale-BahRht8U.js} +0 -0
  275. /package/payload/server/public/assets/{dist-CLmrZbY7.js → dist-C9D_xz81.js} +0 -0
  276. /package/payload/server/public/assets/{init-CxnBAgcf.js → init-DoIvjoJ_.js} +0 -0
  277. /package/payload/server/public/assets/{katex-DojHoLIr.js → katex-Bfn1OZEl.js} +0 -0
  278. /package/payload/server/public/assets/{path-JZiojYzQ.js → path-CTezU8L5.js} +0 -0
  279. /package/payload/server/public/assets/{preload-helper-CT1Z6Pdu.js → preload-helper-D_sPnjeL.js} +0 -0
  280. /package/payload/server/public/assets/{rough.esm-D5NinLFK.js → rough.esm-BJo4pvXu.js} +0 -0
  281. /package/payload/server/public/assets/{src-DQ0MSD6q.js → src-BTKk8KAG.js} +0 -0
  282. /package/payload/server/public/assets/{useSubAccountSwitcher-DotTrvX3.js → useSubAccountSwitcher-Q941cZYV.js} +0 -0
@@ -0,0 +1,121 @@
1
+ # Task 1899 — the account-dir reconcile reports and never moves
2
+
3
+ Design agreed 2026-07-22. Source task: `.tasks/1899-account-dir-reconcile-moves-live-state.md`.
4
+
5
+ ## The problem this removes
6
+
7
+ `reconcileAccount` treats every account-root entry that is neither in the account's
8
+ `SCHEMA.md` `allowed-top-level` block nor in the hand-maintained
9
+ `PROTECTED_TOP_LEVEL` set as a stray, and moves it into `<account>/.quarantine/`.
10
+ The closed list has never matched what the platform and its agents write at an
11
+ account root, so every mismatch is a live feature or live data going dark with no
12
+ error anywhere. `PROTECTED_TOP_LEVEL` has been extended once per casualty
13
+ (`calendar-availability.json` was Task 1877). The sweep is what changes; the
14
+ allowed-list is not made complete here, because once nothing moves an undeclared
15
+ entry costs one log line rather than a broken feature.
16
+
17
+ ## Units
18
+
19
+ ### 1. `platform/services/claude-session-manager/src/account-dir-schema-reconcile.ts`
20
+
21
+ The move block, `quarantineDest`, and the `mkdirSync` / `renameSync` /
22
+ `appendFileSync` imports are deleted. `ReconcileCounts` loses `quarantined` and
23
+ stays the pure formatter's input, so `formatReconcileLine` still takes exactly
24
+ what it prints:
25
+
26
+ ```
27
+ [fs-reconcile] stray-top-level=<n> over-deep=<n> bad-name=<n>
28
+ ```
29
+
30
+ `reconcileAccount` returns a new `ReconcileResult`: the counts plus
31
+ `strayNames: string[]`. `PROTECTED_TOP_LEVEL` and `QUARANTINE_DIR` both stay.
32
+ They no longer protect anything from being moved, because nothing moves; they
33
+ keep known platform entries and existing quarantine directories out of the stray
34
+ count so the standing line stays readable and a clean account still reports zero.
35
+
36
+ ### 2. `runReconcile` names the strays
37
+
38
+ One line per account that has any, emitted before the summary:
39
+
40
+ ```
41
+ [fs-reconcile] op=strays account=<id> names=<a,b,c>
42
+ ```
43
+
44
+ No cap on the name list. A count alone cannot distinguish six pieces of debris
45
+ from a live plugin config, and a truncated list would hide the entry an operator
46
+ is hunting for.
47
+
48
+ ### 3. `platform/services/claude-session-manager/src/index.ts`
49
+
50
+ The call-site comment states that the pass quarantines. It is rewritten to state
51
+ that it reports and never moves, and why: a closed allowed-list that lags what
52
+ the platform writes turns a metric into data loss.
53
+
54
+ ### 4. `platform/scripts/quarantine-restore.mjs`
55
+
56
+ One-shot, run per install with `--accounts-root <dir>`, plus `--dry-run`. For
57
+ each `<account>/.quarantine/manifest.jsonl` it walks records newest-first and for
58
+ each `originalPath`:
59
+
60
+ | On disk | Action | Log |
61
+ |---|---|---|
62
+ | Target free at the account root | move it back | `op=restore account=<id> name=<n> result=moved` |
63
+ | Target occupied by a copy the platform rewrote | leave the quarantined copy alone | `result=skipped-live-copy` |
64
+ | Target occupied by a newer copy this same run restored | leave the quarantined copy alone | `result=skipped-superseded` |
65
+ | Source absent (already restored by hand) | nothing | `result=absent` |
66
+
67
+ Three further outcomes were added during code review, each because its absence
68
+ would have been a silent skip of exactly the kind this task exists to end:
69
+
70
+ | Condition | Log | Effect |
71
+ |---|---|---|
72
+ | A manifest line that will not parse, or lacks a string `originalPath` / `quarantinePath` | `line=<n> result=unparsable` | counted, run continues |
73
+ | A record whose source escapes `<account>/.quarantine/` or whose target escapes the account | `line=<n> result=refused-outside-account` | counted, exit code 1 |
74
+ | The rename fails, or the manifest will not open | `result=failed` / `result=manifest-unreadable` | counted, exit code 1 |
75
+
76
+ The containment check exists because `.quarantine` is in the write guard's
77
+ allowed-top-level set, so an account-scoped agent can append to its own manifest,
78
+ while the script runs as the platform user across every account. Without it a
79
+ crafted record moves one account's live data into another. Every record the
80
+ reconcile ever wrote was a bare basename plus a path under `.quarantine/`, so the
81
+ check refuses nothing genuine.
82
+
83
+ Newest-first is what makes the occupied case correct for a name quarantined more
84
+ than once: the most recent copy is considered first, and every older copy of that
85
+ name then finds the target occupied. The live copy wins because the platform
86
+ rewrote it after the move, which the four `wa-channel-bindings.json` records on
87
+ account `5e99bba5` prove happened.
88
+
89
+ A restored record is appended to `<account>/.quarantine/restored.jsonl` and the
90
+ manifest is left intact, so the history of what moved and what came back
91
+ survives. The script never deletes.
92
+
93
+ ### 5. `platform/templates/account-schema/SCHEMA.md`
94
+
95
+ The paragraph telling the agent that `.quarantine/` holds entries the reconcile
96
+ moved out of the top level describes behaviour this task deletes. It is reworded
97
+ to say the directory is a historical store from the pre-1899 sweep that nothing
98
+ adds to any more. Re-filing its contents through the `data-manager` specialist
99
+ still stands.
100
+
101
+ ## Tests
102
+
103
+ New and rewritten cases go in the existing
104
+ `src/__tests__/account-dir-schema-reconcile.test.ts`; several of its 18 cases
105
+ assert quarantine moves and are rewritten to assert no move. The restore script
106
+ gets `platform/scripts/__tests__/quarantine-restore.test.sh`, matching how every
107
+ other `platform/scripts/` test is written.
108
+
109
+ ## Out of scope
110
+
111
+ - Completing the `account-owned-dirs` declarations, and extending declarations
112
+ from dirs to files so `data-portal.json` has a declaration path. Task 1902.
113
+ - The portal index push. It is correct and was starved of its config.
114
+ - The standing portal audit going silent when a config disappears. Task 1901.
115
+ - `over-deep` and `bad-name`. Both already count without moving.
116
+ - Installer payload twins. The payload has no committed copy of any file touched
117
+ here; `packages/create-maxy-code/scripts/bundle.js` copies `platform/` at
118
+ bundle time.
119
+ - Publishing, installing on the laptop's three reconcile-carrying installs, and
120
+ running the restore. All await the operator's publish command, because the
121
+ currently-installed sweep would re-take anything restored before it.
@@ -0,0 +1,103 @@
1
+ # Task 1910 — Two-way portal file exchange (design)
2
+
3
+ Design for [`.tasks/1910-two-way-portal-file-exchange.md`](../../../../.tasks/1910-two-way-portal-file-exchange.md). Operator decision 2026-07-22: the data portal becomes a two-way folder exchange, reversing the one-way scope decisions of Tasks 1831, 1905 and 1906.
4
+
5
+ ## The problem in one line
6
+
7
+ Today the portal is two stores that never meet: a flat, destination-less R2 upload area the client writes to, and a read-only device folder index the client browses. Neither can reach the other.
8
+
9
+ ## Manifest state machine
10
+
11
+ Three new columns on `manifest`: `targetPath` (the exposed folder chosen at upload, empty means untargeted), `devicePath` (where the bytes actually landed, written only after a verified device write, so its presence *is* the proof the move happened), and `deleted` (the client asked to withdraw a file that has already moved).
12
+
13
+ | State | Columns | Bytes live in | Uploads panel |
14
+ |---|---|---|---|
15
+ | In transit | `ingested=0`, `devicePath=''` | R2 | shown, downloads from R2 |
16
+ | Moved | `ingested=1`, `devicePath` set, R2 object gone | device | shown only when the file is not reachable in the folder tree |
17
+ | Withdrawn | `deleted=1` | device, pending removal | hidden |
18
+
19
+ **One listing rule:** the uploads panel shows a row when the bytes are still in R2, or when the moved file is not reachable in the folder tree. A targeted file therefore leaves the panel and appears in its folder; an untargeted file stays in the panel and downloads through the device. No file is ever listed twice, and no file becomes unreachable.
20
+
21
+ `devicePath` rather than a boolean is deliberate: a boolean would say a move happened without saying where, and the audit's `unpublishedRouted` check needs the path to compare against the published index.
22
+
23
+ **Withdrawal ends in row deletion, not a tombstone.** Once the device file is confirmed absent the row is removed. A tombstone would grow the manifest without bound and force every listing read to filter it, and the R2 object is already gone, so the row describes nothing.
24
+
25
+ ### Serving a moved untargeted file
26
+
27
+ A moved untargeted file sits at `uploads/<ownerId>/<filename>`, and `uploads` is deliberately never exposed (`schema-exposed-dirs.mjs:9-14`). `resolveFetchTarget` refuses any path whose top segment is outside the exposed set (`portal-fetch.ts:182`), and the signed message carries `accountId`, `relPath` and an expiry but no owner (`portal-fetch.ts:56`), so as it stands the device would both refuse the path and be unable to tell whose uploads folder was asked for.
28
+
29
+ Two link types with two distinct signed messages resolve this:
30
+
31
+ | Link type | Signed message | Device admission rule |
32
+ |---|---|---|
33
+ | Exposed-folder file | `accountId \n relPath \n exp` (unchanged) | top segment ∈ exposed set |
34
+ | Own-upload file | `accountId \n uploads \n ownerId \n relPath \n exp` | path is exactly `uploads/<signed ownerId>/…` |
35
+
36
+ The owner is inside the HMAC, so a portal cannot mint a link into another person's upload folder, and the device still decides admission itself rather than trusting the path. `uploads/` gains no exposure to the folder tree; only a link naming its own owner reaches it.
37
+
38
+ Two message shapes rather than one extended shape, because the exposed-folder message is unchanged and an already-deployed portal never mints the second type. Device and portal redeploy at different times (installer publish versus `site-deploy`), so a single extended message would break every existing download in the window between them.
39
+
40
+ ## Upload key
41
+
42
+ `ownerId/targetPath/filename`, collapsing to `ownerId/filename` when untargeted.
43
+
44
+ The target must be in the key. With a flat `ownerId/filename` key, the same filename dropped into two different folders collides on `objectKey UNIQUE`: the upsert would overwrite the first object, reset `ingested`, and the first file would never land. `authorizeKey` (`_lib/authorize.ts:22-32`) already admits deeper keys, rejecting only `..` and a leading separator, so the owner-isolation gate is unchanged by this.
45
+
46
+ Same owner, same folder, same filename still upserts, which is success criterion 7's replace case.
47
+
48
+ ## Target validation, and where the boundary actually is
49
+
50
+ A target is legal when it passes the person's grant (`grantAllows`, `_lib/session.ts:87`) **and** carries a folder row (`isDir=1`) in the published `directory` index for that account.
51
+
52
+ This means you can only drop where you can see, which introduces no new permission concept: the set of legal upload targets is exactly the set of folders already rendered to that person. An empty target is always legal.
53
+
54
+ **The portal check is convenience; the device check is the boundary.** The pull re-derives the exposed set itself through `resolveExposedDirs`, refusing anything outside it, the same two-ended pattern `portal-fetch.ts` uses for signed downloads. A compromised portal cannot route a file into a folder the device does not expose. The two ends are pinned to agree by a parity test over one shared fixture set, mirroring the existing HMAC parity test.
55
+
56
+ Refusal fails closed: the file stays pending, the refusal is logged with the exact failing segment, and it is never silently rerouted to the default. A silent reroute would put a client's file somewhere they did not choose and could not find.
57
+
58
+ ## Large uploads
59
+
60
+ The client slices anything above one part into 25 MiB pieces against an R2 multipart session; files at or below one part keep today's single request. The cap is 1 GiB, checked at session creation against the declared size and again at completion against the summed parts.
61
+
62
+ Slicing is on the client because the ceiling is the isolate: an upload is read wholly into Function memory (`upload.ts:137`), which is why 25 MiB was chosen in the first place (`upload.ts:8-15`). Parts preserve that bound exactly while removing the total-size limit. Cloudflare's proxied-body cap is not the constraint here; the portal is on Pages, not behind the tunnel that bounds the admin `/data` page (Task 1174).
63
+
64
+ ## Atomic index replace
65
+
66
+ `directory` gains a `generation` column and a per-account pointer table. The push writes rows at the next generation, flips the pointer in one statement, then deletes superseded rows.
67
+
68
+ The flip is the atomic point, so a listing racing a push sees the old tree or the new one, never a half-built one. Cleanup afterwards need not be atomic because nothing reads a superseded generation. This supersedes backlog Task 1842, which recorded the delete-then-insert window as an open defect; at 60 second cadence that window would otherwise be hit sixty times more often than at hourly.
69
+
70
+ ## Cadence
71
+
72
+ The index push becomes a 60 second loop in the admin server's loop registry, beside the pull that already runs there. The scheduling heartbeat's hourly gate and its `auditPortalIndexPush` are retired.
73
+
74
+ That heartbeat spawn is the path whose own header records it has "never been observed running on a device" (`portal-index-push.mjs:11-13`). Moving the push into the loop registry replaces an unobserved spawn with the mechanism the pull demonstrably uses, and removes the hourly gate that made the client's tree stale by up to two hours. Importing across the tree boundary is precedented: `portal-fetch.ts:22` already imports `schema-exposed-dirs.mjs` from the plugins tree. The script stays runnable standalone.
75
+
76
+ ## Audit
77
+
78
+ The five-minute `[data-portal-audit]` gains four fields, each answering a failure that emits no event of its own:
79
+
80
+ - `lastPushAgeSec` — the push stopped running. Healthy under 180.
81
+ - `unmovedObjects` — an R2 object survived its own pull, so the move leaked.
82
+ - `deletePendingRows` — a withdrawal never reached the device.
83
+ - `unpublishedRouted` — a routed file landed somewhere the index never published, so the client cannot see what they uploaded.
84
+
85
+ Enumeration becomes the union of accounts presenting a config and accounts with a prior push record, reporting `config-absent` rather than falling silent. That is Task 1901's fix, folded in here because this task retires the audit 1901 would have patched.
86
+
87
+ ## Download routing
88
+
89
+ `/api/download?key=` keeps serving R2 bytes while the row is in transit. Once the row has moved the object is gone, so the same request mints an own-upload signed link and 302s to the device, which is what keeps criterion 5 true without the client learning that the bytes changed home.
90
+
91
+ ## Client
92
+
93
+ Folder rows and the open listing become drop targets with a drop state. The picker takes multiple files and a multi-file drop uploads each in turn. Upload rows gain `DownloadURL` drag-out, matching what device rows already have (`portal.js:226-233`). Uploads show per-file progress and distinct messages per failure class, replacing today's single "Upload failed." for every status. The page re-reads every 60 seconds while its tab is visible.
94
+
95
+ Polling was rejected in Tasks 1885 and 1900 on the explicit reasoning that "a poll against an hourly tree is waste". The premise dies with the hourly push: against a 60 second tree the poll is the thing that makes a device-side change visible without the client clicking anything, which is success criterion 4.
96
+
97
+ ## Out of scope
98
+
99
+ Client deletion or rename of device-authored files the client did not upload; the trust boundary is that a client may withdraw their own file, not destroy a business record. Folder upload and zip download, both directions being files only. Portal-side folder creation, rename and move. The third D1 client (Task 1843). The admin `/data` page and Task 1174. Graph ingestion of pulled files, unchanged from Task 1906.
100
+
101
+ ## Dependency
102
+
103
+ Live acceptance is blocked until Task 1909 restores the quarantined `data-portal.json` on both sitedesk accounts; the portal has enumerated zero accounts since 2026-07-21T19:16:05Z. Code and unit verification do not depend on it.
@@ -86,4 +86,55 @@ FOR (p:Person) REQUIRE p.email IS UNIQUE;
86
86
  (0, node_test_1.default)("nearestLabel: empty candidates returns null", () => {
87
87
  strict_1.default.equal((0, schema_cypher_parser_js_1.nearestLabel)("Person", []), null);
88
88
  });
89
+ (0, node_test_1.default)("parseLabelsFromSchemaCypher: extracts labels from a multi-label union", () => {
90
+ const text = `
91
+ CREATE FULLTEXT INDEX entity_search_admin IF NOT EXISTS
92
+ FOR (n:Person|Organization|KnowledgeDocument)
93
+ ON EACH [n.name, n.body];
94
+ `;
95
+ const labels = (0, schema_cypher_parser_js_1.parseLabelsFromSchemaCypher)(text);
96
+ strict_1.default.deepEqual(labels, ["KnowledgeDocument", "Organization", "Person"]);
97
+ });
98
+ (0, node_test_1.default)("parseLabelsFromSchemaCypher: extracts a union spanning several lines", () => {
99
+ const text = `
100
+ CREATE FULLTEXT INDEX entity_search_admin IF NOT EXISTS
101
+ FOR (n:Person|UserProfile
102
+ |FAQPage|EmailAccount
103
+ |AssistantMessage)
104
+ ON EACH [n.name];
105
+ `;
106
+ const labels = (0, schema_cypher_parser_js_1.parseLabelsFromSchemaCypher)(text);
107
+ strict_1.default.deepEqual(labels, [
108
+ "AssistantMessage",
109
+ "EmailAccount",
110
+ "FAQPage",
111
+ "Person",
112
+ "UserProfile",
113
+ ]);
114
+ });
115
+ (0, node_test_1.default)("parseLabelsFromSchemaCypher: single and union forms coexist", () => {
116
+ const text = `
117
+ CREATE CONSTRAINT person_email_unique IF NOT EXISTS
118
+ FOR (p:Person) REQUIRE p.email IS UNIQUE;
119
+
120
+ CREATE FULLTEXT INDEX entity_search_admin IF NOT EXISTS
121
+ FOR (n:Task|Project)
122
+ ON EACH [n.name];
123
+ `;
124
+ const labels = (0, schema_cypher_parser_js_1.parseLabelsFromSchemaCypher)(text);
125
+ strict_1.default.deepEqual(labels, ["Person", "Project", "Task"]);
126
+ });
127
+ (0, node_test_1.default)("parseLabelsFromSchemaCypher: strips backticks from a reserved-word label", () => {
128
+ // Cypher escapes a label colliding with a reserved word in backticks. The
129
+ // backticks are quoting, not part of the label name.
130
+ const text = `
131
+ CREATE INDEX order_account IF NOT EXISTS
132
+ FOR (n:\`Order\`) ON (n.accountId);
133
+
134
+ CREATE INDEX case_account IF NOT EXISTS
135
+ FOR (n:\`Case\`) ON (n.accountId);
136
+ `;
137
+ const labels = (0, schema_cypher_parser_js_1.parseLabelsFromSchemaCypher)(text);
138
+ strict_1.default.deepEqual(labels, ["Case", "Order"]);
139
+ });
89
140
  //# sourceMappingURL=schema-cypher-parser.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-cypher-parser.test.js","sourceRoot":"","sources":["../../src/__tests__/schema-cypher-parser.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema-cypher-parser.test.js","sourceRoot":"","sources":["../../src/__tests__/schema-cypher-parser.test.ts"],"names":[],"mappings":"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"}
@@ -22,8 +22,8 @@
22
22
  */
23
23
  /**
24
24
  * Extract every Neo4j label declared in a `schema.cypher` file by scanning
25
- * `FOR (alias:Label)` constraint and index forms. Returns a sorted, de-duped
26
- * list. Pure function — no I/O.
25
+ * `FOR (alias:Label)` and `FOR (alias:A|B|C)` constraint and index forms.
26
+ * Returns a sorted, de-duped list. Pure function — no I/O.
27
27
  */
28
28
  export declare function parseLabelsFromSchemaCypher(schemaCypher: string): string[];
29
29
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"schema-cypher-parser.d.ts","sourceRoot":"","sources":["../src/schema-cypher-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CAM1E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBxD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC5B,WAAW,SAAI,GACd,MAAM,GAAG,IAAI,CAYf"}
1
+ {"version":3,"file":"schema-cypher-parser.d.ts","sourceRoot":"","sources":["../src/schema-cypher-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAgBH;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CAS1E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBxD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC5B,WAAW,SAAI,GACd,MAAM,GAAG,IAAI,CAYf"}
@@ -25,16 +25,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.parseLabelsFromSchemaCypher = parseLabelsFromSchemaCypher;
26
26
  exports.levenshtein = levenshtein;
27
27
  exports.nearestLabel = nearestLabel;
28
- const LABEL_PATTERN = /FOR\s*\(\s*\w+\s*:\s*(\w+)\s*\)/g;
28
+ // Matches both declaration forms:
29
+ // FOR (p:Person) single-label constraint / index
30
+ // FOR (n:Person|Organization|...) the entity_search_admin fulltext union,
31
+ // which may wrap across several lines
32
+ // Before Task 1893 only the single form was matched, so seven labels declared
33
+ // solely in the union (AdminConversation, AssistantMessage, EmailAccount,
34
+ // FAQPage, OpeningHoursSpecification, PublicConversation, UserMessage) read as
35
+ // undeclared to every consumer of this parser.
36
+ // Backticks are allowed in the label run because Cypher escapes a label whose
37
+ // name collides with a reserved word that way — `:`Order`` and `:`Case`` are
38
+ // declared escaped in schema.cypher. The backticks are quoting, not part of the
39
+ // label, so they are stripped below.
40
+ const LABEL_PATTERN = /FOR\s*\(\s*\w+\s*:\s*([A-Za-z0-9_|\s`]+?)\s*\)/g;
29
41
  /**
30
42
  * Extract every Neo4j label declared in a `schema.cypher` file by scanning
31
- * `FOR (alias:Label)` constraint and index forms. Returns a sorted, de-duped
32
- * list. Pure function — no I/O.
43
+ * `FOR (alias:Label)` and `FOR (alias:A|B|C)` constraint and index forms.
44
+ * Returns a sorted, de-duped list. Pure function — no I/O.
33
45
  */
34
46
  function parseLabelsFromSchemaCypher(schemaCypher) {
35
47
  const found = new Set();
36
48
  for (const match of schemaCypher.matchAll(LABEL_PATTERN)) {
37
- found.add(match[1]);
49
+ for (const part of match[1].split("|")) {
50
+ const label = part.replace(/`/g, "").trim();
51
+ if (label)
52
+ found.add(label);
53
+ }
38
54
  }
39
55
  return [...found].sort();
40
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"schema-cypher-parser.js","sourceRoot":"","sources":["../src/schema-cypher-parser.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;AASH,kEAMC;AAQD,kCAgBC;AAOD,oCAgBC;AA5DD,MAAM,aAAa,GAAG,kCAAkC,CAAC;AAEzD;;;;GAIG;AACH,SAAgB,2BAA2B,CAAC,YAAoB;IAC9D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACzD,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,SAAgB,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3C,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACvE,CAAC;QACD,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,SAAgB,YAAY,CAC1B,OAAe,EACf,UAA4B,EAC5B,WAAW,GAAG,CAAC;IAEf,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,IAAI,QAAQ,GAAG,QAAQ,CAAC;IACxB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC;YACjB,QAAQ,GAAG,CAAC,CAAC;YACb,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,IAAI,KAAK,IAAI,IAAI,QAAQ,GAAG,WAAW;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"schema-cypher-parser.js","sourceRoot":"","sources":["../src/schema-cypher-parser.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;AAqBH,kEASC;AAQD,kCAgBC;AAOD,oCAgBC;AA3ED,kCAAkC;AAClC,uEAAuE;AACvE,+EAA+E;AAC/E,2EAA2E;AAC3E,8EAA8E;AAC9E,0EAA0E;AAC1E,+EAA+E;AAC/E,+CAA+C;AAC/C,8EAA8E;AAC9E,6EAA6E;AAC7E,gFAAgF;AAChF,qCAAqC;AACrC,MAAM,aAAa,GAAG,iDAAiD,CAAC;AAExE;;;;GAIG;AACH,SAAgB,2BAA2B,CAAC,YAAoB;IAC9D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACzD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,KAAK;gBAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,SAAgB,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3C,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACvE,CAAC;QACD,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,SAAgB,YAAY,CAC1B,OAAe,EACf,UAA4B,EAC5B,WAAW,GAAG,CAAC;IAEf,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,IAAI,QAAQ,GAAG,QAAQ,CAAC;IACxB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC;YACjB,QAAQ,GAAG,CAAC,CAAC;YACb,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,IAAI,KAAK,IAAI,IAAI,QAAQ,GAAG,WAAW;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -97,3 +97,58 @@ test("nearestLabel: returns null when no match within maxDistance", () => {
97
97
  test("nearestLabel: empty candidates returns null", () => {
98
98
  assert.equal(nearestLabel("Person", []), null);
99
99
  });
100
+
101
+ test("parseLabelsFromSchemaCypher: extracts labels from a multi-label union", () => {
102
+ const text = `
103
+ CREATE FULLTEXT INDEX entity_search_admin IF NOT EXISTS
104
+ FOR (n:Person|Organization|KnowledgeDocument)
105
+ ON EACH [n.name, n.body];
106
+ `;
107
+ const labels = parseLabelsFromSchemaCypher(text);
108
+ assert.deepEqual(labels, ["KnowledgeDocument", "Organization", "Person"]);
109
+ });
110
+
111
+ test("parseLabelsFromSchemaCypher: extracts a union spanning several lines", () => {
112
+ const text = `
113
+ CREATE FULLTEXT INDEX entity_search_admin IF NOT EXISTS
114
+ FOR (n:Person|UserProfile
115
+ |FAQPage|EmailAccount
116
+ |AssistantMessage)
117
+ ON EACH [n.name];
118
+ `;
119
+ const labels = parseLabelsFromSchemaCypher(text);
120
+ assert.deepEqual(labels, [
121
+ "AssistantMessage",
122
+ "EmailAccount",
123
+ "FAQPage",
124
+ "Person",
125
+ "UserProfile",
126
+ ]);
127
+ });
128
+
129
+ test("parseLabelsFromSchemaCypher: single and union forms coexist", () => {
130
+ const text = `
131
+ CREATE CONSTRAINT person_email_unique IF NOT EXISTS
132
+ FOR (p:Person) REQUIRE p.email IS UNIQUE;
133
+
134
+ CREATE FULLTEXT INDEX entity_search_admin IF NOT EXISTS
135
+ FOR (n:Task|Project)
136
+ ON EACH [n.name];
137
+ `;
138
+ const labels = parseLabelsFromSchemaCypher(text);
139
+ assert.deepEqual(labels, ["Person", "Project", "Task"]);
140
+ });
141
+
142
+ test("parseLabelsFromSchemaCypher: strips backticks from a reserved-word label", () => {
143
+ // Cypher escapes a label colliding with a reserved word in backticks. The
144
+ // backticks are quoting, not part of the label name.
145
+ const text = `
146
+ CREATE INDEX order_account IF NOT EXISTS
147
+ FOR (n:\`Order\`) ON (n.accountId);
148
+
149
+ CREATE INDEX case_account IF NOT EXISTS
150
+ FOR (n:\`Case\`) ON (n.accountId);
151
+ `;
152
+ const labels = parseLabelsFromSchemaCypher(text);
153
+ assert.deepEqual(labels, ["Case", "Order"]);
154
+ });
@@ -21,17 +21,32 @@
21
21
  * exported one targets human-readable error UX, not refresh debouncing.
22
22
  */
23
23
 
24
- const LABEL_PATTERN = /FOR\s*\(\s*\w+\s*:\s*(\w+)\s*\)/g;
24
+ // Matches both declaration forms:
25
+ // FOR (p:Person) single-label constraint / index
26
+ // FOR (n:Person|Organization|...) the entity_search_admin fulltext union,
27
+ // which may wrap across several lines
28
+ // Before Task 1893 only the single form was matched, so seven labels declared
29
+ // solely in the union (AdminConversation, AssistantMessage, EmailAccount,
30
+ // FAQPage, OpeningHoursSpecification, PublicConversation, UserMessage) read as
31
+ // undeclared to every consumer of this parser.
32
+ // Backticks are allowed in the label run because Cypher escapes a label whose
33
+ // name collides with a reserved word that way — `:`Order`` and `:`Case`` are
34
+ // declared escaped in schema.cypher. The backticks are quoting, not part of the
35
+ // label, so they are stripped below.
36
+ const LABEL_PATTERN = /FOR\s*\(\s*\w+\s*:\s*([A-Za-z0-9_|\s`]+?)\s*\)/g;
25
37
 
26
38
  /**
27
39
  * Extract every Neo4j label declared in a `schema.cypher` file by scanning
28
- * `FOR (alias:Label)` constraint and index forms. Returns a sorted, de-duped
29
- * list. Pure function — no I/O.
40
+ * `FOR (alias:Label)` and `FOR (alias:A|B|C)` constraint and index forms.
41
+ * Returns a sorted, de-duped list. Pure function — no I/O.
30
42
  */
31
43
  export function parseLabelsFromSchemaCypher(schemaCypher: string): string[] {
32
44
  const found = new Set<string>();
33
45
  for (const match of schemaCypher.matchAll(LABEL_PATTERN)) {
34
- found.add(match[1]);
46
+ for (const part of match[1].split("|")) {
47
+ const label = part.replace(/`/g, "").trim();
48
+ if (label) found.add(label);
49
+ }
35
50
  }
36
51
  return [...found].sort();
37
52
  }
@@ -24,6 +24,30 @@
24
24
  */
25
25
  export declare const GRAPH_LABEL_COLOURS: Record<string, string>;
26
26
  export declare const FALLBACK_LABEL_COLOUR = "#94A3B8";
27
+ /**
28
+ * Labels that are an additional label stacked on a base node rather than a
29
+ * standalone graph node. Each surfaces as its base node's chip and colour, so
30
+ * none carries a `GRAPH_LABEL_COLOURS` hue of its own.
31
+ *
32
+ * Single declaration, two consumers (Task 1893):
33
+ * - `platform/ui/server/lib/top-level-labels.ts` drops these from the
34
+ * `/graph` chip allowlist, so a vertical granting one a
35
+ * `## Top-level node types` row (which the account file-schema projection
36
+ * needs for its bucket) does not silently add a fallback-grey chip.
37
+ * - The memory plugin's schema-cypher drift guard exempts these from needing
38
+ * a `CREATE CONSTRAINT` / `CREATE INDEX` line, because no standalone node
39
+ * of the label ever exists.
40
+ *
41
+ * Membership is read from what the schema docs already state, not decided here:
42
+ * - `Applicant` on `:Person` (schema-estate-agent.md)
43
+ * - `Engineer` on `:Person` (schema-construction.md)
44
+ * - `Supplier` on `:Organization` (schema-construction.md)
45
+ * - `GuestProfile` on `:Person` (schema-hospitality.md)
46
+ *
47
+ * Adding a fifth member is an ontology decision: state it in the vertical's
48
+ * schema prose first, then add it here.
49
+ */
50
+ export declare const ADDITIONAL_BASE_LABELS: ReadonlySet<string>;
27
51
  export declare const ALL_GRAPH_LABELS: readonly string[];
28
52
  /**
29
53
  * Select the sublabel whose colour should drive the node hue. Walks the
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAqItD,CAAA;AAED,eAAO,MAAM,qBAAqB,YAAY,CAAA;AAE9C,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAE7C,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAOzE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAiBD,eAAO,MAAM,qBAAqB,sFAQxB,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnE,eAAO,MAAM,oBAAoB,EAAE,cAAsB,CAAA;AAUzD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAE3D;AA8ED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAY7D;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,EAAE,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,kBAAkB,CAU/E;AASD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAKD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI7D;AAED,iEAAiE;AACjE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIjE;AA2BD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAqH1D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAoF3D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAqItD,CAAA;AAED,eAAO,MAAM,qBAAqB,YAAY,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAE/B,CAAA;AAExB,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAE7C,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAOzE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAiBD,eAAO,MAAM,qBAAqB,sFAQxB,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnE,eAAO,MAAM,oBAAoB,EAAE,cAAsB,CAAA;AAUzD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAE3D;AA8ED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAY7D;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,EAAE,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,kBAAkB,CAU/E;AASD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAKD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI7D;AAED,iEAAiE;AACjE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIjE;AA2BD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAqH1D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAoF3D"}
@@ -19,7 +19,7 @@
19
19
  * (resolved at runtime; this module supplies the schema-default fallback)
20
20
  */
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.FALLBACK_LABEL_SHAPE = exports.EXTERNAL_LABEL_SHAPES = exports.ALL_GRAPH_LABELS = exports.FALLBACK_LABEL_COLOUR = exports.GRAPH_LABEL_COLOURS = void 0;
22
+ exports.FALLBACK_LABEL_SHAPE = exports.EXTERNAL_LABEL_SHAPES = exports.ALL_GRAPH_LABELS = exports.ADDITIONAL_BASE_LABELS = exports.FALLBACK_LABEL_COLOUR = exports.GRAPH_LABEL_COLOURS = void 0;
23
23
  exports.pickDisplayLabel = pickDisplayLabel;
24
24
  exports.colourForLabel = colourForLabel;
25
25
  exports.shapeForLabel = shapeForLabel;
@@ -157,6 +157,30 @@ exports.GRAPH_LABEL_COLOURS = {
157
157
  CashEntry: '#7D5A2B',
158
158
  };
159
159
  exports.FALLBACK_LABEL_COLOUR = '#94A3B8';
160
+ /**
161
+ * Labels that are an additional label stacked on a base node rather than a
162
+ * standalone graph node. Each surfaces as its base node's chip and colour, so
163
+ * none carries a `GRAPH_LABEL_COLOURS` hue of its own.
164
+ *
165
+ * Single declaration, two consumers (Task 1893):
166
+ * - `platform/ui/server/lib/top-level-labels.ts` drops these from the
167
+ * `/graph` chip allowlist, so a vertical granting one a
168
+ * `## Top-level node types` row (which the account file-schema projection
169
+ * needs for its bucket) does not silently add a fallback-grey chip.
170
+ * - The memory plugin's schema-cypher drift guard exempts these from needing
171
+ * a `CREATE CONSTRAINT` / `CREATE INDEX` line, because no standalone node
172
+ * of the label ever exists.
173
+ *
174
+ * Membership is read from what the schema docs already state, not decided here:
175
+ * - `Applicant` on `:Person` (schema-estate-agent.md)
176
+ * - `Engineer` on `:Person` (schema-construction.md)
177
+ * - `Supplier` on `:Organization` (schema-construction.md)
178
+ * - `GuestProfile` on `:Person` (schema-hospitality.md)
179
+ *
180
+ * Adding a fifth member is an ontology decision: state it in the vertical's
181
+ * schema prose first, then add it here.
182
+ */
183
+ exports.ADDITIONAL_BASE_LABELS = Object.freeze(new Set(['Applicant', 'Engineer', 'GuestProfile', 'Supplier']));
160
184
  exports.ALL_GRAPH_LABELS = Object.freeze(Object.keys(exports.GRAPH_LABEL_COLOURS));
161
185
  /**
162
186
  * Select the sublabel whose colour should drive the node hue. Walks the