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.
- procesio_cli/__init__.py +18 -0
- procesio_cli/_cli.py +71 -0
- procesio_cli/agents/__init__.py +0 -0
- procesio_cli/agents/_lib/__init__.py +0 -0
- procesio_cli/agents/_lib/actiondef.py +24 -0
- procesio_cli/agents/_lib/errors.py +40 -0
- procesio_cli/agents/_lib/reversibility.py +236 -0
- procesio_cli/agents/_lib/toolrunner.py +141 -0
- procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-PLAYBOOK.md +62 -0
- procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-PROCESIO-INTEROP.md +76 -0
- procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-TROUBLESHOOTING.md +60 -0
- procesio_cli/agents/connector-builder/README.md +51 -0
- procesio_cli/agents/connector-builder/agent.yaml +61 -0
- procesio_cli/agents/connector-builder/context.py +19 -0
- procesio_cli/agents/connector-builder/handlers/__init__.py +1 -0
- procesio_cli/agents/connector-builder/handlers/checklist.py +71 -0
- procesio_cli/agents/connector-builder/handlers/guidance.py +37 -0
- procesio_cli/agents/connector-builder/handlers/nextstep.py +221 -0
- procesio_cli/agents/connector-builder/handlers/status.py +35 -0
- procesio_cli/agents/connector-builder/knowledge.py +47 -0
- procesio_cli/agents/connector-builder/main.py +93 -0
- procesio_cli/agents/connector-builder/tests/conftest.py +95 -0
- procesio_cli/agents/connector-builder/tests/test_cba_agent.py +148 -0
- procesio_cli/agents/orchestrator/__init__.py +0 -0
- procesio_cli/agents/orchestrator/bootstraplib.py +166 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/00-INDEX.md +60 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/01-PROCESSES.md +230 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/02-FORMS.md +287 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/03-INTEGRATIONS.md +384 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/04-DATABASE.md +293 -0
- procesio_cli/agents/procesio/PROCESIO-API-RELIABILITY-DOCTRINE.md +113 -0
- procesio_cli/agents/procesio/PROCESIO-BEST-PRACTICES.md +316 -0
- procesio_cli/agents/procesio/PROCESIO-BUILD-AND-TEST-PLAYBOOK.md +217 -0
- procesio_cli/agents/procesio/PROCESIO-DATASTORE.md +72 -0
- procesio_cli/agents/procesio/PROCESIO-ENVIRONMENTS.md +62 -0
- procesio_cli/agents/procesio/PROCESIO-SCHEDULING.md +47 -0
- procesio_cli/agents/procesio/PROCESIO-VISUAL-ORGANIZATION.md +175 -0
- procesio_cli/agents/procesio/README.md +97 -0
- procesio_cli/agents/procesio/__init__.py +0 -0
- procesio_cli/agents/procesio/agent.yaml +73 -0
- procesio_cli/agents/procesio/audit.py +226 -0
- procesio_cli/agents/procesio/checklist.py +135 -0
- procesio_cli/agents/procesio/context.py +19 -0
- procesio_cli/agents/procesio/handlers/__init__.py +0 -0
- procesio_cli/agents/procesio/handlers/audit.py +39 -0
- procesio_cli/agents/procesio/handlers/checklist.py +34 -0
- procesio_cli/agents/procesio/handlers/guidance.py +36 -0
- procesio_cli/agents/procesio/handlers/status.py +33 -0
- procesio_cli/agents/procesio/handlers/verify.py +39 -0
- procesio_cli/agents/procesio/knowledge.py +115 -0
- procesio_cli/agents/procesio/main.py +89 -0
- procesio_cli/agents/procesio/tests/__init__.py +0 -0
- procesio_cli/agents/procesio/tests/conftest.py +62 -0
- procesio_cli/agents/procesio/tests/test_audit.py +91 -0
- procesio_cli/agents/procesio/tests/test_checklist.py +28 -0
- procesio_cli/agents/procesio/tests/test_guidance.py +78 -0
- procesio_cli/agents/procesio/tests/test_manifest_sync.py +48 -0
- procesio_cli/agents/procesio/tests/test_verify.py +134 -0
- procesio_cli/agents/procesio/tests/test_workspace_scope.py +97 -0
- procesio_cli/agents/procesio/verifylib.py +222 -0
- procesio_cli/dashboard/README.md +117 -0
- procesio_cli/dashboard/__init__.py +18 -0
- procesio_cli/dashboard/serve.py +98 -0
- procesio_cli/dashboard/server/__init__.py +1 -0
- procesio_cli/dashboard/server/config_schemas.py +92 -0
- procesio_cli/dashboard/server/google.py +103 -0
- procesio_cli/dashboard/server/handler.py +160 -0
- procesio_cli/dashboard/server/inventory.py +213 -0
- procesio_cli/dashboard/server/jobs.py +118 -0
- procesio_cli/dashboard/server/llmtest.py +181 -0
- procesio_cli/dashboard/server/procesio_env.py +94 -0
- procesio_cli/dashboard/server/routes.py +74 -0
- procesio_cli/dashboard/server/runner.py +150 -0
- procesio_cli/dashboard/server/security.py +29 -0
- procesio_cli/dashboard/server/setup.py +211 -0
- procesio_cli/dashboard/server/validate.py +152 -0
- procesio_cli/dashboard/tests/conftest.py +16 -0
- procesio_cli/dashboard/tests/test_dashboard.py +225 -0
- procesio_cli/dashboard/tests/test_procesio_env.py +159 -0
- procesio_cli/dashboard/web/actions.js +274 -0
- procesio_cli/dashboard/web/api.js +79 -0
- procesio_cli/dashboard/web/app.js +385 -0
- procesio_cli/dashboard/web/copilot.js +72 -0
- procesio_cli/dashboard/web/google.js +121 -0
- procesio_cli/dashboard/web/index.html +67 -0
- procesio_cli/dashboard/web/procesio_env.js +187 -0
- procesio_cli/dashboard/web/styles.css +207 -0
- procesio_cli/registry.py +370 -0
- procesio_cli/scripts/build-router.py +249 -0
- procesio_cli/scripts/build-tool-skill.py +202 -0
- procesio_cli/scripts/check-requirements.py +131 -0
- procesio_cli/scripts/check-skill-governance.py +174 -0
- procesio_cli/scripts/evaluate-skill-routing.py +217 -0
- procesio_cli/scripts/evaluate-skills.py +316 -0
- procesio_cli/scripts/get-skill.py +122 -0
- procesio_cli/scripts/list-skills.py +68 -0
- procesio_cli/scripts/list-tools.py +73 -0
- procesio_cli/scripts/run-agent.py +59 -0
- procesio_cli/scripts/run-tool.py +123 -0
- procesio_cli/scripts/secret_scan.py +584 -0
- procesio_cli/scripts/set-credential.py +126 -0
- procesio_cli/scripts/validate-skills.py +530 -0
- procesio_cli/skills/PORTFOLIO.md +134 -0
- procesio_cli/skills/__init__.py +6 -0
- procesio_cli/skills/agent-skill-engineer/SKILL.md +226 -0
- procesio_cli/skills/agent-skill-engineer/evals/evals.json +422 -0
- procesio_cli/skills/agent-skill-engineer/references/authoring-standard.md +178 -0
- procesio_cli/skills/agent-skill-engineer/references/decision-framework.md +107 -0
- procesio_cli/skills/agent-skill-engineer/references/domain-evidence-protocol.md +248 -0
- procesio_cli/skills/agent-skill-engineer/references/evaluation-standard.md +191 -0
- procesio_cli/skills/agent-skill-engineer/references/expert-lenses.md +167 -0
- procesio_cli/skills/agent-skill-engineer/references/field-gate-standard.md +231 -0
- procesio_cli/skills/agent-skill-engineer/references/optimization-protocol.md +233 -0
- procesio_cli/skills/agent-skill-engineer/references/optimizer-contract.md +268 -0
- procesio_cli/skills/agent-skill-engineer/references/portability-standard.md +99 -0
- procesio_cli/skills/agent-skill-engineer/references/research-basis.md +128 -0
- procesio_cli/skills/agent-skill-engineer/references/review-standard.md +143 -0
- procesio_cli/skills/agent-skill-engineer/scripts/audit_skill.py +959 -0
- procesio_cli/skills/agent-skill-engineer/scripts/optimize_skill.py +891 -0
- procesio_cli/skills/agent-skill-engineer/scripts/scaffold_skill.py +397 -0
- procesio_cli/skills/evals/baseline-catalog.json +14 -0
- procesio_cli/skills/evals/baseline-routing-v2.json +38 -0
- procesio_cli/skills/evals/baseline.json +37 -0
- procesio_cli/skills/evals/behavioral.json +367 -0
- procesio_cli/skills/evals/field-boundary-development-v1.json +126 -0
- procesio_cli/skills/evals/field-closeout-correction-development-v1.json +23 -0
- procesio_cli/skills/evals/field-closeout-development-v1.json +89 -0
- procesio_cli/skills/evals/portfolio-pressure-training-v1.json +83 -0
- procesio_cli/skills/evals/routing.json +363 -0
- procesio_cli/skills/evals/validation-baseline.json +4 -0
- procesio_cli/skills/procesio-cli/SKILL.md +134 -0
- procesio_cli/skills/procesio-cli/evals/evals.json +111 -0
- procesio_cli/skills/procesio-cli/evals/workflows.json +16 -0
- procesio_cli/skills/procesio-cli/references/connector-lifecycle.md +47 -0
- procesio_cli/skills/procesio-cli/references/credentials-admin.md +113 -0
- procesio_cli/skills/procesio-cli/references/data-verification.md +71 -0
- procesio_cli/skills/procesio-cli/references/documents-files.md +44 -0
- procesio_cli/skills/procesio-cli/references/form-e2e.md +57 -0
- procesio_cli/skills/procesio-cli/references/operation-contract.md +138 -0
- procesio_cli/skills/procesio-cli/references/process-debugging.md +65 -0
- procesio_cli/skills/procesio-cli/references/process-lifecycle.md +88 -0
- procesio_cli/skills/procesio-cli/references/schedules-webhooks.md +58 -0
- procesio_cli/skills/procesio-cli/references/transport-environments.md +53 -0
- procesio_cli/skills/procesio-cli-maintainer/SKILL.md +84 -0
- procesio_cli/skills/procesio-cli-maintainer/evals/evals.json +116 -0
- procesio_cli/skills/procesio-cli-maintainer/references/change-contract.md +34 -0
- procesio_cli/skills/procesio-cli-maintainer/references/mcp-compatibility.md +13 -0
- procesio_cli/skills/procesio-cli-maintainer/references/skill-authoring.md +51 -0
- procesio_cli/skills/procesio-platform-advisor/SKILL.md +82 -0
- procesio_cli/skills/procesio-platform-advisor/evals/evals.json +116 -0
- procesio_cli/skills/procesio-platform-advisor/references/capability-map.md +22 -0
- procesio_cli/skills/procesio-platform-advisor/references/fit-framework.md +35 -0
- procesio_cli/skills/procesio-platform-advisor/references/freshness-policy.md +25 -0
- procesio_cli/skills/procesio-platform-advisor/references/sizing-method.md +70 -0
- procesio_cli/skills/procesio-platform-advisor/scripts/estimate_capacity.py +99 -0
- procesio_cli/skills/procesio-setup/SKILL.md +180 -0
- procesio_cli/skills/sql-server-optimizer/SKILL.md +86 -0
- procesio_cli/skills/sql-server-optimizer/evals/evals.json +116 -0
- procesio_cli/skills/sql-server-optimizer/references/db-context.md +44 -0
- procesio_cli/skills/sql-server-optimizer/references/generic-mode.md +32 -0
- procesio_cli/skills/sql-server-optimizer/references/procesio-mode.md +40 -0
- procesio_cli/skills/sql-server-optimizer/references/sql-rules.md +43 -0
- procesio_cli/skills/sql-server-optimizer/scripts/export-indexes.sql +178 -0
- procesio_cli/skills/sql-server-optimizer/scripts/export-procs-and-functions.sql +118 -0
- procesio_cli/skills/sql-server-optimizer/scripts/export-tables.sql +367 -0
- procesio_cli/skills/tests/__init__.py +0 -0
- procesio_cli/skills/tests/test_agent_skill_engineer.py +240 -0
- procesio_cli/skills/tests/test_auth_failure_stop.py +25 -0
- procesio_cli/skills/tests/test_capacity_estimator.py +85 -0
- procesio_cli/skills/tests/test_field_boundary_learning.py +100 -0
- procesio_cli/skills/tests/test_field_closeout_learning.py +123 -0
- procesio_cli/skills/tests/test_field_gate_standard.py +76 -0
- procesio_cli/skills/tests/test_operational_playbooks.py +51 -0
- procesio_cli/skills/tests/test_portfolio_pressure_training.py +35 -0
- procesio_cli/skills/tests/test_process_lifecycle_single_run.py +25 -0
- procesio_cli/skills/tests/test_skill_evaluator.py +67 -0
- procesio_cli/skills/tests/test_skill_optimizer.py +473 -0
- procesio_cli/skills/tests/test_skill_portfolio_review.py +70 -0
- procesio_cli/skills/tests/test_skill_release_binding.py +204 -0
- procesio_cli/skills/tests/test_skill_routing.py +74 -0
- procesio_cli/skills/tests/test_skill_scaffold_v2.py +116 -0
- procesio_cli/skills/tests/test_validate_skills.py +115 -0
- procesio_cli/tools/__init__.py +0 -0
- procesio_cli/tools/_lib/README.md +119 -0
- procesio_cli/tools/_lib/__init__.py +0 -0
- procesio_cli/tools/_lib/accounting.py +120 -0
- procesio_cli/tools/_lib/contacts.py +544 -0
- procesio_cli/tools/_lib/creds.py +83 -0
- procesio_cli/tools/_lib/creds_backends.py +578 -0
- procesio_cli/tools/_lib/dbprofiles.py +147 -0
- procesio_cli/tools/_lib/dbquery.py +286 -0
- procesio_cli/tools/_lib/dbschema.py +225 -0
- procesio_cli/tools/_lib/io.py +54 -0
- procesio_cli/tools/_lib/manifest.py +297 -0
- procesio_cli/tools/_lib/skill_release.py +127 -0
- procesio_cli/tools/_lib/skill_resources.py +118 -0
- procesio_cli/tools/_lib/userdata.py +228 -0
- procesio_cli/tools/_lib/webserialize/__init__.py +16 -0
- procesio_cli/tools/_lib/webserialize/admin.py +61 -0
- procesio_cli/tools/_lib/webserialize/client.py +264 -0
- procesio_cli/tools/_lib/webserialize/config.py +96 -0
- procesio_cli/tools/_lib/webserialize/errors.py +27 -0
- procesio_cli/tools/_lib/webserialize/framing.py +45 -0
- procesio_cli/tools/_lib/webserialize/registry.py +47 -0
- procesio_cli/tools/_lib/webserialize/runtime.py +61 -0
- procesio_cli/tools/_lib/webserialize/server.py +510 -0
- procesio_cli/tools/_lib/websession.py +194 -0
- procesio_cli/tools/connector-builder/CONNECTOR-BUILDER-API-NOTES.md +91 -0
- procesio_cli/tools/connector-builder/README.md +101 -0
- procesio_cli/tools/connector-builder/SKILL.md +248 -0
- procesio_cli/tools/connector-builder/__init__.py +7 -0
- procesio_cli/tools/connector-builder/actiondef.py +24 -0
- procesio_cli/tools/connector-builder/client.py +251 -0
- procesio_cli/tools/connector-builder/errors.py +44 -0
- procesio_cli/tools/connector-builder/gen_manifest.py +142 -0
- procesio_cli/tools/connector-builder/handlers/__init__.py +1 -0
- procesio_cli/tools/connector-builder/handlers/admin.py +182 -0
- procesio_cli/tools/connector-builder/handlers/auth.py +74 -0
- procesio_cli/tools/connector-builder/handlers/builds.py +396 -0
- procesio_cli/tools/connector-builder/handlers/common.py +104 -0
- procesio_cli/tools/connector-builder/handlers/files.py +111 -0
- procesio_cli/tools/connector-builder/handlers/raw.py +38 -0
- procesio_cli/tools/connector-builder/handlers/telemetry.py +39 -0
- procesio_cli/tools/connector-builder/main.py +94 -0
- procesio_cli/tools/connector-builder/tests/conftest.py +146 -0
- procesio_cli/tools/connector-builder/tests/test_cb_client.py +86 -0
- procesio_cli/tools/connector-builder/tests/test_cb_dispatch.py +105 -0
- procesio_cli/tools/connector-builder/tests/test_cb_manifest_sync.py +76 -0
- procesio_cli/tools/connector-builder/tests/test_cb_wait.py +123 -0
- procesio_cli/tools/connector-builder/tool.yaml +671 -0
- procesio_cli/tools/framework-map/.gitattributes +2 -0
- procesio_cli/tools/framework-map/README.md +98 -0
- procesio_cli/tools/framework-map/SKILL.md +30 -0
- procesio_cli/tools/framework-map/app.js +188 -0
- procesio_cli/tools/framework-map/fm_builder.py +476 -0
- procesio_cli/tools/framework-map/fm_catalog.py +19 -0
- procesio_cli/tools/framework-map/fm_ro_catalog.py +94 -0
- procesio_cli/tools/framework-map/fm_strings.py +101 -0
- procesio_cli/tools/framework-map/main.py +95 -0
- procesio_cli/tools/framework-map/styles.css +214 -0
- procesio_cli/tools/framework-map/tests/conftest.py +33 -0
- procesio_cli/tools/framework-map/tests/test_fm_build.py +126 -0
- procesio_cli/tools/framework-map/tests/test_fm_manifest_sync.py +63 -0
- procesio_cli/tools/framework-map/tests/test_fm_store_tree.py +75 -0
- procesio_cli/tools/framework-map/tool.yaml +61 -0
- procesio_cli/tools/mysql/MOVED-TO-CENTRAL.md +29 -0
- procesio_cli/tools/mysql/README.md +196 -0
- procesio_cli/tools/mysql/SKILL.md +37 -0
- procesio_cli/tools/mysql/__init__.py +0 -0
- procesio_cli/tools/mysql/connection.py +191 -0
- procesio_cli/tools/mysql/main.py +487 -0
- procesio_cli/tools/mysql/profiles.template.json +1 -0
- procesio_cli/tools/mysql/schema.py +434 -0
- procesio_cli/tools/mysql/tests/__init__.py +0 -0
- procesio_cli/tools/mysql/tests/conftest.py +21 -0
- procesio_cli/tools/mysql/tests/test_manifest_sync.py +68 -0
- procesio_cli/tools/mysql/tests/test_mysql.py +201 -0
- procesio_cli/tools/mysql/tests/test_mysql_schema.py +337 -0
- procesio_cli/tools/mysql/tool.yaml +112 -0
- procesio_cli/tools/procesio/DTO-SUBTOOLS-NOTE.md +360 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/00-INDEX.md +67 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/01-ANATOMY.md +209 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/02-CODE-INJECTION.md +309 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/03-DOM-CONTRACT.md +370 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/04-INTERACTION-RECIPES.md +600 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/05-STEPPER-AND-MOTION.md +426 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/06-PROCESS-INTEGRATION.md +347 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/07-DEPLOY-WORKFLOW.md +294 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/08-PITFALLS.md +569 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/09-NAVIGATION-AND-HOSTING.md +308 -0
- procesio_cli/tools/procesio/PHASE4-E2E-NOTES.md +594 -0
- procesio_cli/tools/procesio/PROCESIO-API-CORRECTIONS.md +295 -0
- procesio_cli/tools/procesio/PROCESIO-API-NOTES.md +6976 -0
- procesio_cli/tools/procesio/PROCESIO-AUTH-NOTES.md +226 -0
- procesio_cli/tools/procesio/PROCESIO-CARD-BUILD-NOTES.md +183 -0
- procesio_cli/tools/procesio/PROCESIO-CONNECTORS-NOTES.md +141 -0
- procesio_cli/tools/procesio/PROCESIO-CUSTOM-ACTION-NOTES.md +179 -0
- procesio_cli/tools/procesio/PROCESIO-DOCS-FIX-REPORT.md +123 -0
- procesio_cli/tools/procesio/PROCESIO-ENVIRONMENTS-NOTES.md +94 -0
- procesio_cli/tools/procesio/PROCESIO-FE-VALIDATION-NOTES.md +107 -0
- procesio_cli/tools/procesio/PROCESIO-FOREACH-NOTES.md +199 -0
- procesio_cli/tools/procesio/PROCESIO-FORM-API-HANG-NOTE.md +101 -0
- procesio_cli/tools/procesio/PROCESIO-FORM-SUBMISSION-NOTES.md +172 -0
- procesio_cli/tools/procesio/PROCESIO-HTML-TO-DOCX-NOTES.md +110 -0
- procesio_cli/tools/procesio/PROCESIO-INSTANCE-HISTORY-NOTES.md +242 -0
- procesio_cli/tools/procesio/PROCESIO-METERING-NOTES.md +201 -0
- procesio_cli/tools/procesio/PROCESIO-NODE-CODE-NOTES.md +279 -0
- procesio_cli/tools/procesio/PROCESIO-NODE-MODULE-WHITELIST.md +113 -0
- procesio_cli/tools/procesio/PROCESIO-PYTHON-ACTION-NOTES.md +165 -0
- procesio_cli/tools/procesio/PROCESIO-QUERY-STORE-NOTES.md +826 -0
- procesio_cli/tools/procesio/PROCESIO-RECONCILIATION-PATTERNS.md +104 -0
- procesio_cli/tools/procesio/PROCESIO-RESOURCE-MODEL-NOTES.md +1100 -0
- procesio_cli/tools/procesio/PROCESIO-SEND-EMAIL-CRED-OPTIONALITY.md +45 -0
- procesio_cli/tools/procesio/PROCESIO-SEND-EMAIL-NOTES.md +252 -0
- procesio_cli/tools/procesio/PROCESIO-SQL-ACTIONS-NOTES.md +284 -0
- procesio_cli/tools/procesio/PROCESIO-USAGE-GUIDE.md +219 -0
- procesio_cli/tools/procesio/README.md +341 -0
- procesio_cli/tools/procesio/SCHEDULE-INPUT-SECURITY-NOTES.md +63 -0
- procesio_cli/tools/procesio/SKILL.md +694 -0
- procesio_cli/tools/procesio/__init__.py +1 -0
- procesio_cli/tools/procesio/actiondef.py +27 -0
- procesio_cli/tools/procesio/assets/html2docx/html2docx.js +2155 -0
- procesio_cli/tools/procesio/auth.py +354 -0
- procesio_cli/tools/procesio/autogen.py +121 -0
- procesio_cli/tools/procesio/client.py +355 -0
- procesio_cli/tools/procesio/config.py +96 -0
- procesio_cli/tools/procesio/data/endpoints.json +2901 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/01-authentication.md +180 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/02-conventions.md +116 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/ANONYMOUS-SURFACE.md +51 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/README.md +104 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/SYMBOL-INDEX.md +444 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/03-auth-users-workspaces.md +731 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/04-processes.md +913 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/05-actions.md +605 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/06-files.md +237 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/07-forms-documents.md +985 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/08-credentials.md +532 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/09-datastore-datatypes.md +830 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/10-custom-urls.md +319 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/11-subscriptions-resources-analytics.md +455 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/12-webhooks-notifications-misc.md +519 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/13-realtime-signalr.md +153 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/99-internal-and-other-services.md +52 -0
- procesio_cli/tools/procesio/docs_info/FormBuilder mock.ts +5725 -0
- procesio_cli/tools/procesio/docs_info/Procesio Platform Actions.json +30219 -0
- procesio_cli/tools/procesio/docs_info/form-config-enums.ts +92 -0
- procesio_cli/tools/procesio/docs_info/form-theme.ts +1603 -0
- procesio_cli/tools/procesio/docs_info/process-validation-reference-1.md +2739 -0
- procesio_cli/tools/procesio/dto/__init__.py +13 -0
- procesio_cli/tools/procesio/dto/credential/builder.py +179 -0
- procesio_cli/tools/procesio/dto/credential/config.schema.json +19 -0
- procesio_cli/tools/procesio/dto/credential/description.md +193 -0
- procesio_cli/tools/procesio/dto/credential/fixtures/rest.config.json +11 -0
- procesio_cli/tools/procesio/dto/credential/fixtures/rest.dto.json +28 -0
- procesio_cli/tools/procesio/dto/credential/fixtures/rest.template.json +44 -0
- procesio_cli/tools/procesio/dto/credential/template.dto.json +16 -0
- procesio_cli/tools/procesio/dto/data/_refresh.py +147 -0
- procesio_cli/tools/procesio/dto/data/action_catalog.json +30825 -0
- procesio_cli/tools/procesio/dto/data/action_families.json +55 -0
- procesio_cli/tools/procesio/dto/data/platform_types.json +182 -0
- procesio_cli/tools/procesio/dto/datatype/builder.py +223 -0
- procesio_cli/tools/procesio/dto/datatype/config.schema.json +47 -0
- procesio_cli/tools/procesio/dto/datatype/description.md +85 -0
- procesio_cli/tools/procesio/dto/datatype/fixtures/customer.config.json +41 -0
- procesio_cli/tools/procesio/dto/datatype/fixtures/customer.dto.json +164 -0
- procesio_cli/tools/procesio/dto/datatype/template.dto.json +37 -0
- procesio_cli/tools/procesio/dto/document/builder.py +193 -0
- procesio_cli/tools/procesio/dto/document/config.schema.json +33 -0
- procesio_cli/tools/procesio/dto/document/description.md +124 -0
- procesio_cli/tools/procesio/dto/document/fixtures/invoice.config.json +16 -0
- procesio_cli/tools/procesio/dto/document/fixtures/invoice.dto.json +32 -0
- procesio_cli/tools/procesio/dto/document/template.dto.json +14 -0
- procesio_cli/tools/procesio/dto/form/FORM-STYLING-NOTES.md +142 -0
- procesio_cli/tools/procesio/dto/form/GOLDEN-DRIFT-NOTES.md +72 -0
- procesio_cli/tools/procesio/dto/form/RUN-PROCESS-RESULT-NOTES.md +80 -0
- procesio_cli/tools/procesio/dto/form/addelement.py +277 -0
- procesio_cli/tools/procesio/dto/form/builder.py +1009 -0
- procesio_cli/tools/procesio/dto/form/capture_goldens.py +106 -0
- procesio_cli/tools/procesio/dto/form/code_cipher.py +67 -0
- procesio_cli/tools/procesio/dto/form/config.schema.json +76 -0
- procesio_cli/tools/procesio/dto/form/data_shell.json +1 -0
- procesio_cli/tools/procesio/dto/form/description.md +346 -0
- procesio_cli/tools/procesio/dto/form/elements/approval.json +259 -0
- procesio_cli/tools/procesio/dto/form/elements/assignee.json +137 -0
- procesio_cli/tools/procesio/dto/form/elements/button.json +125 -0
- procesio_cli/tools/procesio/dto/form/elements/chart.json +341 -0
- procesio_cli/tools/procesio/dto/form/elements/chat.json +199 -0
- procesio_cli/tools/procesio/dto/form/elements/checkbox.json +153 -0
- procesio_cli/tools/procesio/dto/form/elements/column.json +77 -0
- procesio_cli/tools/procesio/dto/form/elements/columns.json +78 -0
- procesio_cli/tools/procesio/dto/form/elements/datetime-input.json +304 -0
- procesio_cli/tools/procesio/dto/form/elements/divider.json +54 -0
- procesio_cli/tools/procesio/dto/form/elements/dropdown.json +130 -0
- procesio_cli/tools/procesio/dto/form/elements/dynamic-table-row.json +253 -0
- procesio_cli/tools/procesio/dto/form/elements/file-upload.json +196 -0
- procesio_cli/tools/procesio/dto/form/elements/file-viewer.json +169 -0
- procesio_cli/tools/procesio/dto/form/elements/heading.json +79 -0
- procesio_cli/tools/procesio/dto/form/elements/icon.json +107 -0
- procesio_cli/tools/procesio/dto/form/elements/image.json +174 -0
- procesio_cli/tools/procesio/dto/form/elements/input.json +206 -0
- procesio_cli/tools/procesio/dto/form/elements/list.json +202 -0
- procesio_cli/tools/procesio/dto/form/elements/number-input.json +202 -0
- procesio_cli/tools/procesio/dto/form/elements/paragraph.json +79 -0
- procesio_cli/tools/procesio/dto/form/elements/radiobox.json +174 -0
- procesio_cli/tools/procesio/dto/form/elements/section.json +114 -0
- procesio_cli/tools/procesio/dto/form/elements/select.json +217 -0
- procesio_cli/tools/procesio/dto/form/elements/side-panel.json +146 -0
- procesio_cli/tools/procesio/dto/form/elements/signature-pad.json +141 -0
- procesio_cli/tools/procesio/dto/form/elements/static-table-row.json +84 -0
- procesio_cli/tools/procesio/dto/form/elements/step.json +105 -0
- procesio_cli/tools/procesio/dto/form/elements/stepper.json +126 -0
- procesio_cli/tools/procesio/dto/form/elements/tab.json +77 -0
- procesio_cli/tools/procesio/dto/form/elements/table.json +279 -0
- procesio_cli/tools/procesio/dto/form/elements/tabs.json +114 -0
- procesio_cli/tools/procesio/dto/form/elements/textarea.json +213 -0
- procesio_cli/tools/procesio/dto/form/enum_map.json +68 -0
- procesio_cli/tools/procesio/dto/form/fieldpath.py +284 -0
- procesio_cli/tools/procesio/dto/form/fixtures/reg.config.json +32 -0
- procesio_cli/tools/procesio/dto/form/fixtures/reg.elements.json +638 -0
- procesio_cli/tools/procesio/dto/form/sync_from_mock.py +386 -0
- procesio_cli/tools/procesio/dto/framework.py +125 -0
- procesio_cli/tools/procesio/dto/process/builder.py +1991 -0
- procesio_cli/tools/procesio/dto/process/config.schema.json +159 -0
- procesio_cli/tools/procesio/dto/process/description.md +218 -0
- procesio_cli/tools/procesio/dto/process/fixtures/greet.config.json +32 -0
- procesio_cli/tools/procesio/dto/process/fixtures/greet.dto.json +317 -0
- procesio_cli/tools/procesio/dto/process/naming.py +305 -0
- procesio_cli/tools/procesio/dto/process/normalize.py +74 -0
- procesio_cli/tools/procesio/dto/process/template.dto.json +32 -0
- procesio_cli/tools/procesio/dto/refdata.py +87 -0
- procesio_cli/tools/procesio/dto/registry.py +27 -0
- procesio_cli/tools/procesio/dto/webhook/builder.py +142 -0
- procesio_cli/tools/procesio/dto/webhook/config.schema.json +25 -0
- procesio_cli/tools/procesio/dto/webhook/description.md +92 -0
- procesio_cli/tools/procesio/dto/webhook/fixtures/order.config.json +10 -0
- procesio_cli/tools/procesio/dto/webhook/fixtures/order.dto.json +25 -0
- procesio_cli/tools/procesio/dto/webhook/template.dto.json +14 -0
- procesio_cli/tools/procesio/environments.py +264 -0
- procesio_cli/tools/procesio/errors.py +166 -0
- procesio_cli/tools/procesio/flowmodel/README.md +37 -0
- procesio_cli/tools/procesio/flowmodel/__init__.py +14 -0
- procesio_cli/tools/procesio/flowmodel/digest.py +223 -0
- procesio_cli/tools/procesio/flowmodel/families.py +34 -0
- procesio_cli/tools/procesio/flowmodel/fevalidation.py +1098 -0
- procesio_cli/tools/procesio/flowmodel/formdigest.py +232 -0
- procesio_cli/tools/procesio/flowmodel/inspect.py +78 -0
- procesio_cli/tools/procesio/flowmodel/model.py +82 -0
- procesio_cli/tools/procesio/flowmodel/nodeparam.py +871 -0
- procesio_cli/tools/procesio/flowmodel/reader.py +191 -0
- procesio_cli/tools/procesio/flowmodel/sqlparam.py +410 -0
- procesio_cli/tools/procesio/form_code_keys.py +50 -0
- procesio_cli/tools/procesio/formlint.py +292 -0
- procesio_cli/tools/procesio/gen_manifest.py +118 -0
- procesio_cli/tools/procesio/handlers/__init__.py +1 -0
- procesio_cli/tools/procesio/handlers/auth_actions.py +103 -0
- procesio_cli/tools/procesio/handlers/catalog.py +147 -0
- procesio_cli/tools/procesio/handlers/common.py +90 -0
- procesio_cli/tools/procesio/handlers/customaction_ops.py +146 -0
- procesio_cli/tools/procesio/handlers/datastore_ops.py +482 -0
- procesio_cli/tools/procesio/handlers/datatype_ops.py +275 -0
- procesio_cli/tools/procesio/handlers/dto_actions.py +258 -0
- procesio_cli/tools/procesio/handlers/environment_admin.py +146 -0
- procesio_cli/tools/procesio/handlers/fevalidate.py +236 -0
- procesio_cli/tools/procesio/handlers/files.py +141 -0
- procesio_cli/tools/procesio/handlers/flowgraph.py +229 -0
- procesio_cli/tools/procesio/handlers/flowlint.py +269 -0
- procesio_cli/tools/procesio/handlers/form_chains.py +336 -0
- procesio_cli/tools/procesio/handlers/form_code.py +277 -0
- procesio_cli/tools/procesio/handlers/form_element.py +199 -0
- procesio_cli/tools/procesio/handlers/form_events.py +441 -0
- procesio_cli/tools/procesio/handlers/form_update.py +270 -0
- procesio_cli/tools/procesio/handlers/formprocess.py +211 -0
- procesio_cli/tools/procesio/handlers/generic.py +135 -0
- procesio_cli/tools/procesio/handlers/layout.py +137 -0
- procesio_cli/tools/procesio/handlers/nodeparams.py +1018 -0
- procesio_cli/tools/procesio/handlers/process_layout.py +189 -0
- procesio_cli/tools/procesio/handlers/process_naming.py +213 -0
- procesio_cli/tools/procesio/handlers/processes.py +416 -0
- procesio_cli/tools/procesio/handlers/profile_admin.py +184 -0
- procesio_cli/tools/procesio/handlers/resource_ops.py +447 -0
- procesio_cli/tools/procesio/handlers/schedules.py +320 -0
- procesio_cli/tools/procesio/handlers/sqlactions.py +232 -0
- procesio_cli/tools/procesio/handlers/transport.py +463 -0
- procesio_cli/tools/procesio/handlers/usageguide.py +222 -0
- procesio_cli/tools/procesio/layout/README.md +196 -0
- procesio_cli/tools/procesio/layout/__init__.py +11 -0
- procesio_cli/tools/procesio/layout/adapter.py +736 -0
- procesio_cli/tools/procesio/layout/cycles.py +186 -0
- procesio_cli/tools/procesio/layout/dispatch.py +39 -0
- procesio_cli/tools/procesio/layout/elk/elk_runner.mjs +50 -0
- procesio_cli/tools/procesio/layout/elk/package-lock.json +21 -0
- procesio_cli/tools/procesio/layout/elk/package.json +11 -0
- procesio_cli/tools/procesio/layout/elk_engine.py +568 -0
- procesio_cli/tools/procesio/layout/engine.py +1311 -0
- procesio_cli/tools/procesio/layout/report.py +187 -0
- procesio_cli/tools/procesio/main.py +179 -0
- procesio_cli/tools/procesio/profiles.py +193 -0
- procesio_cli/tools/procesio/reliability.py +193 -0
- procesio_cli/tools/procesio/sqlformat.py +167 -0
- procesio_cli/tools/procesio/swagger.py +51 -0
- procesio_cli/tools/procesio/tests/__init__.py +0 -0
- procesio_cli/tools/procesio/tests/conftest.py +129 -0
- procesio_cli/tools/procesio/tests/test_auth.py +200 -0
- procesio_cli/tools/procesio/tests/test_autogen.py +78 -0
- procesio_cli/tools/procesio/tests/test_builder_attrpath_sqlbind.py +180 -0
- procesio_cli/tools/procesio/tests/test_builder_custom_action_catalog.py +72 -0
- procesio_cli/tools/procesio/tests/test_client.py +114 -0
- procesio_cli/tools/procesio/tests/test_client_reliability.py +139 -0
- procesio_cli/tools/procesio/tests/test_customaction.py +103 -0
- procesio_cli/tools/procesio/tests/test_customaction_test.py +80 -0
- procesio_cli/tools/procesio/tests/test_cycles.py +69 -0
- procesio_cli/tools/procesio/tests/test_datastore.py +378 -0
- procesio_cli/tools/procesio/tests/test_datastore_node.py +94 -0
- procesio_cli/tools/procesio/tests/test_dispatch.py +170 -0
- procesio_cli/tools/procesio/tests/test_document_page_size.py +19 -0
- procesio_cli/tools/procesio/tests/test_dto.py +734 -0
- procesio_cli/tools/procesio/tests/test_edit_dryrun_preview.py +96 -0
- procesio_cli/tools/procesio/tests/test_elk_layout.py +221 -0
- procesio_cli/tools/procesio/tests/test_environments.py +214 -0
- procesio_cli/tools/procesio/tests/test_fevalidate_gate.py +104 -0
- procesio_cli/tools/procesio/tests/test_fevalidation.py +317 -0
- procesio_cli/tools/procesio/tests/test_fieldpath_variables.py +87 -0
- procesio_cli/tools/procesio/tests/test_files.py +72 -0
- procesio_cli/tools/procesio/tests/test_flow_digest.py +202 -0
- procesio_cli/tools/procesio/tests/test_flowlint.py +114 -0
- procesio_cli/tools/procesio/tests/test_flowmodel.py +158 -0
- procesio_cli/tools/procesio/tests/test_form_add_element.py +256 -0
- procesio_cli/tools/procesio/tests/test_form_approval_gaps.py +319 -0
- procesio_cli/tools/procesio/tests/test_form_auto_names.py +60 -0
- procesio_cli/tools/procesio/tests/test_form_chains.py +217 -0
- procesio_cli/tools/procesio/tests/test_form_code_actions.py +188 -0
- procesio_cli/tools/procesio/tests/test_form_code_keys.py +92 -0
- procesio_cli/tools/procesio/tests/test_form_code_style.py +178 -0
- procesio_cli/tools/procesio/tests/test_form_delete_element.py +99 -0
- procesio_cli/tools/procesio/tests/test_form_element_config.py +141 -0
- procesio_cli/tools/procesio/tests/test_form_event_maps.py +135 -0
- procesio_cli/tools/procesio/tests/test_form_event_ops.py +376 -0
- procesio_cli/tools/procesio/tests/test_form_event_replace.py +86 -0
- procesio_cli/tools/procesio/tests/test_form_events.py +92 -0
- procesio_cli/tools/procesio/tests/test_form_parity.py +171 -0
- procesio_cli/tools/procesio/tests/test_form_select_source.py +74 -0
- procesio_cli/tools/procesio/tests/test_form_sync.py +35 -0
- procesio_cli/tools/procesio/tests/test_form_update.py +133 -0
- procesio_cli/tools/procesio/tests/test_formlint.py +155 -0
- procesio_cli/tools/procesio/tests/test_formlint_css_scope.py +38 -0
- procesio_cli/tools/procesio/tests/test_formlint_field_names.py +83 -0
- procesio_cli/tools/procesio/tests/test_guards.py +110 -0
- procesio_cli/tools/procesio/tests/test_hidden_setting_types.py +39 -0
- procesio_cli/tools/procesio/tests/test_html2docx_asset.py +132 -0
- procesio_cli/tools/procesio/tests/test_key_handle_terminology.py +45 -0
- procesio_cli/tools/procesio/tests/test_layout.py +574 -0
- procesio_cli/tools/procesio/tests/test_layout_report.py +88 -0
- procesio_cli/tools/procesio/tests/test_manifest_sync.py +68 -0
- procesio_cli/tools/procesio/tests/test_nodedelete.py +151 -0
- procesio_cli/tools/procesio/tests/test_nodeparam.py +826 -0
- procesio_cli/tools/procesio/tests/test_normalize.py +70 -0
- procesio_cli/tools/procesio/tests/test_payload_docs.py +86 -0
- procesio_cli/tools/procesio/tests/test_process_action_naming.py +170 -0
- procesio_cli/tools/procesio/tests/test_process_call_api_seed.py +46 -0
- procesio_cli/tools/procesio/tests/test_process_ext.py +373 -0
- procesio_cli/tools/procesio/tests/test_process_invariants.py +183 -0
- procesio_cli/tools/procesio/tests/test_process_layout.py +117 -0
- procesio_cli/tools/procesio/tests/test_process_naming.py +188 -0
- procesio_cli/tools/procesio/tests/test_process_query_store.py +172 -0
- procesio_cli/tools/procesio/tests/test_processinfo_variable.py +56 -0
- procesio_cli/tools/procesio/tests/test_profiles.py +127 -0
- procesio_cli/tools/procesio/tests/test_put_projects.py +41 -0
- procesio_cli/tools/procesio/tests/test_reliability.py +107 -0
- procesio_cli/tools/procesio/tests/test_removal_gate.py +95 -0
- procesio_cli/tools/procesio/tests/test_resource_ops.py +173 -0
- procesio_cli/tools/procesio/tests/test_save_validity.py +207 -0
- procesio_cli/tools/procesio/tests/test_schedule_cron_shape.py +111 -0
- procesio_cli/tools/procesio/tests/test_schedules.py +309 -0
- procesio_cli/tools/procesio/tests/test_sqlconvert.py +234 -0
- procesio_cli/tools/procesio/tests/test_sqlformat.py +83 -0
- procesio_cli/tools/procesio/tests/test_sqlparam.py +187 -0
- procesio_cli/tools/procesio/tests/test_superseded_actions.py +54 -0
- procesio_cli/tools/procesio/tests/test_surgical_concurrency.py +156 -0
- procesio_cli/tools/procesio/tests/test_tier4.py +123 -0
- procesio_cli/tools/procesio/tests/test_transport.py +147 -0
- procesio_cli/tools/procesio/tests/test_transport_data_stores.py +168 -0
- procesio_cli/tools/procesio/tests/test_transport_mapper_repair.py +150 -0
- procesio_cli/tools/procesio/tests/test_transport_webhook_strip.py +253 -0
- procesio_cli/tools/procesio/tests/test_usage_guide.py +96 -0
- procesio_cli/tools/procesio/tool.yaml +12486 -0
- procesio_cli/tools/sqlserver/MOVED-TO-CENTRAL.md +29 -0
- procesio_cli/tools/sqlserver/README.md +192 -0
- procesio_cli/tools/sqlserver/SKILL.md +37 -0
- procesio_cli/tools/sqlserver/SQLSERVER-SCHEMA-NOTES.md +284 -0
- procesio_cli/tools/sqlserver/__init__.py +0 -0
- procesio_cli/tools/sqlserver/connection.py +242 -0
- procesio_cli/tools/sqlserver/main.py +521 -0
- procesio_cli/tools/sqlserver/profiles.template.json +1 -0
- procesio_cli/tools/sqlserver/schema.py +600 -0
- procesio_cli/tools/sqlserver/tests/__init__.py +0 -0
- procesio_cli/tools/sqlserver/tests/conftest.py +21 -0
- procesio_cli/tools/sqlserver/tests/test_connection.py +75 -0
- procesio_cli/tools/sqlserver/tests/test_dbprofiles.py +92 -0
- procesio_cli/tools/sqlserver/tests/test_dbquery.py +220 -0
- procesio_cli/tools/sqlserver/tests/test_manifest_sync.py +68 -0
- procesio_cli/tools/sqlserver/tests/test_sqlserver.py +326 -0
- procesio_cli/tools/sqlserver/tests/test_sqlserver_schema.py +488 -0
- procesio_cli/tools/sqlserver/tool.yaml +123 -0
- procesio_cli/tools/web/README.md +381 -0
- procesio_cli/tools/web/SKILL.md +37 -0
- procesio_cli/tools/web/WEB-AUTOMATION-NOTES.md +277 -0
- procesio_cli/tools/web/__init__.py +21 -0
- procesio_cli/tools/web/actiondef.py +24 -0
- procesio_cli/tools/web/api.py +444 -0
- procesio_cli/tools/web/brokered.py +107 -0
- procesio_cli/tools/web/driver.py +839 -0
- procesio_cli/tools/web/drivercontract.py +44 -0
- procesio_cli/tools/web/errors.py +54 -0
- procesio_cli/tools/web/handlers/__init__.py +1 -0
- procesio_cli/tools/web/handlers/archive.py +367 -0
- procesio_cli/tools/web/handlers/browse.py +182 -0
- procesio_cli/tools/web/handlers/contacts.py +74 -0
- procesio_cli/tools/web/handlers/sessmgmt.py +120 -0
- procesio_cli/tools/web/main.py +128 -0
- procesio_cli/tools/web/sessions.py +253 -0
- procesio_cli/tools/web/steps.py +162 -0
- procesio_cli/tools/web/tests/__init__.py +0 -0
- procesio_cli/tools/web/tests/conftest.py +197 -0
- procesio_cli/tools/web/tests/test_api.py +330 -0
- procesio_cli/tools/web/tests/test_archive.py +190 -0
- procesio_cli/tools/web/tests/test_brokered.py +138 -0
- procesio_cli/tools/web/tests/test_dispatch.py +132 -0
- procesio_cli/tools/web/tests/test_errors.py +60 -0
- procesio_cli/tools/web/tests/test_manifest_sync.py +76 -0
- procesio_cli/tools/web/tests/test_page_contacts.py +68 -0
- procesio_cli/tools/web/tests/test_sessions.py +145 -0
- procesio_cli/tools/web/tests/test_steps.py +210 -0
- procesio_cli/tools/web/tool.yaml +184 -0
- procesio_cli/tools/xlsx/README.md +34 -0
- procesio_cli/tools/xlsx/SKILL.md +28 -0
- procesio_cli/tools/xlsx/__init__.py +0 -0
- procesio_cli/tools/xlsx/main.py +129 -0
- procesio_cli/tools/xlsx/reader.py +91 -0
- procesio_cli/tools/xlsx/tests/__init__.py +0 -0
- procesio_cli/tools/xlsx/tests/conftest.py +39 -0
- procesio_cli/tools/xlsx/tests/test_xlsx.py +103 -0
- procesio_cli/tools/xlsx/tool.yaml +30 -0
- procesio_cli/webplatform/aat_mcp/AAT-MCP-NOTES.md +244 -0
- procesio_cli/webplatform/aat_mcp/README.md +78 -0
- procesio_cli/webplatform/aat_mcp/bridge.py +705 -0
- procesio_cli/webplatform/aat_mcp/chat_server.py +194 -0
- procesio_cli/webplatform/aat_mcp/chat_surface.snapshot.json +668 -0
- procesio_cli/webplatform/aat_mcp/chat_surface.yaml +372 -0
- procesio_cli/webplatform/aat_mcp/gate.py +68 -0
- procesio_cli/webplatform/aat_mcp/http_server.py +139 -0
- procesio_cli/webplatform/aat_mcp/protocol.py +175 -0
- procesio_cli/webplatform/aat_mcp/server.py +306 -0
- procesio_cli/webplatform/aat_mcp/tests/conftest.py +8 -0
- procesio_cli/webplatform/aat_mcp/tests/test_aat_mcp.py +255 -0
- procesio_cli/webplatform/aat_mcp/tests/test_chat_surface.py +247 -0
- procesio_cli/webplatform/aat_mcp/tests/test_discovery.py +489 -0
- procesio_cli/webplatform/aat_mcp/tests/test_multiuser.py +86 -0
- procesio_cli/webplatform/aat_mcp/tests/test_progressive_discovery.py +89 -0
- procesio_cli/webplatform/aat_mcp/tests/test_protocol.py +180 -0
- procesio_cli/webplatform/aat_mcp/tests/test_skill_discovery.py +23 -0
- procesio_cli-0.1.1.dist-info/METADATA +467 -0
- procesio_cli-0.1.1.dist-info/RECORD +647 -0
- procesio_cli-0.1.1.dist-info/WHEEL +4 -0
- procesio_cli-0.1.1.dist-info/entry_points.txt +7 -0
- procesio_cli-0.1.1.dist-info/licenses/LICENSE +201 -0
- 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.
|