@atmem/architect 0.0.0-stage → 0.2.0-alpha.4.1
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.
- package/CONSTITUTION.md +144 -0
- package/README.md +33 -2
- package/agent-skills/discovery/SKILL.md +48 -0
- package/agent-skills/discovery/topics.json +139 -0
- package/agent-skills/generation-capabilities.json +760 -0
- package/agent-skills/service-lifecycle.json +21 -0
- package/agent-skills/specialists/architecture/SKILL.md +15 -0
- package/agent-skills/specialists/aws/SKILL.md +17 -0
- package/agent-skills/specialists/azure/SKILL.md +7 -0
- package/agent-skills/specialists/composition/SKILL.md +17 -0
- package/agent-skills/specialists/devops/SKILL.md +10 -0
- package/agent-skills/specialists/finops/SKILL.md +7 -0
- package/agent-skills/specialists/gcp/SKILL.md +7 -0
- package/agent-skills/specialists/ibm/SKILL.md +7 -0
- package/agent-skills/specialists/manifest.json +13 -0
- package/agent-skills/specialists/pulumi/SKILL.md +7 -0
- package/agent-skills/specialists/security/SKILL.md +7 -0
- package/agent-skills/specialists/terraform/SKILL.md +14 -0
- package/agent-skills/upstream/aws/LICENSE +175 -0
- package/agent-skills/upstream/aws/SKILL.md +125 -0
- package/agent-skills/upstream/azure/LICENSE +21 -0
- package/agent-skills/upstream/azure/SKILL.md +387 -0
- package/agent-skills/upstream/gcp/LICENSE +202 -0
- package/agent-skills/upstream/gcp/SKILL.md +159 -0
- package/agent-skills/upstream/manifest.json +57 -0
- package/agent-skills/upstream/pulumi/LICENSE +201 -0
- package/agent-skills/upstream/pulumi/SKILL.md +501 -0
- package/agent-skills/upstream/terraform/LICENSE +373 -0
- package/agent-skills/upstream/terraform/SKILL.md +316 -0
- package/agent-skills/workflows/workflows.json +601 -0
- package/cli/architect.mjs +3 -0
- package/cli/runtime.mjs +115 -0
- package/dist/assets/html2canvas.esm-QH1iLAAe.js +22 -0
- package/dist/assets/index-B97MkVMs.css +1 -0
- package/dist/assets/index-qtCM2D7O.js +21271 -0
- package/dist/assets/index.es-C6C0sneB.js +18 -0
- package/dist/assets/purify.es-CAi2OJFm.js +3 -0
- package/dist/assets/visual-export-DjbjgQbA.js +173 -0
- package/dist/favicon.svg +2 -0
- package/dist/fonts/DM-Sans-OFL.txt +93 -0
- package/dist/fonts/Manrope-OFL.txt +93 -0
- package/dist/fonts/NotoSans-Regular.ttf +0 -0
- package/dist/fonts/OFL.txt +94 -0
- package/dist/fonts/dm-sans-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/manrope-latin-wght-normal.woff2 +0 -0
- package/dist/index.html +4 -0
- package/dist/licenses/aws-architect-LICENSE.txt +17 -0
- package/dist/licenses/aws-mcp-LICENSE.txt +175 -0
- package/dist/licenses/aws-mcp-NOTICE.txt +1 -0
- package/dist/licenses/cloud-emulation-notices.txt +36619 -0
- package/dist/licenses/dependency-notices.txt +6379 -0
- package/dist/licenses/emulation-notices.txt +717 -0
- package/dist/licenses/github-copilot-sdk-LICENSE.txt +21 -0
- package/dist/licenses/memory-dependency-notices.txt +3319 -0
- package/dist/licenses/openarchflow-LICENSE.txt +21 -0
- package/dist/licenses/python-dependency-notices.txt +11951 -0
- package/docs/upstream-lock.json +26 -0
- package/knowledge/engineering-practices.json +536 -0
- package/package.json +30 -4
- package/packaging/uv-release-lock.json +54 -0
- package/public/licenses/aws-architect-LICENSE.txt +17 -0
- package/public/licenses/aws-mcp-LICENSE.txt +175 -0
- package/public/licenses/aws-mcp-NOTICE.txt +1 -0
- package/public/licenses/cloud-emulation-notices.txt +36619 -0
- package/public/licenses/dependency-notices.txt +6379 -0
- package/public/licenses/emulation-notices.txt +717 -0
- package/public/licenses/github-copilot-sdk-LICENSE.txt +21 -0
- package/public/licenses/memory-dependency-notices.txt +3319 -0
- package/public/licenses/openarchflow-LICENSE.txt +21 -0
- package/public/licenses/python-dependency-notices.txt +11951 -0
- package/scripts/terraform-render.mjs +8 -0
- package/server/__init__.py +0 -0
- package/server/abstraction.py +28 -0
- package/server/agent_runtime.py +153 -0
- package/server/app.py +257 -0
- package/server/architect.py +112 -0
- package/server/architecture_activity.py +67 -0
- package/server/attachments.py +101 -0
- package/server/cloud-emulation-requirements.txt +29 -0
- package/server/codex_app_server.py +219 -0
- package/server/codex_engineering_backend.py +266 -0
- package/server/codex_worker_policy.py +170 -0
- package/server/contracts.py +315 -0
- package/server/copilot_worker.py +16 -0
- package/server/dependency_mirror.py +54 -0
- package/server/design_patch.py +43 -0
- package/server/emulation-requirements.txt +8 -0
- package/server/emulation_adapters.py +20 -0
- package/server/emulation_routes.py +98 -0
- package/server/emulation_worker.py +351 -0
- package/server/engineering_artifacts.py +215 -0
- package/server/engineering_assets/application-runtime.json +4372 -0
- package/server/engineering_assets/architecture_work.schema.json +79 -0
- package/server/engineering_assets/materialize_runtime.py +62 -0
- package/server/engineering_assets/prepare_tools.py +96 -0
- package/server/engineering_assets/sql_validation.py +59 -0
- package/server/engineering_contexts.py +120 -0
- package/server/engineering_delivery.py +210 -0
- package/server/engineering_finalization.py +210 -0
- package/server/engineering_handoff.py +163 -0
- package/server/engineering_jobs.py +206 -0
- package/server/engineering_knowledge.py +75 -0
- package/server/engineering_reconciliation.py +159 -0
- package/server/engineering_routes.py +469 -0
- package/server/engineering_sessions.py +273 -0
- package/server/engineering_tools.py +166 -0
- package/server/generation_lint.py +18 -0
- package/server/local_providers.py +181 -0
- package/server/local_validation.py +236 -0
- package/server/memory-requirements.lock +181 -0
- package/server/memory-requirements.txt +6 -0
- package/server/memory_routes.py +82 -0
- package/server/memory_service.py +273 -0
- package/server/memory_worker.py +66 -0
- package/server/openai_voice.py +80 -0
- package/server/packaged_app.py +9 -0
- package/server/pricing.py +84 -0
- package/server/process_control_observation.py +31 -0
- package/server/prompts.py +123 -0
- package/server/public_faq_emulation_worker.py +44 -0
- package/server/requirements.lock +1618 -0
- package/server/requirements.txt +68 -0
- package/server/runtime_dependency_locks.py +27 -0
- package/server/runtime_evidence.py +93 -0
- package/server/settings.py +14 -0
- package/server/skills.py +103 -0
- package/server/storage_emulation_worker.py +160 -0
- package/server/structured_emulation_worker.py +48 -0
- package/server/terraform_routes.py +118 -0
- package/server/upstream/__init__.py +0 -0
- package/server/upstream/agent/__init__.py +1 -0
- package/server/upstream/agent/aws_architect.py +600 -0
- package/server/upstream/agent/config/__init__.py +1 -0
- package/server/upstream/agent/config/agent_config.py +82 -0
- package/server/upstream/agent/config/prompts.py +138 -0
- package/server/upstream/agent/rate_limiter.py +62 -0
- package/server/upstream/utils/__init__.py +1 -0
- package/server/upstream/utils/file_writer.py +43 -0
- package/server/validation_applicability.py +85 -0
- package/server/validation_jobs.py +252 -0
- package/server/validation_workers.py +48 -0
- package/server/voice_routes.py +91 -0
- package/server/voice_worker.py +26 -0
- package/specs/028-convergence-evaluation/evidence/20261110-ibm-invoice-native/registry-structured/qualification.json +72 -0
- package/specs/028-convergence-evaluation/evidence/20261110-invoice-runtime/durable-current/receipt.json +61 -0
- package/specs/029-general-purpose-engine/evidence/20261111-runtime-dependencies/sdk/receipt.json +22 -0
- package/src/ArchitectureActivity.jsx +54 -0
- package/src/ArchitectureWorkPanel.jsx +46 -0
- package/src/ArtifactChecks.jsx +49 -0
- package/src/BlockRequirements.jsx +13 -0
- package/src/BrainActivity.jsx +11 -0
- package/src/ChatMessage.jsx +19 -0
- package/src/ChatResizeHandle.jsx +6 -0
- package/src/CodePreview.jsx +8 -0
- package/src/ComponentReview.jsx +31 -0
- package/src/DeliveryViews.jsx +59 -0
- package/src/EmulationPanel.jsx +44 -0
- package/src/EngagementViews.jsx +385 -0
- package/src/EngineeringPanel.jsx +151 -0
- package/src/ImplementationDelivery.jsx +55 -0
- package/src/JourneyViews.jsx +400 -0
- package/src/MemoryPanel.jsx +46 -0
- package/src/OpenAIVoicePanel.jsx +65 -0
- package/src/OutputsView.jsx +65 -0
- package/src/TerraformView.jsx +57 -0
- package/src/WorkbenchDialogs.jsx +532 -0
- package/src/activity-presentation.mjs +28 -0
- package/src/ai-qualification-scenarios.mjs +31 -0
- package/src/approval-application-files.mjs +32 -0
- package/src/approval-applications.mjs +82 -0
- package/src/approval-spec-kit.mjs +30 -0
- package/src/architecture-activity.css +28 -0
- package/src/architecture-activity.mjs +63 -0
- package/src/architecture-work.mjs +176 -0
- package/src/artifact-work.mjs +103 -0
- package/src/async-documents.mjs +191 -0
- package/src/atflow.css +1140 -0
- package/src/atforma.css +748 -0
- package/src/atmem-tokens.css +79 -0
- package/src/aws-preflight.mjs +60 -0
- package/src/aws-public-faq.mjs +102 -0
- package/src/aws-validation-scenarios.mjs +10 -0
- package/src/aws-validation.mjs +48 -0
- package/src/azure-plan-mocks.mjs +25 -0
- package/src/azure-structured.mjs +133 -0
- package/src/bedrock-summary.mjs +152 -0
- package/src/bounded-rag.mjs +60 -0
- package/src/brand.css +9 -0
- package/src/canvas-view.mjs +28 -0
- package/src/capability-composition.mjs +44 -0
- package/src/chat-attachments.mjs +13 -0
- package/src/cloud-provider-locks.mjs +9 -0
- package/src/cloud-storage-scenarios.mjs +2 -0
- package/src/component-review.mjs +55 -0
- package/src/composition-spec-kit.mjs +30 -0
- package/src/consultation-context.mjs +15 -0
- package/src/consultation.mjs +52 -0
- package/src/container-contracts.mjs +5 -0
- package/src/container-inspection.mjs +75 -0
- package/src/container-runtime.mjs +30 -0
- package/src/container-security-inspection.mjs +49 -0
- package/src/container-terraform.mjs +109 -0
- package/src/conversation.mjs +17 -0
- package/src/core.mjs +863 -0
- package/src/cross-cloud-storage.mjs +100 -0
- package/src/delivery-capabilities.mjs +25 -0
- package/src/delivery.mjs +58 -0
- package/src/deployment-ownership.mjs +10 -0
- package/src/deployment-region.mjs +11 -0
- package/src/derive.mjs +31 -0
- package/src/design-build-flow.mjs +10 -0
- package/src/diagram-attention.mjs +24 -0
- package/src/discovery.mjs +359 -0
- package/src/document.mjs +170 -0
- package/src/emulation-feedback.mjs +61 -0
- package/src/emulation-guidance.mjs +37 -0
- package/src/engagement.mjs +274 -0
- package/src/engineering-client.mjs +73 -0
- package/src/engineering-defaults.mjs +17 -0
- package/src/engineering.css +31 -0
- package/src/enterprise-application.mjs +76 -0
- package/src/enterprise-aws-host-infrastructure.mjs +62 -0
- package/src/enterprise-aws-host-tests.mjs +15 -0
- package/src/enterprise-aws-infrastructure-tests.mjs +38 -0
- package/src/enterprise-aws-infrastructure.mjs +138 -0
- package/src/enterprise-google-infrastructure.mjs +84 -0
- package/src/enterprise-google-tests.mjs +25 -0
- package/src/estimate.mjs +188 -0
- package/src/existing-references.mjs +18 -0
- package/src/external-identities.mjs +17 -0
- package/src/faq-lambda-build.mjs +28 -0
- package/src/gcp-structured.mjs +104 -0
- package/src/generation-provider.mjs +7 -0
- package/src/generation-quality.mjs +69 -0
- package/src/handoff.mjs +141 -0
- package/src/ibm-registry.mjs +12 -0
- package/src/ibm-structured.mjs +103 -0
- package/src/identifiers.mjs +15 -0
- package/src/implementation-map.mjs +14 -0
- package/src/implementation-outcome.mjs +8 -0
- package/src/incomplete-terraform.mjs +9 -0
- package/src/integrations.ts +137 -0
- package/src/interface-contracts.mjs +30 -0
- package/src/invoice-application.mjs +66 -0
- package/src/invoice-aws-operations.mjs +136 -0
- package/src/invoice-aws.mjs +143 -0
- package/src/invoice-azure-operations.mjs +147 -0
- package/src/invoice-azure.mjs +134 -0
- package/src/invoice-builder.mjs +35 -0
- package/src/invoice-dependencies.mjs +43 -0
- package/src/invoice-gcp-operations.mjs +178 -0
- package/src/invoice-gcp.mjs +104 -0
- package/src/invoice-ibm-operations.mjs +278 -0
- package/src/invoice-ibm.mjs +142 -0
- package/src/invoice-operations.mjs +143 -0
- package/src/invoice-plan.mjs +45 -0
- package/src/invoice-region.mjs +6 -0
- package/src/invoice-spec-kit.mjs +61 -0
- package/src/invoice-state.mjs +7 -0
- package/src/invoice-terraform.mjs +29 -0
- package/src/invoice-tests.mjs +122 -0
- package/src/journey-workspace.css +72 -0
- package/src/journey.mjs +87 -0
- package/src/levels.mjs +145 -0
- package/src/local-agent.mjs +184 -0
- package/src/main.jsx +2458 -0
- package/src/model-policy-preflight.mjs +34 -0
- package/src/openai-voice.mjs +102 -0
- package/src/output-bundle.mjs +11 -0
- package/src/ownership-inspection.mjs +28 -0
- package/src/pack.mjs +201 -0
- package/src/pricing.mjs +31 -0
- package/src/production-state.mjs +8 -0
- package/src/project-copy.mjs +10 -0
- package/src/project-file.mjs +160 -0
- package/src/project-links.mjs +3 -0
- package/src/protected-state.mjs +43 -0
- package/src/public-faq-builder.mjs +19 -0
- package/src/public-faq-clouds.mjs +251 -0
- package/src/public-faq.mjs +122 -0
- package/src/pulumi.mjs +123 -0
- package/src/python-container.mjs +25 -0
- package/src/python-runtime.mjs +50 -0
- package/src/rag-handler.mjs +43 -0
- package/src/rag-web-assets.mjs +52 -0
- package/src/rag-web.mjs +68 -0
- package/src/records.mjs +177 -0
- package/src/resources.mjs +163 -0
- package/src/review.mjs +252 -0
- package/src/runtime/EnterpriseDocument.java +89 -0
- package/src/runtime/approved_http_destination.py +72 -0
- package/src/runtime/aws-lambda-qualification-lock.json +7 -0
- package/src/runtime/aws-python-lock.json +82 -0
- package/src/runtime/aws-web-python-lock.json +887 -0
- package/src/runtime/aws_json_model.py +42 -0
- package/src/runtime/azure-python-lock.json +863 -0
- package/src/runtime/azure-sources.json +8 -0
- package/src/runtime/azure-test-lock.json +23 -0
- package/src/runtime/azure_acceptance.py +192 -0
- package/src/runtime/azure_structured.py +153 -0
- package/src/runtime/bounded_rag.py +126 -0
- package/src/runtime/catalog_admin.py +70 -0
- package/src/runtime/cloud-sql-proxy-lock.json +8 -0
- package/src/runtime/cloud-sql-proxy-security-review.json +30 -0
- package/src/runtime/connector_cli.py +50 -0
- package/src/runtime/connector_sync.py +70 -0
- package/src/runtime/durable-python-lock.json +247 -0
- package/src/runtime/durable_workflow.py +325 -0
- package/src/runtime/enterprise-application-manifest.json +110 -0
- package/src/runtime/enterprise-application-sources.json +266 -0
- package/src/runtime/enterprise-auth-python-lock.json +592 -0
- package/src/runtime/enterprise-aws-host-bootstrap-source.json +1 -0
- package/src/runtime/enterprise-aws-host-probe-source.json +1 -0
- package/src/runtime/enterprise-aws-task-template.json +235 -0
- package/src/runtime/enterprise-document-lock.json +18 -0
- package/src/runtime/enterprise-document.Dockerfile +15 -0
- package/src/runtime/enterprise-runtime-aws-python-lock.json +1115 -0
- package/src/runtime/enterprise-runtime-azure-python-lock.json +1106 -0
- package/src/runtime/enterprise-runtime-gcp-python-lock.json +1060 -0
- package/src/runtime/enterprise-runtime-ibm-python-lock.json +1096 -0
- package/src/runtime/enterprise-runtime-python-lock.json +1058 -0
- package/src/runtime/enterprise-runtime.Dockerfile +15 -0
- package/src/runtime/enterprise-web-app.js +1 -0
- package/src/runtime/enterprise-web-entry.js +105 -0
- package/src/runtime/enterprise-web-lock.json +29 -0
- package/src/runtime/enterprise-web-notices.txt +229 -0
- package/src/runtime/enterprise-web.css +4 -0
- package/src/runtime/enterprise-web.html +8 -0
- package/src/runtime/enterprise_audit.py +91 -0
- package/src/runtime/enterprise_audit_schema.sql +25 -0
- package/src/runtime/enterprise_auth_schema.sql +67 -0
- package/src/runtime/enterprise_auth_store.py +241 -0
- package/src/runtime/enterprise_aws_host_bootstrap.py +120 -0
- package/src/runtime/enterprise_aws_host_probe.py +105 -0
- package/src/runtime/enterprise_aws_hosts.py +136 -0
- package/src/runtime/enterprise_aws_jobs.py +300 -0
- package/src/runtime/enterprise_aws_objects.py +93 -0
- package/src/runtime/enterprise_aws_transfer.py +83 -0
- package/src/runtime/enterprise_azure_batch_api.py +128 -0
- package/src/runtime/enterprise_azure_host.py +599 -0
- package/src/runtime/enterprise_azure_jobs.py +294 -0
- package/src/runtime/enterprise_azure_objects.py +234 -0
- package/src/runtime/enterprise_azure_pool.py +161 -0
- package/src/runtime/enterprise_azure_transfer.py +122 -0
- package/src/runtime/enterprise_configuration.py +136 -0
- package/src/runtime/enterprise_document.py +116 -0
- package/src/runtime/enterprise_document_cli.py +32 -0
- package/src/runtime/enterprise_document_contract.py +54 -0
- package/src/runtime/enterprise_document_job.py +109 -0
- package/src/runtime/enterprise_document_jobs.py +122 -0
- package/src/runtime/enterprise_document_jobs_schema.sql +23 -0
- package/src/runtime/enterprise_extraction_cache.py +186 -0
- package/src/runtime/enterprise_extraction_schema.sql +17 -0
- package/src/runtime/enterprise_google_jobs.py +275 -0
- package/src/runtime/enterprise_google_transfer.py +85 -0
- package/src/runtime/enterprise_http.py +212 -0
- package/src/runtime/enterprise_identity.py +52 -0
- package/src/runtime/enterprise_knowledge.py +152 -0
- package/src/runtime/enterprise_models.py +94 -0
- package/src/runtime/enterprise_native.py +181 -0
- package/src/runtime/enterprise_oauth.py +132 -0
- package/src/runtime/enterprise_operator.py +160 -0
- package/src/runtime/enterprise_parser_service.py +136 -0
- package/src/runtime/enterprise_parser_transport.py +68 -0
- package/src/runtime/enterprise_queries.py +255 -0
- package/src/runtime/enterprise_queries_schema.sql +27 -0
- package/src/runtime/enterprise_query_service.py +111 -0
- package/src/runtime/enterprise_secrets.py +110 -0
- package/src/runtime/enterprise_sessions.py +95 -0
- package/src/runtime/enterprise_sources.py +260 -0
- package/src/runtime/enterprise_web.py +39 -0
- package/src/runtime/enterprise_worker.py +68 -0
- package/src/runtime/export_contract_acceptance.py +18 -0
- package/src/runtime/faq-output-contract.json +40 -0
- package/src/runtime/faq-web-app.js +41 -0
- package/src/runtime/faq-web.css +1 -0
- package/src/runtime/faq-web.html +6 -0
- package/src/runtime/gcp-sources.json +9 -0
- package/src/runtime/gcp_acceptance.py +110 -0
- package/src/runtime/gcp_structured.py +150 -0
- package/src/runtime/http_acceptance.py +39 -0
- package/src/runtime/iap-web-app.js +36 -0
- package/src/runtime/iap-web-source.json +1 -0
- package/src/runtime/ibm-python-lock.json +856 -0
- package/src/runtime/ibm-sources.json +9 -0
- package/src/runtime/ibm_acceptance.py +133 -0
- package/src/runtime/ibm_structured.py +113 -0
- package/src/runtime/invoice-azapi-provider-lock.json +7 -0
- package/src/runtime/invoice-ibm-provider-lock.json +7 -0
- package/src/runtime/invoice-parser-lock.json +1199 -0
- package/src/runtime/invoice-parser-python-lock.json +56 -0
- package/src/runtime/invoice-parser.Dockerfile +190 -0
- package/src/runtime/invoice-sources.json +58 -0
- package/src/runtime/invoice-test-sources.json +24 -0
- package/src/runtime/invoice-web.css +2 -0
- package/src/runtime/invoice-web.html +12 -0
- package/src/runtime/invoice-web.js +112 -0
- package/src/runtime/invoice_configuration.py +17 -0
- package/src/runtime/invoice_document.py +95 -0
- package/src/runtime/invoice_extraction.py +96 -0
- package/src/runtime/invoice_http.py +129 -0
- package/src/runtime/invoice_identity.py +5 -0
- package/src/runtime/invoice_maintenance.py +40 -0
- package/src/runtime/invoice_native.py +77 -0
- package/src/runtime/invoice_object_store.py +153 -0
- package/src/runtime/invoice_operations.py +108 -0
- package/src/runtime/invoice_operator.py +79 -0
- package/src/runtime/invoice_operator_core.py +39 -0
- package/src/runtime/invoice_operator_job.py +147 -0
- package/src/runtime/invoice_parser_cli.py +17 -0
- package/src/runtime/invoice_parser_identity.py +39 -0
- package/src/runtime/invoice_parser_service.py +89 -0
- package/src/runtime/invoice_parser_transport.py +89 -0
- package/src/runtime/invoice_processing.py +24 -0
- package/src/runtime/invoice_retention.py +16 -0
- package/src/runtime/invoice_runtime_secrets.py +43 -0
- package/src/runtime/invoice_stage.py +80 -0
- package/src/runtime/invoice_web.py +39 -0
- package/src/runtime/invoice_worker.py +50 -0
- package/src/runtime/lambda_faq.py +5 -0
- package/src/runtime/parser-requirements.lock +4 -0
- package/src/runtime/policy_review.py +18 -0
- package/src/runtime/postgres-qualification-lock.json +21 -0
- package/src/runtime/public-faq-sources.json +22 -0
- package/src/runtime/public_catalog_admin.py +122 -0
- package/src/runtime/public_catalog_store.py +125 -0
- package/src/runtime/public_faq.py +144 -0
- package/src/runtime/public_faq_app.py +49 -0
- package/src/runtime/python-container-lock.json +18 -0
- package/src/runtime/rag-sources.json +10 -0
- package/src/runtime/rag_acceptance.py +60 -0
- package/src/runtime/rag_aws_adapter.py +95 -0
- package/src/runtime/rds-ca-source.json +9 -0
- package/src/runtime/rds-global-ca.pem +2888 -0
- package/src/runtime/state-contract-check-source.json +4 -0
- package/src/runtime/state_contract_check.py +338 -0
- package/src/runtime/structured-source.json +1 -0
- package/src/runtime/structured-web-app.js +71 -0
- package/src/runtime/structured-web-source.json +1 -0
- package/src/runtime/structured_http.py +105 -0
- package/src/runtime/structured_output.py +48 -0
- package/src/runtime/web-python-lock.json +802 -0
- package/src/runtime/web_user_admin.py +44 -0
- package/src/runtime/workflow_application.py +62 -0
- package/src/runtime/workflow_bindings.py +128 -0
- package/src/runtime/workflow_configuration.py +40 -0
- package/src/runtime/workflow_connection.py +105 -0
- package/src/runtime/workflow_contract.py +54 -0
- package/src/runtime/workflow_database.py +94 -0
- package/src/runtime/workflow_errors.py +6 -0
- package/src/runtime/workflow_http.py +178 -0
- package/src/runtime/workflow_identity.py +69 -0
- package/src/runtime/workflow_payload.py +118 -0
- package/src/runtime/workflow_process_stage.py +88 -0
- package/src/runtime/workflow_profile.py +91 -0
- package/src/runtime/workflow_review.py +106 -0
- package/src/runtime/workflow_role_bootstrap.py +104 -0
- package/src/runtime/workflow_schema.sql +72 -0
- package/src/runtime/workflow_worker.py +142 -0
- package/src/runtime-settings.mjs +15 -0
- package/src/s3-access.mjs +14 -0
- package/src/source-contracts.mjs +17 -0
- package/src/spec-kit.mjs +186 -0
- package/src/state-custody.mjs +32 -0
- package/src/state-inspection.mjs +45 -0
- package/src/state-spec-kit.mjs +13 -0
- package/src/storage-tests.mjs +25 -0
- package/src/structured-api.mjs +133 -0
- package/src/structured-contract.mjs +19 -0
- package/src/structured-operation-policy.mjs +2 -0
- package/src/structured-web.mjs +17 -0
- package/src/style.css +1 -0
- package/src/system-description.mjs +26 -0
- package/src/terraform-admission.mjs +66 -0
- package/src/terraform-composition.mjs +83 -0
- package/src/terraform-expressions.mjs +46 -0
- package/src/terraform-validation-coverage.mjs +11 -0
- package/src/terraform.mjs +128 -0
- package/src/text-statistics.mjs +51 -0
- package/src/upstream/openarchflow/format-payload.ts +26 -0
- package/src/upstream/openarchflow/import/mermaid.ts +205 -0
- package/src/upstream/openarchflow/import/sql-ddl.ts +109 -0
- package/src/upstream/openarchflow/layout.ts +54 -0
- package/src/upstream/openarchflow/providers/aws.ts +196 -0
- package/src/upstream/openarchflow/providers/azure.ts +126 -0
- package/src/upstream/openarchflow/providers/cloudnative.ts +71 -0
- package/src/upstream/openarchflow/providers/generic.ts +227 -0
- package/src/upstream/openarchflow/providers/integration.ts +22 -0
- package/src/upstream/openarchflow/providers/types.ts +2 -0
- package/src/upstream/openarchflow/templates.ts +937 -0
- package/src/upstream-catalog.mjs +10 -0
- package/src/useChatScroll.jsx +36 -0
- package/src/useEmulation.jsx +36 -0
- package/src/validation-receipt.mjs +10 -0
- package/src/visual-export.mjs +92 -0
- package/src/voice-conversation.mjs +117 -0
- package/src/work-intake.mjs +24 -0
- package/src/workflow-runtime-inventory.mjs +3 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Explicit user onboarding/disable for the generated Cognito pool and catalog.
|
|
2
|
+
|
|
3
|
+
No invitation email is sent. Passwords come from a terminal prompt, never argv or
|
|
4
|
+
Terraform state. The user enrolls their own software-token MFA at first sign-in.
|
|
5
|
+
"""
|
|
6
|
+
import argparse
|
|
7
|
+
import getpass
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import re
|
|
11
|
+
import boto3
|
|
12
|
+
from botocore.config import Config
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
parser=argparse.ArgumentParser(description=__doc__)
|
|
17
|
+
for name in ['pool','table','account','region','username','receipt']:parser.add_argument('--'+name,required=True)
|
|
18
|
+
parser.add_argument('--subject');parser.add_argument('--employee-region')
|
|
19
|
+
parser.add_argument('--disable',action='store_true');parser.add_argument('--confirm-change',action='store_true',required=True)
|
|
20
|
+
args=parser.parse_args()
|
|
21
|
+
if not args.disable and (not args.subject or not re.fullmatch(r'[A-Za-z0-9_-]{1,100}',args.subject) or not args.employee_region or not re.fullmatch(r'[A-Z]{2}',args.employee_region)):parser.error('Create requires an authoritative subject and two-letter employee region')
|
|
22
|
+
session=boto3.Session(region_name=args.region);config=Config(ignore_configured_endpoint_urls=True,connect_timeout=3,read_timeout=10,retries={'total_max_attempts':2})
|
|
23
|
+
if session.client('sts',config=config).get_caller_identity()['Account']!=args.account:raise SystemExit('Account mismatch')
|
|
24
|
+
cognito=session.client('cognito-idp',config=config);db=session.client('dynamodb',config=config)
|
|
25
|
+
pool=cognito.describe_user_pool(UserPoolId=args.pool)['UserPool']
|
|
26
|
+
if pool.get('UserPoolTags',{}).get('ManagedBy')!='AtMem AI Architect':raise SystemExit('Pool is not a generated AtMem target')
|
|
27
|
+
receipt=Path(args.receipt);receipt.parent.mkdir(parents=True,exist_ok=True)
|
|
28
|
+
record={'status':'in_progress','operation':'disable' if args.disable else 'create','pool':args.pool,'username':args.username}
|
|
29
|
+
def save():receipt.write_text(json.dumps(record,indent=2)+'\n');receipt.chmod(0o600)
|
|
30
|
+
save()
|
|
31
|
+
if args.disable:
|
|
32
|
+
user=cognito.admin_get_user(UserPoolId=args.pool,Username=args.username)
|
|
33
|
+
subject=next(a['Value'] for a in user['UserAttributes'] if a['Name']=='sub')
|
|
34
|
+
db.update_item(TableName=args.table,Key={'recordId':{'S':'IDENTITY#COGNITO#'+subject}},UpdateExpression='SET enabled = :disabled',ConditionExpression='attribute_exists(recordId)',ExpressionAttributeValues={':disabled':{'BOOL':False}})
|
|
35
|
+
record['directoryDisabled']=True;save()
|
|
36
|
+
cognito.admin_disable_user(UserPoolId=args.pool,Username=args.username)
|
|
37
|
+
else:
|
|
38
|
+
password=getpass.getpass('Temporary password (not logged): ')
|
|
39
|
+
user=cognito.admin_create_user(UserPoolId=args.pool,Username=args.username,TemporaryPassword=password,MessageAction='SUPPRESS')['User']
|
|
40
|
+
subject=next(a['Value'] for a in user['Attributes'] if a['Name']=='sub')
|
|
41
|
+
record['userCreated']=True;record['subjectId']=subject;save()
|
|
42
|
+
db.put_item(TableName=args.table,Item={'recordId':{'S':'IDENTITY#COGNITO#'+subject},'kind':{'S':'IDENTITY'},'enabled':{'BOOL':True},'subject':{'S':args.subject},'employeeRegion':{'S':args.employee_region}},ConditionExpression='attribute_not_exists(recordId)')
|
|
43
|
+
record['status']='completed';save();print(json.dumps({'status':'completed','receipt':str(receipt)}))
|
|
44
|
+
if __name__=='__main__':main()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Owned application assembly; imports neither API/JWT nor cloud SDK at startup.
|
|
2
|
+
|
|
3
|
+
Profiles are nonsecret reviewed artifacts. They do not select Python modules,
|
|
4
|
+
credential paths, migrations or deployment actions. Domain stages remain trusted
|
|
5
|
+
source supplied by the bootstrap, never executable strings from client data.
|
|
6
|
+
"""
|
|
7
|
+
import hashlib
|
|
8
|
+
from workflow_payload import encoded
|
|
9
|
+
from workflow_configuration import native_database,native_identity_realm
|
|
10
|
+
from workflow_profile import ApplicationProfile,load_profile,IDENTITY_KEYS
|
|
11
|
+
|
|
12
|
+
def checked_environment(profile,environment):
|
|
13
|
+
if type(profile)is not ApplicationProfile:raise ValueError('Use an immutable owned application profile')
|
|
14
|
+
copied=dict(environment);data=profile.data
|
|
15
|
+
for key,expected in [('PROVIDER',data['provider']),('ACCOUNT_ID',data['accountId']),('ENVIRONMENT',data['environment']),('WORKFLOW_NAMESPACE',profile.namespace),('PUBLIC_ORIGIN',data['publicOrigin'])]:
|
|
16
|
+
if key in copied and copied[key]!=expected:raise ValueError('Runtime configuration differs from the owned application profile')
|
|
17
|
+
for key,expected in data['identitySettings'].items():
|
|
18
|
+
if key in copied and copied[key]!=expected:raise ValueError('Native identity differs from the owned application profile')
|
|
19
|
+
return copied
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def membership_key(profile,subject):
|
|
23
|
+
"""Derived enrollment key from the complete verified subject and native realm.
|
|
24
|
+
|
|
25
|
+
This is not an authentication function. Enrollment is an operator action;
|
|
26
|
+
the API calls it only after cryptographic native verification. No subject
|
|
27
|
+
characters are clipped; the native IdP remains the authoritative identity.
|
|
28
|
+
"""
|
|
29
|
+
if type(profile)is not ApplicationProfile or type(subject)is not str or not 1<=len(subject.encode('utf-8'))<=16384 or any(ord(c)<32 for c in subject):raise ValueError('Use the complete verified native subject')
|
|
30
|
+
data=profile.data
|
|
31
|
+
return 'native-'+hashlib.sha256(encoded({'provider':data['provider'],'issuer':native_identity_realm(data['provider'],data['identitySettings']),'subject':subject})).hexdigest()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create_native_api(profile,environment,*,connection=None,jwks=None,review_policy=None):
|
|
35
|
+
from workflow_identity import native_staff_identity
|
|
36
|
+
from workflow_worker import producer_store
|
|
37
|
+
from workflow_http import create_app
|
|
38
|
+
env=checked_environment(profile,environment);data=profile.data;provider=data['provider']
|
|
39
|
+
identity=native_staff_identity(provider,data['identitySettings'],jwks=jwks)
|
|
40
|
+
dsn=connection if connection is not None else native_database(provider,env,'api')
|
|
41
|
+
store=producer_store(dsn,destination_contract=profile.destination,namespace=profile.namespace,payload_contract=profile.payload,operation_kind=data['operationKind'],review_policy=review_policy)
|
|
42
|
+
def verify(token):return membership_key(profile,identity.verify(token))
|
|
43
|
+
return create_app(verify,store,account_id=data['accountId'],public_origin=data['publicOrigin'],operation_kind=data['operationKind'],token_header=identity.token_header)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _OwnedStage:
|
|
47
|
+
def __init__(self,stage,account,kind):self.stage=stage;self.account=account;self.kind=kind
|
|
48
|
+
def __getattr__(self,name):return getattr(self.stage,name)
|
|
49
|
+
def __call__(self,row,*args,**kwargs):
|
|
50
|
+
from workflow_errors import InvalidDispatch
|
|
51
|
+
if str(row.get('account_id'))!=self.account:raise InvalidDispatch('Work belongs to a different application account; no stage was invoked')
|
|
52
|
+
if row.get('kind')!=self.kind:raise InvalidDispatch('Work has a different operation policy; no stage was invoked')
|
|
53
|
+
return self.stage(row,*args,**kwargs)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def create_native_worker(profile,environment,processor,deliver,*,connection=None,idempotent_destination=False,review_policy=None):
|
|
57
|
+
from workflow_worker import WorkflowRuntime
|
|
58
|
+
env=checked_environment(profile,environment);data=profile.data
|
|
59
|
+
if not callable(processor)or not callable(deliver):raise ValueError('Supply trusted application stages')
|
|
60
|
+
dsn=connection if connection is not None else native_database(data['provider'],env,'worker')
|
|
61
|
+
return WorkflowRuntime(dsn,_OwnedStage(processor,data['accountId'],data['operationKind']),_OwnedStage(deliver,data['accountId'],data['operationKind']),
|
|
62
|
+
destination_contract=profile.destination,payload_contract=profile.payload,operation_kind=data['operationKind'],namespace=profile.namespace,idempotent_destination=idempotent_destination,review_policy=review_policy)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Pure deferred public-output binding. No cloud lookup or approval inference."""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import re
|
|
7
|
+
if __package__:
|
|
8
|
+
from .workflow_profile import ApplicationProfile,IDENTITY_KEYS
|
|
9
|
+
from .workflow_payload import encoded
|
|
10
|
+
else:
|
|
11
|
+
from workflow_profile import ApplicationProfile,IDENTITY_KEYS
|
|
12
|
+
from workflow_payload import encoded
|
|
13
|
+
|
|
14
|
+
ENVIRONMENTS={'staging','production','qualification'}
|
|
15
|
+
PROVIDERS={'aws','azure','gcp','ibm','tools'}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def reference(value):
|
|
19
|
+
if type(value)is not dict or set(value)!={'nativeOutput'}:raise ValueError('Use an explicit public native-output reference')
|
|
20
|
+
ref=value['nativeOutput']
|
|
21
|
+
if type(ref)is not dict or set(ref)!={'componentId','provider','environment','name'}:raise ValueError('Use an exact native-output owner')
|
|
22
|
+
if type(ref['componentId'])is not str or not 1<=len(ref['componentId'].encode())<=512 or any(ord(c)<32 for c in ref['componentId']):raise ValueError('Use the complete output component identity')
|
|
23
|
+
if type(ref['provider'])is not str or ref['provider']not in PROVIDERS or type(ref['environment'])is not str or ref['environment']not in ENVIRONMENTS:raise ValueError('Use the exact output provider/environment')
|
|
24
|
+
if type(ref['name'])is not str or not re.fullmatch(r'[A-Za-z][A-Za-z0-9_]{0,79}',ref['name'])or re.search(r'secret|password|credential|private_?key',ref['name'],re.I):raise ValueError('Only reviewed public output names may bind')
|
|
25
|
+
return ref
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True,init=False)
|
|
29
|
+
class ApplicationTemplate:
|
|
30
|
+
_encoded:bytes
|
|
31
|
+
|
|
32
|
+
def __init__(self,value):
|
|
33
|
+
raw=encoded(value);data=json.loads(raw)
|
|
34
|
+
if type(data)is not dict or data.get('format')!='approval-application-template-v1':raise ValueError('Use a versioned application template')
|
|
35
|
+
trial=json.loads(raw);trial['format']='approval-application-v1';refs=[]
|
|
36
|
+
if type(trial.get('publicOrigin'))is dict:
|
|
37
|
+
refs.append(('publicOrigin',reference(trial['publicOrigin'])));trial['publicOrigin']='https://unresolved.invalid'
|
|
38
|
+
identity=trial.get('identitySettings')
|
|
39
|
+
if type(identity)is dict:
|
|
40
|
+
for key,val in identity.items():
|
|
41
|
+
if type(val)is dict:refs.append(('identitySettings/'+key,reference(val)));identity[key]='UNRESOLVED_NATIVE_OUTPUT'
|
|
42
|
+
dest=trial.get('destinationContract')
|
|
43
|
+
if type(dest)is dict:
|
|
44
|
+
for key in ['endpointSha256','scope']:
|
|
45
|
+
if type(dest.get(key))is dict:
|
|
46
|
+
refs.append(('destinationContract/'+key,reference(dest[key])));dest[key]='0'*64 if key=='endpointSha256'else'unresolved'
|
|
47
|
+
ApplicationProfile(trial)
|
|
48
|
+
if not refs:raise ValueError('A deferred template must retain at least one output reference')
|
|
49
|
+
for path,ref in refs:
|
|
50
|
+
if ref['environment']!=data['environment']:raise ValueError('Output environment differs from the application template')
|
|
51
|
+
if path.startswith('identitySettings/')and ref['provider']!=data['provider']:raise ValueError('Native identity output provider differs from the selected adapter')
|
|
52
|
+
object.__setattr__(self,'_encoded',raw)
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def data(self):return json.loads(self._encoded)
|
|
56
|
+
@property
|
|
57
|
+
def sha256(self):return hashlib.sha256(self._encoded).hexdigest()
|
|
58
|
+
@property
|
|
59
|
+
def references(self):
|
|
60
|
+
data=self.data;result=[]
|
|
61
|
+
for key in ['publicOrigin']:
|
|
62
|
+
if type(data[key])is dict:result.append({'path':key,**reference(data[key])})
|
|
63
|
+
for section in ['identitySettings','destinationContract']:
|
|
64
|
+
for key,value in data[section].items():
|
|
65
|
+
if type(value)is dict:result.append({'path':section+'/'+key,**reference(value)})
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def application_declaration(value):
|
|
70
|
+
return ApplicationTemplate(value)if type(value)is dict and value.get('format')=='approval-application-template-v1'else ApplicationProfile(value)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def resolve_template(template,ledger):
|
|
74
|
+
if type(template)is not ApplicationTemplate:raise ValueError('Use an immutable owned template')
|
|
75
|
+
raw=encoded(ledger);ledger=json.loads(raw)
|
|
76
|
+
if type(ledger)is not dict or set(ledger)!={'version','outputs'}or type(ledger['version'])is not int or ledger['version']!=1 or type(ledger['outputs'])is not list or not 1<=len(ledger['outputs'])<=128:raise ValueError('Use a bounded public-output ledger')
|
|
77
|
+
by_key={};snapshots={}
|
|
78
|
+
for item in ledger['outputs']:
|
|
79
|
+
if type(item)is not dict or set(item)!={'componentId','provider','environment','name','value','sensitive','sourceSha256'}:raise ValueError('Use exact public output records')
|
|
80
|
+
reference({'nativeOutput':{key:item[key]for key in ['componentId','provider','environment','name']}})
|
|
81
|
+
if item['sensitive']is not False or type(item['value'])is not str or not 1<=len(item['value'].encode())<=2000:raise ValueError('Sensitive or unbounded values cannot enter application profiles')
|
|
82
|
+
if re.search(r'-----BEGIN .*PRIVATE KEY-----|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|sk-(?:ant-)?[A-Za-z0-9_-]{20,}',item['value']):raise ValueError('Credential-like output refused')
|
|
83
|
+
if type(item['sourceSha256'])is not str or not re.fullmatch(r'[0-9a-f]{64}',item['sourceSha256']):raise ValueError('Retain the source artifact identity')
|
|
84
|
+
owner=tuple(item[key]for key in ['componentId','provider','environment']);key=owner+(item['name'],)
|
|
85
|
+
if key in by_key:raise ValueError('Duplicate output identity; no version was chosen')
|
|
86
|
+
if owner in snapshots and snapshots[owner]!=item['sourceSha256']:raise ValueError('One component cannot mix output snapshots')
|
|
87
|
+
snapshots[owner]=item['sourceSha256'];by_key[key]=item
|
|
88
|
+
data=template.data;data['format']='approval-application-v1';used={}
|
|
89
|
+
for ref in template.references:
|
|
90
|
+
key=tuple(ref[k]for k in ['componentId','provider','environment','name']);item=by_key.get(key)
|
|
91
|
+
if item is None:raise ValueError('A required native output is unresolved; no default was substituted')
|
|
92
|
+
keys=ref['path'].split('/');target=data
|
|
93
|
+
for part in keys[:-1]:target=target[part]
|
|
94
|
+
target[keys[-1]]=item['value'];used[key]=item
|
|
95
|
+
profile=ApplicationProfile(data)
|
|
96
|
+
receipt={'format':'atmem.application-binding','version':1,'templateSha256':template.sha256,'profileSha256':profile.sha256,'inputs':{'version':1,'outputs':[used[key]for key in sorted(used)]},'status':'structurally_resolved','cloudVerified':False}
|
|
97
|
+
return profile,receipt
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def read_json(path):
|
|
101
|
+
with Path(path).open('rb')as stream:raw=stream.read(64001)
|
|
102
|
+
if len(raw)>64000:raise ValueError('Binding artifact exceeds its byte boundary')
|
|
103
|
+
def unique(pairs):
|
|
104
|
+
result={}
|
|
105
|
+
for key,value in pairs:
|
|
106
|
+
if key in result:raise ValueError('Duplicate binding artifact field')
|
|
107
|
+
result[key]=value
|
|
108
|
+
return result
|
|
109
|
+
return json.loads(raw.decode(),object_pairs_hook=unique)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def load_bound_profile(template_path,directory):
|
|
113
|
+
template=ApplicationTemplate(read_json(template_path));directory=Path(directory)
|
|
114
|
+
receipt=read_json(directory/'binding-receipt.json')
|
|
115
|
+
if type(receipt)is not dict or set(receipt)!={'format','version','templateSha256','profileSha256','inputs','status','cloudVerified'}:raise ValueError('Missing or invalid binding receipt; resolve public outputs before starting')
|
|
116
|
+
profile,expected=resolve_template(template,receipt['inputs'])
|
|
117
|
+
if encoded(receipt)!=encoded(expected) or ApplicationProfile(read_json(directory/'application-profile.json')).data!=profile.data:raise ValueError('Binding/profile bytes differ from the owned template and inputs')
|
|
118
|
+
return profile
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def write_binding(template_path,ledger_path,directory):
|
|
122
|
+
profile,receipt=resolve_template(ApplicationTemplate(read_json(template_path)),read_json(ledger_path))
|
|
123
|
+
profile_bytes,receipt_bytes=encoded(profile.data),encoded(receipt)
|
|
124
|
+
directory=Path(directory);directory.mkdir(mode=0o700,parents=False,exist_ok=False)
|
|
125
|
+
# Receipt is the commit marker. Readers reject an interrupted/partial bundle.
|
|
126
|
+
with(directory/'application-profile.json').open('xb')as stream:stream.write(profile_bytes)
|
|
127
|
+
with(directory/'binding-receipt.json').open('xb')as stream:stream.write(receipt_bytes)
|
|
128
|
+
return receipt
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Provider-native database configuration and reviewed public origins.
|
|
2
|
+
|
|
3
|
+
No cloud request, secret read, HTTP or JWT adapter is loaded at import.
|
|
4
|
+
"""
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from workflow_profile import owned_origin
|
|
7
|
+
from workflow_connection import aws_database,azure_database,gcp_database,ibm_database
|
|
8
|
+
|
|
9
|
+
def mounted_secret(path,*,maximum=500):
|
|
10
|
+
if not isinstance(path,str)or not Path(path).is_absolute()or type(maximum)is not int or not 1<=maximum<=20000:raise ValueError('Use the owned mounted secret file')
|
|
11
|
+
def read():
|
|
12
|
+
with Path(path).open('rb')as stream:raw=stream.read(maximum+1)
|
|
13
|
+
if not raw or len(raw)>maximum or b'\0'in raw:raise ValueError('Mounted secret is unavailable')
|
|
14
|
+
return raw.decode('utf-8')
|
|
15
|
+
return read
|
|
16
|
+
|
|
17
|
+
def native_database(provider,environment,role):
|
|
18
|
+
if role not in {'api','worker','migration'}:raise ValueError('Use an owned runtime database role')
|
|
19
|
+
env=environment;common={'database':env['DATABASE_NAME'],'role':role}
|
|
20
|
+
if provider=='gcp':
|
|
21
|
+
transport=env.get('DATABASE_TRANSPORT','cloudsql-socket')
|
|
22
|
+
if transport not in {'cloudsql-socket','cloudsql-private-proxy'}:raise ValueError('Use the owned Cloud SQL transport')
|
|
23
|
+
return gcp_database(**common,connection_name=env['DATABASE_CONNECTION_NAME'],service_account_email=env['DATABASE_SERVICE_ACCOUNT'],private_proxy=transport=='cloudsql-private-proxy')
|
|
24
|
+
common.update({'host':env['DATABASE_HOST'],'user':env['DATABASE_USER'],'ca_file':env['DATABASE_CA_FILE']})
|
|
25
|
+
if provider=='aws':return aws_database(**common,region=env['REGION'])
|
|
26
|
+
if provider=='azure':return azure_database(**common,managed_identity_client_id=env['WORKLOAD_IDENTITY_CLIENT_ID'])
|
|
27
|
+
if provider=='ibm':return ibm_database(**common,port=int(env['DATABASE_PORT']),password=mounted_secret(env['DATABASE_PASSWORD_FILE']))
|
|
28
|
+
raise ValueError('Unsupported owned database provider')
|
|
29
|
+
|
|
30
|
+
def native_identity_realm(provider,settings):
|
|
31
|
+
"""Issuer identity from owned settings, never unverified token claims.
|
|
32
|
+
|
|
33
|
+
Native verifiers separately enforce client/audience/scope. Changing those
|
|
34
|
+
controls must not rename a person within the same issuer's membership.
|
|
35
|
+
"""
|
|
36
|
+
if provider=='aws':return 'https://cognito-idp.'+settings['REGION']+'.amazonaws.com/'+settings['POOL_ID']
|
|
37
|
+
if provider=='azure':return 'https://login.microsoftonline.com/'+settings['TENANT_ID']+'/v2.0'
|
|
38
|
+
if provider=='gcp':return 'https://cloud.google.com/iap'
|
|
39
|
+
if provider=='ibm':return settings['APPID_ISSUER']
|
|
40
|
+
raise ValueError('Unsupported owned identity provider')
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Provider-native fresh PostgreSQL credentials with explicit transport identity.
|
|
2
|
+
|
|
3
|
+
No connection or cloud request occurs at import. Constructors accept deployment
|
|
4
|
+
configuration, not browser/model input. Managed Cloud SQL Unix transport relies on
|
|
5
|
+
the separately configured native connector for upstream TLS; it is never a plain
|
|
6
|
+
TCP fallback. IBM database passwords are distinct from IBM control-plane IAM.
|
|
7
|
+
"""
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import re
|
|
10
|
+
import time
|
|
11
|
+
import threading
|
|
12
|
+
import uuid
|
|
13
|
+
from psycopg.conninfo import make_conninfo
|
|
14
|
+
from workflow_errors import TemporaryWork
|
|
15
|
+
|
|
16
|
+
class DatabaseCredentialUnavailable(TemporaryWork):pass
|
|
17
|
+
|
|
18
|
+
class DatabaseConnection:
|
|
19
|
+
def __init__(self,provider,host,port,database,user,credential,*,ca_file=None,managed_cloudsql=False,managed_proxy_tcp=False,role='worker'):
|
|
20
|
+
if provider not in {'aws','azure','gcp','ibm'}or role not in {'api','worker','migration'}or type(managed_cloudsql)is not bool or type(managed_proxy_tcp)is not bool or(managed_cloudsql and managed_proxy_tcp):raise ValueError('Invalid owned database identity')
|
|
21
|
+
if not isinstance(database,str)or not re.fullmatch(r'[a-zA-Z][a-zA-Z0-9_]{0,62}',database)or not isinstance(user,str)or not 1<=len(user.encode('utf-8'))<=63 or any(ord(c)<33 or ord(c)>126 for c in user):raise ValueError('Use exact native database and role names')
|
|
22
|
+
if type(port)is not int or not 1<=port<=65535 or not callable(credential):raise ValueError('Invalid owned database credential source')
|
|
23
|
+
if managed_proxy_tcp:
|
|
24
|
+
# An explicit compiler-owned same-instance proxy, never a fallback
|
|
25
|
+
# after native TLS failure. The pinned proxy enforces upstream TLS
|
|
26
|
+
# and private-IP connectivity. No configurable TCP host/port here.
|
|
27
|
+
if provider!='gcp'or host!='127.0.0.1'or port!=5432 or ca_file is not None:raise ValueError('Use the exact owned Cloud SQL loopback proxy')
|
|
28
|
+
transport={'sslmode':'disable'}
|
|
29
|
+
elif managed_cloudsql:
|
|
30
|
+
if provider!='gcp'or not isinstance(host,str)or not re.fullmatch(r'/cloudsql/[a-z][a-z0-9-]{4,28}[a-z0-9]:[a-z]+-[a-z]+[1-9][0-9]?:[a-z][a-z0-9-]{0,62}',host)or len((host+'/.s.PGSQL.'+str(port)).encode())>103 or ca_file is not None:raise ValueError('Use the owned Cloud SQL managed Unix socket with upstream connector TLS')
|
|
31
|
+
transport={'sslmode':'disable'}
|
|
32
|
+
else:
|
|
33
|
+
if not isinstance(host,str)or not re.fullmatch(r'[a-z0-9](?:[a-z0-9.-]{0,251}[a-z0-9])?',host)or'..'in host:raise ValueError('Use an exact owned native database hostname')
|
|
34
|
+
if not isinstance(ca_file,(str,Path))or not Path(ca_file).is_absolute()or not Path(ca_file).is_file():raise ValueError('Supply the reviewed native CA certificate file')
|
|
35
|
+
if b'-----BEGIN CERTIFICATE-----'not in Path(ca_file).read_bytes():raise ValueError('Native database CA must contain PEM certificates')
|
|
36
|
+
transport={'sslmode':'verify-full','sslrootcert':str(ca_file),'ssl_min_protocol_version':'TLSv1.2'}
|
|
37
|
+
self.provider=provider;self.credential=credential
|
|
38
|
+
self.parameters={'host':host,'port':port,'dbname':database,'user':user,'application_name':'atmem-workflow-'+role,'connect_timeout':5,'gssencmode':'disable','passfile':'/dev/null',**transport}
|
|
39
|
+
def __repr__(self):return'<Reviewed '+self.provider+' database credential factory>'
|
|
40
|
+
def __call__(self):
|
|
41
|
+
try:
|
|
42
|
+
password=self.credential()
|
|
43
|
+
if not isinstance(password,str)or not password or '\0'in password or len(password.encode('utf-8'))>20000:raise ValueError('Invalid credential')
|
|
44
|
+
return make_conninfo(**self.parameters,password=password)
|
|
45
|
+
except Exception:raise DatabaseCredentialUnavailable('Database credential is temporarily unavailable')from None
|
|
46
|
+
|
|
47
|
+
def aws_database(*,host,region,database,user,ca_file,role='worker',client=None):
|
|
48
|
+
if not isinstance(region,str)or not re.fullmatch(r'[a-z]{2}-[a-z]+-[0-9]',region)or not isinstance(host,str)or not host.endswith('.'+region+'.rds.amazonaws.com'):raise ValueError('Use the actual regional RDS endpoint, not a custom DNS alias')
|
|
49
|
+
cached={'client':client};client_lock=threading.Lock()
|
|
50
|
+
def password():
|
|
51
|
+
if cached['client']is None:
|
|
52
|
+
with client_lock:
|
|
53
|
+
if cached['client']is None:
|
|
54
|
+
import boto3
|
|
55
|
+
from botocore.config import Config
|
|
56
|
+
cached['client']=boto3.Session().client('rds',region_name=region,config=Config(proxies={},connect_timeout=1,read_timeout=2,retries={'total_max_attempts':1}))
|
|
57
|
+
return cached['client'].generate_db_auth_token(DBHostname=host,Port=5432,DBUsername=user,Region=region)
|
|
58
|
+
return DatabaseConnection('aws',host,5432,database,user,password,ca_file=ca_file,role=role)
|
|
59
|
+
|
|
60
|
+
def azure_database(*,host,database,user,ca_file,managed_identity_client_id,role='worker',credential=None):
|
|
61
|
+
if not isinstance(host,str)or not re.fullmatch(r'[a-z0-9][a-z0-9-]{1,61}[a-z0-9]\.postgres\.database\.azure\.com',host):raise ValueError('Use the actual Azure PostgreSQL endpoint')
|
|
62
|
+
if not isinstance(managed_identity_client_id,str)or str(uuid.UUID(managed_identity_client_id))!=managed_identity_client_id:raise ValueError('Use the dedicated database workload identity client ID')
|
|
63
|
+
cached={'credential':credential}
|
|
64
|
+
def password():
|
|
65
|
+
if cached['credential']is None:
|
|
66
|
+
from azure.identity import ManagedIdentityCredential
|
|
67
|
+
cached['credential']=ManagedIdentityCredential(client_id=managed_identity_client_id,retry_total=0,connection_timeout=1,read_timeout=2)
|
|
68
|
+
access=cached['credential'].get_token('https://ossrdbms-aad.database.windows.net/.default')
|
|
69
|
+
if type(access.expires_on)is not int or access.expires_on<time.time()+30:raise ValueError('Database token is expiring')
|
|
70
|
+
return access.token
|
|
71
|
+
return DatabaseConnection('azure',host,5432,database,user,password,ca_file=ca_file,role=role)
|
|
72
|
+
|
|
73
|
+
def google_metadata_token(session=None):
|
|
74
|
+
import requests
|
|
75
|
+
import json
|
|
76
|
+
session=session or requests.Session();session.trust_env=False;deadline=time.monotonic()+4
|
|
77
|
+
try:
|
|
78
|
+
with session.get('http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token',headers={'Metadata-Flavor':'Google'},timeout=(1,2),allow_redirects=False,stream=True)as response:
|
|
79
|
+
if response.status_code!=200 or response.headers.get('Metadata-Flavor')!='Google':raise ValueError('Unverified workload identity')
|
|
80
|
+
raw=bytearray()
|
|
81
|
+
for chunk in response.iter_content(1):
|
|
82
|
+
raw.extend(chunk)
|
|
83
|
+
if len(raw)>8192 or time.monotonic()>deadline:raise ValueError('Workload identity response boundary')
|
|
84
|
+
def unique(pairs):
|
|
85
|
+
value={}
|
|
86
|
+
for key,item in pairs:
|
|
87
|
+
if key in value:raise ValueError('Duplicate credential field')
|
|
88
|
+
value[key]=item
|
|
89
|
+
return value
|
|
90
|
+
value=json.loads(raw,object_pairs_hook=unique)
|
|
91
|
+
if not isinstance(value,dict)or value.get('token_type')!='Bearer'or type(value.get('expires_in'))is not int or value['expires_in']<60 or not isinstance(value.get('access_token'),str)or not value['access_token']:raise ValueError('Unverified workload token')
|
|
92
|
+
return value['access_token']
|
|
93
|
+
except Exception:raise DatabaseCredentialUnavailable('Database workload identity is temporarily unavailable')from None
|
|
94
|
+
|
|
95
|
+
def gcp_database(*,connection_name,service_account_email,database,role='worker',credential=None,private_proxy=False):
|
|
96
|
+
if not isinstance(connection_name,str)or not re.fullmatch(r'[a-z][a-z0-9-]{4,28}[a-z0-9]:[a-z]+-[a-z]+[1-9][0-9]?:[a-z][a-z0-9-]{0,62}',connection_name)or type(private_proxy)is not bool:raise ValueError('Use the owned Cloud SQL connection name and explicit transport')
|
|
97
|
+
if not isinstance(service_account_email,str)or not re.fullmatch(r'[a-z][a-z0-9-]{4,29}@[a-z][a-z0-9-]{4,28}[a-z0-9]\.iam\.gserviceaccount\.com',service_account_email):raise ValueError('Use the owned service account IAM database identity')
|
|
98
|
+
user=service_account_email.removesuffix('.gserviceaccount.com')
|
|
99
|
+
return DatabaseConnection('gcp','127.0.0.1'if private_proxy else'/cloudsql/'+connection_name,5432,database,user,credential or google_metadata_token,managed_cloudsql=not private_proxy,managed_proxy_tcp=private_proxy,role=role)
|
|
100
|
+
|
|
101
|
+
def ibm_database(*,host,port,database,user,ca_file,password,role='worker'):
|
|
102
|
+
if not isinstance(host,str)or not host.endswith('.databases.appdomain.cloud'):raise ValueError('Use the native IBM database endpoint and its instance CA')
|
|
103
|
+
# The callable retrieves the dedicated nonadministrative SQL role password
|
|
104
|
+
# from its separately authorized secret store. An IAM token is not substituted.
|
|
105
|
+
return DatabaseConnection('ibm',host,port,database,user,password,ca_file=ca_file,role=role)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Nonsecret, immutable integration identity included in every human approval."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
if __package__:
|
|
6
|
+
from .workflow_payload import PayloadContract
|
|
7
|
+
else:
|
|
8
|
+
from workflow_payload import PayloadContract
|
|
9
|
+
|
|
10
|
+
def reviewed_contract(value):
|
|
11
|
+
fields={'id','endpointSha256','scope','mappingVersion'}
|
|
12
|
+
optional={'payloadContractSha256','applicationContractSha256','reviewPolicySha256'}
|
|
13
|
+
if (not isinstance(value,dict)or not fields<=set(value)<=fields|optional
|
|
14
|
+
or not isinstance(value['id'],str)or not re.fullmatch(r'[a-z0-9-]{1,80}',value['id'])
|
|
15
|
+
or not isinstance(value['endpointSha256'],str)or not re.fullmatch(r'[0-9a-f]{64}',value['endpointSha256'])
|
|
16
|
+
or not isinstance(value['scope'],str)or not 1<=len(value['scope'])<=200 or any(ord(c)<33 or ord(c)>126 for c in value['scope'])
|
|
17
|
+
or type(value['mappingVersion'])is not int or not 1<=value['mappingVersion']<=10000
|
|
18
|
+
or any(key in value and(not isinstance(value[key],str)or not re.fullmatch(r'[0-9a-f]{64}',value[key]))for key in optional)):raise ValueError('Supply the reviewed nonsecret destination contract identity')
|
|
19
|
+
return json.loads(json.dumps(value))
|
|
20
|
+
|
|
21
|
+
def normalized_contract(endpoint,scope):
|
|
22
|
+
return reviewed_contract({'id':'normalized-action-v1','endpointSha256':hashlib.sha256(endpoint.encode('utf-8')).hexdigest(),'scope':scope,'mappingVersion':1})
|
|
23
|
+
|
|
24
|
+
def bind_payload_contract(destination,payload_contract):
|
|
25
|
+
result=reviewed_contract(destination)
|
|
26
|
+
if payload_contract is not None and type(payload_contract)is not PayloadContract:raise ValueError('Use an owned immutable application payload contract')
|
|
27
|
+
bound=result.get('payloadContractSha256')
|
|
28
|
+
if bound is not None and(payload_contract is None or bound!=payload_contract.sha256):raise ValueError('Application contract does not match its destination binding')
|
|
29
|
+
if payload_contract is not None:result['payloadContractSha256']=payload_contract.sha256
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
def bind_application_contract(destination,sha256):
|
|
33
|
+
result=reviewed_contract(destination)
|
|
34
|
+
if type(sha256)is not str or not re.fullmatch(r'[0-9a-f]{64}',sha256):raise ValueError('Use an exact owned application declaration digest')
|
|
35
|
+
if 'applicationContractSha256'in result and result['applicationContractSha256']!=sha256:raise ValueError('Application declaration binding changed')
|
|
36
|
+
result['applicationContractSha256']=sha256
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
def bind_review_policy(destination,payload_contract,review_policy):
|
|
40
|
+
if __package__:
|
|
41
|
+
from .workflow_review import ReviewPolicy
|
|
42
|
+
else:
|
|
43
|
+
from workflow_review import ReviewPolicy
|
|
44
|
+
result=bind_payload_contract(destination,payload_contract)
|
|
45
|
+
bound=result.get('reviewPolicySha256')
|
|
46
|
+
if review_policy is None:
|
|
47
|
+
if bound is not None:raise ValueError('The destination requires its exact review policy')
|
|
48
|
+
return result
|
|
49
|
+
if type(review_policy)is not ReviewPolicy or payload_contract is None or review_policy.data['payloadContractSha256']!=payload_contract.sha256:
|
|
50
|
+
raise ValueError('Use an owned review policy matching the exact payload contract')
|
|
51
|
+
review_policy.ensure_current()
|
|
52
|
+
if bound is not None and bound!=review_policy.sha256:raise ValueError('Review policy differs from its destination binding')
|
|
53
|
+
result['reviewPolicySha256']=review_policy.sha256
|
|
54
|
+
return result
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Operator migration/bootstrap API; never an application HTTP endpoint.
|
|
2
|
+
|
|
3
|
+
Use the separate approved migration credential. Native cloud principal mapping,
|
|
4
|
+
connection TLS and account authorization are provider-specific setup steps.
|
|
5
|
+
"""
|
|
6
|
+
import hashlib
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
import psycopg
|
|
10
|
+
from psycopg import sql
|
|
11
|
+
from psycopg.rows import tuple_row
|
|
12
|
+
import procrastinate
|
|
13
|
+
from procrastinate.schema import SchemaManager
|
|
14
|
+
|
|
15
|
+
QUEUE_TABLES=['procrastinate_jobs','procrastinate_events','procrastinate_workers','procrastinate_periodic_defers']
|
|
16
|
+
SCHEMA_VERSION=2
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def require_current_schema(conn):
|
|
20
|
+
"""Read-only admission; never migrate/adopt a database during runtime use."""
|
|
21
|
+
expected=hashlib.sha256((Path(__file__).parent/'workflow_schema.sql').read_bytes()).hexdigest()
|
|
22
|
+
with conn.cursor(row_factory=tuple_row)as cursor:
|
|
23
|
+
row=cursor.execute('SELECT version,source_sha256,queue_version FROM workflow.schema_version').fetchone()
|
|
24
|
+
if row!=(SCHEMA_VERSION,expected,'3.10.0'):
|
|
25
|
+
raise ValueError('Use the owned exact schema version; review migrations before using this runtime')
|
|
26
|
+
|
|
27
|
+
def initialize_database(conn):
|
|
28
|
+
if procrastinate.__version__!='3.10.0':raise ValueError('Use the reviewed queue dependency before migration')
|
|
29
|
+
# CREATE, not CREATE IF NOT EXISTS: do not adopt an unrelated existing schema
|
|
30
|
+
# or run a fresh schema over a production version that needs real migrations.
|
|
31
|
+
body=(Path(__file__).parent/'workflow_schema.sql').read_bytes()
|
|
32
|
+
conn.execute('CREATE SCHEMA workflow')
|
|
33
|
+
conn.execute('SET LOCAL search_path=workflow,pg_catalog')
|
|
34
|
+
conn.execute(SchemaManager.get_schema())
|
|
35
|
+
conn.execute(body.decode('utf-8'))
|
|
36
|
+
conn.execute('INSERT INTO workflow.schema_version(version,source_sha256,queue_version) VALUES(%s,%s,%s)',(SCHEMA_VERSION,hashlib.sha256(body).hexdigest(),procrastinate.__version__))
|
|
37
|
+
conn.execute('REVOKE ALL ON SCHEMA workflow FROM PUBLIC')
|
|
38
|
+
conn.execute('REVOKE ALL ON ALL TABLES IN SCHEMA workflow FROM PUBLIC')
|
|
39
|
+
conn.execute('REVOKE ALL ON ALL SEQUENCES IN SCHEMA workflow FROM PUBLIC')
|
|
40
|
+
conn.execute('REVOKE ALL ON ALL FUNCTIONS IN SCHEMA workflow FROM PUBLIC')
|
|
41
|
+
|
|
42
|
+
def grant_runtime_roles(conn,api_role,worker_role,*,native_provider=None):
|
|
43
|
+
if conn.autocommit:raise ValueError('Use one operator transaction for runtime grant admission')
|
|
44
|
+
# Grants for distinct logins still update the same pg_namespace/pg_class ACL
|
|
45
|
+
# tuples. Serialize this owned bootstrap across deployments in this database;
|
|
46
|
+
# unrelated schema/client changes remain outside this lock and are not adopted.
|
|
47
|
+
conn.execute("SELECT pg_advisory_xact_lock(hashtextextended('atmem-workflow-runtime-grants-v1',0))")
|
|
48
|
+
authentication_roles={None:set(),'aws':{'rds_iam'},'azure':set(),'gcp':{'cloudsqliamuser','cloudsqliamserviceaccount'},'ibm':set()}
|
|
49
|
+
if native_provider not in authentication_roles:raise ValueError('Use an explicitly reviewed native database grant profile')
|
|
50
|
+
require_current_schema(conn)
|
|
51
|
+
if api_role==worker_role:raise ValueError('Application and worker database identities must be separate')
|
|
52
|
+
for role in [api_role,worker_role]:
|
|
53
|
+
if not isinstance(role,str)or not re.fullmatch(r'[A-Za-z0-9_.@-]{1,63}',role):raise ValueError('Use the reviewed existing native database principal name')
|
|
54
|
+
with conn.cursor(row_factory=tuple_row)as cursor:
|
|
55
|
+
flags=cursor.execute('SELECT rolsuper,rolcreatedb,rolcreaterole,rolreplication,rolbypassrls,rolcanlogin FROM pg_roles WHERE rolname=%s',(role,)).fetchone()
|
|
56
|
+
if not flags or any(flags[:5])or not flags[5]:raise ValueError('A runtime identity must be an existing nonadministrative database login')
|
|
57
|
+
with conn.cursor(row_factory=tuple_row)as cursor:
|
|
58
|
+
elevated=cursor.execute("WITH RECURSIVE parents AS (SELECT oid,rolsuper,rolcreatedb,rolcreaterole,rolreplication,rolbypassrls FROM pg_roles WHERE rolname=%s UNION SELECT r.oid,r.rolsuper,r.rolcreatedb,r.rolcreaterole,r.rolreplication,r.rolbypassrls FROM pg_roles r JOIN pg_auth_members m ON m.roleid=r.oid JOIN parents p ON p.oid=m.member) SELECT EXISTS(SELECT 1 FROM parents WHERE rolsuper OR rolcreatedb OR rolcreaterole OR rolreplication OR rolbypassrls) OR has_database_privilege(%s,current_database(),'CREATE')",(role,role)).fetchone()[0]
|
|
59
|
+
if elevated:raise ValueError('Do not use an inherited administrator or database owner as a runtime identity')
|
|
60
|
+
with conn.cursor(row_factory=tuple_row)as cursor:
|
|
61
|
+
parents=cursor.execute("WITH RECURSIVE parents AS(SELECT m.roleid FROM pg_auth_members m JOIN pg_roles r ON r.oid=m.member WHERE r.rolname=%s UNION SELECT m.roleid FROM pg_auth_members m JOIN parents p ON p.roleid=m.member)SELECT r.rolname FROM parents p JOIN pg_roles r ON r.oid=p.roleid",(role,)).fetchall()
|
|
62
|
+
parent_names={item[0]for item in parents}
|
|
63
|
+
if not parent_names<=authentication_roles[native_provider]:raise ValueError('Runtime database memberships are outside the reviewed native authentication profile')
|
|
64
|
+
with conn.cursor(row_factory=tuple_row)as cursor:
|
|
65
|
+
owned=cursor.execute("SELECT EXISTS(SELECT 1 FROM pg_namespace n JOIN pg_roles r ON r.oid=n.nspowner WHERE r.rolname=ANY(%s)) OR EXISTS(SELECT 1 FROM pg_class c JOIN pg_roles r ON r.oid=c.relowner WHERE r.rolname=ANY(%s)) OR EXISTS(SELECT 1 FROM pg_proc p JOIN pg_roles r ON r.oid=p.proowner WHERE r.rolname=ANY(%s)) OR EXISTS(SELECT 1 FROM pg_database d JOIN pg_roles r ON r.oid=d.datdba WHERE r.rolname=ANY(%s))",([role,*parent_names],)*4).fetchone()[0]
|
|
66
|
+
if owned:raise ValueError('Runtime and authentication roles must not own database objects')
|
|
67
|
+
name=sql.Identifier(role)
|
|
68
|
+
conn.execute(sql.SQL('REVOKE ALL ON SCHEMA workflow FROM {}').format(name))
|
|
69
|
+
conn.execute(sql.SQL('REVOKE ALL ON ALL TABLES IN SCHEMA workflow FROM {}').format(name))
|
|
70
|
+
# PostgreSQL 17's table REVOKE also removes corresponding column grants;
|
|
71
|
+
# keep a native regression for reused role grants rather than assuming it.
|
|
72
|
+
# https://www.postgresql.org/docs/17/sql-revoke.html
|
|
73
|
+
conn.execute(sql.SQL('REVOKE ALL ON ALL SEQUENCES IN SCHEMA workflow FROM {}').format(name))
|
|
74
|
+
conn.execute(sql.SQL('REVOKE ALL ON ALL FUNCTIONS IN SCHEMA workflow FROM {}').format(name))
|
|
75
|
+
with conn.cursor(row_factory=tuple_row)as cursor:
|
|
76
|
+
inherited_access=cursor.execute("SELECT has_schema_privilege(%s,'workflow','CREATE') OR EXISTS(SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace WHERE n.nspname='workflow'AND CASE WHEN c.relkind IN('r','p','v','m','f')THEN(has_table_privilege(%s,c.oid,'SELECT,INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER')OR has_any_column_privilege(%s,c.oid,'SELECT,INSERT,UPDATE,REFERENCES'))ELSE false END) OR EXISTS(SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace WHERE n.nspname='workflow'AND CASE WHEN c.relkind='S'THEN has_sequence_privilege(%s,c.oid,'SELECT,USAGE,UPDATE')ELSE false END)",(role,role,role,role)).fetchone()[0]
|
|
77
|
+
if inherited_access:raise ValueError('Public or inherited grants broaden the owned workflow boundary; review them before granting runtime access')
|
|
78
|
+
conn.execute(sql.SQL('GRANT USAGE ON SCHEMA workflow TO {}').format(name))
|
|
79
|
+
conn.execute(sql.SQL('GRANT USAGE,SELECT ON ALL SEQUENCES IN SCHEMA workflow TO {}').format(name))
|
|
80
|
+
conn.execute(sql.SQL('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA workflow TO {}').format(name))
|
|
81
|
+
conn.execute(sql.SQL('GRANT SELECT ON workflow.schema_version,workflow.accounts,workflow.memberships,workflow.operations,workflow.events,workflow.results_history TO {}').format(name))
|
|
82
|
+
# PostgreSQL requires UPDATE on at least one column for SELECT FOR SHARE.
|
|
83
|
+
# Only this inert locking column is writable; roles/active/identity are not.
|
|
84
|
+
# https://www.postgresql.org/docs/17/ddl-priv.html
|
|
85
|
+
conn.execute(sql.SQL('GRANT UPDATE(authorization_lock) ON workflow.memberships TO {}').format(name))
|
|
86
|
+
conn.execute(sql.SQL('GRANT INSERT,UPDATE ON workflow.operations TO {}').format(name))
|
|
87
|
+
conn.execute(sql.SQL('GRANT INSERT ON workflow.events TO {}').format(name))
|
|
88
|
+
conn.execute(sql.SQL('GRANT INSERT ON workflow.results_history TO {}').format(name))
|
|
89
|
+
# API notifications are append-only; it cannot finish/prune/rewrite worker
|
|
90
|
+
# jobs, alter memberships, delete audit history or migrate the schema.
|
|
91
|
+
api=sql.Identifier(api_role);worker=sql.Identifier(worker_role)
|
|
92
|
+
conn.execute(sql.SQL('GRANT SELECT,INSERT ON workflow.procrastinate_jobs,workflow.procrastinate_events TO {}').format(api))
|
|
93
|
+
for table in QUEUE_TABLES:
|
|
94
|
+
conn.execute(sql.SQL('GRANT SELECT,INSERT,UPDATE,DELETE ON workflow.{} TO {}').format(sql.Identifier(table),worker))
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Sanitized failure categories; never carry raw model/document/provider text."""
|
|
2
|
+
class TemporaryWork(Exception):pass
|
|
3
|
+
class PermanentWork(Exception):pass
|
|
4
|
+
class ExternalOutcomeUnknown(Exception):pass
|
|
5
|
+
class DestinationNotAttempted(TemporaryWork):pass
|
|
6
|
+
class InvalidDispatch(PermanentWork):pass
|