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,1009 @@
|
|
|
1
|
+
"""Form template builder — PURE config -> FormTemplateDto.
|
|
2
|
+
|
|
3
|
+
A form's layout lives in `Data.elements` (plaintext control tree). PROVEN LIVE
|
|
4
|
+
(2026-06-24): the runtime renders from `elements` — the encrypted `Data.code`
|
|
5
|
+
blob is NOT required (a form with code="" and modified elements rendered the
|
|
6
|
+
modified elements). So forms are fully buildable server-side.
|
|
7
|
+
|
|
8
|
+
Approach (template-merge): for each control in config, clone a captured golden
|
|
9
|
+
element of that type (dto/form/elements/<type>.json) so every config field stays
|
|
10
|
+
valid, assign fresh ids, and override the semantic configs (label, name, required,
|
|
11
|
+
placeholder, options, default, submit, ...). theme/messages/dataModel/variables
|
|
12
|
+
come from a captured Data shell.
|
|
13
|
+
|
|
14
|
+
API (verified live):
|
|
15
|
+
- Create: POST /api/FormTemplate (FormTemplateDto) -> {id} ; client supplies Id
|
|
16
|
+
- Get: GET /api/FormTemplate/{id}
|
|
17
|
+
- Edit: PUT /api/FormTemplate
|
|
18
|
+
- Publish/state: Status=1 (PUBLISHED) + State=true on the body, or PATCH /api/FormTemplate/{id}?state=
|
|
19
|
+
- Render: create a CustomUrl, open forms.procesio.app/{tinyUrl}
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import copy
|
|
24
|
+
import json
|
|
25
|
+
import re
|
|
26
|
+
import uuid
|
|
27
|
+
from functools import lru_cache
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
from tools.procesio.client import VARIABLES_ONLY
|
|
31
|
+
from tools.procesio.dto.form import fieldpath
|
|
32
|
+
from tools.procesio.dto.framework import Component
|
|
33
|
+
from tools.procesio.errors import UsageError
|
|
34
|
+
|
|
35
|
+
DIR = Path(__file__).resolve().parent
|
|
36
|
+
_ELEMENTS = DIR / "elements"
|
|
37
|
+
|
|
38
|
+
_FORM_TYPE = {"usertask": 0, "general": 1}
|
|
39
|
+
|
|
40
|
+
# config convenience key -> the element config `key` it sets
|
|
41
|
+
_OVERRIDE_KEYS = {
|
|
42
|
+
"label", "placeholder", "tooltip", "info-text", "infoText", "defaultValue",
|
|
43
|
+
"required", "readonly", "visible", "rows", "mode", "multiple", "searchable",
|
|
44
|
+
"clearable", "regex", "submit", "disabledIfFormIsInvalid", "variation", "hasNow",
|
|
45
|
+
"description", "src", "url", "alt", "title", "width", "height", "linkUrl",
|
|
46
|
+
"maxFileSize", "canAdd", "canRemove", "addText", "min", "max", "outlined",
|
|
47
|
+
# task / assignment (section, tabs, table, approval controls)
|
|
48
|
+
"assignee", "assigneeReplacement", "approver", "approverReplacement",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# control event -> (config key holding the events, the event object `type`).
|
|
52
|
+
# VERIFIED 2026-06-25 against ui-builder source: trigger.ts TriggerKey enum (config keys),
|
|
53
|
+
# FieldEvents.component.vue (EventType<->key pairing), and the EventType enum in
|
|
54
|
+
# model/config/events/index.ts. (Fixed: open/close=SIDE_PANEL_*, step*=STEPPER_*,
|
|
55
|
+
# message*=CHAT_*, approvalsubmit=*_APPROVE_OR_REJECT.)
|
|
56
|
+
_EVENT_KEY = {
|
|
57
|
+
"click": "onClickEvents", "input": "onInputEvents", "change": "onInputEvents",
|
|
58
|
+
"focus": "onFocusEvents", "blur": "onBlurEvents", "hover": "onHoverEvents",
|
|
59
|
+
"ready": "onReadyEvents",
|
|
60
|
+
"open": "onOpenEvents", "close": "onCloseEvents",
|
|
61
|
+
"tabchange": "onTabChangeEvents", "rowadded": "onTableRowAddedEvents",
|
|
62
|
+
"rowdeleted": "onTableRowDeletedEvents", "paginated": "onPaginationChangedEvents",
|
|
63
|
+
"stepchange": "onStepChangeEvents", "nextstep": "onNextStepEvents",
|
|
64
|
+
"previousstep": "onPreviousStepEvents",
|
|
65
|
+
"beforeapprove": "onBeforeApproveEvents", "afterapprove": "onAfterApproveEvents",
|
|
66
|
+
"beforereject": "onBeforeRejectEvents", "afterreject": "onAfterRejectEvents",
|
|
67
|
+
"beforeapprovalsubmit": "onBeforeApprovalSubmitEvents",
|
|
68
|
+
"afterapprovalsubmit": "onAfterApprovalSubmitEvents",
|
|
69
|
+
"messagesfetch": "onMessagesFetchEvents", "messagesent": "onMessageSentEvents",
|
|
70
|
+
}
|
|
71
|
+
_EVENT_TYPE = {
|
|
72
|
+
"click": "CLICK", "input": "INPUT", "change": "INPUT", "focus": "FOCUS",
|
|
73
|
+
"blur": "BLUR", "hover": "HOVER", "ready": "READY",
|
|
74
|
+
"open": "SIDE_PANEL_OPEN", "close": "SIDE_PANEL_CLOSE", "tabchange": "TAB_CHANGE",
|
|
75
|
+
"rowadded": "TABLE_ROW_ADDED", "rowdeleted": "TABLE_ROW_DELETED",
|
|
76
|
+
"paginated": "TABLE_PAGINATED",
|
|
77
|
+
"stepchange": "STEPPER_STEP_CHANGE", "nextstep": "STEPPER_NEXT_STEP",
|
|
78
|
+
"previousstep": "STEPPER_PREVIOUS_STEP",
|
|
79
|
+
"beforeapprove": "BEFORE_APPROVE", "afterapprove": "AFTER_APPROVE",
|
|
80
|
+
"beforereject": "BEFORE_REJECT", "afterreject": "AFTER_REJECT",
|
|
81
|
+
"beforeapprovalsubmit": "BEFORE_APPROVE_OR_REJECT",
|
|
82
|
+
"afterapprovalsubmit": "AFTER_APPROVE_OR_REJECT",
|
|
83
|
+
"messagesfetch": "CHAT_MESSAGES_FETCH", "messagesent": "CHAT_MESSAGE_SENT",
|
|
84
|
+
}
|
|
85
|
+
_KEY_ALIASES = {"infoText": "info-text", "default": "defaultValue"}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _new_id(ctx) -> str:
|
|
89
|
+
return (ctx.get("new_id") or (lambda: str(uuid.uuid4())))()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@lru_cache(maxsize=None)
|
|
93
|
+
def _golden(kind: str) -> dict:
|
|
94
|
+
path = _ELEMENTS / f"{kind}.json"
|
|
95
|
+
if not path.exists():
|
|
96
|
+
avail = sorted(p.stem for p in _ELEMENTS.glob("*.json"))
|
|
97
|
+
raise UsageError(f"unknown form control type {kind!r}; available: {avail}")
|
|
98
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@lru_cache(maxsize=1)
|
|
102
|
+
def _shell() -> dict:
|
|
103
|
+
return json.loads((DIR / "data_shell.json").read_text(encoding="utf-8"))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def control_types() -> list[str]:
|
|
107
|
+
return sorted(p.stem for p in _ELEMENTS.glob("*.json"))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _slug(s: str) -> str:
|
|
111
|
+
return re.sub(r"[^A-Za-z0-9_]", "", (s or "").title().replace(" ", "")) or "field"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _field_slug(s: str) -> str:
|
|
115
|
+
"""An auto-generated field NAME: a single lowercase word. A designer save lowercases
|
|
116
|
+
and camelCases around underscores, so a single lowercase word survives it unchanged
|
|
117
|
+
and formlint.lint_field_names does not flag it. Human-readable text stays in the
|
|
118
|
+
field's label, not its name (_slug, which keeps case for flowgraph display, is left
|
|
119
|
+
for that)."""
|
|
120
|
+
return re.sub(r"[^a-z0-9]", "", (s or "").lower()) or "field"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _options(raw):
|
|
124
|
+
"""Normalize options to [{name, value}]."""
|
|
125
|
+
out = []
|
|
126
|
+
for o in raw or []:
|
|
127
|
+
if isinstance(o, dict):
|
|
128
|
+
out.append({"name": o.get("name", o.get("label", o.get("value"))),
|
|
129
|
+
"value": o.get("value", o.get("name"))})
|
|
130
|
+
else:
|
|
131
|
+
out.append({"name": str(o), "value": str(o)})
|
|
132
|
+
return out
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _set_config(configs: list, key: str, value, ctx) -> None:
|
|
136
|
+
for c in configs:
|
|
137
|
+
if c.get("key") == key:
|
|
138
|
+
c["value"] = value
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@lru_cache(maxsize=1)
|
|
143
|
+
def _style_groups() -> list:
|
|
144
|
+
"""Resolved theme groups (label, types, {cssVariable: {label,type}}) from the shell.
|
|
145
|
+
Used for per-element styling: the runtime applies an element's `style` config (a
|
|
146
|
+
CSSProperty[] of ENABLED overrides) on top of the theme."""
|
|
147
|
+
groups = []
|
|
148
|
+
for g in _shell().get("theme") or []:
|
|
149
|
+
props = {p["cssVariable"]: {"label": p.get("label", ""), "type": p.get("type")}
|
|
150
|
+
for p in g.get("properties") or [] if p.get("cssVariable")}
|
|
151
|
+
groups.append((g.get("label"), set(g.get("types") or []), props))
|
|
152
|
+
return groups
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# Element alignment feature: three flex properties on container elements, each keyed by
|
|
156
|
+
# an element-scope SUFFIX. The 18 real vars are --fd/--jc/--ai × these 6 suffixes:
|
|
157
|
+
# --fd-<suffix> flex-direction ("Inner direction")
|
|
158
|
+
# --jc-<suffix> justify-content ("Inner main axis")
|
|
159
|
+
# --ai-<suffix> align-items ("Inner cross axis")
|
|
160
|
+
# Element type -> its alignment suffix (ui-builder theme.ts registrations).
|
|
161
|
+
_ALIGNMENT_SUFFIX = {
|
|
162
|
+
"section": "section", "list": "list-item", "columns": "column",
|
|
163
|
+
"stepper": "step", "tabs": "tab", "side-panel": "side-panel",
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _alignment_props(el_type: str) -> dict:
|
|
168
|
+
"""The flex-alignment style vars a container element accepts. Additive to the golden
|
|
169
|
+
theme groups so alignment is usable before the golden is refreshed from the launched
|
|
170
|
+
form-theme.ts; when the golden ships them natively the golden's definition wins (it
|
|
171
|
+
carries the proper dropdown type + option list). Values pass through (standard CSS,
|
|
172
|
+
e.g. flex-direction row|column, justify-content flex-start|center|space-between,
|
|
173
|
+
align-items flex-start|center|stretch)."""
|
|
174
|
+
suffix = _ALIGNMENT_SUFFIX.get(el_type)
|
|
175
|
+
if not suffix:
|
|
176
|
+
return {}
|
|
177
|
+
return {
|
|
178
|
+
f"--fd-{suffix}": {"label": "Inner direction", "type": None},
|
|
179
|
+
f"--jc-{suffix}": {"label": "Inner main axis", "type": None},
|
|
180
|
+
f"--ai-{suffix}": {"label": "Inner cross axis", "type": None},
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _style_props_for(el_type: str):
|
|
185
|
+
"""cssVariable -> {label,type} stylable on this element type, or None. Mirrors the
|
|
186
|
+
frontend getCssGroupByElementType: the FIRST theme group whose `types` include the
|
|
187
|
+
type (so e.g. select uses the Input group), plus the alignment vars for container
|
|
188
|
+
types. Golden definitions win over the additive alignment fallback."""
|
|
189
|
+
for _label, types, props in _style_groups():
|
|
190
|
+
if el_type in types:
|
|
191
|
+
extra = _alignment_props(el_type)
|
|
192
|
+
return {**extra, **props} if extra else props
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _apply_element_style(spec: dict, el_type: str, configs: list, ctx) -> None:
|
|
197
|
+
"""Set the element's `style` config from spec['style'] = {cssVariable: value} (or a
|
|
198
|
+
list of {cssVariable,value[,enabled]}). Stored as CSSProperty[] with enabled:true; the
|
|
199
|
+
runtime applies only enabled props. Unknown vars fail loud (no silent drop)."""
|
|
200
|
+
raw = spec.get("style")
|
|
201
|
+
if not raw:
|
|
202
|
+
return
|
|
203
|
+
props = _style_props_for(el_type)
|
|
204
|
+
if props is None:
|
|
205
|
+
raise UsageError(f"form control {el_type!r} has no stylable properties")
|
|
206
|
+
pairs = raw.items() if isinstance(raw, dict) else (
|
|
207
|
+
(d.get("cssVariable"), d.get("value")) for d in raw)
|
|
208
|
+
items = []
|
|
209
|
+
for cssvar, value in pairs:
|
|
210
|
+
meta = props.get(cssvar)
|
|
211
|
+
if not meta:
|
|
212
|
+
raise UsageError(f"{el_type}: unknown style variable {cssvar!r}; "
|
|
213
|
+
f"valid: {sorted(props)}")
|
|
214
|
+
items.append({"label": meta["label"], "value": str(value),
|
|
215
|
+
"cssVariable": cssvar, "type": meta["type"], "enabled": True})
|
|
216
|
+
_set_config(configs, "style", items, ctx)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
_FIELDS_NS = fieldpath.FIELDS_NS # platform "fields" container id (constant across forms)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _map_rows(rows, ctx) -> list:
|
|
223
|
+
"""Normalize an inputMap/outputMap/mapping list to [{id,left,right}]. A side that
|
|
224
|
+
names a form field (registered in ctx['field_paths']) is resolved to its full
|
|
225
|
+
value-path formId.FIELDS.elementId.valueConfigId; process-variable ids and
|
|
226
|
+
literals pass through."""
|
|
227
|
+
paths = ctx.get("field_paths", {})
|
|
228
|
+
|
|
229
|
+
def resolve(v):
|
|
230
|
+
if isinstance(v, str) and v in paths:
|
|
231
|
+
return paths[v]
|
|
232
|
+
return v if isinstance(v, str) else str(v)
|
|
233
|
+
out = []
|
|
234
|
+
for i, m in enumerate(rows or []):
|
|
235
|
+
left = m.get("to", m.get("left"))
|
|
236
|
+
right = m.get("from", m.get("value", m.get("right", "")))
|
|
237
|
+
out.append({"id": i, "left": resolve(left), "right": resolve(right)})
|
|
238
|
+
return out
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _proc_map(rows, ctx, pvars: dict, kind: str) -> list:
|
|
242
|
+
"""RUN_PROCESS input/output map. One side names a PROCESS VARIABLE (resolved to its
|
|
243
|
+
GUID via pvars — the backend requires the id, not the name; live-confirmed 2026-06-29),
|
|
244
|
+
the other names a FORM FIELD (resolved to its value-path) or a literal/process-var id.
|
|
245
|
+
Real exports show BOTH maps as left=process-variable-GUID, right=form-field-path
|
|
246
|
+
(verified across OMS/Diligentic 2026-06-29) -- the orientation is the SAME for input
|
|
247
|
+
and output; only which spec key names the process var differs:
|
|
248
|
+
kind='in': {to: procVar, from: formField} -> left=procVarId(to), right=fieldPath(from)
|
|
249
|
+
kind='out': {to: formField, from: procVar} -> left=procVarId(from), right=fieldPath(to)"""
|
|
250
|
+
paths = ctx.get("field_paths", {})
|
|
251
|
+
|
|
252
|
+
def field(v):
|
|
253
|
+
return paths.get(v, v if isinstance(v, str) else str(v))
|
|
254
|
+
|
|
255
|
+
def pvar(v):
|
|
256
|
+
return pvars.get(v, v if isinstance(v, str) else str(v))
|
|
257
|
+
out = []
|
|
258
|
+
for i, m in enumerate(rows or []):
|
|
259
|
+
a = m.get("to", m.get("left"))
|
|
260
|
+
b = m.get("from", m.get("value", m.get("right", "")))
|
|
261
|
+
if kind == "in": # process input <- form field
|
|
262
|
+
out.append({"id": i, "left": pvar(a), "right": field(b)})
|
|
263
|
+
else: # form field <- process output (process var still on the LEFT)
|
|
264
|
+
out.append({"id": i, "left": pvar(b), "right": field(a)})
|
|
265
|
+
return out
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _ds_map(rows) -> list:
|
|
269
|
+
"""A DataStore operation input/output map: a list of DataStoreMapItem
|
|
270
|
+
{id, left, right}. Accepts rows as {left,right} or {to,from} and passes the two
|
|
271
|
+
sides through unchanged (their orientation is DataStore-op specific and confirmed
|
|
272
|
+
live), only assigning the row id."""
|
|
273
|
+
out = []
|
|
274
|
+
for i, m in enumerate(rows or []):
|
|
275
|
+
if not isinstance(m, dict):
|
|
276
|
+
continue
|
|
277
|
+
left = m.get("left", m.get("to"))
|
|
278
|
+
right = m.get("right", m.get("from", m.get("value")))
|
|
279
|
+
out.append({"id": m.get("id", i), "left": left, "right": right})
|
|
280
|
+
return out
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _trigger(ev: dict, ctx) -> dict:
|
|
284
|
+
"""The {action, config} for one event trigger.
|
|
285
|
+
do = map | process | js | form | datastore."""
|
|
286
|
+
do = (ev.get("do") or "").strip().lower()
|
|
287
|
+
if do in ("process", "run_process"):
|
|
288
|
+
pid = ev["processId"]
|
|
289
|
+
pvars = (ctx.get("process_vars") or {}).get(pid, {})
|
|
290
|
+
return {"action": "RUN_PROCESS", "config": {
|
|
291
|
+
"processId": pid, "syncRun": bool(ev.get("syncRun", False)),
|
|
292
|
+
"mapLatestTriggerOnly": bool(ev.get("mapLatestTriggerOnly", False)),
|
|
293
|
+
"inputMap": _proc_map(ev.get("inputs"), ctx, pvars, "in"),
|
|
294
|
+
"outputMap": _proc_map(ev.get("outputs"), ctx, pvars, "out"),
|
|
295
|
+
"conditions": [], "areConditionsConfigured": True}}
|
|
296
|
+
if do in ("js", "javascript", "run_javascript"):
|
|
297
|
+
return {"action": "RUN_JAVASCRIPT", "config": {"code": ev.get("code", "")}}
|
|
298
|
+
if do in ("map", "map_form_data"):
|
|
299
|
+
return {"action": "MAP_FORM_DATA", "config": {"mapping": _map_rows(ev.get("mapping"), ctx)}}
|
|
300
|
+
if do in ("form", "trigger_form", "run_form", "open_form"):
|
|
301
|
+
# EventAction.TRIGGER_FORM; config = EventTriggerFormConfig {formId, mapping, ...}
|
|
302
|
+
# (verified vs ui-builder model/config/events/index.ts — NOT "RUN_FORM"/inputMap).
|
|
303
|
+
return {"action": "TRIGGER_FORM", "config": {
|
|
304
|
+
"formId": ev.get("formId"),
|
|
305
|
+
"mapping": _map_rows(ev.get("mapping") or ev.get("inputs"), ctx)}}
|
|
306
|
+
if do in ("datastore", "data_store", "run_data_store_operation", "run_data_store"):
|
|
307
|
+
# EventAction.RUN_DATA_STORE_OPERATION; config = EventRunDataStoreOperationConfig
|
|
308
|
+
# {dataStoreId, operation, inputMap, outputMap, filters?, areFiltersConfigured?}
|
|
309
|
+
# (ui-builder model/config/events/index.ts). operation = DataStoreOperation
|
|
310
|
+
# READ/ADD/UPDATE/DELETE; filters apply to every op except ADD. The map item is
|
|
311
|
+
# {id, left, right} (DataStoreMapItem); the operation wire encoding + map
|
|
312
|
+
# orientation must be re-verified live. See PROCESIO-RESOURCE-MODEL-NOTES.md.
|
|
313
|
+
cfg = {
|
|
314
|
+
"dataStoreId": ev.get("dataStoreId"),
|
|
315
|
+
"operation": ev.get("operation"),
|
|
316
|
+
"inputMap": _ds_map(ev.get("inputs") or ev.get("inputMap")),
|
|
317
|
+
"outputMap": _ds_map(ev.get("outputs") or ev.get("outputMap")),
|
|
318
|
+
}
|
|
319
|
+
filters = ev.get("filters")
|
|
320
|
+
if filters is not None:
|
|
321
|
+
cfg["filters"] = filters
|
|
322
|
+
cfg["areFiltersConfigured"] = bool(filters)
|
|
323
|
+
else:
|
|
324
|
+
cfg["areFiltersConfigured"] = bool(ev.get("areFiltersConfigured", False))
|
|
325
|
+
return {"action": "RUN_DATA_STORE_OPERATION", "config": cfg}
|
|
326
|
+
raise UsageError(
|
|
327
|
+
f"unknown event trigger do={ev.get('do')!r} (use map|process|js|form|datastore)")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _apply_events(spec: dict, configs: list, ctx) -> None:
|
|
331
|
+
"""Attach control events. Each spec event: {on, do, ...}. Grouped by the
|
|
332
|
+
control's event config key as {debounce, events:[{id,type,action,config}]}."""
|
|
333
|
+
grouped: dict[str, list] = {}
|
|
334
|
+
for ev in spec.get("events") or []:
|
|
335
|
+
on = (ev.get("on") or "click").strip().lower()
|
|
336
|
+
key = _EVENT_KEY.get(on)
|
|
337
|
+
if not key:
|
|
338
|
+
raise UsageError(f"unknown event on={ev.get('on')!r}; known: {sorted(_EVENT_KEY)}")
|
|
339
|
+
obj = {"id": _new_id(ctx), "type": _EVENT_TYPE[on]}
|
|
340
|
+
obj.update(_trigger(ev, ctx))
|
|
341
|
+
grouped.setdefault(key, []).append(obj)
|
|
342
|
+
for key, objs in grouped.items():
|
|
343
|
+
_set_config(configs, key, {"debounce": 0, "events": objs}, ctx)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _resolve_late_field_refs(elements: list, ctx) -> None:
|
|
347
|
+
"""Second pass over built elements: resolve event map rows that named a form field
|
|
348
|
+
declared AFTER the element carrying the event.
|
|
349
|
+
|
|
350
|
+
`ctx['field_paths']` is filled as elements are built, so a trigger placed above its
|
|
351
|
+
target fields keeps the raw field NAME in the map instead of the value-path. That is
|
|
352
|
+
the normal layout for any form that shows results (submit button above the results
|
|
353
|
+
panel), and the failure is silent: the API accepts the row, the designer renders it
|
|
354
|
+
blank, and the fields never populate. Runs once every path is known, so ordering in
|
|
355
|
+
the config no longer changes the wiring."""
|
|
356
|
+
paths = ctx.get("field_paths") or {}
|
|
357
|
+
if not paths:
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
def fix(rows) -> None:
|
|
361
|
+
for row in rows or []:
|
|
362
|
+
if not isinstance(row, dict):
|
|
363
|
+
continue
|
|
364
|
+
for side in ("left", "right"):
|
|
365
|
+
v = row.get(side)
|
|
366
|
+
if isinstance(v, str) and v in paths:
|
|
367
|
+
row[side] = paths[v]
|
|
368
|
+
|
|
369
|
+
for el in elements:
|
|
370
|
+
for cfg in el.get("configs") or []:
|
|
371
|
+
val = cfg.get("value")
|
|
372
|
+
if not isinstance(val, dict):
|
|
373
|
+
continue
|
|
374
|
+
for ev in val.get("events") or []:
|
|
375
|
+
ecfg = (ev or {}).get("config") or {}
|
|
376
|
+
for key in ("inputMap", "outputMap", "mapping"):
|
|
377
|
+
fix(ecfg.get(key))
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
_HEADER = {"heading"}
|
|
381
|
+
_FOOTER = {"button"}
|
|
382
|
+
_CLEAR_DEFAULT = ("placeholder", "tooltip", "info-text") # reset golden leftovers
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _config_value(el: dict, key: str):
|
|
386
|
+
return next((c.get("value") for c in el.get("configs", []) if c.get("key") == key), None)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
# Controls whose field-source config is NOT named `value`. A file-viewer holds its file
|
|
390
|
+
# in `src` (the platform's own empty state for it reads "Field source not set"), and
|
|
391
|
+
# _VALUE_TYPE already types file-viewer as File — so without this the control gets a
|
|
392
|
+
# data-model attribute but no field path, and an outputMap row targeting it silently
|
|
393
|
+
# keeps the raw field name instead of resolving to a value path.
|
|
394
|
+
#
|
|
395
|
+
# Shared with the surgical event editor (handlers/form_events.py), which has to CHECK the
|
|
396
|
+
# same paths against a live form: one rule, one place. Two copies would be a rule nothing
|
|
397
|
+
# enforces, and a wrong value path is accepted by the platform without any error at all.
|
|
398
|
+
_VALUE_CONFIG_KEY = fieldpath.VALUE_CONFIG_KEY
|
|
399
|
+
_value_key = fieldpath.value_key
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
_SKIP_OVERRIDE = {"type", "name", "options", "children", "configs", "events",
|
|
403
|
+
"columns", "cells", "hasHeader", "hasIndexColumn"}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _build_element(spec: dict, ctx: dict, out: list) -> dict:
|
|
407
|
+
"""Build one element (flat) into `out`, recursing children with parentId set.
|
|
408
|
+
PROCESIO uses a FLAT elements list + parentId (NOT nested `children`)."""
|
|
409
|
+
kind = spec.get("type")
|
|
410
|
+
if not kind:
|
|
411
|
+
raise UsageError("each form element needs a 'type'")
|
|
412
|
+
if kind == "table":
|
|
413
|
+
return _build_table(spec, ctx, out)
|
|
414
|
+
el = copy.deepcopy(_golden(kind))
|
|
415
|
+
el["id"] = _new_id(ctx)
|
|
416
|
+
el.pop("children", None)
|
|
417
|
+
el["parentId"] = ctx.get("parent_id")
|
|
418
|
+
section = el.get("section")
|
|
419
|
+
if section not in ("header", "body", "footer"):
|
|
420
|
+
section = "header" if kind in _HEADER else "footer" if kind in _FOOTER else "body"
|
|
421
|
+
el["section"] = section
|
|
422
|
+
configs = el.get("configs", [])
|
|
423
|
+
for c in configs:
|
|
424
|
+
c["id"] = _new_id(ctx)
|
|
425
|
+
if c.get("key") == "visible":
|
|
426
|
+
c["value"] = True
|
|
427
|
+
if c.get("key") in ("required", "readonly"):
|
|
428
|
+
c["value"] = False
|
|
429
|
+
if c.get("key") in _CLEAR_DEFAULT:
|
|
430
|
+
c["value"] = ""
|
|
431
|
+
field = spec.get("name") or _field_slug(spec.get("label") or kind)
|
|
432
|
+
_set_config(configs, "name", field, ctx)
|
|
433
|
+
_set_config(configs, "id", field, ctx)
|
|
434
|
+
val_cfg = next((c for c in configs if c.get("key") == _value_key(kind)), None)
|
|
435
|
+
if val_cfg and ctx.get("form_id"):
|
|
436
|
+
# The field-path value segment MUST be the element's OWN `value` config id.
|
|
437
|
+
# Verified against real forms (OMS, eTransport): the data-model value-attr id
|
|
438
|
+
# equals the element's value config id. A fresh id here resolves to "Unknown"
|
|
439
|
+
# in the process-designer trigger-map "Form variable" selector.
|
|
440
|
+
vaid = val_cfg["id"]
|
|
441
|
+
ctx.setdefault("value_attr_ids", {})[el["id"]] = vaid
|
|
442
|
+
ctx.setdefault("field_paths", {})[field] = fieldpath.build(
|
|
443
|
+
ctx["dm_root_id"], el["id"], vaid)
|
|
444
|
+
for ck, cv in spec.items():
|
|
445
|
+
if ck in _SKIP_OVERRIDE:
|
|
446
|
+
continue
|
|
447
|
+
key = _KEY_ALIASES.get(ck, ck)
|
|
448
|
+
if key in _OVERRIDE_KEYS:
|
|
449
|
+
_set_config(configs, key, cv, ctx)
|
|
450
|
+
for ck, cv in (spec.get("configs") or {}).items():
|
|
451
|
+
_set_config(configs, ck, cv, ctx)
|
|
452
|
+
if "optionsSource" in spec:
|
|
453
|
+
src = spec["optionsSource"] or {}
|
|
454
|
+
st = {"json": "JSON", "static-list": "static-list", "url": "URL"}.get(
|
|
455
|
+
str(src.get("type", "static-list")).lower(), "static-list")
|
|
456
|
+
val = src.get("value")
|
|
457
|
+
_set_config(configs, "sourceType", st, ctx)
|
|
458
|
+
_set_config(configs, "sourceValue",
|
|
459
|
+
_options(val) if st == "static-list" and isinstance(val, list) else val, ctx)
|
|
460
|
+
elif "options" in spec:
|
|
461
|
+
_set_config(configs, "sourceType", "static-list", ctx)
|
|
462
|
+
_set_config(configs, "sourceValue", _options(spec["options"]), ctx)
|
|
463
|
+
_apply_events(spec, configs, ctx)
|
|
464
|
+
_apply_element_style(spec, kind, configs, ctx)
|
|
465
|
+
# array-valued control -> its `value` defaults to an empty array + is typed isList,
|
|
466
|
+
# so the runtime emits/sends an ARRAY (not a single object/null). See _value_is_list.
|
|
467
|
+
if val_cfg is not None and _value_is_list(el):
|
|
468
|
+
val_cfg["value"] = []
|
|
469
|
+
val_cfg["isList"] = True
|
|
470
|
+
out.append(el)
|
|
471
|
+
# children -> FLAT with parentId; columns/tabs keep an ordered child-name list
|
|
472
|
+
child_els = [_build_element(ch, {**ctx, "parent_id": el["id"]}, out)
|
|
473
|
+
for ch in spec.get("children") or []]
|
|
474
|
+
if kind == "columns":
|
|
475
|
+
_set_config(configs, "columns", [_config_value(ce, "name") for ce in child_els], ctx)
|
|
476
|
+
elif kind == "tabs":
|
|
477
|
+
_set_config(configs, "tabs", [_config_value(ce, "name") for ce in child_els], ctx)
|
|
478
|
+
return el
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def _build_table(spec: dict, ctx: dict, out: list) -> dict:
|
|
482
|
+
"""A table = the table element + a static-table-row + the row's cell controls,
|
|
483
|
+
wired by `childrenIdPerColumn` (colId -> [cellElementId]). All FLAT in `out`."""
|
|
484
|
+
el = copy.deepcopy(_golden("table"))
|
|
485
|
+
el["id"] = _new_id(ctx)
|
|
486
|
+
el.pop("children", None)
|
|
487
|
+
el["parentId"] = ctx.get("parent_id")
|
|
488
|
+
el["section"] = "body"
|
|
489
|
+
configs = el.get("configs", [])
|
|
490
|
+
for c in configs:
|
|
491
|
+
c["id"] = _new_id(ctx)
|
|
492
|
+
if c.get("key") == "visible":
|
|
493
|
+
c["value"] = True
|
|
494
|
+
name = spec.get("name") or _field_slug(spec.get("label") or "table")
|
|
495
|
+
_set_config(configs, "name", name, ctx)
|
|
496
|
+
_set_config(configs, "id", name, ctx)
|
|
497
|
+
if spec.get("label") is not None:
|
|
498
|
+
_set_config(configs, "label", spec["label"], ctx)
|
|
499
|
+
_set_config(configs, "hasHeader", bool(spec.get("hasHeader", True)), ctx)
|
|
500
|
+
_set_config(configs, "hasIndexColumn", bool(spec.get("hasIndexColumn", False)), ctx)
|
|
501
|
+
cols, col_ids = [], {}
|
|
502
|
+
for col in spec.get("columns", []):
|
|
503
|
+
cid = _new_id(ctx)
|
|
504
|
+
col_ids[col["key"]] = cid
|
|
505
|
+
cols.append({"id": cid, "key": col["key"], "label": col.get("label", col["key"])})
|
|
506
|
+
_set_config(configs, "tableColumnsSourceType", "static-list", ctx)
|
|
507
|
+
_set_config(configs, "tableColumnsSourceValue", cols, ctx)
|
|
508
|
+
out.append(el)
|
|
509
|
+
# the row: dynamic-table-row (user can add/remove rows) when spec.dynamic, else a
|
|
510
|
+
# fixed static-table-row. Cells live in childrenIdPerColumn[colId] either way.
|
|
511
|
+
dynamic = bool(spec.get("dynamic"))
|
|
512
|
+
row = copy.deepcopy(_golden("dynamic-table-row" if dynamic else "static-table-row"))
|
|
513
|
+
row["id"] = _new_id(ctx)
|
|
514
|
+
row.pop("children", None)
|
|
515
|
+
row["parentId"] = el["id"]
|
|
516
|
+
row["section"] = "body"
|
|
517
|
+
rcfg = row.get("configs", [])
|
|
518
|
+
for c in rcfg:
|
|
519
|
+
c["id"] = _new_id(ctx)
|
|
520
|
+
rowname = name + "row"
|
|
521
|
+
_set_config(rcfg, "name", rowname, ctx)
|
|
522
|
+
_set_config(rcfg, "id", rowname, ctx)
|
|
523
|
+
out.append(row)
|
|
524
|
+
cipc: dict = {}
|
|
525
|
+
for col in spec.get("columns", []):
|
|
526
|
+
cell = col.get("cell")
|
|
527
|
+
if not cell:
|
|
528
|
+
continue
|
|
529
|
+
ce = _build_element(dict(cell), {**ctx, "parent_id": row["id"]}, out)
|
|
530
|
+
cipc[col_ids[col["key"]]] = [ce["id"]]
|
|
531
|
+
_set_config(rcfg, "childrenIdPerColumn", cipc, ctx)
|
|
532
|
+
if dynamic:
|
|
533
|
+
# the dynamic row carries the add/remove affordances + its own column mirror
|
|
534
|
+
_set_config(rcfg, "canAdd", True, ctx)
|
|
535
|
+
_set_config(rcfg, "canRemove", True, ctx)
|
|
536
|
+
_set_config(rcfg, "addText", spec.get("addText", "Add row"), ctx)
|
|
537
|
+
_set_config(rcfg, "hasHeader", bool(spec.get("hasHeader", True)), ctx)
|
|
538
|
+
_set_config(rcfg, "hasIndexColumn", bool(spec.get("hasIndexColumn", False)), ctx)
|
|
539
|
+
_set_config(rcfg, "tableColumnsSourceType", "static-list", ctx)
|
|
540
|
+
_set_config(rcfg, "tableColumnsSourceValue", cols, ctx)
|
|
541
|
+
_apply_events(spec, rcfg, ctx) # onTableRowAdded/Deleted events, if any
|
|
542
|
+
_set_config(configs, "rows", [rowname], ctx)
|
|
543
|
+
return el
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _build_code(config: dict, ctx: dict) -> str:
|
|
547
|
+
"""Form-level CSS + JavaScript (the designer's 'Switch to code' editor) -> the
|
|
548
|
+
encrypted `Data.code` blob. Accepts `css`/`javascript` (or a `code:{css,javascript}`
|
|
549
|
+
object). Empty -> "" (forms render fine without it; verified live). The AES key comes
|
|
550
|
+
from ctx['form_code_key'] (tests) or the key stored for the call's environment
|
|
551
|
+
(form_code_keys: one per installation)."""
|
|
552
|
+
code = config.get("code") if isinstance(config.get("code"), dict) else {}
|
|
553
|
+
css = code.get("css", config.get("css"))
|
|
554
|
+
js = code.get("javascript", config.get("javascript", config.get("js")))
|
|
555
|
+
if not (css or js):
|
|
556
|
+
return ""
|
|
557
|
+
key = ctx.get("form_code_key")
|
|
558
|
+
if not key:
|
|
559
|
+
# The key stored for THIS call's environment, never another installation's:
|
|
560
|
+
# a blob encrypted with the wrong key blanks the form.
|
|
561
|
+
from tools.procesio import form_code_keys
|
|
562
|
+
key = form_code_keys.key_for(ctx.get("form_code_env"))
|
|
563
|
+
from tools.procesio.dto.form import code_cipher
|
|
564
|
+
return code_cipher.encrypt_code(js or "", css or "", key)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def _apply_theme(theme: list, overrides: dict) -> list:
|
|
568
|
+
"""Apply {cssVariable: value} overrides onto the shell theme (deep-copied)."""
|
|
569
|
+
if not overrides:
|
|
570
|
+
return theme
|
|
571
|
+
theme = copy.deepcopy(theme)
|
|
572
|
+
norm = {(k if k.startswith("--") else f"--{k}"): v for k, v in overrides.items()}
|
|
573
|
+
for section in theme or []:
|
|
574
|
+
for prop in section.get("properties", []):
|
|
575
|
+
cv = prop.get("cssVariable")
|
|
576
|
+
if cv in norm:
|
|
577
|
+
prop["value"] = norm[cv]
|
|
578
|
+
return theme
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
# DARK_PALETTE — the 16 dark-mode color variables (ui-builder theme.ts DARK_PALETTE).
|
|
582
|
+
# A form carries both a light and a dark palette; the runtime swaps by themeMode
|
|
583
|
+
# (store state themeMode/activeThemeMode; getPaletteStyleSheet injects the active one).
|
|
584
|
+
# Persisted INSIDE the single `theme` array on the COLORS group as {light, dark}
|
|
585
|
+
# (see _apply_dark). Emitted only when the caller opts in via config `dark`/`themeDark`.
|
|
586
|
+
_DARK_PALETTE = {
|
|
587
|
+
"--c-primary": "#4663f5", "--c-primary-variant": "#8aa0ff",
|
|
588
|
+
"--c-error": "#ff6b6b", "--c-success": "#32d583", "--c-info": "#53b1fd",
|
|
589
|
+
"--c-neutral--900": "#f8fafd", "--c-neutral--800": "#f2f5fa",
|
|
590
|
+
"--c-neutral--700": "#c8cfdb", "--c-neutral--600": "#b0b8c4",
|
|
591
|
+
"--c-neutral--500": "#98a0ae", "--c-neutral--400": "#3b4557",
|
|
592
|
+
"--c-neutral--300": "#2a3241", "--c-neutral--200": "#222936",
|
|
593
|
+
"--c-neutral--100": "#1a202b", "--c-neutral--50": "#0b0e14",
|
|
594
|
+
"--c-neutral--0": "#141922",
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def _apply_dark(theme_list: list, overrides: dict) -> list:
|
|
599
|
+
"""Enable dark mode on a built theme: turn the COLORS group's `properties` into a
|
|
600
|
+
{light, dark} ModeProperties object (the shape ui-builder's getPaletteStyleSheet reads).
|
|
601
|
+
light = the theme-applied color props; dark = the same props with values from the 16-var
|
|
602
|
+
DARK_PALETTE plus any caller `themeDark` overrides, keyed by cssVariable. Every other
|
|
603
|
+
group keeps a flat CSSProperty[]. themeMode is editor-only and never persisted."""
|
|
604
|
+
theme_list = copy.deepcopy(theme_list)
|
|
605
|
+
dark_vals = {**_DARK_PALETTE, **(overrides or {})}
|
|
606
|
+
dark_vals = {(k if k.startswith("--") else f"--{k}"): v for k, v in dark_vals.items()}
|
|
607
|
+
for group in theme_list or []:
|
|
608
|
+
if str(group.get("label", "")).strip().lower() == "colors":
|
|
609
|
+
light = group.get("properties")
|
|
610
|
+
if isinstance(light, list):
|
|
611
|
+
dark = [{**pr, "value": dark_vals.get(pr.get("cssVariable"), pr.get("value"))}
|
|
612
|
+
for pr in light]
|
|
613
|
+
group["properties"] = {"light": light, "dark": dark}
|
|
614
|
+
break
|
|
615
|
+
return theme_list
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
_STR = "0317bfee-b2f5-4bde-bfe8-121212121214"
|
|
619
|
+
_BOOL = "0317bfee-b2f5-4bde-bfe8-121212121210"
|
|
620
|
+
_INT = "0317bfee-b2f5-4bde-bfe8-121212121211"
|
|
621
|
+
_DT = "0317bfee-b2f5-4bde-bfe8-121212121218"
|
|
622
|
+
_FILE = "10c6ac59-3929-49e6-99dc-121212121219"
|
|
623
|
+
_JSON = "0317bfee-b2f5-4bde-bfe8-121212121220"
|
|
624
|
+
# PROCESIO's generic Object type. A select's STATIC option list is a list of these in
|
|
625
|
+
# the data model (designer capture, 2026-10-01).
|
|
626
|
+
_OBJECT = "0317bfee-b2f5-4bde-bfe8-121212121221"
|
|
627
|
+
_BOOL_CFG = {"visible", "readonly", "required", "submit", "multiple", "searchable",
|
|
628
|
+
"clearable", "hasNow", "disabledIfFormIsInvalid", "canAdd", "canRemove",
|
|
629
|
+
"outlined", "disabled"}
|
|
630
|
+
_VALUE_TYPE = {"number-input": _INT, "checkbox": _BOOL, "datetime-input": _DT,
|
|
631
|
+
"file-upload": _FILE, "file-viewer": _FILE, "chat": _JSON}
|
|
632
|
+
# Controls whose `value` is a JSON ARRAY -> DM attr isList:true + value config default [].
|
|
633
|
+
# Inherently array-valued: chat + the array elements (list/table/dynamic-table-row).
|
|
634
|
+
# Array-valued ONLY when multiple:true: file-upload/select. Confirmed by Liza (PROCESIO):
|
|
635
|
+
# a multiple file-upload MUST be isList + default [] or the form sends a single object and
|
|
636
|
+
# a List<File> process input fails at launch ("... item is not an array: StartObject").
|
|
637
|
+
_VALUE_LIST = {"chat", "list", "table", "dynamic-table-row"}
|
|
638
|
+
_MULTIPLE_ARRAY_TYPES = {"file-upload", "select"}
|
|
639
|
+
|
|
640
|
+
def _value_is_list(el: dict) -> bool:
|
|
641
|
+
t = el.get("type")
|
|
642
|
+
if t in _VALUE_LIST:
|
|
643
|
+
return True
|
|
644
|
+
if t in _MULTIPLE_ARRAY_TYPES:
|
|
645
|
+
return bool(next((c.get("value") for c in el.get("configs", [])
|
|
646
|
+
if c.get("key") == "multiple"), None))
|
|
647
|
+
return False
|
|
648
|
+
# Configs that get NO data-model attribute. `sourceType` / `sourceValue` used to be here,
|
|
649
|
+
# grouped with the layout configs, but the designer writes both into the data model, and
|
|
650
|
+
# only data-model attributes reach `ProcesioForm` in form JS: with them skipped, a script
|
|
651
|
+
# on an API-built form could never read a select's options (measured live 2026-10-01;
|
|
652
|
+
# FORM-DEV-GUIDE/08, "A script reads a select's options and gets nothing").
|
|
653
|
+
_SKIP_DM_ATTR = {"options", "childrenIdPerColumn",
|
|
654
|
+
"tableColumnsSourceValue", "tableColumnsSourceType", "rows", "columns",
|
|
655
|
+
"tabs", "style"}
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _attr_name(key: str) -> str:
|
|
659
|
+
"""Data-model attribute name = camelCase of the config key (PROCESIO compiles
|
|
660
|
+
`info-text` -> `infoText`). Single-word keys pass through unchanged."""
|
|
661
|
+
parts = str(key).split("-")
|
|
662
|
+
return parts[0] + "".join(p[:1].upper() + p[1:] for p in parts[1:] if p)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def _dm_attr(name, aid, type_id, parent, is_list=False):
|
|
666
|
+
return {"id": aid, "name": name, "displayName": name, "dataTypeId": type_id,
|
|
667
|
+
"isDataModel": False, "isList": is_list, "isProcesio": False,
|
|
668
|
+
"isPublic": False, "parentDataTypeId": parent, "jsonProperty": None,
|
|
669
|
+
"attributes": [], "hidden": False}
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def _dm_shape(el: dict, c: dict) -> tuple[str, bool] | None:
|
|
673
|
+
"""(dataTypeId, isList) of the data-model attribute one element config gets, or None
|
|
674
|
+
when it gets none. Shared by form-create and form-add-element, so the two can never
|
|
675
|
+
disagree about what the designer would write.
|
|
676
|
+
|
|
677
|
+
`sourceValue` follows a designer capture: a STATIC option list is a list of Objects.
|
|
678
|
+
A JSON or URL source has not been captured from the designer, so its `sourceValue`
|
|
679
|
+
stays out rather than be given a guessed type. `sourceType` is a plain string."""
|
|
680
|
+
k = c.get("key")
|
|
681
|
+
if not k or k.endswith("Events") or k in _SKIP_DM_ATTR:
|
|
682
|
+
return None
|
|
683
|
+
if k == "sourceValue":
|
|
684
|
+
return (_OBJECT, True) if isinstance(c.get("value"), list) else None
|
|
685
|
+
vkey = _value_key(el.get("type"))
|
|
686
|
+
if k == vkey:
|
|
687
|
+
return _VALUE_TYPE.get(el.get("type"), _STR), _value_is_list(el)
|
|
688
|
+
return (_BOOL if k in _BOOL_CFG else _STR), False
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _build_data_model(form_id: str, elements: list, ctx: dict) -> dict:
|
|
692
|
+
"""Build the form's `Data.dataModel` mirror. PROCESIO backs every form control
|
|
693
|
+
with a data-model attribute: root "form" (id=form_id) -> `fields` container
|
|
694
|
+
(id=_FIELDS_NS) -> one sub-model per element (id=elementId) -> an attribute per
|
|
695
|
+
config (incl. `value`). RUN_PROCESS/MAP triggers reference fields by the path
|
|
696
|
+
formId.fields.elementId.valueAttrId, and the runtime stores the field value in
|
|
697
|
+
that attribute. Without this, the designer shows raw guids and values don't flow."""
|
|
698
|
+
dm = copy.deepcopy(_shell().get("dataModel") or {})
|
|
699
|
+
dm["id"] = form_id
|
|
700
|
+
dm["name"] = dm.get("name") or "form"
|
|
701
|
+
attrs = dm.get("attributes") or []
|
|
702
|
+
fields = next((a for a in attrs if a.get("id") == _FIELDS_NS), None)
|
|
703
|
+
if fields is None:
|
|
704
|
+
fields = {"id": _FIELDS_NS, "dataTypeId": _FIELDS_NS, "name": "fields",
|
|
705
|
+
"displayName": "fields", "isDataModel": True, "isList": False,
|
|
706
|
+
"isProcesio": False, "isPublic": True, "jsonProperty": None,
|
|
707
|
+
"attributes": []}
|
|
708
|
+
attrs.append(fields)
|
|
709
|
+
dm["attributes"] = attrs
|
|
710
|
+
fields["parentDataTypeId"] = form_id
|
|
711
|
+
val_ids = ctx.get("value_attr_ids", {})
|
|
712
|
+
submodels = []
|
|
713
|
+
for el in elements:
|
|
714
|
+
eid = el["id"]
|
|
715
|
+
nm = next((c.get("value") for c in el.get("configs", []) if c.get("key") == "name"), None) or eid
|
|
716
|
+
sub = {"id": eid, "dataTypeId": eid, "name": nm, "displayName": nm,
|
|
717
|
+
"isDataModel": True, "isList": False, "isProcesio": False,
|
|
718
|
+
"isPublic": False, "parentDataTypeId": _FIELDS_NS, "jsonProperty": None,
|
|
719
|
+
"attributes": []}
|
|
720
|
+
for c in el.get("configs", []):
|
|
721
|
+
shape = _dm_shape(el, c)
|
|
722
|
+
if shape is None:
|
|
723
|
+
continue
|
|
724
|
+
# The data-model attribute id MUST equal the element's config id for that
|
|
725
|
+
# key (verified against real forms: every fields sub-model attr id == the
|
|
726
|
+
# element's config id; 15/16 in OMS vs 0/16 with fresh ids -> "Unknown" in
|
|
727
|
+
# the trigger-map UI). Reuse the config's own id.
|
|
728
|
+
aid = c.get("id") or _new_id(ctx)
|
|
729
|
+
sub["attributes"].append(
|
|
730
|
+
_dm_attr(_attr_name(c["key"]), aid, shape[0], eid, shape[1]))
|
|
731
|
+
submodels.append(sub)
|
|
732
|
+
fields["attributes"] = submodels
|
|
733
|
+
# Every top-level attribute (instance, flow, fields, preventFormSubmit, browserTitle — the
|
|
734
|
+
# set the designer's generateFormDataModel writes) hangs off the root.
|
|
735
|
+
for a in attrs:
|
|
736
|
+
a["parentDataTypeId"] = form_id
|
|
737
|
+
return dm
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
# The designer writes "1" for a form it creates (user-playground Builder.component.vue: a new
|
|
741
|
+
# form gets FormDataModelAttributeFormatVersion.VERSION_1 = "same attribute as name"). A form
|
|
742
|
+
# with NO version is read as the legacy "0" format, and the designer's next save lower-cases
|
|
743
|
+
# every field attribute (measured on QA 2026-09-25: `RequestText` -> `requesttext`), which
|
|
744
|
+
# breaks any form script that addresses the field by its real name. This builder names field
|
|
745
|
+
# attributes exactly as the element name, i.e. format "1", so it must say so.
|
|
746
|
+
_DM_FORMAT_VERSION = "1"
|
|
747
|
+
|
|
748
|
+
# Who works a step and who approves it, read the way the designer does before it saves
|
|
749
|
+
# (ui-builder form-builder/assignee::getElementAssignees and approval::getElementApprovers).
|
|
750
|
+
_ASSIGNEE_KEYS = ("assignee", "assigneeReplacement", "additionalAssignment")
|
|
751
|
+
_APPROVER_KEYS = ("approver", "approverReplacement", "additionalApprovers")
|
|
752
|
+
_ASSIGNEE, _APPROVER = 1, 2
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def _unique_ids(values) -> list:
|
|
756
|
+
users: list = []
|
|
757
|
+
for uid in values:
|
|
758
|
+
if isinstance(uid, str) and uid.strip() and uid not in users:
|
|
759
|
+
users.append(uid)
|
|
760
|
+
return users
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def _step_users(el: dict, keys: tuple) -> list:
|
|
764
|
+
values: list = []
|
|
765
|
+
for key in keys:
|
|
766
|
+
value = _config_value(el, key)
|
|
767
|
+
values.extend(value if isinstance(value, list) else [value])
|
|
768
|
+
return _unique_ids(values)
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def _hidden_by_config(el: dict) -> bool:
|
|
772
|
+
# ui-builder elementAvailability::isElementHiddenByConfig: hidden only when `visible` is
|
|
773
|
+
# present and falsy.
|
|
774
|
+
visible = next((c.get("value") for c in el.get("configs", []) if c.get("key") == "visible"),
|
|
775
|
+
None)
|
|
776
|
+
return any(c.get("key") == "visible" for c in el.get("configs", [])) and not visible
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def _approver_steps(el: dict, by_id: dict) -> list:
|
|
780
|
+
"""[(stepId, users)] for one element, as ui-builder approval::getElementApproversByStepId.
|
|
781
|
+
|
|
782
|
+
A dynamic table row does not approve as itself: each entry of its `approvalSteps` with a
|
|
783
|
+
`rowIndex` is its own step (id = the step id, users = approver + approverReplacement), and
|
|
784
|
+
only while the row sits in a table and is not hidden (model/config/table/approvals.ts::
|
|
785
|
+
getDynamicRowApprovalSteps). Every other element is one step under its own id.
|
|
786
|
+
"""
|
|
787
|
+
if el.get("type") != "dynamic-table-row":
|
|
788
|
+
return [(el["id"], _step_users(el, _APPROVER_KEYS))]
|
|
789
|
+
parent = by_id.get(el.get("parentId"))
|
|
790
|
+
if not parent or parent.get("type") != "table" or _hidden_by_config(el):
|
|
791
|
+
return []
|
|
792
|
+
steps = _config_value(el, "approvalSteps") or []
|
|
793
|
+
out = []
|
|
794
|
+
for step in steps if isinstance(steps, list) else []:
|
|
795
|
+
if not isinstance(step, dict) or step.get("rowIndex") is None or not step.get("id"):
|
|
796
|
+
continue
|
|
797
|
+
replacement = step.get("approverReplacement") or []
|
|
798
|
+
out.append((step["id"], _unique_ids([step.get("approver"), *replacement])))
|
|
799
|
+
return out
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def derive_assignees(elements: list) -> list:
|
|
803
|
+
"""The form's top-level `Assignees`, built like the designer's save.
|
|
804
|
+
|
|
805
|
+
ASSIGNEE (1) steps first, then APPROVER (2) steps, `orderId` counting 1, 2, 3 within each
|
|
806
|
+
list, a step only when it has users, each step id once, users sent as `{userId}` only (the
|
|
807
|
+
platform fills in the names). Mirrors ui-builder utils/formBuilder/payload.ts::
|
|
808
|
+
getFormBuilderUsersFullData and user-playground Builder.component.vue::save. Checked against
|
|
809
|
+
19 designer-saved forms on QA (2026-10-05), dynamic-row approvals included: identical.
|
|
810
|
+
The designer also drops users who are not in the workspace; that needs a live lookup and is
|
|
811
|
+
not done here.
|
|
812
|
+
"""
|
|
813
|
+
by_id = {e.get("id"): e for e in elements}
|
|
814
|
+
out: list = []
|
|
815
|
+
for kind in (_ASSIGNEE, _APPROVER):
|
|
816
|
+
order, seen = 0, set()
|
|
817
|
+
for el in elements:
|
|
818
|
+
steps = ([(el["id"], _step_users(el, _ASSIGNEE_KEYS))] if kind == _ASSIGNEE
|
|
819
|
+
else _approver_steps(el, by_id))
|
|
820
|
+
for step_id, users in steps:
|
|
821
|
+
if not users or step_id in seen:
|
|
822
|
+
continue
|
|
823
|
+
seen.add(step_id)
|
|
824
|
+
order += 1
|
|
825
|
+
out.append({"stepId": step_id, "orderId": order, "type": kind,
|
|
826
|
+
"users": [{"userId": u} for u in users]})
|
|
827
|
+
return out
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
def _assignee_key(rows) -> list:
|
|
831
|
+
"""Assignees compared by what routes a step (names and key casing ignored)."""
|
|
832
|
+
out = []
|
|
833
|
+
for r in rows or []:
|
|
834
|
+
g = {str(k).lower(): v for k, v in r.items()}
|
|
835
|
+
users = [str({str(k).lower(): v for k, v in u.items()}.get("userid")) for u in g.get("users") or []]
|
|
836
|
+
out.append((str(g.get("stepid")), int(g.get("orderid") or 0), int(g.get("type") or 0), tuple(users)))
|
|
837
|
+
return out
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
def reconcile_assignees(current: list, before: list, after: list) -> tuple[list, dict | None]:
|
|
841
|
+
"""(the Assignees to save, a report or None) for a surgical change from `before` to `after`
|
|
842
|
+
elements.
|
|
843
|
+
|
|
844
|
+
The designer re-derives the list on every save, so a surgical edit that echoes the old list
|
|
845
|
+
leaves a removed approver still routed, or a new approval control unrouted. Only a change
|
|
846
|
+
that alters the derived routing re-derives it, and then the WHOLE list is saved as the
|
|
847
|
+
designer would save it; any other edit keeps `current` byte-for-byte, so it never rewrites
|
|
848
|
+
routing it did not touch."""
|
|
849
|
+
derived = derive_assignees(after)
|
|
850
|
+
if _assignee_key(derive_assignees(before)) == _assignee_key(derived):
|
|
851
|
+
return current, None
|
|
852
|
+
return derived, {"rederived": True, "before": _assignee_key(current),
|
|
853
|
+
"after": _assignee_key(derived)}
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
def build(config: dict, ctx: dict) -> dict:
|
|
857
|
+
ctx = dict(ctx)
|
|
858
|
+
ctx["form_id"] = ctx.get("form_id") or _new_id(ctx)
|
|
859
|
+
shell = _shell()
|
|
860
|
+
# The designer's field-name resolver walks from the "form" handle variable
|
|
861
|
+
# (Data.variables[0]) into Data.dataModel. So the dataModel ROOT id, the
|
|
862
|
+
# field-path first segment, AND variables[0].id MUST be the SAME id, else a stored
|
|
863
|
+
# field path can't be turned back into its name -> input shows the raw guid, output
|
|
864
|
+
# shows blank (live-confirmed: variables[0]=65396456 but a fresh root id => unresolved).
|
|
865
|
+
# The shell ships the FORM-scope root (scopes:["FORM"]) as that shared id; derive the
|
|
866
|
+
# root from it so the three always agree. It is still != the form template id (what the
|
|
867
|
+
# earlier "root != form id" fix required), so both invariants hold.
|
|
868
|
+
_form_var = (shell.get("variables") or [{}])[0]
|
|
869
|
+
ctx["dm_root_id"] = (ctx.get("dm_root_id") or _form_var.get("dataType")
|
|
870
|
+
or _form_var.get("id") or _new_id(ctx))
|
|
871
|
+
ctx["field_paths"] = {}
|
|
872
|
+
ctx["value_attr_ids"] = {}
|
|
873
|
+
elements: list = []
|
|
874
|
+
for e in config.get("elements", []):
|
|
875
|
+
_build_element(e, ctx, elements)
|
|
876
|
+
_resolve_late_field_refs(elements, ctx)
|
|
877
|
+
data = {
|
|
878
|
+
"code": _build_code(config, ctx),
|
|
879
|
+
"theme": _apply_theme(shell.get("theme"), config.get("theme")),
|
|
880
|
+
"events": shell.get("events", []),
|
|
881
|
+
"images": shell.get("images", {"logo": None, "header": None}),
|
|
882
|
+
"elements": elements,
|
|
883
|
+
"messages": shell.get("messages"),
|
|
884
|
+
"dataModel": _build_data_model(ctx["dm_root_id"], elements, ctx),
|
|
885
|
+
"variables": shell.get("variables"),
|
|
886
|
+
"browserTitle": config.get("browserTitle", config["name"]),
|
|
887
|
+
"hideBranding": bool(config.get("hideBranding", False)),
|
|
888
|
+
"dataModelAttributeFormatVersion": _DM_FORMAT_VERSION,
|
|
889
|
+
}
|
|
890
|
+
if config.get("chainConfig") is not None:
|
|
891
|
+
data["chainConfig"] = config["chainConfig"]
|
|
892
|
+
# Dark theme (opt-in): the dark palette is persisted INSIDE the single `theme` array —
|
|
893
|
+
# the COLORS group's `properties` becomes a {light, dark} ModeProperties object (verified
|
|
894
|
+
# vs ui-builder main: getPaletteStyleSheet reads properties.light/.dark). themeMode is
|
|
895
|
+
# editor-only and never saved. Emitted only when the caller opts in via `dark`/`themeDark`,
|
|
896
|
+
# so light-only forms are byte-identical.
|
|
897
|
+
if config.get("dark") or config.get("themeDark") is not None or config.get("themeMode") == "dark":
|
|
898
|
+
data["theme"] = _apply_dark(data["theme"], config.get("themeDark"))
|
|
899
|
+
# Guard the field-name-resolution invariant (see dm_root_id above): the dataModel
|
|
900
|
+
# root and every field-path root must equal the form handle variable id, or the
|
|
901
|
+
# designer renders process<->form maps as raw guids/blanks. Fail loudly here so a
|
|
902
|
+
# future change can't silently reintroduce the bug.
|
|
903
|
+
_fv = (data.get("variables") or [{}])[0].get("id")
|
|
904
|
+
if data["dataModel"].get("id") != _fv:
|
|
905
|
+
raise UsageError(f"form data-model root {data['dataModel'].get('id')!r} must "
|
|
906
|
+
f"equal the form handle variable id {_fv!r}")
|
|
907
|
+
_bad = [p for p in ctx["field_paths"].values() if not str(p).startswith(f"{_fv}.")]
|
|
908
|
+
if _bad:
|
|
909
|
+
raise UsageError(f"form field paths must start with the form root {_fv!r}: {_bad[:3]}")
|
|
910
|
+
# An explicit `assignees` list still wins verbatim; otherwise derive it the way the
|
|
911
|
+
# designer's save does, so an approver actually receives the approval step.
|
|
912
|
+
assignees = config["assignees"] if "assignees" in config else derive_assignees(elements)
|
|
913
|
+
# The designer saves a form that involves users as a USER_TASK. Only fill the gap when
|
|
914
|
+
# the caller did not choose a type.
|
|
915
|
+
if "type" in config:
|
|
916
|
+
ftype = config["type"]
|
|
917
|
+
else:
|
|
918
|
+
involves_users = bool(assignees) or any(e.get("type") == "assignee" for e in elements)
|
|
919
|
+
ftype = "usertask" if involves_users else "general"
|
|
920
|
+
return {
|
|
921
|
+
"Id": ctx["form_id"],
|
|
922
|
+
"Name": config["name"],
|
|
923
|
+
"IsPrivate": bool(config.get("isPrivate", False)),
|
|
924
|
+
"Type": _FORM_TYPE.get(str(ftype).strip().lower(), ftype),
|
|
925
|
+
"Status": 1 if config.get("publish", True) else 0,
|
|
926
|
+
"State": bool(config.get("enabled", True)),
|
|
927
|
+
"Assignees": assignees,
|
|
928
|
+
"Data": data,
|
|
929
|
+
"CustomUrl": None,
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
def _collect_process_ids(elements: list, out: set) -> None:
|
|
934
|
+
for e in elements or []:
|
|
935
|
+
for ev in e.get("events") or []:
|
|
936
|
+
if (ev.get("do") or "").strip().lower() in ("process", "run_process") and ev.get("processId"):
|
|
937
|
+
out.add(ev["processId"])
|
|
938
|
+
_collect_process_ids(e.get("children"), out)
|
|
939
|
+
for col in e.get("columns") or []:
|
|
940
|
+
if col.get("cell"):
|
|
941
|
+
_collect_process_ids([col["cell"]], out)
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def _prepare_ctx(client, config: dict) -> dict:
|
|
945
|
+
"""Impure pre-build step (has the live client): resolve every process referenced by a
|
|
946
|
+
RUN_PROCESS event to its variable name->id map, so the builder can wire inputMap/
|
|
947
|
+
outputMap by GUID (the backend rejects variable names). Fail loud on a bad processId.
|
|
948
|
+
Also records the call's environment, which decides the form-code-key."""
|
|
949
|
+
env = getattr(client, "env", None)
|
|
950
|
+
ctx: dict = {"form_code_env": env.get("name") if isinstance(env, dict) else None}
|
|
951
|
+
pids: set = set()
|
|
952
|
+
_collect_process_ids(config.get("elements"), pids)
|
|
953
|
+
if not pids:
|
|
954
|
+
return ctx
|
|
955
|
+
pvars: dict = {}
|
|
956
|
+
for pid in pids:
|
|
957
|
+
try:
|
|
958
|
+
body = client.get(f"/api/Projects/{pid}", VARIABLES_ONLY)
|
|
959
|
+
except Exception as e: # noqa: BLE001
|
|
960
|
+
raise UsageError(f"RUN_PROCESS references process {pid!r} which could not be "
|
|
961
|
+
f"fetched ({e}); check the processId.")
|
|
962
|
+
if isinstance(body, dict) and "flow" not in body and isinstance(body.get("result"), dict):
|
|
963
|
+
body = body["result"]
|
|
964
|
+
flow = body.get("flow") if isinstance(body, dict) else None
|
|
965
|
+
src = flow if isinstance(flow, dict) else (body if isinstance(body, dict) else {})
|
|
966
|
+
variables = src.get("variables") or []
|
|
967
|
+
pvars[pid] = {v.get("name"): v.get("id") for v in variables if v.get("name")}
|
|
968
|
+
ctx["process_vars"] = pvars
|
|
969
|
+
return ctx
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
def _extract_id(resp, dto):
|
|
973
|
+
if isinstance(resp, dict) and (resp.get("id") or resp.get("Id")):
|
|
974
|
+
return resp.get("id") or resp.get("Id")
|
|
975
|
+
if isinstance(resp, str) and resp.strip():
|
|
976
|
+
return resp.strip().strip('"')
|
|
977
|
+
return dto.get("Id") if isinstance(dto, dict) else None
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
def _edit(client, resource_id, config, ctx):
|
|
981
|
+
ctx = dict(ctx)
|
|
982
|
+
ctx["form_id"] = resource_id
|
|
983
|
+
dto = build(config, ctx)
|
|
984
|
+
client.put("/api/FormTemplate", dto)
|
|
985
|
+
return client.get(f"/api/FormTemplate/{resource_id}")
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
def _lint(resource) -> list[str]:
|
|
989
|
+
"""Non-blocking formlint warnings for a built DTO (`Data`) or a fetched form (`data`)."""
|
|
990
|
+
from tools.procesio import formlint
|
|
991
|
+
if not isinstance(resource, dict):
|
|
992
|
+
return []
|
|
993
|
+
return formlint.lint_form_data(resource.get("Data") or resource.get("data"))
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
COMPONENT = Component(
|
|
997
|
+
name="form",
|
|
998
|
+
description="PROCESIO form template: a control tree (elements) rendered at runtime.",
|
|
999
|
+
dir=DIR,
|
|
1000
|
+
build=build,
|
|
1001
|
+
create_endpoint=("POST", "/api/FormTemplate"),
|
|
1002
|
+
get_path="/api/FormTemplate/{id}",
|
|
1003
|
+
extract_id=_extract_id,
|
|
1004
|
+
prepare_ctx=_prepare_ctx,
|
|
1005
|
+
edit=_edit,
|
|
1006
|
+
patch_action="form-update",
|
|
1007
|
+
add_action="form-add-element",
|
|
1008
|
+
lint=_lint,
|
|
1009
|
+
)
|