procesio-cli 0.1.1__py3-none-any.whl

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 (647) hide show
  1. procesio_cli/__init__.py +18 -0
  2. procesio_cli/_cli.py +71 -0
  3. procesio_cli/agents/__init__.py +0 -0
  4. procesio_cli/agents/_lib/__init__.py +0 -0
  5. procesio_cli/agents/_lib/actiondef.py +24 -0
  6. procesio_cli/agents/_lib/errors.py +40 -0
  7. procesio_cli/agents/_lib/reversibility.py +236 -0
  8. procesio_cli/agents/_lib/toolrunner.py +141 -0
  9. procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-PLAYBOOK.md +62 -0
  10. procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-PROCESIO-INTEROP.md +76 -0
  11. procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-TROUBLESHOOTING.md +60 -0
  12. procesio_cli/agents/connector-builder/README.md +51 -0
  13. procesio_cli/agents/connector-builder/agent.yaml +61 -0
  14. procesio_cli/agents/connector-builder/context.py +19 -0
  15. procesio_cli/agents/connector-builder/handlers/__init__.py +1 -0
  16. procesio_cli/agents/connector-builder/handlers/checklist.py +71 -0
  17. procesio_cli/agents/connector-builder/handlers/guidance.py +37 -0
  18. procesio_cli/agents/connector-builder/handlers/nextstep.py +221 -0
  19. procesio_cli/agents/connector-builder/handlers/status.py +35 -0
  20. procesio_cli/agents/connector-builder/knowledge.py +47 -0
  21. procesio_cli/agents/connector-builder/main.py +93 -0
  22. procesio_cli/agents/connector-builder/tests/conftest.py +95 -0
  23. procesio_cli/agents/connector-builder/tests/test_cba_agent.py +148 -0
  24. procesio_cli/agents/orchestrator/__init__.py +0 -0
  25. procesio_cli/agents/orchestrator/bootstraplib.py +166 -0
  26. procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/00-INDEX.md +60 -0
  27. procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/01-PROCESSES.md +230 -0
  28. procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/02-FORMS.md +287 -0
  29. procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/03-INTEGRATIONS.md +384 -0
  30. procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/04-DATABASE.md +293 -0
  31. procesio_cli/agents/procesio/PROCESIO-API-RELIABILITY-DOCTRINE.md +113 -0
  32. procesio_cli/agents/procesio/PROCESIO-BEST-PRACTICES.md +316 -0
  33. procesio_cli/agents/procesio/PROCESIO-BUILD-AND-TEST-PLAYBOOK.md +217 -0
  34. procesio_cli/agents/procesio/PROCESIO-DATASTORE.md +72 -0
  35. procesio_cli/agents/procesio/PROCESIO-ENVIRONMENTS.md +62 -0
  36. procesio_cli/agents/procesio/PROCESIO-SCHEDULING.md +47 -0
  37. procesio_cli/agents/procesio/PROCESIO-VISUAL-ORGANIZATION.md +175 -0
  38. procesio_cli/agents/procesio/README.md +97 -0
  39. procesio_cli/agents/procesio/__init__.py +0 -0
  40. procesio_cli/agents/procesio/agent.yaml +73 -0
  41. procesio_cli/agents/procesio/audit.py +226 -0
  42. procesio_cli/agents/procesio/checklist.py +135 -0
  43. procesio_cli/agents/procesio/context.py +19 -0
  44. procesio_cli/agents/procesio/handlers/__init__.py +0 -0
  45. procesio_cli/agents/procesio/handlers/audit.py +39 -0
  46. procesio_cli/agents/procesio/handlers/checklist.py +34 -0
  47. procesio_cli/agents/procesio/handlers/guidance.py +36 -0
  48. procesio_cli/agents/procesio/handlers/status.py +33 -0
  49. procesio_cli/agents/procesio/handlers/verify.py +39 -0
  50. procesio_cli/agents/procesio/knowledge.py +115 -0
  51. procesio_cli/agents/procesio/main.py +89 -0
  52. procesio_cli/agents/procesio/tests/__init__.py +0 -0
  53. procesio_cli/agents/procesio/tests/conftest.py +62 -0
  54. procesio_cli/agents/procesio/tests/test_audit.py +91 -0
  55. procesio_cli/agents/procesio/tests/test_checklist.py +28 -0
  56. procesio_cli/agents/procesio/tests/test_guidance.py +78 -0
  57. procesio_cli/agents/procesio/tests/test_manifest_sync.py +48 -0
  58. procesio_cli/agents/procesio/tests/test_verify.py +134 -0
  59. procesio_cli/agents/procesio/tests/test_workspace_scope.py +97 -0
  60. procesio_cli/agents/procesio/verifylib.py +222 -0
  61. procesio_cli/dashboard/README.md +117 -0
  62. procesio_cli/dashboard/__init__.py +18 -0
  63. procesio_cli/dashboard/serve.py +98 -0
  64. procesio_cli/dashboard/server/__init__.py +1 -0
  65. procesio_cli/dashboard/server/config_schemas.py +92 -0
  66. procesio_cli/dashboard/server/google.py +103 -0
  67. procesio_cli/dashboard/server/handler.py +160 -0
  68. procesio_cli/dashboard/server/inventory.py +213 -0
  69. procesio_cli/dashboard/server/jobs.py +118 -0
  70. procesio_cli/dashboard/server/llmtest.py +181 -0
  71. procesio_cli/dashboard/server/procesio_env.py +94 -0
  72. procesio_cli/dashboard/server/routes.py +74 -0
  73. procesio_cli/dashboard/server/runner.py +150 -0
  74. procesio_cli/dashboard/server/security.py +29 -0
  75. procesio_cli/dashboard/server/setup.py +211 -0
  76. procesio_cli/dashboard/server/validate.py +152 -0
  77. procesio_cli/dashboard/tests/conftest.py +16 -0
  78. procesio_cli/dashboard/tests/test_dashboard.py +225 -0
  79. procesio_cli/dashboard/tests/test_procesio_env.py +159 -0
  80. procesio_cli/dashboard/web/actions.js +274 -0
  81. procesio_cli/dashboard/web/api.js +79 -0
  82. procesio_cli/dashboard/web/app.js +385 -0
  83. procesio_cli/dashboard/web/copilot.js +72 -0
  84. procesio_cli/dashboard/web/google.js +121 -0
  85. procesio_cli/dashboard/web/index.html +67 -0
  86. procesio_cli/dashboard/web/procesio_env.js +187 -0
  87. procesio_cli/dashboard/web/styles.css +207 -0
  88. procesio_cli/registry.py +370 -0
  89. procesio_cli/scripts/build-router.py +249 -0
  90. procesio_cli/scripts/build-tool-skill.py +202 -0
  91. procesio_cli/scripts/check-requirements.py +131 -0
  92. procesio_cli/scripts/check-skill-governance.py +174 -0
  93. procesio_cli/scripts/evaluate-skill-routing.py +217 -0
  94. procesio_cli/scripts/evaluate-skills.py +316 -0
  95. procesio_cli/scripts/get-skill.py +122 -0
  96. procesio_cli/scripts/list-skills.py +68 -0
  97. procesio_cli/scripts/list-tools.py +73 -0
  98. procesio_cli/scripts/run-agent.py +59 -0
  99. procesio_cli/scripts/run-tool.py +123 -0
  100. procesio_cli/scripts/secret_scan.py +584 -0
  101. procesio_cli/scripts/set-credential.py +126 -0
  102. procesio_cli/scripts/validate-skills.py +530 -0
  103. procesio_cli/skills/PORTFOLIO.md +134 -0
  104. procesio_cli/skills/__init__.py +6 -0
  105. procesio_cli/skills/agent-skill-engineer/SKILL.md +226 -0
  106. procesio_cli/skills/agent-skill-engineer/evals/evals.json +422 -0
  107. procesio_cli/skills/agent-skill-engineer/references/authoring-standard.md +178 -0
  108. procesio_cli/skills/agent-skill-engineer/references/decision-framework.md +107 -0
  109. procesio_cli/skills/agent-skill-engineer/references/domain-evidence-protocol.md +248 -0
  110. procesio_cli/skills/agent-skill-engineer/references/evaluation-standard.md +191 -0
  111. procesio_cli/skills/agent-skill-engineer/references/expert-lenses.md +167 -0
  112. procesio_cli/skills/agent-skill-engineer/references/field-gate-standard.md +231 -0
  113. procesio_cli/skills/agent-skill-engineer/references/optimization-protocol.md +233 -0
  114. procesio_cli/skills/agent-skill-engineer/references/optimizer-contract.md +268 -0
  115. procesio_cli/skills/agent-skill-engineer/references/portability-standard.md +99 -0
  116. procesio_cli/skills/agent-skill-engineer/references/research-basis.md +128 -0
  117. procesio_cli/skills/agent-skill-engineer/references/review-standard.md +143 -0
  118. procesio_cli/skills/agent-skill-engineer/scripts/audit_skill.py +959 -0
  119. procesio_cli/skills/agent-skill-engineer/scripts/optimize_skill.py +891 -0
  120. procesio_cli/skills/agent-skill-engineer/scripts/scaffold_skill.py +397 -0
  121. procesio_cli/skills/evals/baseline-catalog.json +14 -0
  122. procesio_cli/skills/evals/baseline-routing-v2.json +38 -0
  123. procesio_cli/skills/evals/baseline.json +37 -0
  124. procesio_cli/skills/evals/behavioral.json +367 -0
  125. procesio_cli/skills/evals/field-boundary-development-v1.json +126 -0
  126. procesio_cli/skills/evals/field-closeout-correction-development-v1.json +23 -0
  127. procesio_cli/skills/evals/field-closeout-development-v1.json +89 -0
  128. procesio_cli/skills/evals/portfolio-pressure-training-v1.json +83 -0
  129. procesio_cli/skills/evals/routing.json +363 -0
  130. procesio_cli/skills/evals/validation-baseline.json +4 -0
  131. procesio_cli/skills/procesio-cli/SKILL.md +134 -0
  132. procesio_cli/skills/procesio-cli/evals/evals.json +111 -0
  133. procesio_cli/skills/procesio-cli/evals/workflows.json +16 -0
  134. procesio_cli/skills/procesio-cli/references/connector-lifecycle.md +47 -0
  135. procesio_cli/skills/procesio-cli/references/credentials-admin.md +113 -0
  136. procesio_cli/skills/procesio-cli/references/data-verification.md +71 -0
  137. procesio_cli/skills/procesio-cli/references/documents-files.md +44 -0
  138. procesio_cli/skills/procesio-cli/references/form-e2e.md +57 -0
  139. procesio_cli/skills/procesio-cli/references/operation-contract.md +138 -0
  140. procesio_cli/skills/procesio-cli/references/process-debugging.md +65 -0
  141. procesio_cli/skills/procesio-cli/references/process-lifecycle.md +88 -0
  142. procesio_cli/skills/procesio-cli/references/schedules-webhooks.md +58 -0
  143. procesio_cli/skills/procesio-cli/references/transport-environments.md +53 -0
  144. procesio_cli/skills/procesio-cli-maintainer/SKILL.md +84 -0
  145. procesio_cli/skills/procesio-cli-maintainer/evals/evals.json +116 -0
  146. procesio_cli/skills/procesio-cli-maintainer/references/change-contract.md +34 -0
  147. procesio_cli/skills/procesio-cli-maintainer/references/mcp-compatibility.md +13 -0
  148. procesio_cli/skills/procesio-cli-maintainer/references/skill-authoring.md +51 -0
  149. procesio_cli/skills/procesio-platform-advisor/SKILL.md +82 -0
  150. procesio_cli/skills/procesio-platform-advisor/evals/evals.json +116 -0
  151. procesio_cli/skills/procesio-platform-advisor/references/capability-map.md +22 -0
  152. procesio_cli/skills/procesio-platform-advisor/references/fit-framework.md +35 -0
  153. procesio_cli/skills/procesio-platform-advisor/references/freshness-policy.md +25 -0
  154. procesio_cli/skills/procesio-platform-advisor/references/sizing-method.md +70 -0
  155. procesio_cli/skills/procesio-platform-advisor/scripts/estimate_capacity.py +99 -0
  156. procesio_cli/skills/procesio-setup/SKILL.md +180 -0
  157. procesio_cli/skills/sql-server-optimizer/SKILL.md +86 -0
  158. procesio_cli/skills/sql-server-optimizer/evals/evals.json +116 -0
  159. procesio_cli/skills/sql-server-optimizer/references/db-context.md +44 -0
  160. procesio_cli/skills/sql-server-optimizer/references/generic-mode.md +32 -0
  161. procesio_cli/skills/sql-server-optimizer/references/procesio-mode.md +40 -0
  162. procesio_cli/skills/sql-server-optimizer/references/sql-rules.md +43 -0
  163. procesio_cli/skills/sql-server-optimizer/scripts/export-indexes.sql +178 -0
  164. procesio_cli/skills/sql-server-optimizer/scripts/export-procs-and-functions.sql +118 -0
  165. procesio_cli/skills/sql-server-optimizer/scripts/export-tables.sql +367 -0
  166. procesio_cli/skills/tests/__init__.py +0 -0
  167. procesio_cli/skills/tests/test_agent_skill_engineer.py +240 -0
  168. procesio_cli/skills/tests/test_auth_failure_stop.py +25 -0
  169. procesio_cli/skills/tests/test_capacity_estimator.py +85 -0
  170. procesio_cli/skills/tests/test_field_boundary_learning.py +100 -0
  171. procesio_cli/skills/tests/test_field_closeout_learning.py +123 -0
  172. procesio_cli/skills/tests/test_field_gate_standard.py +76 -0
  173. procesio_cli/skills/tests/test_operational_playbooks.py +51 -0
  174. procesio_cli/skills/tests/test_portfolio_pressure_training.py +35 -0
  175. procesio_cli/skills/tests/test_process_lifecycle_single_run.py +25 -0
  176. procesio_cli/skills/tests/test_skill_evaluator.py +67 -0
  177. procesio_cli/skills/tests/test_skill_optimizer.py +473 -0
  178. procesio_cli/skills/tests/test_skill_portfolio_review.py +70 -0
  179. procesio_cli/skills/tests/test_skill_release_binding.py +204 -0
  180. procesio_cli/skills/tests/test_skill_routing.py +74 -0
  181. procesio_cli/skills/tests/test_skill_scaffold_v2.py +116 -0
  182. procesio_cli/skills/tests/test_validate_skills.py +115 -0
  183. procesio_cli/tools/__init__.py +0 -0
  184. procesio_cli/tools/_lib/README.md +119 -0
  185. procesio_cli/tools/_lib/__init__.py +0 -0
  186. procesio_cli/tools/_lib/accounting.py +120 -0
  187. procesio_cli/tools/_lib/contacts.py +544 -0
  188. procesio_cli/tools/_lib/creds.py +83 -0
  189. procesio_cli/tools/_lib/creds_backends.py +578 -0
  190. procesio_cli/tools/_lib/dbprofiles.py +147 -0
  191. procesio_cli/tools/_lib/dbquery.py +286 -0
  192. procesio_cli/tools/_lib/dbschema.py +225 -0
  193. procesio_cli/tools/_lib/io.py +54 -0
  194. procesio_cli/tools/_lib/manifest.py +297 -0
  195. procesio_cli/tools/_lib/skill_release.py +127 -0
  196. procesio_cli/tools/_lib/skill_resources.py +118 -0
  197. procesio_cli/tools/_lib/userdata.py +228 -0
  198. procesio_cli/tools/_lib/webserialize/__init__.py +16 -0
  199. procesio_cli/tools/_lib/webserialize/admin.py +61 -0
  200. procesio_cli/tools/_lib/webserialize/client.py +264 -0
  201. procesio_cli/tools/_lib/webserialize/config.py +96 -0
  202. procesio_cli/tools/_lib/webserialize/errors.py +27 -0
  203. procesio_cli/tools/_lib/webserialize/framing.py +45 -0
  204. procesio_cli/tools/_lib/webserialize/registry.py +47 -0
  205. procesio_cli/tools/_lib/webserialize/runtime.py +61 -0
  206. procesio_cli/tools/_lib/webserialize/server.py +510 -0
  207. procesio_cli/tools/_lib/websession.py +194 -0
  208. procesio_cli/tools/connector-builder/CONNECTOR-BUILDER-API-NOTES.md +91 -0
  209. procesio_cli/tools/connector-builder/README.md +101 -0
  210. procesio_cli/tools/connector-builder/SKILL.md +248 -0
  211. procesio_cli/tools/connector-builder/__init__.py +7 -0
  212. procesio_cli/tools/connector-builder/actiondef.py +24 -0
  213. procesio_cli/tools/connector-builder/client.py +251 -0
  214. procesio_cli/tools/connector-builder/errors.py +44 -0
  215. procesio_cli/tools/connector-builder/gen_manifest.py +142 -0
  216. procesio_cli/tools/connector-builder/handlers/__init__.py +1 -0
  217. procesio_cli/tools/connector-builder/handlers/admin.py +182 -0
  218. procesio_cli/tools/connector-builder/handlers/auth.py +74 -0
  219. procesio_cli/tools/connector-builder/handlers/builds.py +396 -0
  220. procesio_cli/tools/connector-builder/handlers/common.py +104 -0
  221. procesio_cli/tools/connector-builder/handlers/files.py +111 -0
  222. procesio_cli/tools/connector-builder/handlers/raw.py +38 -0
  223. procesio_cli/tools/connector-builder/handlers/telemetry.py +39 -0
  224. procesio_cli/tools/connector-builder/main.py +94 -0
  225. procesio_cli/tools/connector-builder/tests/conftest.py +146 -0
  226. procesio_cli/tools/connector-builder/tests/test_cb_client.py +86 -0
  227. procesio_cli/tools/connector-builder/tests/test_cb_dispatch.py +105 -0
  228. procesio_cli/tools/connector-builder/tests/test_cb_manifest_sync.py +76 -0
  229. procesio_cli/tools/connector-builder/tests/test_cb_wait.py +123 -0
  230. procesio_cli/tools/connector-builder/tool.yaml +671 -0
  231. procesio_cli/tools/framework-map/.gitattributes +2 -0
  232. procesio_cli/tools/framework-map/README.md +98 -0
  233. procesio_cli/tools/framework-map/SKILL.md +30 -0
  234. procesio_cli/tools/framework-map/app.js +188 -0
  235. procesio_cli/tools/framework-map/fm_builder.py +476 -0
  236. procesio_cli/tools/framework-map/fm_catalog.py +19 -0
  237. procesio_cli/tools/framework-map/fm_ro_catalog.py +94 -0
  238. procesio_cli/tools/framework-map/fm_strings.py +101 -0
  239. procesio_cli/tools/framework-map/main.py +95 -0
  240. procesio_cli/tools/framework-map/styles.css +214 -0
  241. procesio_cli/tools/framework-map/tests/conftest.py +33 -0
  242. procesio_cli/tools/framework-map/tests/test_fm_build.py +126 -0
  243. procesio_cli/tools/framework-map/tests/test_fm_manifest_sync.py +63 -0
  244. procesio_cli/tools/framework-map/tests/test_fm_store_tree.py +75 -0
  245. procesio_cli/tools/framework-map/tool.yaml +61 -0
  246. procesio_cli/tools/mysql/MOVED-TO-CENTRAL.md +29 -0
  247. procesio_cli/tools/mysql/README.md +196 -0
  248. procesio_cli/tools/mysql/SKILL.md +37 -0
  249. procesio_cli/tools/mysql/__init__.py +0 -0
  250. procesio_cli/tools/mysql/connection.py +191 -0
  251. procesio_cli/tools/mysql/main.py +487 -0
  252. procesio_cli/tools/mysql/profiles.template.json +1 -0
  253. procesio_cli/tools/mysql/schema.py +434 -0
  254. procesio_cli/tools/mysql/tests/__init__.py +0 -0
  255. procesio_cli/tools/mysql/tests/conftest.py +21 -0
  256. procesio_cli/tools/mysql/tests/test_manifest_sync.py +68 -0
  257. procesio_cli/tools/mysql/tests/test_mysql.py +201 -0
  258. procesio_cli/tools/mysql/tests/test_mysql_schema.py +337 -0
  259. procesio_cli/tools/mysql/tool.yaml +112 -0
  260. procesio_cli/tools/procesio/DTO-SUBTOOLS-NOTE.md +360 -0
  261. procesio_cli/tools/procesio/FORM-DEV-GUIDE/00-INDEX.md +67 -0
  262. procesio_cli/tools/procesio/FORM-DEV-GUIDE/01-ANATOMY.md +209 -0
  263. procesio_cli/tools/procesio/FORM-DEV-GUIDE/02-CODE-INJECTION.md +309 -0
  264. procesio_cli/tools/procesio/FORM-DEV-GUIDE/03-DOM-CONTRACT.md +370 -0
  265. procesio_cli/tools/procesio/FORM-DEV-GUIDE/04-INTERACTION-RECIPES.md +600 -0
  266. procesio_cli/tools/procesio/FORM-DEV-GUIDE/05-STEPPER-AND-MOTION.md +426 -0
  267. procesio_cli/tools/procesio/FORM-DEV-GUIDE/06-PROCESS-INTEGRATION.md +347 -0
  268. procesio_cli/tools/procesio/FORM-DEV-GUIDE/07-DEPLOY-WORKFLOW.md +294 -0
  269. procesio_cli/tools/procesio/FORM-DEV-GUIDE/08-PITFALLS.md +569 -0
  270. procesio_cli/tools/procesio/FORM-DEV-GUIDE/09-NAVIGATION-AND-HOSTING.md +308 -0
  271. procesio_cli/tools/procesio/PHASE4-E2E-NOTES.md +594 -0
  272. procesio_cli/tools/procesio/PROCESIO-API-CORRECTIONS.md +295 -0
  273. procesio_cli/tools/procesio/PROCESIO-API-NOTES.md +6976 -0
  274. procesio_cli/tools/procesio/PROCESIO-AUTH-NOTES.md +226 -0
  275. procesio_cli/tools/procesio/PROCESIO-CARD-BUILD-NOTES.md +183 -0
  276. procesio_cli/tools/procesio/PROCESIO-CONNECTORS-NOTES.md +141 -0
  277. procesio_cli/tools/procesio/PROCESIO-CUSTOM-ACTION-NOTES.md +179 -0
  278. procesio_cli/tools/procesio/PROCESIO-DOCS-FIX-REPORT.md +123 -0
  279. procesio_cli/tools/procesio/PROCESIO-ENVIRONMENTS-NOTES.md +94 -0
  280. procesio_cli/tools/procesio/PROCESIO-FE-VALIDATION-NOTES.md +107 -0
  281. procesio_cli/tools/procesio/PROCESIO-FOREACH-NOTES.md +199 -0
  282. procesio_cli/tools/procesio/PROCESIO-FORM-API-HANG-NOTE.md +101 -0
  283. procesio_cli/tools/procesio/PROCESIO-FORM-SUBMISSION-NOTES.md +172 -0
  284. procesio_cli/tools/procesio/PROCESIO-HTML-TO-DOCX-NOTES.md +110 -0
  285. procesio_cli/tools/procesio/PROCESIO-INSTANCE-HISTORY-NOTES.md +242 -0
  286. procesio_cli/tools/procesio/PROCESIO-METERING-NOTES.md +201 -0
  287. procesio_cli/tools/procesio/PROCESIO-NODE-CODE-NOTES.md +279 -0
  288. procesio_cli/tools/procesio/PROCESIO-NODE-MODULE-WHITELIST.md +113 -0
  289. procesio_cli/tools/procesio/PROCESIO-PYTHON-ACTION-NOTES.md +165 -0
  290. procesio_cli/tools/procesio/PROCESIO-QUERY-STORE-NOTES.md +826 -0
  291. procesio_cli/tools/procesio/PROCESIO-RECONCILIATION-PATTERNS.md +104 -0
  292. procesio_cli/tools/procesio/PROCESIO-RESOURCE-MODEL-NOTES.md +1100 -0
  293. procesio_cli/tools/procesio/PROCESIO-SEND-EMAIL-CRED-OPTIONALITY.md +45 -0
  294. procesio_cli/tools/procesio/PROCESIO-SEND-EMAIL-NOTES.md +252 -0
  295. procesio_cli/tools/procesio/PROCESIO-SQL-ACTIONS-NOTES.md +284 -0
  296. procesio_cli/tools/procesio/PROCESIO-USAGE-GUIDE.md +219 -0
  297. procesio_cli/tools/procesio/README.md +341 -0
  298. procesio_cli/tools/procesio/SCHEDULE-INPUT-SECURITY-NOTES.md +63 -0
  299. procesio_cli/tools/procesio/SKILL.md +694 -0
  300. procesio_cli/tools/procesio/__init__.py +1 -0
  301. procesio_cli/tools/procesio/actiondef.py +27 -0
  302. procesio_cli/tools/procesio/assets/html2docx/html2docx.js +2155 -0
  303. procesio_cli/tools/procesio/auth.py +354 -0
  304. procesio_cli/tools/procesio/autogen.py +121 -0
  305. procesio_cli/tools/procesio/client.py +355 -0
  306. procesio_cli/tools/procesio/config.py +96 -0
  307. procesio_cli/tools/procesio/data/endpoints.json +2901 -0
  308. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/01-authentication.md +180 -0
  309. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/02-conventions.md +116 -0
  310. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/ANONYMOUS-SURFACE.md +51 -0
  311. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/README.md +104 -0
  312. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/SYMBOL-INDEX.md +444 -0
  313. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/03-auth-users-workspaces.md +731 -0
  314. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/04-processes.md +913 -0
  315. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/05-actions.md +605 -0
  316. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/06-files.md +237 -0
  317. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/07-forms-documents.md +985 -0
  318. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/08-credentials.md +532 -0
  319. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/09-datastore-datatypes.md +830 -0
  320. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/10-custom-urls.md +319 -0
  321. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/11-subscriptions-resources-analytics.md +455 -0
  322. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/12-webhooks-notifications-misc.md +519 -0
  323. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/13-realtime-signalr.md +153 -0
  324. procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/99-internal-and-other-services.md +52 -0
  325. procesio_cli/tools/procesio/docs_info/FormBuilder mock.ts +5725 -0
  326. procesio_cli/tools/procesio/docs_info/Procesio Platform Actions.json +30219 -0
  327. procesio_cli/tools/procesio/docs_info/form-config-enums.ts +92 -0
  328. procesio_cli/tools/procesio/docs_info/form-theme.ts +1603 -0
  329. procesio_cli/tools/procesio/docs_info/process-validation-reference-1.md +2739 -0
  330. procesio_cli/tools/procesio/dto/__init__.py +13 -0
  331. procesio_cli/tools/procesio/dto/credential/builder.py +179 -0
  332. procesio_cli/tools/procesio/dto/credential/config.schema.json +19 -0
  333. procesio_cli/tools/procesio/dto/credential/description.md +193 -0
  334. procesio_cli/tools/procesio/dto/credential/fixtures/rest.config.json +11 -0
  335. procesio_cli/tools/procesio/dto/credential/fixtures/rest.dto.json +28 -0
  336. procesio_cli/tools/procesio/dto/credential/fixtures/rest.template.json +44 -0
  337. procesio_cli/tools/procesio/dto/credential/template.dto.json +16 -0
  338. procesio_cli/tools/procesio/dto/data/_refresh.py +147 -0
  339. procesio_cli/tools/procesio/dto/data/action_catalog.json +30825 -0
  340. procesio_cli/tools/procesio/dto/data/action_families.json +55 -0
  341. procesio_cli/tools/procesio/dto/data/platform_types.json +182 -0
  342. procesio_cli/tools/procesio/dto/datatype/builder.py +223 -0
  343. procesio_cli/tools/procesio/dto/datatype/config.schema.json +47 -0
  344. procesio_cli/tools/procesio/dto/datatype/description.md +85 -0
  345. procesio_cli/tools/procesio/dto/datatype/fixtures/customer.config.json +41 -0
  346. procesio_cli/tools/procesio/dto/datatype/fixtures/customer.dto.json +164 -0
  347. procesio_cli/tools/procesio/dto/datatype/template.dto.json +37 -0
  348. procesio_cli/tools/procesio/dto/document/builder.py +193 -0
  349. procesio_cli/tools/procesio/dto/document/config.schema.json +33 -0
  350. procesio_cli/tools/procesio/dto/document/description.md +124 -0
  351. procesio_cli/tools/procesio/dto/document/fixtures/invoice.config.json +16 -0
  352. procesio_cli/tools/procesio/dto/document/fixtures/invoice.dto.json +32 -0
  353. procesio_cli/tools/procesio/dto/document/template.dto.json +14 -0
  354. procesio_cli/tools/procesio/dto/form/FORM-STYLING-NOTES.md +142 -0
  355. procesio_cli/tools/procesio/dto/form/GOLDEN-DRIFT-NOTES.md +72 -0
  356. procesio_cli/tools/procesio/dto/form/RUN-PROCESS-RESULT-NOTES.md +80 -0
  357. procesio_cli/tools/procesio/dto/form/addelement.py +277 -0
  358. procesio_cli/tools/procesio/dto/form/builder.py +1009 -0
  359. procesio_cli/tools/procesio/dto/form/capture_goldens.py +106 -0
  360. procesio_cli/tools/procesio/dto/form/code_cipher.py +67 -0
  361. procesio_cli/tools/procesio/dto/form/config.schema.json +76 -0
  362. procesio_cli/tools/procesio/dto/form/data_shell.json +1 -0
  363. procesio_cli/tools/procesio/dto/form/description.md +346 -0
  364. procesio_cli/tools/procesio/dto/form/elements/approval.json +259 -0
  365. procesio_cli/tools/procesio/dto/form/elements/assignee.json +137 -0
  366. procesio_cli/tools/procesio/dto/form/elements/button.json +125 -0
  367. procesio_cli/tools/procesio/dto/form/elements/chart.json +341 -0
  368. procesio_cli/tools/procesio/dto/form/elements/chat.json +199 -0
  369. procesio_cli/tools/procesio/dto/form/elements/checkbox.json +153 -0
  370. procesio_cli/tools/procesio/dto/form/elements/column.json +77 -0
  371. procesio_cli/tools/procesio/dto/form/elements/columns.json +78 -0
  372. procesio_cli/tools/procesio/dto/form/elements/datetime-input.json +304 -0
  373. procesio_cli/tools/procesio/dto/form/elements/divider.json +54 -0
  374. procesio_cli/tools/procesio/dto/form/elements/dropdown.json +130 -0
  375. procesio_cli/tools/procesio/dto/form/elements/dynamic-table-row.json +253 -0
  376. procesio_cli/tools/procesio/dto/form/elements/file-upload.json +196 -0
  377. procesio_cli/tools/procesio/dto/form/elements/file-viewer.json +169 -0
  378. procesio_cli/tools/procesio/dto/form/elements/heading.json +79 -0
  379. procesio_cli/tools/procesio/dto/form/elements/icon.json +107 -0
  380. procesio_cli/tools/procesio/dto/form/elements/image.json +174 -0
  381. procesio_cli/tools/procesio/dto/form/elements/input.json +206 -0
  382. procesio_cli/tools/procesio/dto/form/elements/list.json +202 -0
  383. procesio_cli/tools/procesio/dto/form/elements/number-input.json +202 -0
  384. procesio_cli/tools/procesio/dto/form/elements/paragraph.json +79 -0
  385. procesio_cli/tools/procesio/dto/form/elements/radiobox.json +174 -0
  386. procesio_cli/tools/procesio/dto/form/elements/section.json +114 -0
  387. procesio_cli/tools/procesio/dto/form/elements/select.json +217 -0
  388. procesio_cli/tools/procesio/dto/form/elements/side-panel.json +146 -0
  389. procesio_cli/tools/procesio/dto/form/elements/signature-pad.json +141 -0
  390. procesio_cli/tools/procesio/dto/form/elements/static-table-row.json +84 -0
  391. procesio_cli/tools/procesio/dto/form/elements/step.json +105 -0
  392. procesio_cli/tools/procesio/dto/form/elements/stepper.json +126 -0
  393. procesio_cli/tools/procesio/dto/form/elements/tab.json +77 -0
  394. procesio_cli/tools/procesio/dto/form/elements/table.json +279 -0
  395. procesio_cli/tools/procesio/dto/form/elements/tabs.json +114 -0
  396. procesio_cli/tools/procesio/dto/form/elements/textarea.json +213 -0
  397. procesio_cli/tools/procesio/dto/form/enum_map.json +68 -0
  398. procesio_cli/tools/procesio/dto/form/fieldpath.py +284 -0
  399. procesio_cli/tools/procesio/dto/form/fixtures/reg.config.json +32 -0
  400. procesio_cli/tools/procesio/dto/form/fixtures/reg.elements.json +638 -0
  401. procesio_cli/tools/procesio/dto/form/sync_from_mock.py +386 -0
  402. procesio_cli/tools/procesio/dto/framework.py +125 -0
  403. procesio_cli/tools/procesio/dto/process/builder.py +1991 -0
  404. procesio_cli/tools/procesio/dto/process/config.schema.json +159 -0
  405. procesio_cli/tools/procesio/dto/process/description.md +218 -0
  406. procesio_cli/tools/procesio/dto/process/fixtures/greet.config.json +32 -0
  407. procesio_cli/tools/procesio/dto/process/fixtures/greet.dto.json +317 -0
  408. procesio_cli/tools/procesio/dto/process/naming.py +305 -0
  409. procesio_cli/tools/procesio/dto/process/normalize.py +74 -0
  410. procesio_cli/tools/procesio/dto/process/template.dto.json +32 -0
  411. procesio_cli/tools/procesio/dto/refdata.py +87 -0
  412. procesio_cli/tools/procesio/dto/registry.py +27 -0
  413. procesio_cli/tools/procesio/dto/webhook/builder.py +142 -0
  414. procesio_cli/tools/procesio/dto/webhook/config.schema.json +25 -0
  415. procesio_cli/tools/procesio/dto/webhook/description.md +92 -0
  416. procesio_cli/tools/procesio/dto/webhook/fixtures/order.config.json +10 -0
  417. procesio_cli/tools/procesio/dto/webhook/fixtures/order.dto.json +25 -0
  418. procesio_cli/tools/procesio/dto/webhook/template.dto.json +14 -0
  419. procesio_cli/tools/procesio/environments.py +264 -0
  420. procesio_cli/tools/procesio/errors.py +166 -0
  421. procesio_cli/tools/procesio/flowmodel/README.md +37 -0
  422. procesio_cli/tools/procesio/flowmodel/__init__.py +14 -0
  423. procesio_cli/tools/procesio/flowmodel/digest.py +223 -0
  424. procesio_cli/tools/procesio/flowmodel/families.py +34 -0
  425. procesio_cli/tools/procesio/flowmodel/fevalidation.py +1098 -0
  426. procesio_cli/tools/procesio/flowmodel/formdigest.py +232 -0
  427. procesio_cli/tools/procesio/flowmodel/inspect.py +78 -0
  428. procesio_cli/tools/procesio/flowmodel/model.py +82 -0
  429. procesio_cli/tools/procesio/flowmodel/nodeparam.py +871 -0
  430. procesio_cli/tools/procesio/flowmodel/reader.py +191 -0
  431. procesio_cli/tools/procesio/flowmodel/sqlparam.py +410 -0
  432. procesio_cli/tools/procesio/form_code_keys.py +50 -0
  433. procesio_cli/tools/procesio/formlint.py +292 -0
  434. procesio_cli/tools/procesio/gen_manifest.py +118 -0
  435. procesio_cli/tools/procesio/handlers/__init__.py +1 -0
  436. procesio_cli/tools/procesio/handlers/auth_actions.py +103 -0
  437. procesio_cli/tools/procesio/handlers/catalog.py +147 -0
  438. procesio_cli/tools/procesio/handlers/common.py +90 -0
  439. procesio_cli/tools/procesio/handlers/customaction_ops.py +146 -0
  440. procesio_cli/tools/procesio/handlers/datastore_ops.py +482 -0
  441. procesio_cli/tools/procesio/handlers/datatype_ops.py +275 -0
  442. procesio_cli/tools/procesio/handlers/dto_actions.py +258 -0
  443. procesio_cli/tools/procesio/handlers/environment_admin.py +146 -0
  444. procesio_cli/tools/procesio/handlers/fevalidate.py +236 -0
  445. procesio_cli/tools/procesio/handlers/files.py +141 -0
  446. procesio_cli/tools/procesio/handlers/flowgraph.py +229 -0
  447. procesio_cli/tools/procesio/handlers/flowlint.py +269 -0
  448. procesio_cli/tools/procesio/handlers/form_chains.py +336 -0
  449. procesio_cli/tools/procesio/handlers/form_code.py +277 -0
  450. procesio_cli/tools/procesio/handlers/form_element.py +199 -0
  451. procesio_cli/tools/procesio/handlers/form_events.py +441 -0
  452. procesio_cli/tools/procesio/handlers/form_update.py +270 -0
  453. procesio_cli/tools/procesio/handlers/formprocess.py +211 -0
  454. procesio_cli/tools/procesio/handlers/generic.py +135 -0
  455. procesio_cli/tools/procesio/handlers/layout.py +137 -0
  456. procesio_cli/tools/procesio/handlers/nodeparams.py +1018 -0
  457. procesio_cli/tools/procesio/handlers/process_layout.py +189 -0
  458. procesio_cli/tools/procesio/handlers/process_naming.py +213 -0
  459. procesio_cli/tools/procesio/handlers/processes.py +416 -0
  460. procesio_cli/tools/procesio/handlers/profile_admin.py +184 -0
  461. procesio_cli/tools/procesio/handlers/resource_ops.py +447 -0
  462. procesio_cli/tools/procesio/handlers/schedules.py +320 -0
  463. procesio_cli/tools/procesio/handlers/sqlactions.py +232 -0
  464. procesio_cli/tools/procesio/handlers/transport.py +463 -0
  465. procesio_cli/tools/procesio/handlers/usageguide.py +222 -0
  466. procesio_cli/tools/procesio/layout/README.md +196 -0
  467. procesio_cli/tools/procesio/layout/__init__.py +11 -0
  468. procesio_cli/tools/procesio/layout/adapter.py +736 -0
  469. procesio_cli/tools/procesio/layout/cycles.py +186 -0
  470. procesio_cli/tools/procesio/layout/dispatch.py +39 -0
  471. procesio_cli/tools/procesio/layout/elk/elk_runner.mjs +50 -0
  472. procesio_cli/tools/procesio/layout/elk/package-lock.json +21 -0
  473. procesio_cli/tools/procesio/layout/elk/package.json +11 -0
  474. procesio_cli/tools/procesio/layout/elk_engine.py +568 -0
  475. procesio_cli/tools/procesio/layout/engine.py +1311 -0
  476. procesio_cli/tools/procesio/layout/report.py +187 -0
  477. procesio_cli/tools/procesio/main.py +179 -0
  478. procesio_cli/tools/procesio/profiles.py +193 -0
  479. procesio_cli/tools/procesio/reliability.py +193 -0
  480. procesio_cli/tools/procesio/sqlformat.py +167 -0
  481. procesio_cli/tools/procesio/swagger.py +51 -0
  482. procesio_cli/tools/procesio/tests/__init__.py +0 -0
  483. procesio_cli/tools/procesio/tests/conftest.py +129 -0
  484. procesio_cli/tools/procesio/tests/test_auth.py +200 -0
  485. procesio_cli/tools/procesio/tests/test_autogen.py +78 -0
  486. procesio_cli/tools/procesio/tests/test_builder_attrpath_sqlbind.py +180 -0
  487. procesio_cli/tools/procesio/tests/test_builder_custom_action_catalog.py +72 -0
  488. procesio_cli/tools/procesio/tests/test_client.py +114 -0
  489. procesio_cli/tools/procesio/tests/test_client_reliability.py +139 -0
  490. procesio_cli/tools/procesio/tests/test_customaction.py +103 -0
  491. procesio_cli/tools/procesio/tests/test_customaction_test.py +80 -0
  492. procesio_cli/tools/procesio/tests/test_cycles.py +69 -0
  493. procesio_cli/tools/procesio/tests/test_datastore.py +378 -0
  494. procesio_cli/tools/procesio/tests/test_datastore_node.py +94 -0
  495. procesio_cli/tools/procesio/tests/test_dispatch.py +170 -0
  496. procesio_cli/tools/procesio/tests/test_document_page_size.py +19 -0
  497. procesio_cli/tools/procesio/tests/test_dto.py +734 -0
  498. procesio_cli/tools/procesio/tests/test_edit_dryrun_preview.py +96 -0
  499. procesio_cli/tools/procesio/tests/test_elk_layout.py +221 -0
  500. procesio_cli/tools/procesio/tests/test_environments.py +214 -0
  501. procesio_cli/tools/procesio/tests/test_fevalidate_gate.py +104 -0
  502. procesio_cli/tools/procesio/tests/test_fevalidation.py +317 -0
  503. procesio_cli/tools/procesio/tests/test_fieldpath_variables.py +87 -0
  504. procesio_cli/tools/procesio/tests/test_files.py +72 -0
  505. procesio_cli/tools/procesio/tests/test_flow_digest.py +202 -0
  506. procesio_cli/tools/procesio/tests/test_flowlint.py +114 -0
  507. procesio_cli/tools/procesio/tests/test_flowmodel.py +158 -0
  508. procesio_cli/tools/procesio/tests/test_form_add_element.py +256 -0
  509. procesio_cli/tools/procesio/tests/test_form_approval_gaps.py +319 -0
  510. procesio_cli/tools/procesio/tests/test_form_auto_names.py +60 -0
  511. procesio_cli/tools/procesio/tests/test_form_chains.py +217 -0
  512. procesio_cli/tools/procesio/tests/test_form_code_actions.py +188 -0
  513. procesio_cli/tools/procesio/tests/test_form_code_keys.py +92 -0
  514. procesio_cli/tools/procesio/tests/test_form_code_style.py +178 -0
  515. procesio_cli/tools/procesio/tests/test_form_delete_element.py +99 -0
  516. procesio_cli/tools/procesio/tests/test_form_element_config.py +141 -0
  517. procesio_cli/tools/procesio/tests/test_form_event_maps.py +135 -0
  518. procesio_cli/tools/procesio/tests/test_form_event_ops.py +376 -0
  519. procesio_cli/tools/procesio/tests/test_form_event_replace.py +86 -0
  520. procesio_cli/tools/procesio/tests/test_form_events.py +92 -0
  521. procesio_cli/tools/procesio/tests/test_form_parity.py +171 -0
  522. procesio_cli/tools/procesio/tests/test_form_select_source.py +74 -0
  523. procesio_cli/tools/procesio/tests/test_form_sync.py +35 -0
  524. procesio_cli/tools/procesio/tests/test_form_update.py +133 -0
  525. procesio_cli/tools/procesio/tests/test_formlint.py +155 -0
  526. procesio_cli/tools/procesio/tests/test_formlint_css_scope.py +38 -0
  527. procesio_cli/tools/procesio/tests/test_formlint_field_names.py +83 -0
  528. procesio_cli/tools/procesio/tests/test_guards.py +110 -0
  529. procesio_cli/tools/procesio/tests/test_hidden_setting_types.py +39 -0
  530. procesio_cli/tools/procesio/tests/test_html2docx_asset.py +132 -0
  531. procesio_cli/tools/procesio/tests/test_key_handle_terminology.py +45 -0
  532. procesio_cli/tools/procesio/tests/test_layout.py +574 -0
  533. procesio_cli/tools/procesio/tests/test_layout_report.py +88 -0
  534. procesio_cli/tools/procesio/tests/test_manifest_sync.py +68 -0
  535. procesio_cli/tools/procesio/tests/test_nodedelete.py +151 -0
  536. procesio_cli/tools/procesio/tests/test_nodeparam.py +826 -0
  537. procesio_cli/tools/procesio/tests/test_normalize.py +70 -0
  538. procesio_cli/tools/procesio/tests/test_payload_docs.py +86 -0
  539. procesio_cli/tools/procesio/tests/test_process_action_naming.py +170 -0
  540. procesio_cli/tools/procesio/tests/test_process_call_api_seed.py +46 -0
  541. procesio_cli/tools/procesio/tests/test_process_ext.py +373 -0
  542. procesio_cli/tools/procesio/tests/test_process_invariants.py +183 -0
  543. procesio_cli/tools/procesio/tests/test_process_layout.py +117 -0
  544. procesio_cli/tools/procesio/tests/test_process_naming.py +188 -0
  545. procesio_cli/tools/procesio/tests/test_process_query_store.py +172 -0
  546. procesio_cli/tools/procesio/tests/test_processinfo_variable.py +56 -0
  547. procesio_cli/tools/procesio/tests/test_profiles.py +127 -0
  548. procesio_cli/tools/procesio/tests/test_put_projects.py +41 -0
  549. procesio_cli/tools/procesio/tests/test_reliability.py +107 -0
  550. procesio_cli/tools/procesio/tests/test_removal_gate.py +95 -0
  551. procesio_cli/tools/procesio/tests/test_resource_ops.py +173 -0
  552. procesio_cli/tools/procesio/tests/test_save_validity.py +207 -0
  553. procesio_cli/tools/procesio/tests/test_schedule_cron_shape.py +111 -0
  554. procesio_cli/tools/procesio/tests/test_schedules.py +309 -0
  555. procesio_cli/tools/procesio/tests/test_sqlconvert.py +234 -0
  556. procesio_cli/tools/procesio/tests/test_sqlformat.py +83 -0
  557. procesio_cli/tools/procesio/tests/test_sqlparam.py +187 -0
  558. procesio_cli/tools/procesio/tests/test_superseded_actions.py +54 -0
  559. procesio_cli/tools/procesio/tests/test_surgical_concurrency.py +156 -0
  560. procesio_cli/tools/procesio/tests/test_tier4.py +123 -0
  561. procesio_cli/tools/procesio/tests/test_transport.py +147 -0
  562. procesio_cli/tools/procesio/tests/test_transport_data_stores.py +168 -0
  563. procesio_cli/tools/procesio/tests/test_transport_mapper_repair.py +150 -0
  564. procesio_cli/tools/procesio/tests/test_transport_webhook_strip.py +253 -0
  565. procesio_cli/tools/procesio/tests/test_usage_guide.py +96 -0
  566. procesio_cli/tools/procesio/tool.yaml +12486 -0
  567. procesio_cli/tools/sqlserver/MOVED-TO-CENTRAL.md +29 -0
  568. procesio_cli/tools/sqlserver/README.md +192 -0
  569. procesio_cli/tools/sqlserver/SKILL.md +37 -0
  570. procesio_cli/tools/sqlserver/SQLSERVER-SCHEMA-NOTES.md +284 -0
  571. procesio_cli/tools/sqlserver/__init__.py +0 -0
  572. procesio_cli/tools/sqlserver/connection.py +242 -0
  573. procesio_cli/tools/sqlserver/main.py +521 -0
  574. procesio_cli/tools/sqlserver/profiles.template.json +1 -0
  575. procesio_cli/tools/sqlserver/schema.py +600 -0
  576. procesio_cli/tools/sqlserver/tests/__init__.py +0 -0
  577. procesio_cli/tools/sqlserver/tests/conftest.py +21 -0
  578. procesio_cli/tools/sqlserver/tests/test_connection.py +75 -0
  579. procesio_cli/tools/sqlserver/tests/test_dbprofiles.py +92 -0
  580. procesio_cli/tools/sqlserver/tests/test_dbquery.py +220 -0
  581. procesio_cli/tools/sqlserver/tests/test_manifest_sync.py +68 -0
  582. procesio_cli/tools/sqlserver/tests/test_sqlserver.py +326 -0
  583. procesio_cli/tools/sqlserver/tests/test_sqlserver_schema.py +488 -0
  584. procesio_cli/tools/sqlserver/tool.yaml +123 -0
  585. procesio_cli/tools/web/README.md +381 -0
  586. procesio_cli/tools/web/SKILL.md +37 -0
  587. procesio_cli/tools/web/WEB-AUTOMATION-NOTES.md +277 -0
  588. procesio_cli/tools/web/__init__.py +21 -0
  589. procesio_cli/tools/web/actiondef.py +24 -0
  590. procesio_cli/tools/web/api.py +444 -0
  591. procesio_cli/tools/web/brokered.py +107 -0
  592. procesio_cli/tools/web/driver.py +839 -0
  593. procesio_cli/tools/web/drivercontract.py +44 -0
  594. procesio_cli/tools/web/errors.py +54 -0
  595. procesio_cli/tools/web/handlers/__init__.py +1 -0
  596. procesio_cli/tools/web/handlers/archive.py +367 -0
  597. procesio_cli/tools/web/handlers/browse.py +182 -0
  598. procesio_cli/tools/web/handlers/contacts.py +74 -0
  599. procesio_cli/tools/web/handlers/sessmgmt.py +120 -0
  600. procesio_cli/tools/web/main.py +128 -0
  601. procesio_cli/tools/web/sessions.py +253 -0
  602. procesio_cli/tools/web/steps.py +162 -0
  603. procesio_cli/tools/web/tests/__init__.py +0 -0
  604. procesio_cli/tools/web/tests/conftest.py +197 -0
  605. procesio_cli/tools/web/tests/test_api.py +330 -0
  606. procesio_cli/tools/web/tests/test_archive.py +190 -0
  607. procesio_cli/tools/web/tests/test_brokered.py +138 -0
  608. procesio_cli/tools/web/tests/test_dispatch.py +132 -0
  609. procesio_cli/tools/web/tests/test_errors.py +60 -0
  610. procesio_cli/tools/web/tests/test_manifest_sync.py +76 -0
  611. procesio_cli/tools/web/tests/test_page_contacts.py +68 -0
  612. procesio_cli/tools/web/tests/test_sessions.py +145 -0
  613. procesio_cli/tools/web/tests/test_steps.py +210 -0
  614. procesio_cli/tools/web/tool.yaml +184 -0
  615. procesio_cli/tools/xlsx/README.md +34 -0
  616. procesio_cli/tools/xlsx/SKILL.md +28 -0
  617. procesio_cli/tools/xlsx/__init__.py +0 -0
  618. procesio_cli/tools/xlsx/main.py +129 -0
  619. procesio_cli/tools/xlsx/reader.py +91 -0
  620. procesio_cli/tools/xlsx/tests/__init__.py +0 -0
  621. procesio_cli/tools/xlsx/tests/conftest.py +39 -0
  622. procesio_cli/tools/xlsx/tests/test_xlsx.py +103 -0
  623. procesio_cli/tools/xlsx/tool.yaml +30 -0
  624. procesio_cli/webplatform/aat_mcp/AAT-MCP-NOTES.md +244 -0
  625. procesio_cli/webplatform/aat_mcp/README.md +78 -0
  626. procesio_cli/webplatform/aat_mcp/bridge.py +705 -0
  627. procesio_cli/webplatform/aat_mcp/chat_server.py +194 -0
  628. procesio_cli/webplatform/aat_mcp/chat_surface.snapshot.json +668 -0
  629. procesio_cli/webplatform/aat_mcp/chat_surface.yaml +372 -0
  630. procesio_cli/webplatform/aat_mcp/gate.py +68 -0
  631. procesio_cli/webplatform/aat_mcp/http_server.py +139 -0
  632. procesio_cli/webplatform/aat_mcp/protocol.py +175 -0
  633. procesio_cli/webplatform/aat_mcp/server.py +306 -0
  634. procesio_cli/webplatform/aat_mcp/tests/conftest.py +8 -0
  635. procesio_cli/webplatform/aat_mcp/tests/test_aat_mcp.py +255 -0
  636. procesio_cli/webplatform/aat_mcp/tests/test_chat_surface.py +247 -0
  637. procesio_cli/webplatform/aat_mcp/tests/test_discovery.py +489 -0
  638. procesio_cli/webplatform/aat_mcp/tests/test_multiuser.py +86 -0
  639. procesio_cli/webplatform/aat_mcp/tests/test_progressive_discovery.py +89 -0
  640. procesio_cli/webplatform/aat_mcp/tests/test_protocol.py +180 -0
  641. procesio_cli/webplatform/aat_mcp/tests/test_skill_discovery.py +23 -0
  642. procesio_cli-0.1.1.dist-info/METADATA +467 -0
  643. procesio_cli-0.1.1.dist-info/RECORD +647 -0
  644. procesio_cli-0.1.1.dist-info/WHEEL +4 -0
  645. procesio_cli-0.1.1.dist-info/entry_points.txt +7 -0
  646. procesio_cli-0.1.1.dist-info/licenses/LICENSE +201 -0
  647. procesio_cli-0.1.1.dist-info/licenses/NOTICE +5 -0
