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,871 @@
|
|
|
1
|
+
"""Surgical read/write of ONE runtime parameter on ONE node of a PROCESIO flow.
|
|
2
|
+
|
|
3
|
+
Why this exists: the desired-state builders (`process-create` / `process-edit`) rebuild a whole flow
|
|
4
|
+
from a config. That is the right tool when you own the config, and the wrong one when a live flow was
|
|
5
|
+
hand-built in the designer and a single literal has to change — an API endpoint that moved host, a
|
|
6
|
+
timeout, a SQL statement, a script body. Rebuilding such a flow from a reconstructed config risks
|
|
7
|
+
losing everything the config cannot express; changing one `parameters[]` value risks nothing.
|
|
8
|
+
|
|
9
|
+
Model (see PROCESIO-API-NOTES.md):
|
|
10
|
+
- a node = `flow['actions'][i]`; its RUNTIME layer is `parameters[]`, each entry
|
|
11
|
+
`{tabPropertyId, value, variable[]}`. `value` is the literal text with `<%N%>` placeholders;
|
|
12
|
+
`variable[]` binds each `N` to a `{id, variableId, attribute}`.
|
|
13
|
+
- the DESIGNER layer (`customData.configuration[].settings[]`) mirrors it, keyed by the SAME id as
|
|
14
|
+
`tabPropertyId`, except a variable ref is the raw variable GUID there. NEVER hand-write it —
|
|
15
|
+
`dto.process.normalize.normalize_designer_layer` regenerates it from the runtime layer.
|
|
16
|
+
|
|
17
|
+
Scope guard: only a STRING parameter value may be set here. List/dict-shaped values (request-payload
|
|
18
|
+
tabs, decisional cases, subprocess maps, document mappers) are structured DTOs whose designer mirror is
|
|
19
|
+
not a clean 1:1 — those belong to the builder, not to a text patch.
|
|
20
|
+
|
|
21
|
+
Pure: mutates a raw flow DTO dict in place; the caller fetches / normalizes / validates / PUTs.
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import re
|
|
26
|
+
|
|
27
|
+
_PLACEHOLDER = re.compile(r"<%(\d+)%>")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def find_node(flow: dict, key: str) -> dict | None:
|
|
31
|
+
"""A node by id or by its canvas label (`actionName`). Exact match, id first."""
|
|
32
|
+
actions = flow.get("actions") or []
|
|
33
|
+
for a in actions:
|
|
34
|
+
if a.get("id") == key:
|
|
35
|
+
return a
|
|
36
|
+
for a in actions:
|
|
37
|
+
if a.get("actionName") == key:
|
|
38
|
+
return a
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _settings_index(node: dict) -> dict:
|
|
43
|
+
"""{setting.id: setting} for a node's designer settings, recursing into side-pannel nesting."""
|
|
44
|
+
byid: dict = {}
|
|
45
|
+
|
|
46
|
+
def walk(settings):
|
|
47
|
+
for s in settings or []:
|
|
48
|
+
if isinstance(s, dict):
|
|
49
|
+
if s.get("id"):
|
|
50
|
+
byid[s["id"]] = s
|
|
51
|
+
if isinstance(s.get("value"), list):
|
|
52
|
+
walk(s["value"])
|
|
53
|
+
for tab in (node.get("customData") or {}).get("configuration") or []:
|
|
54
|
+
walk(tab.get("settings") or [])
|
|
55
|
+
return byid
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _label_of(node: dict, property_id: str) -> str | None:
|
|
59
|
+
s = _settings_index(node).get(property_id)
|
|
60
|
+
return s.get("label") if s else None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def placeholders(value) -> list[int]:
|
|
64
|
+
"""The `<%N%>` indexes a parameter value binds, sorted — the value's variable contract."""
|
|
65
|
+
return sorted({int(m) for m in _PLACEHOLDER.findall(value)}) if isinstance(value, str) else []
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def describe_node(flow: dict, node: dict) -> dict:
|
|
69
|
+
"""One node's parameters with the designer label, value shape and bound variable names —
|
|
70
|
+
everything needed to pick the property to patch, without dumping the whole DTO."""
|
|
71
|
+
vmap = {v.get("id"): v.get("name") for v in flow.get("variables") or []}
|
|
72
|
+
params = []
|
|
73
|
+
for p in node.get("parameters") or []:
|
|
74
|
+
v = p.get("value")
|
|
75
|
+
bound = [{"index": ve.get("id"), "variable": vmap.get(ve.get("variableId")), "id": ve.get("variableId")}
|
|
76
|
+
for ve in (p.get("variable") or [])]
|
|
77
|
+
params.append({
|
|
78
|
+
"property": p.get("tabPropertyId"),
|
|
79
|
+
"label": _label_of(node, p.get("tabPropertyId")),
|
|
80
|
+
"kind": type(v).__name__ if v is not None else "null",
|
|
81
|
+
"editable": isinstance(v, str),
|
|
82
|
+
"value": v if isinstance(v, (str, int, float, type(None))) else "<structured>",
|
|
83
|
+
"binds": bound,
|
|
84
|
+
})
|
|
85
|
+
return {"node": node.get("actionName"), "id": node.get("id"),
|
|
86
|
+
"template": node.get("actionTemplateName"),
|
|
87
|
+
"disabled": bool(node.get("isDisabled")), "parameters": params}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def scan(flow: dict) -> list[dict]:
|
|
91
|
+
"""Every node with its parameter surface."""
|
|
92
|
+
return [describe_node(flow, a) for a in flow.get("actions") or []]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def find_param(node: dict, key: str) -> dict | None:
|
|
96
|
+
"""A parameter by tabPropertyId or by its designer label (case-insensitive)."""
|
|
97
|
+
params = node.get("parameters") or []
|
|
98
|
+
for p in params:
|
|
99
|
+
if p.get("tabPropertyId") == key:
|
|
100
|
+
return p
|
|
101
|
+
want = (key or "").strip().lower()
|
|
102
|
+
for p in params:
|
|
103
|
+
lbl = _label_of(node, p.get("tabPropertyId"))
|
|
104
|
+
if lbl and lbl.strip().lower() == want:
|
|
105
|
+
return p
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def set_param_value(node: dict, param: dict, new_value: str, *, allow_binding_change: bool = False) -> dict:
|
|
110
|
+
"""Set one parameter's literal `value`, in place. Returns {changed, before, after}.
|
|
111
|
+
|
|
112
|
+
Refuses a non-string current value (structured DTO — builder territory) and, unless
|
|
113
|
+
`allow_binding_change`, refuses a new text whose `<%N%>` set differs from the old one: the
|
|
114
|
+
placeholder set IS the contract with `variable[]`, and silently dropping or inventing one
|
|
115
|
+
unbinds a variable at runtime with no validation error.
|
|
116
|
+
"""
|
|
117
|
+
old = param.get("value")
|
|
118
|
+
if not isinstance(old, str):
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"parameter '{_label_of(node, param.get('tabPropertyId')) or param.get('tabPropertyId')}' "
|
|
121
|
+
f"holds a {type(old).__name__}, not text - structured parameters are edited through the "
|
|
122
|
+
f"process builder (process-edit), not as a text patch")
|
|
123
|
+
if not allow_binding_change and placeholders(old) != placeholders(new_value):
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"placeholder set would change {placeholders(old)} -> {placeholders(new_value)}; "
|
|
126
|
+
f"each <%N%> binds variable[] entry N, so this silently unbinds a variable. "
|
|
127
|
+
f"Pass --allow-binding-change only when variable[] is being rewritten too")
|
|
128
|
+
param["value"] = new_value
|
|
129
|
+
return {"changed": old != new_value, "before": old, "after": new_value}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def bind_param_var(node: dict, param: dict, bindings: dict, *, find=None, replace=None) -> dict:
|
|
133
|
+
"""Set a parameter's `variable[]` bindings, optionally rewriting the value first, in place.
|
|
134
|
+
|
|
135
|
+
`bindings` maps each `<%N%>` index to a variable id. A NAMED token like `<%firstName%>` typed as
|
|
136
|
+
literal text is NOT a runtime placeholder (only positional `<%N%>` substitutes), so it renders
|
|
137
|
+
verbatim; the fix is to turn it INTO `<%0%>` and bind index 0 to the variable. Pass `find`/`replace`
|
|
138
|
+
to make that small substitution on the value here (so the 30 KB body need not be re-sent), then the
|
|
139
|
+
value's placeholder set must equal the binding index set, or the write is refused - a `<%N%>` with
|
|
140
|
+
no binding substitutes to nothing, a binding with no `<%N%>` is dead.
|
|
141
|
+
"""
|
|
142
|
+
old = param.get("value")
|
|
143
|
+
if not isinstance(old, str):
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f"parameter '{_label_of(node, param.get('tabPropertyId')) or param.get('tabPropertyId')}' "
|
|
146
|
+
f"holds a {type(old).__name__}, not text - structured parameters are builder territory")
|
|
147
|
+
value = old
|
|
148
|
+
if find is not None:
|
|
149
|
+
if find not in value:
|
|
150
|
+
raise ValueError(f"--find text is not present in the parameter value")
|
|
151
|
+
value = value.replace(find, replace if replace is not None else "")
|
|
152
|
+
idxs = set(placeholders(value))
|
|
153
|
+
if idxs != set(bindings):
|
|
154
|
+
raise ValueError(
|
|
155
|
+
f"after the edit the value binds placeholders {sorted(idxs)} but --bind covers "
|
|
156
|
+
f"{sorted(bindings)} - each <%N%> must have exactly one binding and vice versa")
|
|
157
|
+
param["value"] = value
|
|
158
|
+
param["variable"] = [{"id": i, "variableId": bindings[i], "attribute": None} for i in sorted(bindings)]
|
|
159
|
+
return {"changed": True, "value_changed": old != value, "before": old if old != value else None,
|
|
160
|
+
"after": value if old != value else None, "bindings": {i: bindings[i] for i in sorted(bindings)}}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# --- literal text replacement across a node's runtime + designer layers -------------------------
|
|
164
|
+
#
|
|
165
|
+
# Some values a flow author needs to change live INSIDE a structured parameter — a Map Data row's
|
|
166
|
+
# source expression, a decisional case's literal, a subprocess input's constant. Those are lists of
|
|
167
|
+
# dicts whose designer mirror is not a clean 1:1 with the runtime layer (the runtime holds `<%N%>`,
|
|
168
|
+
# the designer the variable GUID), so the normalizer deliberately leaves them alone and
|
|
169
|
+
# `set_param_value` refuses them. An EXACT-LITERAL replace is the one edit that is still safe there:
|
|
170
|
+
# the same literal appears verbatim in both layers, so replacing it in every string leaf of both
|
|
171
|
+
# keeps them consistent without either layer having to be understood.
|
|
172
|
+
|
|
173
|
+
def _walk_strings(obj, path: str, fn):
|
|
174
|
+
"""Apply `fn(path, str) -> str` to every string leaf of a nested list/dict, in place."""
|
|
175
|
+
if isinstance(obj, dict):
|
|
176
|
+
for k, v in list(obj.items()):
|
|
177
|
+
p = f"{path}.{k}"
|
|
178
|
+
if isinstance(v, str):
|
|
179
|
+
obj[k] = fn(p, v)
|
|
180
|
+
else:
|
|
181
|
+
_walk_strings(v, p, fn)
|
|
182
|
+
elif isinstance(obj, list):
|
|
183
|
+
for i, v in enumerate(obj):
|
|
184
|
+
p = f"{path}[{i}]"
|
|
185
|
+
if isinstance(v, str):
|
|
186
|
+
obj[i] = fn(p, v)
|
|
187
|
+
else:
|
|
188
|
+
_walk_strings(v, p, fn)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def replace_text(node: dict, find: str, replace: str, *, property_key: str | None = None) -> list[dict]:
|
|
192
|
+
"""Replace an exact literal in every string leaf of the node's runtime parameters AND designer
|
|
193
|
+
settings. Returns one hit per changed leaf: {layer, path, before, after, count}.
|
|
194
|
+
|
|
195
|
+
`property_key` narrows the edit to one parameter (by tabPropertyId or designer label); omit it to
|
|
196
|
+
sweep the whole node. Both layers are swept, which is what keeps runtime and designer consistent
|
|
197
|
+
for the structured settings the normalizer cannot regenerate.
|
|
198
|
+
"""
|
|
199
|
+
if not find:
|
|
200
|
+
raise ValueError("--find must be a non-empty literal")
|
|
201
|
+
only = None
|
|
202
|
+
if property_key:
|
|
203
|
+
p = find_param(node, property_key)
|
|
204
|
+
if p is None:
|
|
205
|
+
raise ValueError(f"property not found on node '{node.get('actionName')}': {property_key}")
|
|
206
|
+
only = p.get("tabPropertyId")
|
|
207
|
+
|
|
208
|
+
hits: list[dict] = []
|
|
209
|
+
|
|
210
|
+
def sub(layer):
|
|
211
|
+
def fn(path, s):
|
|
212
|
+
if find not in s:
|
|
213
|
+
return s
|
|
214
|
+
new = s.replace(find, replace)
|
|
215
|
+
hits.append({"layer": layer, "path": path, "before": s, "after": new,
|
|
216
|
+
"count": s.count(find)})
|
|
217
|
+
return new
|
|
218
|
+
return fn
|
|
219
|
+
|
|
220
|
+
for p in node.get("parameters") or []:
|
|
221
|
+
if only and p.get("tabPropertyId") != only:
|
|
222
|
+
continue
|
|
223
|
+
v = p.get("value")
|
|
224
|
+
base = f"parameters[{p.get('tabPropertyId')}]"
|
|
225
|
+
if isinstance(v, str):
|
|
226
|
+
nv = sub("runtime")(base, v)
|
|
227
|
+
p["value"] = nv
|
|
228
|
+
else:
|
|
229
|
+
_walk_strings(v, base, sub("runtime"))
|
|
230
|
+
|
|
231
|
+
settings = _settings_index(node)
|
|
232
|
+
for sid, s in settings.items():
|
|
233
|
+
if only and sid != only:
|
|
234
|
+
continue
|
|
235
|
+
v = s.get("value")
|
|
236
|
+
base = f"customData[{sid}]"
|
|
237
|
+
if isinstance(v, str):
|
|
238
|
+
s["value"] = sub("designer")(base, v)
|
|
239
|
+
else:
|
|
240
|
+
_walk_strings(v, base, sub("designer"))
|
|
241
|
+
return hits
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# --- process-variable type surgery --------------------------------------------------------------
|
|
245
|
+
#
|
|
246
|
+
# A flow variable is `{id, name, dataType, type, isList, isRequired, defaultValue}`. `type` is the
|
|
247
|
+
# DIRECTION: 10 input / 20 process / 30 output / 40 system / 50 auto. Inputs and outputs ARE the
|
|
248
|
+
# process's public contract (the run payload and the webhook response), so retyping one silently
|
|
249
|
+
# breaks every caller — that is why retyping them needs an explicit override here.
|
|
250
|
+
#
|
|
251
|
+
# System (40) and Auto (50) variables are written by the ENGINE, not by the process author: an Auto
|
|
252
|
+
# variable is one an action creates for its own outputs (Call Subprocess exposes the child instance
|
|
253
|
+
# ids that way) and its value is filled in at run time. Retyping or re-defaulting one either does
|
|
254
|
+
# nothing or breaks the action that owns it, so those edits are refused outright, with no override.
|
|
255
|
+
|
|
256
|
+
VAR_DIRECTION = {10: "input", 20: "process", 30: "output", 40: "system", 50: "auto"}
|
|
257
|
+
ENGINE_OWNED_DIRECTIONS = (40, 50)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _refuse_engine_owned(var: dict, what: str) -> None:
|
|
261
|
+
direction = var.get("type")
|
|
262
|
+
if direction in ENGINE_OWNED_DIRECTIONS:
|
|
263
|
+
raise ValueError(
|
|
264
|
+
f"'{var.get('name')}' is a {VAR_DIRECTION[direction]} variable - the engine writes it, "
|
|
265
|
+
f"so its {what} is not the process author's to change")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def find_variable(flow: dict, key: str) -> dict | None:
|
|
269
|
+
"""A variable by id or by name."""
|
|
270
|
+
variables = flow.get("variables") or []
|
|
271
|
+
for v in variables:
|
|
272
|
+
if v.get("id") == key:
|
|
273
|
+
return v
|
|
274
|
+
for v in variables:
|
|
275
|
+
if v.get("name") == key:
|
|
276
|
+
return v
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def set_variable_type(flow: dict, var: dict, data_type: str, *,
|
|
281
|
+
is_list: bool | None = None, allow_contract_change: bool = False) -> dict:
|
|
282
|
+
"""Retype one flow variable in place. Returns {changed, before, after}.
|
|
283
|
+
|
|
284
|
+
Refuses an input (10) or output (30) variable without `allow_contract_change`: those are the run
|
|
285
|
+
payload and the response shape a caller depends on. Refuses a system (40) or auto (50) variable
|
|
286
|
+
always: the engine owns its type.
|
|
287
|
+
"""
|
|
288
|
+
_refuse_engine_owned(var, "type")
|
|
289
|
+
direction = var.get("type")
|
|
290
|
+
if direction in (10, 30) and not allow_contract_change:
|
|
291
|
+
raise ValueError(
|
|
292
|
+
f"'{var.get('name')}' is an {VAR_DIRECTION.get(direction)} variable - retyping it changes "
|
|
293
|
+
f"the process's public contract (run payload / webhook response). Pass "
|
|
294
|
+
f"--allow-contract-change if that is genuinely intended")
|
|
295
|
+
before = {"dataType": var.get("dataType"), "isList": var.get("isList")}
|
|
296
|
+
var["dataType"] = data_type
|
|
297
|
+
if is_list is not None:
|
|
298
|
+
var["isList"] = is_list
|
|
299
|
+
after = {"dataType": var.get("dataType"), "isList": var.get("isList")}
|
|
300
|
+
return {"changed": before != after, "before": before, "after": after,
|
|
301
|
+
"direction": VAR_DIRECTION.get(direction, direction)}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def set_variable_required(flow: dict, var: dict, required: bool, *,
|
|
306
|
+
allow_contract_change: bool = False) -> dict:
|
|
307
|
+
"""Set one INPUT variable's `isRequired` flag in place. Returns {changed, before, after}.
|
|
308
|
+
|
|
309
|
+
Only an input (10) variable has a caller-supplied value, so `isRequired` is meaningless
|
|
310
|
+
anywhere else and setting it there is refused rather than silently written.
|
|
311
|
+
|
|
312
|
+
The two directions are NOT symmetric, and only one of them is guarded:
|
|
313
|
+
|
|
314
|
+
* **Clearing** it (required -> optional) can never break an existing caller: every payload
|
|
315
|
+
that was valid before is still valid. It is allowed outright.
|
|
316
|
+
* **Setting** it (optional -> required) tightens the public contract and breaks every caller
|
|
317
|
+
that legitimately omitted the field, so it needs `allow_contract_change`.
|
|
318
|
+
|
|
319
|
+
Clearing the flag is safe for CALLERS but not automatically safe for the FLOW: a Node body
|
|
320
|
+
that injects the variable through a bare raw placeholder (`var f = <%6%>;`) becomes
|
|
321
|
+
`var f = ;` -- a SyntaxError -- the first time the value is genuinely absent. Guard such an
|
|
322
|
+
injection as `[<%6%>][0]` (which yields `undefined`) BEFORE clearing the flag. This function
|
|
323
|
+
cannot see into node bodies, so it cannot check that for you.
|
|
324
|
+
"""
|
|
325
|
+
direction = var.get("type")
|
|
326
|
+
if direction != 10:
|
|
327
|
+
raise ValueError(
|
|
328
|
+
f"'{var.get('name')}' is a {VAR_DIRECTION.get(direction, direction)} variable - only an "
|
|
329
|
+
f"input variable carries a caller-supplied value, so isRequired means nothing on it")
|
|
330
|
+
required = bool(required)
|
|
331
|
+
before = {"isRequired": var.get("isRequired")}
|
|
332
|
+
if required and not before["isRequired"] and not allow_contract_change:
|
|
333
|
+
raise ValueError(
|
|
334
|
+
f"making input '{var.get('name')}' required tightens the process's public contract and "
|
|
335
|
+
f"breaks every caller that omits it. Pass --allow-contract-change if that is intended")
|
|
336
|
+
var["isRequired"] = required
|
|
337
|
+
after = {"isRequired": var.get("isRequired")}
|
|
338
|
+
return {"changed": before != after, "before": before, "after": after,
|
|
339
|
+
"direction": VAR_DIRECTION.get(direction, direction)}
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
VAR_DIRECTION_BY_NAME = {"input": 10, "process": 20, "output": 30}
|
|
344
|
+
|
|
345
|
+
_DATATYPE_ALIASES = {
|
|
346
|
+
"boolean": "0317bfee-b2f5-4bde-bfe8-121212121210",
|
|
347
|
+
"integer": "0317bfee-b2f5-4bde-bfe8-121212121211",
|
|
348
|
+
"float": "0317bfee-b2f5-4bde-bfe8-121212121212",
|
|
349
|
+
"double": "0317bfee-b2f5-4bde-bfe8-121212121213",
|
|
350
|
+
"string": "0317bfee-b2f5-4bde-bfe8-121212121214",
|
|
351
|
+
"date": "0317bfee-b2f5-4bde-bfe8-121212121215",
|
|
352
|
+
"time": "0317bfee-b2f5-4bde-bfe8-121212121217",
|
|
353
|
+
"datetime":"0317bfee-b2f5-4bde-bfe8-121212121218",
|
|
354
|
+
"guid": "0317bfee-b2f5-4bde-bfe8-121212121222",
|
|
355
|
+
"json": "0317bfee-b2f5-4bde-bfe8-121212121220",
|
|
356
|
+
"object": "0317bfee-b2f5-4bde-bfe8-121212121221",
|
|
357
|
+
"file": "10c6ac59-3929-49e6-99dc-121212121219",
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def resolve_data_type(spec: str) -> str:
|
|
362
|
+
"""A friendly alias ('string', 'file') or a data-type GUID -> the GUID."""
|
|
363
|
+
key = str(spec or "").strip().lower()
|
|
364
|
+
if key in _DATATYPE_ALIASES:
|
|
365
|
+
return _DATATYPE_ALIASES[key]
|
|
366
|
+
if "-" in str(spec):
|
|
367
|
+
return str(spec).strip()
|
|
368
|
+
raise ValueError(
|
|
369
|
+
f"unknown data type {spec!r}; use a GUID or one of: {', '.join(sorted(_DATATYPE_ALIASES))}")
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def add_variable(flow: dict, name: str, data_type: str, direction: str, *,
|
|
373
|
+
is_list: bool = False, default_value=None, is_required: bool = False,
|
|
374
|
+
new_id: str | None = None) -> dict:
|
|
375
|
+
"""Append one variable to a flow in place. Returns the variable that was added.
|
|
376
|
+
|
|
377
|
+
A flow's variables are addressed BY ID everywhere else - node parameter binds, a form's
|
|
378
|
+
input/output maps - so adding one is safe for existing wiring in a way that renaming or
|
|
379
|
+
retyping is not: nothing can already point at an id that did not exist.
|
|
380
|
+
|
|
381
|
+
Refused rather than guessed:
|
|
382
|
+
* a duplicate NAME, even though the platform keys on id. Two variables sharing a name make
|
|
383
|
+
every later name-based lookup ambiguous, and the form-event tooling resolves names to ids.
|
|
384
|
+
* an unknown direction. 10/20/30 are input/process/output; a variable with no direction is
|
|
385
|
+
not addressable from either the run payload or the response.
|
|
386
|
+
"""
|
|
387
|
+
import uuid as _uuid
|
|
388
|
+
|
|
389
|
+
nm = str(name or "").strip()
|
|
390
|
+
if not nm:
|
|
391
|
+
raise ValueError("a variable needs a name")
|
|
392
|
+
dirn = str(direction or "").strip().lower()
|
|
393
|
+
if dirn not in VAR_DIRECTION_BY_NAME:
|
|
394
|
+
raise ValueError(f"direction must be one of input, process, output - got {direction!r}")
|
|
395
|
+
for v in flow.get("variables") or []:
|
|
396
|
+
if str(v.get("name")) == nm:
|
|
397
|
+
raise ValueError(f"a variable named {nm!r} already exists in this flow "
|
|
398
|
+
f"(id {v.get('id')}); pick another name")
|
|
399
|
+
var = {
|
|
400
|
+
"id": new_id or str(_uuid.uuid4()),
|
|
401
|
+
"contextId": None,
|
|
402
|
+
"dataType": resolve_data_type(data_type),
|
|
403
|
+
"type": VAR_DIRECTION_BY_NAME[dirn],
|
|
404
|
+
"name": nm,
|
|
405
|
+
"defaultValue": default_value,
|
|
406
|
+
"isList": bool(is_list),
|
|
407
|
+
"isError": False,
|
|
408
|
+
"isRequired": bool(is_required),
|
|
409
|
+
}
|
|
410
|
+
flow.setdefault("variables", []).append(var)
|
|
411
|
+
return var
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def set_variable_default(flow: dict, var: dict, default_value, *,
|
|
416
|
+
allow_contract_change: bool = False) -> dict:
|
|
417
|
+
"""Set one variable's `defaultValue` in place. Returns {changed, before, after}.
|
|
418
|
+
|
|
419
|
+
This is how an input stops being something the caller must supply. Verified on a File
|
|
420
|
+
variable: a run that omits it entirely still receives the object, and the platform
|
|
421
|
+
RE-STAGES the referenced file into the calling flow's own instance path. So a value a user
|
|
422
|
+
should never have to provide - a configuration, a reference table, an empty placeholder -
|
|
423
|
+
can be set once by an admin and then simply is not a field on anyone's form.
|
|
424
|
+
|
|
425
|
+
Two cautions, both learned rather than assumed:
|
|
426
|
+
|
|
427
|
+
* A File default carries a `path` pointing into some flow instance's storage. If that
|
|
428
|
+
instance is cleaned up - data retention is per-flow and finite - the default becomes a
|
|
429
|
+
dangling reference. Stamp defaults from a file whose lifetime you control, and re-check
|
|
430
|
+
after any retention change.
|
|
431
|
+
* Changing an input's default changes what a run does when the caller says nothing. That
|
|
432
|
+
is a contract change in every sense that matters, even though the signature is unchanged,
|
|
433
|
+
so an input/output variable needs `allow_contract_change`.
|
|
434
|
+
|
|
435
|
+
A system (40) or auto (50) variable is refused always: the engine fills its value at run time.
|
|
436
|
+
"""
|
|
437
|
+
_refuse_engine_owned(var, "default value")
|
|
438
|
+
direction = var.get("type")
|
|
439
|
+
if direction in (10, 30) and not allow_contract_change:
|
|
440
|
+
raise ValueError(
|
|
441
|
+
f"'{var.get('name')}' is an {VAR_DIRECTION.get(direction)} variable - changing its "
|
|
442
|
+
f"default changes what a run does when the caller supplies nothing. Pass "
|
|
443
|
+
f"--allow-contract-change if that is intended")
|
|
444
|
+
before = {"defaultValue": var.get("defaultValue")}
|
|
445
|
+
var["defaultValue"] = default_value
|
|
446
|
+
after = {"defaultValue": var.get("defaultValue")}
|
|
447
|
+
return {"changed": before != after, "before": before, "after": after,
|
|
448
|
+
"direction": VAR_DIRECTION.get(direction, direction)}
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _camel(key: str) -> str:
|
|
453
|
+
"""`TabPropertyId` -> `tabPropertyId`. The DTO builder emits PascalCase (the CREATE shape);
|
|
454
|
+
a flow read back from the API is camelCase. Splicing one into the other without this is the
|
|
455
|
+
silent-corruption case: the API accepts both, the designer renders neither consistently."""
|
|
456
|
+
return key[:1].lower() + key[1:] if key else key
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def to_live_action(dto: dict) -> dict:
|
|
460
|
+
"""Convert a builder ActionDto (PascalCase) into the camelCase shape a LIVE flow carries.
|
|
461
|
+
|
|
462
|
+
Only the top level and the `parameters` rows need it - `customData` is already authored in
|
|
463
|
+
the live casing by the builder, and its `configuration` tree is copied from the template.
|
|
464
|
+
"""
|
|
465
|
+
out = {}
|
|
466
|
+
for k, v in dto.items():
|
|
467
|
+
ck = _camel(k)
|
|
468
|
+
if ck == "parameters" and isinstance(v, list):
|
|
469
|
+
out[ck] = [{_camel(pk): pv for pk, pv in row.items()} if isinstance(row, dict) else row
|
|
470
|
+
for row in v]
|
|
471
|
+
elif ck == "ports" and isinstance(v, list):
|
|
472
|
+
out[ck] = [{_camel(pk): pv for pk, pv in row.items()} if isinstance(row, dict) else row
|
|
473
|
+
for row in v]
|
|
474
|
+
else:
|
|
475
|
+
out[ck] = v
|
|
476
|
+
return out
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def insert_node(flow: dict, after: dict, action: dict) -> tuple[bool, str]:
|
|
480
|
+
"""Splice one already-built action into a flow immediately AFTER `after`, in place.
|
|
481
|
+
|
|
482
|
+
Ports live on the SOURCE action and carry `destinationId`, so an insertion is two edits and
|
|
483
|
+
not one: the new node gets a port to whatever `after` pointed at, and `after`'s own outgoing
|
|
484
|
+
port is repointed at the new node. Do only the first and the new node is unreachable; do only
|
|
485
|
+
the second and the tail of the flow is orphaned. Both failures validate.
|
|
486
|
+
|
|
487
|
+
Refused rather than guessed: an anchor with more than one outgoing port (which branch the new
|
|
488
|
+
node belongs on is a design decision), and an anchor that is not in this flow.
|
|
489
|
+
"""
|
|
490
|
+
actions = flow.get("actions") or []
|
|
491
|
+
anchor_id = after.get("id")
|
|
492
|
+
if not any(a.get("id") == anchor_id for a in actions):
|
|
493
|
+
return False, "anchor %r is not in this flow" % (after.get("actionName") or anchor_id)
|
|
494
|
+
|
|
495
|
+
outgoing = [p for p in after.get("ports") or []
|
|
496
|
+
if p.get("sourceId") == anchor_id and p.get("destinationId")]
|
|
497
|
+
if len(outgoing) > 1:
|
|
498
|
+
return False, ("anchor %r has %d outgoing ports - which branch the new node belongs on is a "
|
|
499
|
+
"design decision, so wire it explicitly instead"
|
|
500
|
+
% (after.get("actionName"), len(outgoing)))
|
|
501
|
+
|
|
502
|
+
new_id = action.get("id")
|
|
503
|
+
successor = outgoing[0]["destinationId"] if outgoing else None
|
|
504
|
+
action.setdefault("ports", [])
|
|
505
|
+
if successor:
|
|
506
|
+
action["ports"].append({
|
|
507
|
+
"id": str(__import__("uuid").uuid4()), "flowId": flow.get("id"),
|
|
508
|
+
"sourceId": new_id, "destinationId": successor,
|
|
509
|
+
"type": 0, "state": 1, "data": {}, "errors": {}, "config": {}})
|
|
510
|
+
outgoing[0]["destinationId"] = new_id
|
|
511
|
+
tail = "between %r and its successor" % (after.get("actionName"),)
|
|
512
|
+
else:
|
|
513
|
+
tail = "after %r, which had no successor" % (after.get("actionName"),)
|
|
514
|
+
|
|
515
|
+
actions.append(action)
|
|
516
|
+
flow["actions"] = actions
|
|
517
|
+
return True, "inserted %r (%s) %s" % (action.get("actionName"),
|
|
518
|
+
action.get("actionTemplateName"), tail)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def set_process_title(flow: dict, title: str) -> dict:
|
|
522
|
+
"""Set a flow's title in place. Returns {changed, before, after}.
|
|
523
|
+
|
|
524
|
+
The title is cosmetic - the platform wires everything by id, never by name (same reasoning as
|
|
525
|
+
rename-actions) - so this touches only the top-level `title` field, no node parameter and no
|
|
526
|
+
designer customData, and cannot break a flow. A `duplicate` always lands as '... (Copy)', so this
|
|
527
|
+
is the follow-up that gives the copy a real name.
|
|
528
|
+
"""
|
|
529
|
+
before = flow.get("title")
|
|
530
|
+
flow["title"] = title
|
|
531
|
+
return {"changed": before != title, "before": before, "after": title}
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
# --------------------------------------------------------------------------- node removal
|
|
535
|
+
|
|
536
|
+
_TERMINALS = ("Start", "Stop")
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def delete_node(flow: dict, node: dict) -> tuple[bool, str]:
|
|
540
|
+
"""Remove one action from a flow and reconnect the graph around it, in place.
|
|
541
|
+
|
|
542
|
+
A dead node is not free: it still executes, still bills an execution, and still throws into
|
|
543
|
+
whatever error variable it was given, so a node whose upstream contract moved on fails on every
|
|
544
|
+
single run with nothing downstream to show for it. Deleting it over the API means healing the
|
|
545
|
+
edges by hand — every port that pointed AT this node is re-pointed at the node's own successor,
|
|
546
|
+
or dropped when it has none. Miss that and the tail of the flow is stranded.
|
|
547
|
+
|
|
548
|
+
Refused rather than guessed: `Start` / `Stop` (a flow needs both), a node with more than one
|
|
549
|
+
outgoing port (which successor inherits the incoming edges is a design decision, not a default),
|
|
550
|
+
and a node that is not in this flow. Variables are left alone — another node may still read them,
|
|
551
|
+
and an unused variable costs nothing. Returns (changed, message).
|
|
552
|
+
"""
|
|
553
|
+
actions = flow.get("actions") or []
|
|
554
|
+
if not any(a is node or a.get("id") == node.get("id") for a in actions):
|
|
555
|
+
return False, "node %r is not in this flow" % (node.get("actionName") or node.get("id"))
|
|
556
|
+
|
|
557
|
+
template = node.get("actionTemplateName")
|
|
558
|
+
if template in _TERMINALS:
|
|
559
|
+
return False, "refusing to delete the %s node - a flow needs both Start and Stop" % template
|
|
560
|
+
|
|
561
|
+
outgoing = [p for p in node.get("ports") or []
|
|
562
|
+
if p.get("destinationId") and p.get("sourceId") == node.get("id")]
|
|
563
|
+
if len(outgoing) > 1:
|
|
564
|
+
return False, ("node %r has more than one outgoing port - which successor inherits its "
|
|
565
|
+
"incoming edges is a design decision, so rewire it explicitly first"
|
|
566
|
+
% node.get("actionName"))
|
|
567
|
+
|
|
568
|
+
node_id = node.get("id")
|
|
569
|
+
successor = outgoing[0]["destinationId"] if outgoing else None
|
|
570
|
+
|
|
571
|
+
rewired, dropped = 0, 0
|
|
572
|
+
for action in actions:
|
|
573
|
+
if action.get("id") == node_id:
|
|
574
|
+
continue
|
|
575
|
+
kept = []
|
|
576
|
+
reachable = {p.get("destinationId") for p in action.get("ports") or []
|
|
577
|
+
if p.get("destinationId") != node_id}
|
|
578
|
+
for port in action.get("ports") or []:
|
|
579
|
+
if port.get("destinationId") != node_id:
|
|
580
|
+
kept.append(port)
|
|
581
|
+
continue
|
|
582
|
+
# A self-loop or a duplicate edge is worse than a missing one: the designer renders both
|
|
583
|
+
# and the engine follows them, so heal only into a successor this node does not reach.
|
|
584
|
+
if successor and successor != action.get("id") and successor not in reachable:
|
|
585
|
+
port["destinationId"] = successor
|
|
586
|
+
port["flowId"] = flow.get("id", port.get("flowId"))
|
|
587
|
+
reachable.add(successor)
|
|
588
|
+
kept.append(port)
|
|
589
|
+
rewired += 1
|
|
590
|
+
else:
|
|
591
|
+
dropped += 1
|
|
592
|
+
action["ports"] = kept
|
|
593
|
+
|
|
594
|
+
flow["actions"] = [a for a in actions if a.get("id") != node_id]
|
|
595
|
+
return True, ("deleted %r (%s); %d edge(s) rewired to its successor, %d dropped"
|
|
596
|
+
% (node.get("actionName"), template, rewired, dropped))
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
# --------------------------------------------------------------------------- error ports
|
|
600
|
+
|
|
601
|
+
# ErrorDataModel: the only datatype the engine accepts on an error-port variable. A plain string
|
|
602
|
+
# variable there validates and the port never fires, which is the silent version of this failing.
|
|
603
|
+
ERROR_DATA_MODEL = "10c6ac59-3929-49e6-99dc-121212121220"
|
|
604
|
+
# Per PROCESIO these templates expose no error port at all.
|
|
605
|
+
NO_ERROR_PORT = ("start", "stop", "join", "for each", "foreach")
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def set_error_port(flow: dict, node: dict, handler: dict | None, *, clear: bool = False) -> dict:
|
|
609
|
+
"""Route one node's error port to `handler`, in place. Returns a report.
|
|
610
|
+
|
|
611
|
+
An error port is THREE things and wiring any two of them is worse than wiring none, because
|
|
612
|
+
the flow still validates: the port itself (`type: 1`, `data: {"isDefault": "error"}`), the
|
|
613
|
+
node's `variableErrorId`, and that variable being `isError` of the error datatype. Written
|
|
614
|
+
together here so a caller cannot produce the half-wired shape.
|
|
615
|
+
|
|
616
|
+
What this buys, and why it is not just error cosmetics: a node that faults takes the whole
|
|
617
|
+
flow down at status 40, so a flow that a FORM launches on load cannot populate the form at
|
|
618
|
+
all. Sending the fault to the node's own successor lets the flow reach Stop and the form
|
|
619
|
+
render with that one section empty - provided the successor tolerates a null input, which is
|
|
620
|
+
the caller's judgement and is why the handler is named rather than inferred.
|
|
621
|
+
|
|
622
|
+
Refused rather than guessed: a template with no error port, a handler outside this flow, and
|
|
623
|
+
a handler that is the node itself (a self-loop the designer draws and the engine follows).
|
|
624
|
+
"""
|
|
625
|
+
import uuid as _uuid
|
|
626
|
+
|
|
627
|
+
template = str(node.get("actionTemplateName") or "").strip()
|
|
628
|
+
if template.lower() in NO_ERROR_PORT:
|
|
629
|
+
raise ValueError("%s has no error port, so there is nothing to route" % (template or "this action"))
|
|
630
|
+
|
|
631
|
+
node_id = node.get("id")
|
|
632
|
+
actions = flow.get("actions") or []
|
|
633
|
+
if not any(a.get("id") == node_id for a in actions):
|
|
634
|
+
raise ValueError("node %r is not in this flow" % (node.get("actionName") or node_id))
|
|
635
|
+
|
|
636
|
+
ports = [p for p in (node.get("ports") or [])]
|
|
637
|
+
existing = [p for p in ports if p.get("type") == 1]
|
|
638
|
+
before = existing[0].get("destinationId") if existing else None
|
|
639
|
+
|
|
640
|
+
if clear:
|
|
641
|
+
node["ports"] = [p for p in ports if p.get("type") != 1]
|
|
642
|
+
freed = node.get("variableErrorId")
|
|
643
|
+
node["variableErrorId"] = None
|
|
644
|
+
# Drop the variable too, but only when nothing else still points at it.
|
|
645
|
+
if freed and not any(a.get("variableErrorId") == freed for a in actions):
|
|
646
|
+
flow["variables"] = [v for v in (flow.get("variables") or []) if v.get("id") != freed]
|
|
647
|
+
return {"node": node.get("actionName"), "cleared": True, "handler_before": before,
|
|
648
|
+
"handler": None, "variable_error_id": None}
|
|
649
|
+
|
|
650
|
+
if handler is None:
|
|
651
|
+
raise ValueError("a handler is required unless clearing")
|
|
652
|
+
handler_id = handler.get("id")
|
|
653
|
+
if handler_id == node_id:
|
|
654
|
+
raise ValueError("a node cannot handle its own error - name a different action")
|
|
655
|
+
if not any(a.get("id") == handler_id for a in actions):
|
|
656
|
+
raise ValueError("handler %r is not in this flow" % (handler.get("actionName") or handler_id))
|
|
657
|
+
|
|
658
|
+
var_id = node.get("variableErrorId")
|
|
659
|
+
var = next((v for v in (flow.get("variables") or []) if v.get("id") == var_id), None)
|
|
660
|
+
if var is None:
|
|
661
|
+
base = "%s_error" % (node.get("actionName") or "node")
|
|
662
|
+
taken = {str(v.get("name")) for v in (flow.get("variables") or [])}
|
|
663
|
+
name, n = base, 2
|
|
664
|
+
while name in taken:
|
|
665
|
+
name, n = "%s_%d" % (base, n), n + 1
|
|
666
|
+
var = {"id": str(_uuid.uuid4()), "contextId": None, "dataType": ERROR_DATA_MODEL,
|
|
667
|
+
"type": 20, "name": name, "defaultValue": None, "isList": False,
|
|
668
|
+
"isError": True, "isRequired": False}
|
|
669
|
+
flow.setdefault("variables", []).append(var)
|
|
670
|
+
created_var = True
|
|
671
|
+
else:
|
|
672
|
+
# An existing binding of the wrong shape is the half-wired case: repair it rather than
|
|
673
|
+
# trusting it, since the flow validates either way.
|
|
674
|
+
var["dataType"], var["isError"] = ERROR_DATA_MODEL, True
|
|
675
|
+
created_var = False
|
|
676
|
+
node["variableErrorId"] = var["id"]
|
|
677
|
+
|
|
678
|
+
node["ports"] = [p for p in ports if p.get("type") != 1] + [{
|
|
679
|
+
"id": str(_uuid.uuid4()), "flowId": flow.get("id"), "sourceId": node_id,
|
|
680
|
+
"destinationId": handler_id, "type": 1, "state": 1,
|
|
681
|
+
"data": {"isDefault": "error"}, "errors": {}, "config": {}}]
|
|
682
|
+
|
|
683
|
+
return {"node": node.get("actionName"), "cleared": False, "handler_before": before,
|
|
684
|
+
"handler": handler.get("actionName"), "variable_error_id": var["id"],
|
|
685
|
+
"variable": var["name"], "variable_created": created_var}
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
# --------------------------------------------------------------------------- Query Store nodes
|
|
689
|
+
|
|
690
|
+
# The Query Store node's property ids end in these suffixes: the statement itself (a code-editor)
|
|
691
|
+
# and the map that binds its @params. Matched on the suffix because the prefix is the template's.
|
|
692
|
+
QUERY_PROP_SUFFIX = "b102"
|
|
693
|
+
PARAMS_PROP_SUFFIX = "b103"
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def find_query_params(node: dict) -> tuple[dict | None, dict | None]:
|
|
697
|
+
"""Return (statement parameter, parameters-map parameter) of a Query Store node.
|
|
698
|
+
|
|
699
|
+
Both are needed together by any caller that renumbers the statement's chips: a node numbers
|
|
700
|
+
its query chips and its Parameters-map sources from ONE `<%N%>` sequence, so a caller that
|
|
701
|
+
rewrites the statement without looking at the map can move a bound @param's source without
|
|
702
|
+
touching it.
|
|
703
|
+
"""
|
|
704
|
+
query = pmap = None
|
|
705
|
+
for p in node.get("parameters") or []:
|
|
706
|
+
tab = str(p.get("tabPropertyId") or "")
|
|
707
|
+
if tab.endswith(QUERY_PROP_SUFFIX):
|
|
708
|
+
query = p
|
|
709
|
+
elif tab.endswith(PARAMS_PROP_SUFFIX):
|
|
710
|
+
pmap = p
|
|
711
|
+
return query, pmap
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def renumber_node_placeholders(node: dict, order: list[dict]) -> int:
|
|
715
|
+
"""Renumber a node's `<%N%>` placeholders so one sequence runs across ALL its parameters.
|
|
716
|
+
|
|
717
|
+
A node does not number each parameter independently: the statement's chips, the
|
|
718
|
+
parameters-map sources and the output bindings draw from ONE sequence. So growing the map by
|
|
719
|
+
two rows silently moves the output bindings, and an output that still says `<%8%>` while its
|
|
720
|
+
binding moved to 10 is written back to the wrong variable - a flow that validates and quietly
|
|
721
|
+
loses its result.
|
|
722
|
+
|
|
723
|
+
`order` is the parameters in the sequence they should occupy; any parameter of the node not
|
|
724
|
+
listed keeps its place at the end, in its existing index order. Returns the next free index.
|
|
725
|
+
"""
|
|
726
|
+
listed = [p for p in order if p is not None]
|
|
727
|
+
listed_ids = {id(p) for p in listed}
|
|
728
|
+
rest = [p for p in (node.get("parameters") or []) if id(p) not in listed_ids]
|
|
729
|
+
|
|
730
|
+
def first_index(p):
|
|
731
|
+
hits = placeholders(p.get("value")) if not isinstance(p.get("value"), str) else \
|
|
732
|
+
[int(x) for x in _PLACEHOLDER.findall(p.get("value") or "")]
|
|
733
|
+
return min(hits) if hits else 10 ** 6
|
|
734
|
+
|
|
735
|
+
rest.sort(key=first_index)
|
|
736
|
+
|
|
737
|
+
nxt = 0
|
|
738
|
+
for p in listed + rest:
|
|
739
|
+
mapping: dict[int, int] = {}
|
|
740
|
+
for old in sorted(_collect_indices(p)):
|
|
741
|
+
mapping[old] = nxt
|
|
742
|
+
nxt += 1
|
|
743
|
+
if mapping:
|
|
744
|
+
_apply_mapping(p, mapping)
|
|
745
|
+
return nxt
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
def _collect_indices(param: dict) -> set:
|
|
749
|
+
"""Every placeholder index this parameter carries.
|
|
750
|
+
|
|
751
|
+
Two sources, and only two: a `<%N%>` token in any string, and the `id` of an entry inside a
|
|
752
|
+
`variable` list. An `id` anywhere else is NOT an index - a parameters-map row carries its own
|
|
753
|
+
ordinal `id`, which collides with the low indices and would be renumbered into nonsense.
|
|
754
|
+
"""
|
|
755
|
+
found: set = set()
|
|
756
|
+
|
|
757
|
+
def walk(o, in_variable=False):
|
|
758
|
+
if isinstance(o, dict):
|
|
759
|
+
if in_variable and isinstance(o.get("id"), int):
|
|
760
|
+
found.add(o["id"])
|
|
761
|
+
for k, v in o.items():
|
|
762
|
+
walk(v, in_variable=(k == "variable"))
|
|
763
|
+
elif isinstance(o, list):
|
|
764
|
+
for x in o:
|
|
765
|
+
walk(x, in_variable=in_variable)
|
|
766
|
+
elif isinstance(o, str):
|
|
767
|
+
for m in _PLACEHOLDER.findall(o):
|
|
768
|
+
found.add(int(m))
|
|
769
|
+
|
|
770
|
+
walk(param.get("value"))
|
|
771
|
+
walk(param.get("variable") or [], in_variable=True)
|
|
772
|
+
return found
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def _apply_mapping(param: dict, mapping: dict) -> None:
|
|
776
|
+
def walk(o, in_variable=False):
|
|
777
|
+
if isinstance(o, dict):
|
|
778
|
+
out = {}
|
|
779
|
+
for k, v in o.items():
|
|
780
|
+
if k == "id" and in_variable and isinstance(v, int):
|
|
781
|
+
out[k] = mapping.get(v, v)
|
|
782
|
+
else:
|
|
783
|
+
out[k] = walk(v, in_variable=(k == "variable"))
|
|
784
|
+
return out
|
|
785
|
+
if isinstance(o, list):
|
|
786
|
+
return [walk(x, in_variable=in_variable) for x in o]
|
|
787
|
+
if isinstance(o, str):
|
|
788
|
+
return _PLACEHOLDER.sub(
|
|
789
|
+
lambda m: "<%%%d%%>" % mapping.get(int(m.group(1)), int(m.group(1))), o)
|
|
790
|
+
return o
|
|
791
|
+
|
|
792
|
+
param["value"] = walk(param.get("value"))
|
|
793
|
+
for b in param.get("variable") or []:
|
|
794
|
+
if isinstance(b.get("id"), int):
|
|
795
|
+
b["id"] = mapping.get(b["id"], b["id"])
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def value_placeholders(value) -> set:
|
|
799
|
+
"""Every `<%N%>` index in a parameter's VALUE, however deeply nested.
|
|
800
|
+
|
|
801
|
+
Separate from _collect_indices, which also counts binding ids: this answers "what does the
|
|
802
|
+
text ask for", which is the half that has to match the bindings.
|
|
803
|
+
"""
|
|
804
|
+
found: set = set()
|
|
805
|
+
|
|
806
|
+
def walk(o):
|
|
807
|
+
if isinstance(o, dict):
|
|
808
|
+
for v in o.values():
|
|
809
|
+
walk(v)
|
|
810
|
+
elif isinstance(o, list):
|
|
811
|
+
for x in o:
|
|
812
|
+
walk(x)
|
|
813
|
+
elif isinstance(o, str):
|
|
814
|
+
for m in _PLACEHOLDER.findall(o):
|
|
815
|
+
found.add(int(m))
|
|
816
|
+
|
|
817
|
+
walk(value)
|
|
818
|
+
return found
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
def add_param_bindings(node: dict, param: dict, bindings: dict, *,
|
|
822
|
+
find=None, replace=None) -> dict:
|
|
823
|
+
"""Add or replace bindings on a parameter, optionally editing its text in the same write.
|
|
824
|
+
|
|
825
|
+
Why this exists next to bind_param_var: that one takes a STRING parameter and REPLACES the
|
|
826
|
+
whole binding list, which is right for a small text parameter and useless for the two edits
|
|
827
|
+
that actually come up - reaching inside a STRUCTURED parameter (an HTTP body template is a
|
|
828
|
+
dict), and ADDING one binding to a parameter that already has nine without restating them.
|
|
829
|
+
|
|
830
|
+
`bindings` maps an index to {variableId, attributeId}. An attribute path matters: a binding
|
|
831
|
+
into a loop item's field is variable + attribute, and a binding with the attribute dropped
|
|
832
|
+
silently resolves to the whole object.
|
|
833
|
+
|
|
834
|
+
Refused rather than guessed: an edit that leaves a `<%N%>` with no binding (it substitutes to
|
|
835
|
+
nothing, and an empty value in a JSON body is usually accepted and then wrong) or a binding
|
|
836
|
+
with no `<%N%>` (dead weight that the next reader has to explain).
|
|
837
|
+
"""
|
|
838
|
+
value = param.get("value")
|
|
839
|
+
if find is not None:
|
|
840
|
+
hits = [0]
|
|
841
|
+
|
|
842
|
+
def sub(_path, s):
|
|
843
|
+
if find in s:
|
|
844
|
+
hits[0] += s.count(find)
|
|
845
|
+
return s.replace(find, replace if replace is not None else "")
|
|
846
|
+
return s
|
|
847
|
+
|
|
848
|
+
if isinstance(value, str):
|
|
849
|
+
if find in value:
|
|
850
|
+
hits[0] += value.count(find)
|
|
851
|
+
value = value.replace(find, replace if replace is not None else "")
|
|
852
|
+
param["value"] = value
|
|
853
|
+
else:
|
|
854
|
+
_walk_strings(value, "", sub)
|
|
855
|
+
if not hits[0]:
|
|
856
|
+
raise ValueError("--find text is not present in this parameter")
|
|
857
|
+
|
|
858
|
+
kept = {b["id"]: b for b in (param.get("variable") or []) if isinstance(b.get("id"), int)}
|
|
859
|
+
for idx, spec in bindings.items():
|
|
860
|
+
kept[idx] = {"id": idx, "variableId": spec["variableId"],
|
|
861
|
+
"attribute": ({"attributeId": spec["attributeId"], "nextAttribute": None}
|
|
862
|
+
if spec.get("attributeId") else None)}
|
|
863
|
+
asked = value_placeholders(param.get("value"))
|
|
864
|
+
if asked != set(kept):
|
|
865
|
+
raise ValueError(
|
|
866
|
+
"after the edit the value uses placeholders %s but the bindings cover %s - each "
|
|
867
|
+
"<%%N%%> needs exactly one binding and vice versa"
|
|
868
|
+
% (sorted(asked), sorted(kept)))
|
|
869
|
+
param["variable"] = [kept[i] for i in sorted(kept)]
|
|
870
|
+
return {"changed": True, "replacements": hits[0] if find is not None else 0,
|
|
871
|
+
"bindings_added": sorted(bindings), "bindings_total": len(kept)}
|