@@ -0,0 +1,826 @@
1
+ # PROCESIO Query Store action - authoring & usage contract
2
+
3
+ The **Query Store** action (`DataStoreQueryAction`, actionId `02577ada-0000-0200-0000-00000000b001`,
4
+ config tab "Configure Query") runs **one SQL statement, or a `;`-separated script of several**
5
+ (multi-statement since v3.0.0, 2026-10; see "Multi-statement scripts" below) against the
6
+ workspace's Data Stores as a single process step. Data Stores are MySQL-backed, so the dialect is **MySQL**.
7
+ There is no credential, connection string or external database. It does both **reads and
8
+ writes** (see the accepted-statements matrix below).
9
+
10
+ Two different datastore actions, do not confuse them:
11
+ - **Data Store action** (the `DataStoreConnector`, TemplateId `02577ada-0000-0100-0000-00000000a001`):
12
+ point-and-click CRUD on ONE store (SelectAll/SelectWhere/Insert/Update/Delete), no SQL.
13
+ - **Query Store** (this note): SQL (one statement or a `;`-separated script), for filtering,
14
+ joins, totals/reports, and bulk writes across a condition.
15
+ - **Execute Query** is unrelated: it targets your own external Microsoft SQL Server.
16
+
17
+ Sources: docs.procesio.com/query-store (read 2026-09-22), a real process export (the node
18
+ encoding below), and a QA behaviour matrix (accepted = run status 50, refused = status 40).
19
+ Where they disagree, the matrix + platform owner win.
20
+
21
+ ## Availability
22
+ Runs in **personal AND organisation** workspaces. (The public doc still says organisation-only,
23
+ "not available on a personal workspace"; that line is STALE as of 2026-09-22.)
24
+ Personal (no-workspace) Data Stores live in their own MySQL database named
25
+ `dsi_<base64 of the user id>`, and since v3.0.0 (2026-10) saving, validating, duplicating and
26
+ testing a Query Store node on a personal workspace no longer returns error 1008.
27
+
28
+ ## I/O (as the catalog serves it)
29
+ - Input `Query` - code-editor, TextFormat `datastore` (chips, see encoding below).
30
+ - Input `Parameters` - map-parameters (`@name` -> value/variable), **bound as data** (safe).
31
+ - Input `Time Out` - seconds, **60..1800, default 1800**; out of range is refused at validate.
32
+ It applies per row-returning statement, not to the whole script (v3.0.0).
33
+ - Output `Result Rows` - a list of JSON objects, one per row; keys are the column names (or
34
+ your `AS` aliases). **Empty for INSERT/UPDATE/DELETE/REPLACE.** Map to a **list of Json**.
35
+ - Output `Total Count` - for a SELECT, rows returned; for INSERT/UPDATE/DELETE/REPLACE, rows
36
+ affected (REPLACE that overwrites reports 2 = delete+insert). Map to an **Integer**.
37
+ In a multi-statement script the affected-rows count covers the whole script (v3.0.0).
38
+ - **No row limit.** A SELECT with no WHERE/LIMIT returns the whole table; add `LIMIT`.
39
+
40
+ ## Saved node encoding (verified from a real export) - what a builder must emit
41
+ The node's `Parameters` is a list of property objects `{TabPropertyId, Variable[], Value}`, one
42
+ per property of the action template `...b001`. Every Data Store / column / process-variable
43
+ reference uses PROCESIO's standard **`<%N%>` placeholder** inside `Value`, resolved by the
44
+ `Variable[]` entry whose `id == N`. Chip `id`s are one sequence across the whole node.
45
+
46
+ - `...b102` **Query**: `Value` is the SQL with chip placeholders, e.g.
47
+ `"select * from <%0%>\nwhere <%1%> <= @threshold"`. `Variable[]` holds the chips:
48
+ - table chip: `{"id":0,"type":"dataStoreTable","variableId":null,"dataStoreId":"<store guid>","attribute":null}`
49
+ - column chip: `{"id":1,"type":"dataStoreColumn","variableId":null,"dataStoreId":"<store guid>","columnId":"<col guid>","attribute":null}`
50
+ `@threshold` is a SQL parameter (NOT a chip); it is resolved by the Parameters map below.
51
+ - `...b103` **Parameters** (map): `Value` is a list of
52
+ `{"id":K,"source":{"value":..,"variable":[..]},"destination":{"value":"<param name>","variable":[]}}`.
53
+ `destination.value` is the SQL parameter name (no `@`). `source.value` is a `<%N%>` placeholder
54
+ resolved by `source.variable[] = [{"id":N,"variableId":"<flow var guid>","attribute":null}]`;
55
+ a fixed literal instead goes straight in `source.value` with an empty `variable[]`.
56
+ - `...b104` **Time Out**: `Value:"1800"`, `Variable:[]`.
57
+ - `...b105` **Result Rows** (output): `Value:"<%N%>"`, `Variable:[{"id":N,"variableId":"<list-of-json var>"}]`.
58
+ - `...b106` **Total Count** (output): `Value:"<%N%>"`, `Variable:[{"id":N,"variableId":"<integer var>"}]`.
59
+
60
+ So the only Query-Store-specific part of the well-known `<%N%>`+`Variable[]` pattern is the
61
+ chip entries typed `dataStoreTable` / `dataStoreColumn`, which carry `dataStoreId` (and
62
+ `columnId`) instead of a `variableId`. Resolve those ids live from the Data Store metadata.
63
+
64
+ ## The `Query` value chips (behaviour)
65
+ A Data Store or column is inserted with Ctrl+I and stored as a chip **bound to the resource
66
+ ID, not its name**: renaming the store/column keeps the query working; deleting one turns the
67
+ chip red ("Unknown") and fails the step. Names with spaces/special chars work because the name
68
+ never enters the SQL. **System columns** `CreatedOn`, `UpdatedOn`, `CreatedById`, `UpdatedById`
69
+ are NOT in the picker; type them by name (e.g. `WHERE CreatedOn > @since`).
70
+
71
+ ## Parameters (always use them for external values)
72
+ - Write `@name` in the SQL; map it below the editor to a fixed value or a process variable.
73
+ A parameter value is always **data, never SQL** (injection-safe).
74
+ - Name rules: start with a letter or `_`, then letters/digits/`_`, **<=64 chars**, each name
75
+ once per query; the leading `@` in the mapping is optional.
76
+ - **An empty parameter row blocks Test Action** - delete it when the query has no parameters.
77
+ - Do NOT paste a variable straight into the SQL via the picker (it inlines the value as text).
78
+
79
+ ## Passing a list (real trap)
80
+ A list variable arrives in the query as a **JSON array**. `IN (@list)` runs but matches
81
+ **nothing**. Use instead:
82
+ - text values: `WHERE JSON_CONTAINS(@codes, JSON_QUOTE(SomeColumn))`
83
+ - whole numbers: `WHERE SomeColumn MEMBER OF (CAST(@numbers AS JSON))`
84
+
85
+ ## A Guid column is BINARY(16), and a guid-shaped STRING parameter is packed into 16 bytes
86
+
87
+ A Data Store `Guid` column is a MySQL `BINARY(16)`: `LENGTH(col)` returns 16 and `CHARSET(col)`
88
+ returns `binary`. Everything below follows from that, and from the binding layer typing a
89
+ parameter by the SHAPE of its value rather than by the column it is written to.
90
+
91
+ **Converting a guid outside SQL:** the **GUID Converter** action (Utilitary, after Generate
92
+ GUID, actionId `77f61c6d-0000-0200-0000-00000000b001`, added in v3.0.0, 2026-10) converts
93
+ between the standard text form, the `0x` hex literal PROCESIO stores in a `BINARY(16)` column
94
+ (its `Hex` output), a decimal number and Base64, in both byte orders (`HexBigEndian`,
95
+ `Base64DotNet`). Input `Input` plus `InputFormat` (AutoDetect / Standard / Hex / HexBigEndian /
96
+ Number / Base64 / Base64DotNet); outputs Standard, Guid, Hex, HexBigEndian, Number, Base64,
97
+ Base64DotNet. AutoDetect refuses ambiguous input (codes 1009-1012), so name the format when
98
+ the source is known. Use it in a process when a value has to cross between the designer's
99
+ spelling and the stored bytes, instead of the SQL byte reorder below.
100
+
101
+ **A parameter whose value looks like a GUID (`8-4-4-4-12` hex) is typed Guid and packed to its 16
102
+ bytes before it reaches the column**, including when the column is a String. Two outcomes, both
103
+ bad:
104
+
105
+ - the packed bytes are not valid in the column's charset - any byte >= 0x80, which is EVERY real
106
+ RFC 4122 UUID, because the variant byte is 0x80-0xbf - so the statement is refused with
107
+ *"One or more values are incompatible with the column type."* (MySQL's `1366 Incorrect string
108
+ value` underneath);
109
+ - the packed bytes happen to be valid UTF-8, so **the write SUCCEEDS and the column now holds 16
110
+ bytes of binary instead of the 36-character string.** Nothing warns. Read it back and you get
111
+ 16 garbage characters, `CHAR_LENGTH` is 16, and every `LIKE` or `SUBSTRING` on it is meaningless.
112
+
113
+ The one-character minimal pair that isolates it (`...-4444-5555555555ff` refused,
114
+ `...-4444-55555555557f` accepted) shows the rule is byte validity, not the guid version or
115
+ variant. It fires wherever the parameter is used, including as a function argument
116
+ (`CONCAT('', @v)`), and only when a row is actually written: the same statement whose WHERE
117
+ matches nothing is accepted. The REST row endpoint (`POST /api/DataStore/{id}/rows`) behaves the
118
+ same, so it is the platform's typing rule and not one endpoint's bug. It has nothing to do with
119
+ length: a 29 KB string in the same column is accepted.
120
+
121
+ **The fix is to type the column Guid**, not to work around the binder. `datastore-update` adds a
122
+ column to an existing store in place (send the full metadata DTO with the existing columns, each
123
+ keeping its `columnId`, plus the new one); rows already in the store are preserved. Where a column
124
+ genuinely cannot be typed (one `Value` column holding everything from a timezone name to a data
125
+ URI), prefix the parameter and strip it in SQL with `SUBSTRING(@v, 2)`.
126
+
127
+ ### The platform and MySQL spell the same Guid column differently
128
+
129
+ The 16 bytes are stored in .NET `Guid.ToByteArray()` order, so MySQL's own UUID functions
130
+ disagree with the designer on every value:
131
+
132
+ SELECT g, BIN_TO_UUID(g) FROM <store>
133
+ -- 1f0d8533-cf4c-44d8-b805-a4d3d6aaca4d what the platform shows
134
+ -- 33850d1f-4ccf-d844-b805-a4d3d6aaca4d what MySQL reads from the same bytes
135
+
136
+ It is symmetrical on writes: `SET g = UUID_TO_BIN('<what the designer shows>')` succeeds and
137
+ stores a DIFFERENT guid, silently. Round-tripping needs either a bound parameter, or this reorder:
138
+
139
+ LOWER(CONCAT_WS('-', HEX(REVERSE(SUBSTRING(g,1,4))), HEX(REVERSE(SUBSTRING(g,5,2))),
140
+ HEX(REVERSE(SUBSTRING(g,7,2))), HEX(SUBSTRING(g,9,2)),
141
+ HEX(SUBSTRING(g,11,6))))
142
+
143
+ ### Reading and writing a Guid column
144
+
145
+ - **write it with a PARAMETER, never a literal.** A 36-character literal is 36 bytes into a
146
+ `BINARY(16)`, so MySQL raises `1406 Data too long` and the platform reports *"values exceed the
147
+ maximum allowed length"*, which points at the wrong thing. `UUID_TO_BIN('...')` fits, but see
148
+ the byte order above before using it.
149
+ - **copying a text column into it fails the same way** (`SET g = SUBSTRING(v,1,36)` is still 36
150
+ bytes). Move such values with one parameterised UPDATE per row, and clear the text column in a
151
+ SEPARATE statement, because a Guid parameter and a String column in one statement is the very
152
+ thing the packing rule refuses.
153
+ - **`CAST(<guidcol> AS CHAR)` returns NULL** - plain MySQL, not the platform. Converting binary to
154
+ a character set yields NULL when the bytes are not valid in it, which for 16 guid bytes is
155
+ almost always (`CAST(_binary'hello' AS CHAR)` gives `hello`, `CAST(UNHEX('84FF') AS CHAR)` gives
156
+ NULL). Use `BIN_TO_UUID()` or `HEX()`, or select the column on its own.
157
+
158
+ ## Four traps found porting a real schema onto Data Stores
159
+
160
+ **`Total Count` is ROWS RETURNED, never a value in them.** `SELECT COUNT(*) AS N ...` reports
161
+ Total Count = **1**, because it returned one row - the `N` inside it is not what you read. A
162
+ "does this exist" test written as `COUNT(*)` therefore can never answer "no". Write it so the
163
+ row itself is the answer: `SELECT 1 AS N FROM <store> WHERE <match>` gives Total Count 1 or 0.
164
+
165
+ **A column alias is re-cased to match a column of the STORE being queried.** `SELECT 1 AS txt,
166
+ 2 AS zzz` against a store that has a `Txt` column returns `Txt` and `zzz`: the alias matching a
167
+ column name case-insensitively is rewritten to the store's spelling, one that matches nothing is
168
+ left alone. Which aliases survive therefore depends on which store the statement happens to
169
+ touch, and the same query against a store with a `Name` column turns `name` into `Name`. The
170
+ external SQL actions do NOT do this (`SELECT 1 AS name` returns `name`), so a query ported from
171
+ one to the other silently changes a key the page reads. Either alias to something no column is
172
+ called, or rename it back in a Node.
173
+
174
+ **`INSERT ... SELECT <aggregate> ... WHERE <guard>` inserts even when the guard is false.** An
175
+ aggregate with no `GROUP BY` returns exactly one row whatever the `WHERE` matched, so the
176
+ classic "allocate the next id" insert
177
+ (`SELECT COALESCE(MAX(id),0)+1 ... WHERE <should we insert?>`) writes a row with id 1 when the
178
+ guard was meant to stop it - and the next real insert then fails on the unique key. Put a guard
179
+ that does not depend on the scanned rows in **`HAVING`**, which filters the aggregated row.
180
+
181
+ **A `{var, path}` parameter binding only works for a typed model.** The path is emitted as a
182
+ data-model `attributeId`, so pointing it at a field of an ordinary Json variable fails at run
183
+ with `Error converting value "<field>" to type 'System.Nullable`1[System.Guid]'`. A Node cannot
184
+ hand a Query Store node a bag of values to index into: give the Node's result a real data model,
185
+ or pass each value as its own variable.
186
+
187
+ ## Mixing a Guid column with text makes the whole result column binary
188
+
189
+ `COALESCE(<guidcol>, <textcol>)` returns a binary column, because MySQL's type aggregation makes
190
+ any expression with a BINARY operand binary. That part is stock MySQL (a plain server returns
191
+ `binary` from `COLLATION(COALESCE(UUID_TO_BIN(UUID()), 'text'))`). What the platform adds is the
192
+ rendering, and it guesses from the LENGTH of the bytes: exactly 16 bytes are shown as a guid,
193
+ anything else as base64. So ordinary text arrives as `bWloYWlAZXhhbXBsZS5jb20=`, and a
194
+ 16-character string arrives looking exactly like a guid (`Europe/Bucharest` as
195
+ `6f727545-6570-422f-7563-686172657374`). Nothing errors, so a settings page simply fills with what
196
+ look like ids.
197
+
198
+ **Fix it in SQL with `COALESCE(BIN_TO_UUID(<guidcol>), <textcol>)`**, which keeps the result
199
+ column character-typed and leaves the plain rows alone. An earlier version of this note said no
200
+ expression mixes them safely and the two columns had to be merged in a Node; that was wrong. Use
201
+ the byte reorder above instead of `BIN_TO_UUID` when the string has to match what the designer
202
+ shows.
203
+
204
+ ## The engine under a Data Store, and what follows from it
205
+
206
+ `SELECT VERSION()` through a Query Store node returns a stock **MySQL 8.0.46**, and `@@sql_mode`
207
+ is MySQL's own default with `STRICT_TRANS_TABLES` included. Before reporting a Query Store
208
+ behaviour as a platform bug, run the same expression against any stock MySQL 8: several of the
209
+ sharpest-looking ones are the engine's, and the mapping layer only obscures them.
210
+
211
+ - **Strict mode explains the write/read asymmetry.** It turns a conversion warning into an error
212
+ in data-change statements only, so a parameter that compares fine in a `SELECT` (`CAST('' AS
213
+ SIGNED)` is 0 with `1292 Truncated incorrect INTEGER value`) makes the same comparison fail in
214
+ an `UPDATE`. An explicit CAST cannot rescue it, because the cast is what truncates. A guard for
215
+ "do nothing unless this field was filled in" therefore has to be a Decisional in front of the
216
+ node, never a WHERE inside it.
217
+ - **The timezone tables are not loaded**, so `CONVERT_TZ` returns NULL for every NAMED zone while
218
+ fixed offsets work. Stock MySQL packages ship this way, and `mysql.time_zone_name` cannot be
219
+ read to confirm it (system schemas are blocked). A fixed offset is not a timezone, so
220
+ DST-correct arithmetic has to leave SQL for a script node, where JavaScript `Intl` carries the
221
+ database MySQL does not.
222
+ - **The platform replaces the MySQL error with one of two sentences**, so `1406`, `1366`, `1292`
223
+ and `1093` all arrive as *"values exceed the maximum allowed length"*, *"values are incompatible
224
+ with the column type"* or *"The query could not be executed."* When a statement is refused for
225
+ no visible reason, bisect it against the MySQL error list rather than the Query Store docs.
226
+ - **A Data Store export carries the schema, not the rows.** An imported store arrives empty, so
227
+ ship a seeding process alongside any pack that is meant to be run in another workspace.
228
+
229
+ ## Placeholder numbering is per-ACTION, and counts the outputs
230
+
231
+ `<%N%>` indices are handed out across a whole action, including the endpoint and the
232
+ output-bound properties, so a request body lifted off a live node arrives numbered `1-8, 11, 12`
233
+ with the gaps belonging to the node's own Response Body / Status. Feed such a template back with
234
+ a contiguous variable list and the unmatched `<%11%>` stays in the source: the request body is
235
+ then invalid JSON (`The Call Api request body has invalid Json format for RAW type value`), and
236
+ a script fails at parse time with `Unexpected token '<'`.
237
+
238
+ When authoring, number each property's placeholders **from 0 over its own variable list** - the
239
+ builder assigns the action-wide indices itself. Renumber a lifted template before reusing it.
240
+
241
+ ## Call API `Request Parameters` is an OBJECT, and only sometimes
242
+
243
+ - With variables bound, pass `{"template": <the object>, "vars": [...]}` - and pass the OBJECT,
244
+ not the same JSON as a string: a string template with variables is stored as text and the
245
+ runtime refuses it with `Error setting value to CallApi RequestParameters property`.
246
+ - With NO variables, `{"template": …, "vars": []}` is also left as text; use the literal form
247
+ `{"value": <the object>}` instead.
248
+
249
+ Lift the credential, verb, endpoint and body off the live node rather than retyping them, and
250
+ match the node by its **verb or endpoint, not its name** - porting renames nodes, so a name
251
+ lookup silently finds nothing the second time.
252
+
253
+ ## Joins
254
+ When two Data Stores share a column name, give each an alias and prefix every column
255
+ (`FROM A AS c JOIN B AS o ON o.k = c.k`); without aliases the join is **refused** (ambiguous).
256
+
257
+ ## Multi-statement scripts (changed in v3.0.0, 2026-10)
258
+
259
+ The `Query` value now runs as ONE batch of `;`-separated MySQL statements. Before v3.0.0 a run
260
+ took exactly one statement and anything stacked was refused. No inputs or outputs were added; the
261
+ catalog tooltip reads "The MySQL statements to run against the Data Store, separated by ;. At
262
+ most one may return rows."
263
+
264
+ - **No transaction.** The statements run with autocommit, so when one fails, the statements
265
+ before it stay committed. `START TRANSACTION` / `BEGIN` / `COMMIT` are still refused, so a
266
+ script that must be all-or-nothing has to be written so a partial run is harmless (idempotent
267
+ guards, a repair path on the error port).
268
+ - **At most ONE statement may return rows.** More than one fails with error 2003 *"Expected
269
+ maximum one table but received N tables!"*, and that check runs AFTER the script: every write
270
+ in it has already happened by the time the step fails.
271
+ - **User variables** (`SET @v = ...`, `SELECT ... INTO @v`, `@v := ...`) are allowed by the
272
+ Data-Store service's own validator so a value one statement computes can feed the next. **Measured live in prod on 2026-10-22:** user variables work, except where one meets a TEXT column. `SET @q = 1; ... WHERE IntCol = @q` runs, and so do Boolean and Guid comparisons and a plain `SELECT @n`. `SET @n = 'x'; ... WHERE TextCol = @n` fails with the generic *"The query could not be executed."*: the variable and the column carry different collations (`utf8mb4_0900_ai_ci` vs `utf8mb4_general_ci`, a MySQL configuration mismatch on the platform side, with no dedicated error message). Workaround that runs: put an explicit collation on the comparison, `WHERE TextCol = @n COLLATE utf8mb4_0900_ai_ci` (either collation name works). Or avoid the text variable and bind the value as a parameter.
273
+ - **Session statements stay refused**: session `SET`, `START`, `BEGIN`, `LOCK`, `PREPARE` and
274
+ similar. When one statement of a script is refused, the message is prefixed `Statement N: `
275
+ so it names the offender.
276
+ - **Hazard: a misspelled `@param` is now silently NULL.** Before v3.0.0 an `@name` with no
277
+ mapping was a 400. Now it reads as an unset user variable, which is NULL: `WHERE col = @thresold`
278
+ matches nothing and `SET col = @vlaue` writes NULL, with no error at all. Check every `@name`
279
+ in the SQL against the Parameters map (and the script's own `SET @...`) before shipping.
280
+ - **Time Out applies per row-returning statement**, not to the whole script.
281
+ - **Total Count** for writes counts the affected rows of the whole script.
282
+ - MySQL `1049` (the workspace has no Data Store database yet) now reads *"Create a Data Store
283
+ first."*
284
+ - The Data Store service response carries a `resultSetCount`.
285
+
286
+ ## Accepted statements (QA matrix, run completes / status 50)
287
+ Each statement of a script is checked against these lists on its own (changed in v3.0.0,
288
+ 2026-10: before, exactly one statement per run). Accepted:
289
+ - **SELECT** - plain, `AS` alias, `WHERE`, `IN (subquery)`, `EXISTS`.
290
+ - **JOIN** - incl. table-aliased `a.col = b.col`.
291
+ - **WITH ... AS** (CTE), incl. **WITH RECURSIVE**.
292
+ - **UNION / UNION ALL** - as arms of a SELECT.
293
+ - **GROUP BY**, **ORDER BY ... DESC**, **COUNT(*)** and the other aggregates, **LIMIT**.
294
+ - **INSERT INTO ... VALUES** (single and multi-row), **INSERT ... SELECT** (copy rows).
295
+ - **UPDATE ... SET ... WHERE** (affected-rows count).
296
+ - **DELETE FROM ... WHERE** (affected-rows count).
297
+ - **REPLACE INTO** (delete+insert; reports 2 affected on an overwrite).
298
+ - `@parameter` binding - constant and variable, bound as data.
299
+ - **User variables** (v3.0.0, service side): `SET @v = ...`, `SELECT ... INTO @v`, `@v := ...`.
300
+ A text variable compared with a text column fails unless the comparison carries an explicit
301
+ `COLLATE` (measured 2026-10-22, see Multi-statement scripts).
302
+ - **Several of the above in one `;`-separated script** (v3.0.0), at most one returning rows.
303
+
304
+ ## Write mechanics
305
+ - **INSERT and REPLACE must fill `CreatedById` and `UpdatedById`** (pass a user GUID as a
306
+ parameter); a missing required column fails with "A required column has no default value
307
+ and was not supplied."
308
+ - An `UPDATE` through Query Store **does not refresh `UpdatedOn`**.
309
+ - A `REPLACE` counts as a new row, so it **resets** the created-by / created-on values.
310
+
311
+ ## Refused (QA matrix, status 40 / done-with-errors) - each with a clear message, never a raw DB error
312
+ - **Multi-statement / stacked**: NO LONGER refused for being stacked (changed in v3.0.0,
313
+ 2026-10). Each statement is checked on its own, so `SELECT ...; DROP ...` is still refused,
314
+ for the `DROP`, with the message prefixed `Statement N: `. More than one row-returning
315
+ statement fails with error 2003 after the script ran (see "Multi-statement scripts"). The
316
+ pre-v3.0.0 matrix also refused comment-hidden stacking (`--`, `#`) and `; SELECT 1`; those
317
+ cases were not re-measured on v3.0.0.
318
+ - **DDL**: `DROP/ALTER/TRUNCATE/RENAME/CREATE TABLE`, incl. case-obfuscated (`dRoP`).
319
+ - **Privileges**: `GRANT`.
320
+ - **Session / txn / lock / prepare**: session `SET`, `START TRANSACTION`, `BEGIN`, `COMMIT`,
321
+ `LOCK TABLES`, `HANDLER`, `PREPARE`, `USE`. Still refused in v3.0.0; only a user-variable
322
+ assignment (`SET @v = ...`) became allowed (a text variable against a text column needs an
323
+ explicit `COLLATE`, see Multi-statement scripts).
324
+ - **File / advisory lock / system schema**: `INTO OUTFILE`, `INTO DUMPFILE`, `LOAD_FILE()`,
325
+ `GET_LOCK()`, `information_schema.*`, `mysql.user`, `performance_schema.*` - including
326
+ UPPERCASE / backtick / spaced / comment-split spellings.
327
+ - **Obfuscated primitives**: executable comments `/*! ... */`, versioned `/*!50000 ... */`.
328
+ - **Nested system-schema refs**: a subquery `EXISTS(SELECT ... information_schema)` or a
329
+ `UNION` arm reaching `mysql.user`.
330
+ - **Other non-whitelisted verbs**: `DO` (e.g. `DO SLEEP(0)`), `SHOW` (e.g. `SHOW GRANTS`),
331
+ comment-only / empty query.
332
+ - **Error-based extraction**: `EXTRACTVALUE`, `UPDATEXML`.
333
+ - Plus: unparseable SQL, and a query that runs past Time Out.
334
+
335
+ Query Store only ever touches the Data Stores of the workspace the process runs in.
336
+
337
+ ## Error handling
338
+ - On failure the step routes to its **error port**; wire an error branch and capture the
339
+ message in an `Error`-typed variable.
340
+ - **Test Action** runs the step alone (the vertical tab on the properties panel).
341
+
342
+ ## Authoring a Query Store node with the tool (process-create / process-edit)
343
+
344
+ The builder emits the whole node from a friendly `queryStore` field on an action (no need to
345
+ hand-write the chip encoding above). Verified live end-to-end in an org workspace across
346
+ SELECT, JOIN across two Data Stores + GROUP BY/SUM, INSERT/UPDATE/DELETE/REPLACE, a list
347
+ parameter, and a refused statement routed to an error branch.
348
+
349
+ ```json
350
+ {"id": "q", "action": "Query Store", "queryStore": {
351
+ "sql": "SELECT c.{{col:Customers.Name}} AS Customer, SUM(o.{{col:Orders.Amount}}) AS Rev FROM {{ds:Customers}} AS c JOIN {{ds:Orders}} AS o ON o.{{col:Orders.CustomerCode}} = c.{{col:Customers.CustomerCode}} WHERE o.{{col:Orders.Status}} <> @cancelled GROUP BY c.{{col:Customers.Name}}",
352
+ "params": {"cancelled": "Cancelled"},
353
+ "resultRows": "rows", "totalCount": "count"}}
354
+ ```
355
+
356
+ - Tokens: `{{ds:StoreNameOrId}}` = a table chip, `{{col:StoreNameOrId.ColNameOrId}}` = a column
357
+ chip. Names resolve to ids live from Data Store metadata (`prepare_ctx` fetches
358
+ `/api/DataStore`); a GUID passes through. An unknown name fails with a clear error.
359
+ - `@param` stays literal in the SQL and is bound by `params` (bare = literal, `{var}` = a
360
+ process variable). A column alias (`c.`, `o.`) is plain text before the column chip.
361
+ - `timeout` (60-1800, default 1800); `resultRows` -> a list-of-Json variable, `totalCount` ->
362
+ an Integer. For a write, bind `totalCount` (affected rows); Result Rows is empty.
363
+ - INSERT/REPLACE: include `CreatedById`/`UpdatedById` (a user GUID param). List param: use
364
+ `JSON_CONTAINS` / `MEMBER OF` (see above), never `IN (@list)`.
365
+ - Error handling: add `"onError": "<handlerActionId>"` to route the step's error port; give the
366
+ error path its own convergence (a second Stop) - a single Stop takes one input port, so two
367
+ branches into one Stop is refused by the BE validator ("Action has too many input ports").
368
+ - The generated node's designer config mirrors the runtime (Query -> `ds.<id>[.<col>]`,
369
+ Parameters -> `{id,destination,source}`), so it shows configured in the Process Designer.
370
+
371
+ ## Upsert, guarded writes, and SELECT * keys (measured 2026-10-17)
372
+
373
+ - `SELECT * FROM {{ds:Store}}` returns rows keyed by the columns' display names, plus the
374
+ system columns (`CreatedOn`, `UpdatedOn`, `CreatedById`, `UpdatedById`); strip those in the
375
+ Node that shapes the result.
376
+ - An upsert keyed on the primary key is one statement:
377
+ `REPLACE INTO {{ds:Store}} ({{col:Store.Key}}, {{col:Store.A}}, CreatedById, UpdatedById, CreatedOn, UpdatedOn)
378
+ VALUES (@k, @a, @uid, @uid, UTC_TIMESTAMP(), UTC_TIMESTAMP())`, with `@uid` a user GUID parameter.
379
+ - A write that must only happen when an upstream check passed can carry the check in SQL:
380
+ `REPLACE INTO ... SELECT @a, @b, ... FROM DUAL WHERE @flag <> '__invalid__'` (the Node that
381
+ validates returns the sentinel for every column when invalid), so no Decisional branch is needed.
382
+ - `UPDATE ... SET col = CASE WHEN (col IS NULL OR col = '') THEN @t ELSE col END` keeps a
383
+ first-set timestamp (e.g. when a lead was first acknowledged).
384
+
385
+ ## Seeding a store from one statement, idempotently
386
+
387
+ An export carries a Data Store's COLUMNS and not its ROWS, so any pack meant to run elsewhere
388
+ has to seed its own reference data. Two things make that one statement instead of hundreds:
389
+
390
+ - **`JSON_TABLE` works.** Pass the rows as a single JSON string parameter and read them as a
391
+ table, so several hundred rows insert in ONE round trip:
392
+
393
+ INSERT INTO {{ds:Store}} ({{col:Store.A}}, CreatedById, UpdatedById)
394
+ SELECT jt.c_A, @uid, @uid
395
+ FROM JSON_TABLE(@j, '$[*]' COLUMNS (c_A VARCHAR(80) PATH '$.A')) AS jt
396
+ WHERE NOT EXISTS (SELECT 1 FROM {{ds:Store}} AS existing)
397
+
398
+ - **The `NOT EXISTS` guard against the target store is allowed here and is genuinely
399
+ idempotent.** Verified: run against an already-populated store it affects 0 rows and raises
400
+ nothing, and the row count is unchanged. This is the one place the target table MAY be named
401
+ in a subquery - an UPDATE or DELETE doing the same thing hits MySQL 1093 instead. Note it is
402
+ an all-or-nothing guard: a partially filled store is left alone, not topped up.
403
+
404
+ **A `JSON_TABLE` column name is a plain identifier, so a reserved word breaks it.** A column
405
+ declared as `Key VARCHAR(100) PATH '$.Key'` fails with the platform's generic
406
+ *"The query contains invalid SQL syntax."* and no hint as to which word is the problem. Declare
407
+ every column under a safe alias (`c_Key`) and map it in the SELECT list; that also covers a
408
+ column added later whose name happens to be reserved.
409
+
410
+ **`CreatedById` / `UpdatedById` are required on any INSERT** and the all-zero guid is accepted,
411
+ which is what a seed should carry: a real user id in an exported pack is a stranger's id at the
412
+ destination. The error when they are missing is unusually good - it names the column:
413
+ *"The required column 'CreatedById' has no default value and was not supplied."*
414
+
415
+ ## Write the statement over lines, always
416
+
417
+ A Query Store node's SQL IS the node (one statement, or since v3.0.0 a `;`-separated script),
418
+ so unreadable SQL is an unreadable node. SQL built
419
+ by concatenating strings arrives as a single line, runs perfectly, and shows in the designer as
420
+ one row scrolling off the screen - 3375 characters in the worst case measured here.
421
+
422
+ **The builder now lays it out on the way in** (`tools/procesio/sqlformat.py`, called from
423
+ `_build_query_store`). The formatter only moves whitespace OUTSIDE string literals, treats
424
+ `<%N%>` and `{{ds:...}}` / `{{col:...}}` as opaque words, and compares its output's token stream
425
+ against the input before returning - handing the input back unchanged if they differ, because a
426
+ formatter that can alter a statement is worse than none. It leaves an already-multi-line
427
+ statement exactly as written.
428
+
429
+ `flow-lint` raises **UNREADABLE_STATEMENT** / **UNREADABLE_SCRIPT** for a statement or script
430
+ body over ~160 characters on one line that reached the flow some other way (a hand-set parameter,
431
+ an import). The lint flags and never rewrites: reflowing someone else's code is not its job.
432
+
433
+ **A script is REFUSED, not reformatted.** SQL can be laid out safely because only whitespace
434
+ outside string literals moves and the token stream is verified afterwards; a script cannot,
435
+ because rewriting someone's JavaScript or Python risks changing it. So the builder raises on a
436
+ `code-editor` body over ~160 characters on ONE line and names the property, and the author lays
437
+ it out. Keep a script multi-line, one statement per line; a single line is fine only when it is
438
+ genuinely one short expression (`return String(x || '')`).
439
+
440
+ The same rules apply to the external SQL actions.
441
+
442
+ ## A column comes back base64 when its EXPRESSION resolves to binary
443
+
444
+ Symptom: a text column arrives as one base64 blob. Decoded, it is the right sentence with raw
445
+ bytes sitting where a value should be. It reaches wherever that column was going - a calendar
446
+ event description, an email body - so it is the user who finds it, not a test.
447
+
448
+ The encoding follows the SQL type of the WHOLE expression, and MySQL derives that by collation
449
+ coercibility, so WHICH arguments are present decides it. Measured over a real store:
450
+
451
+ | Expression | Comes back as |
452
+ | --- | --- |
453
+ | `<Guid column>` alone, or `CONCAT('', <Guid column>)` alone | the canonical GUID text |
454
+ | `CONCAT(<varchar column>, CHAR(10), <Guid column>)` | **base64**, the guid as 16 raw bytes |
455
+ | `CONCAT('x', '
456
+ ', 'id=', CONCAT('', <Guid column>))` | **base64**, same |
457
+ | `CONCAT('a', CHAR(10), 'b')` - literals only | **base64** |
458
+ | `CONCAT(<varchar column>, CHAR(10), 'b')` | text |
459
+ | `CONCAT('a', CHAR(10), <varchar column>)` | text |
460
+ | `CONCAT(<varchar column>, CHAR(10 USING utf8mb4), 'b')` | text |
461
+ | `CONCAT('url?id=', <the conversion below>, '&x=1')` | text, canonical GUID |
462
+
463
+ Two rules, in order of how often they bite:
464
+
465
+ - **A Guid column inside a larger expression forces the whole column binary.** It renders as text
466
+ only when it IS the column - `CONCAT('', g)` works alone because the layer can still attribute
467
+ the result to a Guid chip, and contributes raw bytes the moment anything else is concatenated,
468
+ even with character columns present. This is the one that actually breaks things, and nothing
469
+ warns.
470
+ - **`CHAR(n)` returns a binary string, but only decides the result when every other argument is a
471
+ literal.** One real character column in the expression keeps it character. So `CHAR(n)` is a
472
+ latent hazard rather than the usual cause: a statement that reads fine today turns binary the
473
+ day someone simplifies the surrounding columns out of it. `CHAR(n USING utf8mb4)` removes the
474
+ hazard and costs nothing, which is worth doing on sight - but do not diagnose a base64 column by
475
+ blaming it first; look for the Guid.
476
+
477
+ ### Converting a Guid column to text inside an expression
478
+
479
+ The storage is `BINARY(16)` in .NET `Guid.ToByteArray()` order, so the first three groups are
480
+ little-endian and `BIN_TO_UUID` does not agree with the designer. `HEX()` is what makes the result
481
+ CHARACTER data, which is the point:
482
+
483
+ ```sql
484
+ LOWER(CONCAT_WS('-',
485
+ HEX(REVERSE(SUBSTR(g, 1, 4))),
486
+ HEX(REVERSE(SUBSTR(g, 5, 2))),
487
+ HEX(REVERSE(SUBSTR(g, 7, 2))),
488
+ HEX(SUBSTR(g, 9, 2)),
489
+ HEX(SUBSTR(g, 11, 6))))
490
+ ```
491
+
492
+ Verified against the same row read through the rows API, and the text it produces MATCHES the
493
+ binary column in a filter or a typed parameter - which is what makes it usable in a link the
494
+ product later looks the row up by.
495
+
496
+ **A placeholder is not reusable, so this needs five chips per Guid.** `<%N%>` indices bind
497
+ `Variable[]` positionally and nothing substitutes the same index twice; the way to name one column
498
+ five times is five chips pointing at it, which a config's `{{col:Store.Column}}` token does
499
+ naturally (one index per occurrence). `node-set-param` refuses this edit by design - it holds the
500
+ placeholder set fixed - so reach for `node-set-query`, which rewrites the statement and
501
+ regenerates its chips together.
502
+
503
+ ### Why it matters beyond cosmetics
504
+
505
+ A link assembled this way carries the identifiers the product will later match the row by. Get the
506
+ encoding wrong and the link is not merely ugly: the bytes land raw, they include characters that
507
+ cannot survive a URL (quotes, newlines, control bytes), and the page the link opens can never find
508
+ the record. A customer then has a cancel link that does nothing - and the way that surfaces is a
509
+ second booking next to the first, with the first still marked active.
510
+
511
+ ## JSON_VALID does NOT protect a CAST ... AS JSON in the same expression
512
+
513
+ Reading a JSON payload that came from an HTTP response is the normal case, and an HTTP response
514
+ is not always JSON: an auth failure answers with a page, a gateway answers with text, a timeout
515
+ answers with nothing. So the parse has to be defended, and the obvious defence does not work:
516
+
517
+ ```sql
518
+ -- STILL ERRORS on a non-JSON payload: the guard does not stop the cast being evaluated
519
+ JSON_VALID(@payload) AND EXISTS (SELECT 1 FROM JSON_TABLE(CAST(@payload AS JSON), ...) AS t ...)
520
+ ```
521
+
522
+ Measured: with `@payload` = `Unauthorized`, or empty, the step fails with "The query could not be
523
+ executed." A statement that only runs every few minutes inside a scheduled flow then takes the
524
+ whole flow down, repeatedly, for a reason that has nothing to do with the data it was written for.
525
+
526
+ Sanitise the VALUE before it reaches the cast instead, so the cast never sees anything else:
527
+
528
+ ```sql
529
+ CAST(IF(JSON_VALID(@payload), @payload, '{}') AS JSON)
530
+ ```
531
+
532
+ With that, the same six payloads (a valid object, a valid array, an object with the array missing,
533
+ plain text, an empty string, and the real thing) all evaluate to a plain false and the step
534
+ succeeds. Worth probing with exactly that spread before shipping a JSON predicate: the two that
535
+ break are the two nobody writes a test for.
536
+
537
+ ## A scheduled reconciler should key on the remote's own state, not on a notification
538
+
539
+ A pattern that works, from a booking product whose calendar events live in a third-party
540
+ calendar: the reconciling flow re-reads each future record's remote object every couple of
541
+ minutes and releases the local record when the remote says it is gone. Three signals, and all
542
+ three are needed:
543
+
544
+ | Remote says | Means |
545
+ | --- | --- |
546
+ | HTTP 404 / 410 | the object was deleted outright |
547
+ | 200 with the object's own status `cancelled` | cancelled in place |
548
+ | 200, status still live, but the invited party's `responseStatus` is `declined` | the person is not coming, though the object survives |
549
+
550
+ The third is the one usually missed, and it is the only one that distinguishes "they declined" from
551
+ "nothing happened". Match the decline on the invited party's OWN address rather than on "any
552
+ attendee declined", or one guest's decline cancels the host's meeting. The guard costs one extra
553
+ parameter and is worth it.
554
+
555
+ The broader point: no webhook is involved and none is needed. The remote object IS the state, so
556
+ re-reading it converges no matter which of the two sides acted, and a missed notification cannot
557
+ desynchronise anything. The cost is that a reader who looks between two ticks sees the old value -
558
+ so when verifying, re-read after a tick rather than concluding the mechanism is broken.
559
+
560
+ ## A value injected into a JSON body must carry the JSON escape, not the character
561
+
562
+ A statement that builds text for an HTTP body is building part of a JSON document, because a Call
563
+ API body template substitutes the value RAW into a string:
564
+
565
+ ```json
566
+ { "description": "<%7%>" }
567
+ ```
568
+
569
+ So a real newline in that value is a literal newline inside a JSON string, which is invalid JSON.
570
+ The request is then malformed and the call does nothing - and because the loop around it carries
571
+ on, the flow still reports success. The symptom is an absence: no event, no error, nothing in the
572
+ logs.
573
+
574
+ The value therefore has to contain the two characters `\` and `n`, which the JSON parser turns
575
+ back into a newline. From MySQL that means a doubled backslash in the literal, `'\n'`, since
576
+ `'\n'` is MySQL's own escape and produces the character itself. Easy to get wrong through a
577
+ generator: check the stored statement, not the generator's input - `\n` and `\n` look identical
578
+ in most error-reporting and one of them silently means "newline".
579
+
580
+ Keep the real character where the text is PARSED rather than embedded: splitting a pasted list on
581
+ newlines still wants `CHAR(10 USING utf8mb4)`. Same statement, two different answers, decided by
582
+ where the string is going.
583
+
584
+ ### Why this one hides: one bug can mask another
585
+
586
+ The same statement had the base64 fault described above, and base64 contains no newlines and no
587
+ quotes - so while the column was being base64-encoded, the body it fed was always valid JSON and
588
+ the create worked. Fixing the encoding is what made the newlines real and broke the call. Neither
589
+ bug was visible while the other stood.
590
+
591
+ The lesson is procedural: after repairing how a value is ENCODED, re-test every consumer of that
592
+ value, not just the one whose output looked wrong. The repair changes the bytes, and something
593
+ downstream was relying on the broken shape without anyone knowing.
594
+
595
+ ## Coerce EVERY parameter, or the one you skipped is the one that breaks
596
+
597
+ A write statement fed from a form usually wraps its parameters defensively - `CAST(@n AS SIGNED)`,
598
+ `NULLIF(@s,'')`, `COALESCE(CAST(NULLIF(CAST(@n AS CHAR),'') AS SIGNED), 0)`. The failure mode is
599
+ the single parameter that was left bare, because a form does not send typed values: it sends
600
+ strings, and an untouched control sends an EMPTY string. An empty string into a boolean or numeric
601
+ column answers:
602
+
603
+ ```
604
+ One or more values are incompatible with the column type.
605
+ ```
606
+
607
+ The step fails, the flow ends at status 40, and every output variable is null.
608
+
609
+ Two things make this hard to spot from the outside. The statement looks careful - eight parameters
610
+ guarded, one not - so a reader skims past it. And the user-visible symptom is not an error message
611
+ but a false negative: a page that reads an output variable to decide what to say gets null and
612
+ reports that nothing happened, which is true but says nothing about why. Reproduce by sending
613
+ every parameter as `""`, which is what a form with untouched controls actually sends; typed values
614
+ will keep passing and hide it.
615
+
616
+ Coerce a boolean by VALUE, not by cast: `CAST('true' AS SIGNED)` is 0 in MySQL, so casting the
617
+ string a select sends would silently deactivate everything. Map it:
618
+
619
+ ```sql
620
+ CASE WHEN LOWER(CAST(@flag AS CHAR)) IN ('1','true','yes') THEN 1
621
+ WHEN LOWER(CAST(@flag AS CHAR)) IN ('0','false','no') THEN 0
622
+ ELSE <the default> END
623
+ ```
624
+
625
+ And pick the ELSE per statement, not once: on an INSERT a blank should take the form's own default,
626
+ while on an UPDATE a blank means "the control did not come through", so it must keep the stored
627
+ value (`ELSE <the column>`) - otherwise editing a name silently re-activates something the owner
628
+ had switched off. Keeping the column there needs a second chip for it, since a placeholder cannot
629
+ be repeated.
630
+
631
+ ### A related trap in the page, not the query
632
+
633
+ When a statement's result is the only thing a page inspects to decide its message, check what that
634
+ result MEANS. A chain of guarded statements where each one is a no-op unless its branch applies
635
+ will report the affected-row count of whichever node is bound to the output - typically the last
636
+ one - so a successful INSERT can report 0 while an UPDATE reports 1. A page that treats
637
+ "not null" as success is then right by accident, and a page that treats "greater than zero" as
638
+ success calls every create a failure.
639
+
640
+ ### Auditing a panel for both faults at once
641
+
642
+ Worth doing in one pass rather than discovering them a click at a time. Two checks, and the first
643
+ needs the column type or it drowns in noise:
644
+
645
+ - **Uncoerced writes.** List every `@param` sitting in an INSERT value position or an UPDATE SET,
646
+ resolve the chip it is written to, and report only those whose column is NOT a string. A string
647
+ parameter into a string column is harmless; everything else is a statement waiting for an empty
648
+ control. On a real panel this cut 56 raw hits down to 2 genuine ones, and a comparison in a
649
+ WHERE is not a write - exclude it or it reads as a false alarm.
650
+ - **New-record guards.** Find every create guarded on an id and check what the panel sends for a
651
+ NEW record. `IS NULL` only accepts an empty field; a panel that marks "new" with `0` never
652
+ inserts, and a panel that marks it with `''` works - the same codebase can contain both, so the
653
+ guard has to be read against its own page, not in the abstract. `COALESCE(..., 0) = 0` accepts
654
+ either and is the form to standardise on.
655
+
656
+ - **A data-model input.** Before auditing the statements at all, check whether the process takes a
657
+ data model as a form-facing input: a form publishes only top-level variables, so those parameters
658
+ are NULL no matter how the SQL is written. See "A form publishes only TOP-LEVEL variables" in
659
+ PROCESIO-RESOURCE-MODEL-NOTES.md. This one outranks the other two, because it makes a correct
660
+ statement look wrong.
661
+
662
+ A third pattern shows up in the same audit and is worth knowing even though it is not a failure:
663
+ when several guarded statements share one flow, only one of them is usually bound to the output,
664
+ so the page sees that statement's row count and nothing else. A create then reports 0 while
665
+ succeeding. That is survivable if the page treats "no value at all" as the failure signal, since a
666
+ genuine fault ends the flow and leaves every output unset - but it means the row count itself
667
+ carries no information, and anyone who later tests it for `> 0` turns every create into a reported
668
+ failure.
669
+
670
+ ### "It stopped showing" is a data question before it is a code question
671
+
672
+ A page that deliberately hides a block when its value is empty looks exactly like a page
673
+ that has broken. Both show nothing, and the second reading is the tempting one because
674
+ something was changed recently. Read the stored value first; it costs one query and it
675
+ decides which half of the system to open.
676
+
677
+ Two cheap checks settle it, in this order:
678
+
679
+ - **Is the value there?** If the row is empty, the renderer is probably doing its job.
680
+ Hiding a block whose text is missing is normal and correct - it stops a half-filled
681
+ settings page from showing an orphan image or a stray heading - so an empty block is
682
+ evidence about the data, not about the code.
683
+ - **Was it EVER there?** A key-value settings table carries a per-row updated timestamp,
684
+ and that is the whole history you need. Rows written seconds apart in one run are the
685
+ seed. If the fields in question still carry the seed timestamp, nothing erased them:
686
+ they were never filled, and "it used to work" is a memory of a different build. If they
687
+ carry a later timestamp than their neighbours, something did write over them, and the
688
+ timestamp says when.
689
+
690
+ The same timestamps answer a bigger question for free: **if no two keys that a single save
691
+ would touch share a timestamp, that save has never successfully run.** A panel whose save
692
+ has never run is worth knowing about before trusting it with real content.
693
+
694
+ ### A settings save erases what the page did not send
695
+
696
+ The sibling of the rule above, and the reason to check the statement before inviting
697
+ anyone to use the page. A settings panel usually writes every key in one `CASE` over the
698
+ key column. Each branch that writes its parameter straight through will blank its row
699
+ whenever that control arrives empty - and a control arrives empty for ordinary reasons: a
700
+ collapsed section, a tab the page never populated, a field the person never opened. So
701
+ pressing Save to change one field can quietly clear a dozen others.
702
+
703
+ Guard every branch, not the few that look important:
704
+
705
+ ```sql
706
+ WHEN '<key>' THEN COALESCE(NULLIF(@p<n>,''), <the value column>)
707
+ ```
708
+
709
+ A partly guarded statement is the dangerous shape, because the protected keys survive
710
+ every test and hide the behaviour of the rest. Count the branches against the key list and
711
+ make the guarded number equal the total.
712
+
713
+ The cost is that a value can no longer be cleared from the page, since a blank control and
714
+ a deliberate erase arrive identically over the wire. That is the right trade for settings:
715
+ clearing is rare and recoverable, silent mass erasure is neither. Where clearing must be
716
+ offered, give it its own explicit control rather than inferring it from emptiness.
717
+
718
+ **Verify it the way it will fail:** run the save with every field empty and diff the table
719
+ before and after. Nothing may change. That test fails loudly on a partly guarded statement
720
+ and passes on a fully guarded one, which is exactly the distinction no amount of reading
721
+ reliably catches.
722
+
723
+ ### A setting that holds a URL rots, and a dead one is worse than an empty one
724
+
725
+ The same renderer that politely hides a block with no text will happily emit an
726
+ `<img>` for a URL that no longer resolves, so the careful empty-value handling buys
727
+ nothing once a stale address is stored. Addresses taken from a marketing site or a
728
+ social network are the ones that go: a CDN path changes on the next site rebuild
729
+ (`410 Gone`), and an image served from a social profile is signed and hotlink-blocked
730
+ (`403`), so it works in the browser where it was copied and nowhere else.
731
+
732
+ Check the address before writing it, not after someone reports the page looks broken:
733
+ one request for the status, the content type and the size. Prefer an asset served from
734
+ somewhere under the owner's control, and when there is no good address, store nothing -
735
+ a block that hides itself reads as deliberate, a broken image reads as neglect.
736
+
737
+ Worth re-checking whenever restoring content from an older record: the text survives
738
+ indefinitely, the links beside it usually have not.
739
+
740
+ #### When there is no stable host, embed the image instead of linking it
741
+
742
+ A small decorative asset - a logo mark, an avatar - does not need a host. Encoded as a
743
+ `data:` URI it travels inside the setting itself, so there is no address to go stale, no
744
+ CDN to block the request, and nothing to re-point after a site rebuild. It is the honest
745
+ answer to the previous rule rather than a workaround for it.
746
+
747
+ Two things make it practical:
748
+
749
+ - **Size the image to the box it is drawn in, not to the file you were handed.** A
750
+ portrait rendered in a 56px circle needs 112px for a high-density screen and no more.
751
+ A 1.8 MB source became under 7 KB of `data:` text at that size; the same image embedded
752
+ at its original resolution would be unusable. Encode to a format with alpha that
753
+ compresses well, and the result is smaller than most of the page around it.
754
+ - **The field's own validation will refuse it.** A URL control typically carries a
755
+ pattern like `^https?://.+$`, which a `data:` URI fails, so the panel rejects a value
756
+ the renderer would have been perfectly happy with. Widen the pattern to accept both
757
+ shapes when adopting this, or the setting can only ever be written from behind the page.
758
+
759
+ Verify it the same way as a link: read the row back and compare it byte for byte with
760
+ what was sent (a long value is exactly the kind that gets truncated silently), then load
761
+ the page and check the image reports a non-zero natural size rather than merely existing.
762
+
763
+ ### A reconciler's lag reads as a broken integration
764
+
765
+ When the remote record is created by a sweep rather than at the moment of the action, the
766
+ page says "done" and the calendar, inbox or ledger stays empty for a minute or two. The
767
+ person checks straight away, sees nothing, and reports it broken. Two separate sweeps in
768
+ series double it: an end-to-end wait of two to four minutes is normal with two reconcilers
769
+ on a two-minute timer, and it is not a fault.
770
+
771
+ Before touching anything, settle it with the data: **the id the remote system returns on
772
+ creation, stored on the row, is proof the record exists.** If every row carries one, the
773
+ integration is working and the complaint is about latency. Only a row that is finished but
774
+ has no remote id is a real miss, and that distinction takes one query.
775
+
776
+ The timestamps then measure the lag directly: the gap between the row being written and
777
+ the field the sweep fills tells you what the real wait is, per booking, without
778
+ instrumenting anything.
779
+
780
+ The design choice is worth stating where users can see it. A page that promises a calendar
781
+ invitation should say it arrives within a couple of minutes, or the work should move into
782
+ the action itself. Silence about the delay is what turns a normal wait into a bug report,
783
+ and it costs nothing to fix in the confirmation text.
784
+
785
+ ### A table column can show a field the query never returned
786
+
787
+ Worth knowing before concluding a filter is ignored. A list is often painted through a
788
+ mapping step that computes display-only fields - a formatted timestamp in the reader's
789
+ zone, a label resolved from a code - which exist nowhere in the query result.
790
+
791
+ That makes a table fragile in a specific way: when several controls refill the same table
792
+ and only some of them run the mapping, the others hand over rows that are correct but
793
+ missing the one field the column renders. The column comes back blank, or the control
794
+ keeps what it had, and it reads as "the button did nothing" rather than "the button
795
+ painted unformatted rows".
796
+
797
+ So when a filter looks ignored, check in this order, because each step is cheaper than the
798
+ one after it:
799
+
800
+ 1. **The data.** A filter that matches nothing and a filter that never ran look identical
801
+ on screen. Run the query behind it with the same argument and count the rows first.
802
+ 2. **The parameter.** Whether the control's value reaches the query at all.
803
+ 3. **The repaint.** Whether the control that refills the list computes the same display
804
+ fields as the path that first filled it. Every writer of one list should go through one
805
+ shared mapping; two writers that format differently is a defect waiting for a user to
806
+ find it.
807
+
808
+ A related trap in the same family: a filter whose value is read from a hidden carrier
809
+ field rather than from the visible control. The carrier exists because the visible control
810
+ holds a different shape (a timestamp where the query wants a day), but nothing copies one
811
+ into the other unless a step is wired to do it, and a filter reading an empty carrier
812
+ silently returns everything.
813
+
814
+ #### A step that reads the run's output needs the run marked synchronous
815
+
816
+ In an event chain, the flag that makes a process run synchronously is a tri-state in
817
+ practice: true, false, and never set. Never set is not the same as true. A chain written as
818
+ "run the query, then paint what it returned" works on a fast day and paints the previous
819
+ result on a slow one, which is the worst kind of defect to chase because it reproduces
820
+ only under load.
821
+
822
+ So whenever a later step in a chain reads a variable the run fills, check the flag on the
823
+ run rather than assuming it, and set it explicitly. It costs one field and removes a whole
824
+ class of intermittent "the button shows stale data" reports. The same check is worth
825
+ running across every chain at once: a list of each run with its flag takes seconds to
826
+ produce and the odd one out is usually the one nobody set.