@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,153 @@
|
|
|
1
|
+
"""Private immutable invoice objects with native conditional writes/version reads.
|
|
2
|
+
|
|
3
|
+
Account identity comes from the verified application membership, never an object
|
|
4
|
+
URL or a model. Adapters use operator-owned destinations/workload credentials.
|
|
5
|
+
Cloud IAM, versioning, retention and residency require matching infrastructure.
|
|
6
|
+
"""
|
|
7
|
+
import base64
|
|
8
|
+
from email.utils import formatdate
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
import time
|
|
13
|
+
import threading
|
|
14
|
+
import uuid
|
|
15
|
+
from urllib.parse import quote
|
|
16
|
+
import requests
|
|
17
|
+
|
|
18
|
+
LIMIT=10000000
|
|
19
|
+
FIELDS={'provider','region','bucket','key','version','sha256'}
|
|
20
|
+
class ObjectUnavailable(RuntimeError):pass
|
|
21
|
+
|
|
22
|
+
def digest_pdf(raw):
|
|
23
|
+
if not isinstance(raw,bytes)or not raw.startswith(b'%PDF-')or len(raw)>LIMIT:raise ValueError('Supply a PDF no larger than 10 MB')
|
|
24
|
+
return hashlib.sha256(raw).hexdigest()
|
|
25
|
+
|
|
26
|
+
def object_key(account_id,digest):
|
|
27
|
+
if not isinstance(account_id,str)or str(uuid.UUID(account_id))!=account_id:raise ValueError('Use the verified canonical account identity')
|
|
28
|
+
if not isinstance(digest,str)or not re.fullmatch(r'[0-9a-f]{64}',digest):raise ValueError('Invalid invoice content digest')
|
|
29
|
+
return'accounts/'+account_id+'/invoices/'+digest+'.pdf'
|
|
30
|
+
|
|
31
|
+
class InvoiceObjects:
|
|
32
|
+
def _reference(self,account_id,digest,version):
|
|
33
|
+
if not isinstance(version,str)or not 1<=len(version)<=1024 or version=='null'or any(ord(c)<33 or ord(c)>126 for c in version):raise ObjectUnavailable('A native immutable object version is required')
|
|
34
|
+
if self.provider=='gcp'and not re.fullmatch(r'[1-9][0-9]{0,29}',version):raise ObjectUnavailable('Invalid native object generation')
|
|
35
|
+
return {'provider':self.provider,'region':self.region,'bucket':self.bucket,'key':object_key(account_id,digest),'version':version,'sha256':digest}
|
|
36
|
+
def _check(self,account_id,reference):
|
|
37
|
+
if not isinstance(reference,dict)or set(reference)!=FIELDS:raise ValueError('Use the exact owned object reference')
|
|
38
|
+
expected=self._reference(account_id,reference['sha256'],reference['version'])
|
|
39
|
+
if reference!=expected:raise PermissionError('Object reference is outside the account/destination boundary')
|
|
40
|
+
return expected
|
|
41
|
+
def _content(self,raw,reference):
|
|
42
|
+
if digest_pdf(raw)!=reference['sha256']:raise ObjectUnavailable('Original invoice digest changed')
|
|
43
|
+
return raw
|
|
44
|
+
|
|
45
|
+
class HttpInvoiceObjects(InvoiceObjects):
|
|
46
|
+
def __init__(self,provider,region,bucket,authorization,*,account=None,session=None):
|
|
47
|
+
if not isinstance(bucket,str)or not re.fullmatch(r'[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]',bucket)or'..'in bucket:raise ValueError('Invalid private document destination')
|
|
48
|
+
if not callable(authorization):raise ValueError('Explicit workload identity is required')
|
|
49
|
+
if not isinstance(region,str)or not re.fullmatch(r'[a-z][a-z0-9-]{1,40}',region):raise ValueError('Invalid document region')
|
|
50
|
+
self.provider=provider;self.region=region;self.bucket=bucket;self.authorization=authorization;self.session=session or requests.Session();self.session.trust_env=False
|
|
51
|
+
if provider=='azure':
|
|
52
|
+
if not isinstance(account,str)or not re.fullmatch(r'[a-z0-9]{3,24}',account)or'.'in bucket:raise ValueError('Invalid private Azure storage destination')
|
|
53
|
+
self.origin='https://'+account+'.blob.core.windows.net/'+bucket+'/'
|
|
54
|
+
elif provider=='ibm':
|
|
55
|
+
if not re.fullmatch(r'[a-z]{2}-[a-z]+',region):raise ValueError('Use an explicit regional IBM object destination')
|
|
56
|
+
self.origin='https://s3.'+region+'.cloud-object-storage.appdomain.cloud/'+bucket+'/'
|
|
57
|
+
elif provider=='gcp':
|
|
58
|
+
self.origin='https://storage.googleapis.com/storage/v1/b/'+bucket+'/o/'
|
|
59
|
+
self.upload='https://storage.googleapis.com/upload/storage/v1/b/'+bucket+'/o'
|
|
60
|
+
else:raise ValueError('Unsupported native private object adapter')
|
|
61
|
+
def _headers(self):
|
|
62
|
+
supplied=self.authorization();auth=supplied.get('Authorization')if isinstance(supplied,dict)else None
|
|
63
|
+
if not isinstance(auth,str)or not auth.startswith('Bearer ')or not 8<=len(auth)<=20000 or any(ord(c)<33 or ord(c)>126 for c in auth[7:]):raise PermissionError('Private object workload identity unavailable')
|
|
64
|
+
headers={'Authorization':auth,'Content-Type':'application/pdf'}
|
|
65
|
+
if self.provider=='azure':headers.update({'x-ms-version':'2023-11-03','x-ms-date':formatdate(usegmt=True)})
|
|
66
|
+
return headers
|
|
67
|
+
def _send(self,method,url,*,headers=None,params=None,data=None,limit=LIMIT):
|
|
68
|
+
actual={**self._headers(),**(headers or {})};deadline=time.monotonic()+15
|
|
69
|
+
try:
|
|
70
|
+
with self.session.request(method,url,headers=actual,params=params,data=data,timeout=(2,8),allow_redirects=False,stream=True)as response:
|
|
71
|
+
code=response.status_code
|
|
72
|
+
if code in {401,403}:raise PermissionError('Private object access denied')
|
|
73
|
+
if code in {409,412}:return code,b'',{}
|
|
74
|
+
if code not in {200,201}:raise ObjectUnavailable('Private object operation unavailable')
|
|
75
|
+
raw=bytearray()
|
|
76
|
+
for chunk in response.iter_content(16384):
|
|
77
|
+
raw.extend(chunk)
|
|
78
|
+
if len(raw)>limit or time.monotonic()>deadline:raise ObjectUnavailable('Private object response exceeded its boundary')
|
|
79
|
+
return code,bytes(raw),{key.lower():value for key,value in response.headers.items()}
|
|
80
|
+
except(PermissionError,ObjectUnavailable):raise
|
|
81
|
+
except Exception:raise ObjectUnavailable('Private object operation unavailable')from None
|
|
82
|
+
def _version(self,raw,headers):
|
|
83
|
+
if self.provider=='gcp':
|
|
84
|
+
try:
|
|
85
|
+
def unique(pairs):
|
|
86
|
+
result={}
|
|
87
|
+
for key,value in pairs:
|
|
88
|
+
if key in result:raise ValueError('Duplicate metadata field')
|
|
89
|
+
result[key]=value
|
|
90
|
+
return result
|
|
91
|
+
return json.loads(raw,object_pairs_hook=unique)['generation']
|
|
92
|
+
except(ValueError,TypeError,KeyError):raise ObjectUnavailable('Native object generation unavailable')from None
|
|
93
|
+
return headers.get('x-ms-version-id'if self.provider=='azure'else'x-amz-version-id')
|
|
94
|
+
def put(self,account_id,raw):
|
|
95
|
+
digest=digest_pdf(raw);key=object_key(account_id,digest)
|
|
96
|
+
if self.provider=='gcp':
|
|
97
|
+
code,body,headers=self._send('POST',self.upload,params={'uploadType':'media','name':key,'ifGenerationMatch':'0','fields':'generation'},data=raw,limit=8192)
|
|
98
|
+
else:
|
|
99
|
+
extra={'If-None-Match':'*','Content-MD5':base64.b64encode(hashlib.md5(raw,usedforsecurity=False).digest()).decode()}
|
|
100
|
+
if self.provider=='azure':extra['x-ms-blob-type']='BlockBlob'
|
|
101
|
+
code,body,headers=self._send('PUT',self.origin+quote(key,safe='/'),headers=extra,data=raw,limit=8192)
|
|
102
|
+
if code in {409,412}:
|
|
103
|
+
# A repeated delivery or lost upload acknowledgement must resolve the
|
|
104
|
+
# exact existing content/version, never overwrite it or guess a token.
|
|
105
|
+
if self.provider=='gcp':_,body,headers=self._send('GET',self.origin+quote(key,safe=''),params={'fields':'generation'},limit=8192)
|
|
106
|
+
else:_,body,headers=self._send('GET',self.origin+quote(key,safe='/'))
|
|
107
|
+
reference=self._reference(account_id,digest,self._version(body,headers))
|
|
108
|
+
# Re-read the pinned native version, including after a conflict. This
|
|
109
|
+
# also verifies missing versioning and mismatched object bytes fail closed.
|
|
110
|
+
self.load(account_id,reference)
|
|
111
|
+
return reference
|
|
112
|
+
def load(self,account_id,reference):
|
|
113
|
+
ref=self._check(account_id,reference);key=ref['key'];version=ref['version']
|
|
114
|
+
if self.provider=='gcp':url=self.origin+quote(key,safe='');params={'alt':'media','generation':version}
|
|
115
|
+
else:url=self.origin+quote(key,safe='/');params={'versionid'if self.provider=='azure'else'versionId':version}
|
|
116
|
+
code,raw,headers=self._send('GET',url,params=params)
|
|
117
|
+
if code!=200:raise ObjectUnavailable('Pinned original invoice unavailable')
|
|
118
|
+
version_header='x-goog-generation'if self.provider=='gcp'else'x-ms-version-id'if self.provider=='azure'else'x-amz-version-id'
|
|
119
|
+
if headers.get(version_header)!=version:raise ObjectUnavailable('Native original version mismatch')
|
|
120
|
+
return self._content(raw,ref)
|
|
121
|
+
|
|
122
|
+
class S3InvoiceObjects(InvoiceObjects):
|
|
123
|
+
def __init__(self,bucket,region,owner_account_id,*,client=None):
|
|
124
|
+
if not isinstance(bucket,str)or not re.fullmatch(r'[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]',bucket)or'..'in bucket or not isinstance(region,str)or not re.fullmatch(r'[a-z]{2}-[a-z]+-[0-9]',region)or not isinstance(owner_account_id,str)or not re.fullmatch(r'\d{12}',owner_account_id):raise ValueError('Invalid owned regional S3 invoice destination')
|
|
125
|
+
self.provider='aws';self.bucket=bucket;self.region=region;self.owner_account_id=owner_account_id;self.client=client;self._client_lock=threading.Lock()
|
|
126
|
+
def _client(self):
|
|
127
|
+
if self.client is None:
|
|
128
|
+
with self._client_lock:
|
|
129
|
+
if self.client is None:
|
|
130
|
+
import boto3
|
|
131
|
+
from botocore.config import Config
|
|
132
|
+
# A Session/client must not be constructed through boto3's
|
|
133
|
+
# shared default Session concurrently by first uploads.
|
|
134
|
+
self.client=boto3.Session().client('s3',region_name=self.region,endpoint_url='https://s3.'+self.region+'.amazonaws.com',config=Config(proxies={},connect_timeout=2,read_timeout=8,retries={'total_max_attempts':1}))
|
|
135
|
+
return self.client
|
|
136
|
+
def put(self,account_id,raw):
|
|
137
|
+
digest=digest_pdf(raw);key=object_key(account_id,digest);client=self._client()
|
|
138
|
+
try:reply=client.put_object(Bucket=self.bucket,Key=key,Body=raw,ContentType='application/pdf',ChecksumSHA256=base64.b64encode(hashlib.sha256(raw).digest()).decode(),ExpectedBucketOwner=self.owner_account_id,IfNoneMatch='*')
|
|
139
|
+
except Exception as error:
|
|
140
|
+
code=getattr(error,'response',{}).get('Error',{}).get('Code')
|
|
141
|
+
if code not in {'PreconditionFailed','ConditionalRequestConflict','412','409'}:raise ObjectUnavailable('Private object upload unavailable')from None
|
|
142
|
+
try:reply=client.head_object(Bucket=self.bucket,Key=key,ExpectedBucketOwner=self.owner_account_id)
|
|
143
|
+
except Exception:raise ObjectUnavailable('Existing original invoice unavailable')from None
|
|
144
|
+
reference=self._reference(account_id,digest,reply.get('VersionId'));self.load(account_id,reference);return reference
|
|
145
|
+
def load(self,account_id,reference):
|
|
146
|
+
ref=self._check(account_id,reference)
|
|
147
|
+
try:
|
|
148
|
+
reply=self._client().get_object(Bucket=self.bucket,Key=ref['key'],VersionId=ref['version'],ExpectedBucketOwner=self.owner_account_id);stream=reply['Body']
|
|
149
|
+
try:raw=stream.read(LIMIT+1)
|
|
150
|
+
finally:stream.close()
|
|
151
|
+
except Exception:raise ObjectUnavailable('Pinned original invoice unavailable')from None
|
|
152
|
+
if reply.get('VersionId')!=ref['version']:raise ObjectUnavailable('Native original version mismatch')
|
|
153
|
+
return self._content(raw,ref)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Separate operator authority for retention, recovery and staff membership.
|
|
2
|
+
|
|
3
|
+
Never import this module into an HTTP handler. Runtime SQL logins cannot perform
|
|
4
|
+
these operations. Restore/reconciliation require paused, isolated workloads.
|
|
5
|
+
Expired-only cleanup serializes table mutation and refuses live jobs/leases;
|
|
6
|
+
ordinary active and younger operations are preserved during online maintenance.
|
|
7
|
+
All calls require an explicit transaction; receipts contain counts, not documents.
|
|
8
|
+
"""
|
|
9
|
+
import hashlib
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import re
|
|
12
|
+
import uuid
|
|
13
|
+
from psycopg.rows import tuple_row
|
|
14
|
+
from psycopg.types.json import Jsonb
|
|
15
|
+
from durable_workflow import canonical
|
|
16
|
+
from workflow_database import SCHEMA_VERSION
|
|
17
|
+
|
|
18
|
+
def owned_scope(conn,account_id,namespace):
|
|
19
|
+
if conn.autocommit:raise ValueError('Use one operator transaction')
|
|
20
|
+
if not isinstance(account_id,str)or str(uuid.UUID(account_id))!=account_id:raise ValueError('Use the owned canonical account UUID')
|
|
21
|
+
if not isinstance(namespace,str)or not re.fullmatch(r'[a-zA-Z0-9-]{1,20}',namespace):raise ValueError('Use the owned workflow namespace')
|
|
22
|
+
with conn.cursor(row_factory=tuple_row)as cur:
|
|
23
|
+
authority=cur.execute("SELECT pg_has_role(current_user,nspowner,'USAGE') FROM pg_namespace WHERE nspname='workflow'").fetchone()
|
|
24
|
+
version=cur.execute('SELECT version,source_sha256,queue_version FROM workflow.schema_version').fetchone()
|
|
25
|
+
expected=hashlib.sha256((Path(__file__).parent/'workflow_schema.sql').read_bytes()).hexdigest()
|
|
26
|
+
if not authority or authority[0]is not True:raise PermissionError('Use the separate reviewed schema-owner/operator connection')
|
|
27
|
+
if version!=(SCHEMA_VERSION,expected,'3.10.0'):raise ValueError('Review the schema migration before operating this database')
|
|
28
|
+
conn.execute('SET LOCAL search_path=pg_catalog,workflow,pg_temp')
|
|
29
|
+
conn.execute("SET LOCAL lock_timeout='3s'");conn.execute("SET LOCAL statement_timeout='30s'")
|
|
30
|
+
conn.execute('SELECT pg_advisory_xact_lock(hashtextextended(%s,0))',('atmem-invoice-operations:'+account_id+':'+namespace,))
|
|
31
|
+
return namespace+'.extract',namespace+'.dispatch'
|
|
32
|
+
|
|
33
|
+
def quiescent_rows(conn,account_id,namespace,*,expired=False,restoring=False,limit=500):
|
|
34
|
+
extract,dispatch=owned_scope(conn,account_id,namespace)
|
|
35
|
+
if type(limit)is not int or not 1<=limit<=500:raise ValueError('Use a bounded maintenance batch')
|
|
36
|
+
# One table-lock order for every operator action. A running task retains a
|
|
37
|
+
# doing queue row even while its external network request is outside SQL.
|
|
38
|
+
conn.execute('LOCK TABLE workflow.operations IN SHARE ROW EXCLUSIVE MODE')
|
|
39
|
+
conn.execute('LOCK TABLE workflow.procrastinate_jobs IN SHARE ROW EXCLUSIVE MODE')
|
|
40
|
+
with conn.cursor(row_factory=tuple_row)as cur:
|
|
41
|
+
restoration_filter=" AND(state IN('approved','dispatching','processing')OR EXISTS(SELECT 1 FROM workflow.procrastinate_jobs j WHERE j.task_name=ANY(%s)AND j.args->>'operation_id'=workflow.operations.id::text))"if restoring else''
|
|
42
|
+
rows=cur.execute("SELECT id FROM workflow.operations WHERE account_id=%s AND workflow_key=%s"+(" AND created_at <= clock_timestamp()-interval '90 days'"if expired else'')+restoration_filter+' ORDER BY created_at,id LIMIT %s FOR UPDATE',(account_id,extract,*([[extract,dispatch]]if restoring else[]),limit)).fetchall()
|
|
43
|
+
ids=[str(row[0])for row in rows]
|
|
44
|
+
if ids:
|
|
45
|
+
live=cur.execute("SELECT EXISTS(SELECT 1 FROM workflow.procrastinate_jobs WHERE task_name=ANY(%s)AND args->>'operation_id'=ANY(%s)AND status='doing')OR EXISTS(SELECT 1 FROM workflow.operations WHERE id::text=ANY(%s)AND lease_until>clock_timestamp())",([extract,dispatch],ids,ids)).fetchone()[0]
|
|
46
|
+
if live and not restoring:raise ValueError('Settle live execution jobs/leases before expired-record maintenance')
|
|
47
|
+
return ids,extract,dispatch
|
|
48
|
+
|
|
49
|
+
def purge_expired(conn,*,account_id,namespace='invoice',limit=500):
|
|
50
|
+
ids,extract,dispatch=quiescent_rows(conn,account_id,namespace,expired=True,limit=limit)
|
|
51
|
+
if ids:
|
|
52
|
+
# Only owned task identities and exact operation IDs. Queue event rows
|
|
53
|
+
# cascade through the native library schema; no global queue truncation.
|
|
54
|
+
conn.execute("DELETE FROM workflow.procrastinate_jobs WHERE task_name=ANY(%s)AND args->>'operation_id'=ANY(%s)",([extract,dispatch],ids))
|
|
55
|
+
for table in ['events','results_history','operations']:
|
|
56
|
+
column='id'if table=='operations'else'operation_id'
|
|
57
|
+
conn.execute('DELETE FROM workflow.'+table+' WHERE account_id=%s AND '+column+'::text=ANY(%s)',(account_id,ids))
|
|
58
|
+
return{'purgedOperations':len(ids),'retentionDays':90,'batchLimit':limit,'moreMayRemain':len(ids)==limit,'originalObjects':'Native private-store lifecycle; provider deletion is asynchronous. No object-read or deletion authority is conferred on this SQL tool.'}
|
|
59
|
+
|
|
60
|
+
def quarantine_restored_deliveries(conn,*,account_id,workloads_stopped,namespace='invoice',limit=500):
|
|
61
|
+
# This explicit assertion is operator authorization, not a software proof
|
|
62
|
+
# that a cloud workload is stopped. Run only against the isolated restored
|
|
63
|
+
# database before changing application endpoints or starting any worker.
|
|
64
|
+
if workloads_stopped is not True:raise ValueError('Confirm workloads are stopped and the restored database is isolated')
|
|
65
|
+
ids,extract,dispatch=quiescent_rows(conn,account_id,namespace,restoring=True,limit=limit)
|
|
66
|
+
if not ids:return{'quarantinedDeliveries':0,'resetExtractions':0,'batchLimit':limit,'moreMayRemain':False}
|
|
67
|
+
# Keep payload and approval hashes. A restored database cannot know what the
|
|
68
|
+
# destination accepted after its recovery point; never infer no side effect.
|
|
69
|
+
with conn.cursor(row_factory=tuple_row)as cur:
|
|
70
|
+
rows=cur.execute("UPDATE workflow.operations SET state='unknown',lease_token=NULL,lease_until=NULL,execution_epoch=execution_epoch+1,updated_at=clock_timestamp() WHERE account_id=%s AND id::text=ANY(%s)AND state IN('approved','dispatching') RETURNING id,approved_hash",(account_id,ids)).fetchall()
|
|
71
|
+
for operation,digest in rows:
|
|
72
|
+
conn.execute("INSERT INTO workflow.events(operation_id,account_id,event,actor,detail)VALUES(%s,%s,'outcome_unknown','operator:restore',%s)",(operation,account_id,Jsonb({'reason':'database_restored','payloadSha256':digest})))
|
|
73
|
+
with conn.cursor(row_factory=tuple_row)as cur:
|
|
74
|
+
processing=cur.execute("UPDATE workflow.operations SET state='pending',lease_token=NULL,lease_until=NULL,execution_epoch=execution_epoch+1,updated_at=clock_timestamp() WHERE account_id=%s AND id::text=ANY(%s)AND state='processing' RETURNING id",(account_id,ids)).fetchall()
|
|
75
|
+
for operation, in processing:
|
|
76
|
+
conn.execute("INSERT INTO workflow.events(operation_id,account_id,event,actor,detail)VALUES(%s,%s,'recovered','operator:restore',%s)",(operation,account_id,Jsonb({'reason':'restored_extraction_fenced'})))
|
|
77
|
+
# All restored notifications may carry stale native doing/worker state.
|
|
78
|
+
# Pending extraction intent is reconstructed by reconcile_notifications after
|
|
79
|
+
# expiry cleanup. Unknown delivery intent is never automatically requeued.
|
|
80
|
+
conn.execute("DELETE FROM workflow.procrastinate_jobs WHERE task_name=ANY(%s)AND args->>'operation_id'=ANY(%s)",([extract,dispatch],ids))
|
|
81
|
+
return{'quarantinedDeliveries':len(rows),'resetExtractions':len(processing),'batchLimit':limit,'moreMayRemain':len(ids)==limit}
|
|
82
|
+
|
|
83
|
+
def reconcile_completed(conn,*,account_id,operation_id,payload_sha256,external_id,operator,namespace='invoice'):
|
|
84
|
+
extract,dispatch=owned_scope(conn,account_id,namespace)
|
|
85
|
+
if not isinstance(operation_id,str)or str(uuid.UUID(operation_id))!=operation_id:raise ValueError('Use the exact operation identity')
|
|
86
|
+
if not isinstance(payload_sha256,str)or not re.fullmatch(r'[0-9a-f]{64}',payload_sha256):raise ValueError('Use the independently checked approval digest')
|
|
87
|
+
if not isinstance(external_id,str)or not re.fullmatch(r'[A-Za-z0-9_.:/-]{1,200}',external_id):raise ValueError('Use the independently verified accounting receipt identity')
|
|
88
|
+
if not isinstance(operator,str)or not re.fullmatch(r'[A-Za-z0-9_.@:/-]{1,200}',operator):raise ValueError('Record the accountable operator')
|
|
89
|
+
conn.execute('LOCK TABLE workflow.operations IN SHARE ROW EXCLUSIVE MODE');conn.execute('LOCK TABLE workflow.procrastinate_jobs IN SHARE ROW EXCLUSIVE MODE')
|
|
90
|
+
with conn.cursor(row_factory=tuple_row)as cur:
|
|
91
|
+
row=cur.execute('SELECT state,approved_hash,kind,source_ref,destination_contract,result,result_revision,lease_until FROM workflow.operations WHERE account_id=%s AND id=%s AND workflow_key=%s FOR UPDATE',(account_id,operation_id,extract)).fetchone()
|
|
92
|
+
active=cur.execute("SELECT EXISTS(SELECT 1 FROM workflow.procrastinate_jobs WHERE task_name=%s AND args->>'operation_id'=%s AND status='doing')",(dispatch,operation_id)).fetchone()[0]
|
|
93
|
+
if not row or row[0]not in {'unknown','dead'}or row[1]!=payload_sha256 or active or row[7]is not None:raise ValueError('Reconcile only an inactive uncertain delivery with its exact approval digest')
|
|
94
|
+
snapshot={'kind':row[2],'source':row[3],'destination':row[4],'result':row[5],'revision':row[6]}
|
|
95
|
+
if hashlib.sha256(canonical(snapshot,140000)).hexdigest()!=payload_sha256:raise ValueError('Approved snapshot changed; do not invent completion')
|
|
96
|
+
receipt={'externalId':external_id,'operationId':operation_id,'payloadSha256':payload_sha256,'reconciledBy':operator}
|
|
97
|
+
conn.execute("UPDATE workflow.operations SET state='sent',receipt=%s,updated_at=clock_timestamp() WHERE id=%s",(Jsonb(receipt),operation_id))
|
|
98
|
+
conn.execute("INSERT INTO workflow.events(operation_id,account_id,event,actor,detail)VALUES(%s,%s,'sent',%s,%s)",(operation_id,account_id,'operator:'+operator,Jsonb({'payloadSha256':payload_sha256,'reconciled':True})))
|
|
99
|
+
conn.execute("DELETE FROM workflow.procrastinate_jobs WHERE task_name=%s AND args->>'operation_id'=%s",(dispatch,operation_id))
|
|
100
|
+
return{'state':'sent','receipt':receipt}
|
|
101
|
+
|
|
102
|
+
def set_membership(conn,*,account_id,subject,roles,active=True,namespace='invoice'):
|
|
103
|
+
owned_scope(conn,account_id,namespace)
|
|
104
|
+
if not isinstance(subject,str)or not 1<=len(subject.encode())<=500 or any(ord(c)<32 for c in subject):raise ValueError('Use the verified native login subject, never a display-name guess')
|
|
105
|
+
if type(active)is not bool or not isinstance(roles,list)or not roles or len(set(roles))!=len(roles)or not set(roles)<={'user','reviewer','supervisor','owner'}:raise ValueError('Use an explicit reviewed role set')
|
|
106
|
+
conn.execute('INSERT INTO workflow.accounts(id)VALUES(%s)ON CONFLICT DO NOTHING',(account_id,))
|
|
107
|
+
conn.execute('INSERT INTO workflow.memberships(account_id,subject,roles,active)VALUES(%s,%s,%s,%s)ON CONFLICT(account_id,subject)DO UPDATE SET roles=excluded.roles,active=excluded.active',(account_id,subject,roles,active))
|
|
108
|
+
return{'active':active,'roles':roles}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Explicit operator CLI; never run with the API or worker SQL identity.
|
|
2
|
+
|
|
3
|
+
Connection/credential files are private local inputs, not export payloads. This
|
|
4
|
+
tool performs destination writes only after --execute; obtain authorization first.
|
|
5
|
+
It never contacts a model or the accounting service, and never claims that a human
|
|
6
|
+
supplied receipt has been independently verified by this tool.
|
|
7
|
+
"""
|
|
8
|
+
import argparse
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import stat
|
|
13
|
+
import sys
|
|
14
|
+
sys.path.insert(0,str(Path(__file__).resolve().parent))
|
|
15
|
+
import psycopg
|
|
16
|
+
from invoice_operator_core import validate_deployment,initialize_runtime
|
|
17
|
+
from invoice_operations import purge_expired,quarantine_restored_deliveries,reconcile_completed,set_membership
|
|
18
|
+
from structured_output import _object
|
|
19
|
+
|
|
20
|
+
def private_json(path):
|
|
21
|
+
file=Path(path)
|
|
22
|
+
metadata=file.stat()
|
|
23
|
+
if not stat.S_ISREG(metadata.st_mode)or metadata.st_uid!=os.geteuid()or metadata.st_mode&0o077 or metadata.st_size>20000:raise ValueError('Use an owned mode-0600 bounded JSON input file')
|
|
24
|
+
value=json.loads(file.read_text(),object_pairs_hook=_object)
|
|
25
|
+
if not isinstance(value,dict):raise ValueError('Use a JSON object')
|
|
26
|
+
return value
|
|
27
|
+
|
|
28
|
+
def connection_settings(path):
|
|
29
|
+
value=private_json(path)
|
|
30
|
+
if set(value)!={'host','port','dbname','user','passwordFile','caFile'}:raise ValueError('Use the documented operator connection fields')
|
|
31
|
+
secret=Path(value['passwordFile']);metadata=secret.stat()
|
|
32
|
+
if not secret.is_absolute()or metadata.st_uid!=os.geteuid()or metadata.st_mode&0o077 or not stat.S_ISREG(metadata.st_mode)or not 1<=metadata.st_size<=20000:raise ValueError('Use a private separately supplied database password/token file')
|
|
33
|
+
password=secret.read_text()
|
|
34
|
+
ca=Path(value['caFile'])
|
|
35
|
+
if not ca.is_absolute()or not ca.is_file()or b'-----BEGIN CERTIFICATE-----'not in ca.read_bytes():raise ValueError('Use the reviewed native CA bundle')
|
|
36
|
+
if any(not isinstance(value[key],str)or not value[key]or '\0'in value[key]for key in ['host','dbname','user'])or type(value['port'])is not int or not 1<=value['port']<=65535:raise ValueError('Use the owned native database connection')
|
|
37
|
+
if not password or '\0'in password:raise ValueError('Invalid private operator credential')
|
|
38
|
+
return dict(host=value['host'],port=value['port'],dbname=value['dbname'],user=value['user'],password=password,sslmode='verify-full',sslrootcert=str(ca),ssl_min_protocol_version='TLSv1.2',connect_timeout=5,options='-c search_path=pg_catalog,workflow,pg_temp -c statement_timeout=30000 -c lock_timeout=3000')
|
|
39
|
+
|
|
40
|
+
def main(argv=None):
|
|
41
|
+
parser=argparse.ArgumentParser(description=__doc__)
|
|
42
|
+
parser.add_argument('action',choices=['initialize','membership','purge-expired','quarantine-restore','reconcile-completed'])
|
|
43
|
+
parser.add_argument('--connection-file',required=True);parser.add_argument('--deployment-file',required=True);parser.add_argument('--input-file');parser.add_argument('--execute',action='store_true');parser.add_argument('--confirm-workloads-stopped',action='store_true')
|
|
44
|
+
args=parser.parse_args(argv)
|
|
45
|
+
deployment=private_json(args.deployment_file)
|
|
46
|
+
scope=validate_deployment(deployment)
|
|
47
|
+
payload=private_json(args.input_file)if args.input_file else{}
|
|
48
|
+
if not args.execute:
|
|
49
|
+
print(json.dumps({'action':args.action,'willWrite':False,'next':'Review the destination, private inputs and authorization, then pass --execute. No database connection was made.'}));return
|
|
50
|
+
settings=connection_settings(args.connection_file)
|
|
51
|
+
if args.action=='initialize':
|
|
52
|
+
def passwords(purpose,principal):
|
|
53
|
+
if purpose not in deployment['bindings']or principal!=deployment['bindings'][purpose]['principal']:raise ValueError('Use the exact owned native password binding')
|
|
54
|
+
field=payload.get(purpose+'PasswordFile')
|
|
55
|
+
if not isinstance(field,str):raise ValueError('Supply a private separate IBM role password file')
|
|
56
|
+
path=Path(field);metadata=path.stat()
|
|
57
|
+
if not path.is_absolute()or metadata.st_uid!=os.geteuid()or metadata.st_mode&0o077 or not stat.S_ISREG(metadata.st_mode)or not 24<=metadata.st_size<=500:raise ValueError('Use an owned private strong runtime password file')
|
|
58
|
+
return path.read_text()
|
|
59
|
+
def connect(database=None):return psycopg.connect(**{**settings,**({'dbname':database}if database else{})})
|
|
60
|
+
receipt=initialize_runtime(connect,deployment,passwords=passwords if deployment['provider']=='ibm'else None)
|
|
61
|
+
else:
|
|
62
|
+
if args.action in {'purge-expired','quarantine-restore','reconcile-completed'}and not args.confirm_workloads_stopped:raise ValueError('Pause workloads and explicitly confirm the maintenance boundary')
|
|
63
|
+
with psycopg.connect(**settings)as conn:
|
|
64
|
+
if args.action=='membership':
|
|
65
|
+
if set(payload)!={'subject','roles','active'}:raise ValueError('Supply exact membership fields')
|
|
66
|
+
receipt=set_membership(conn,**scope,**payload)
|
|
67
|
+
elif args.action=='purge-expired':receipt=purge_expired(conn,**scope)
|
|
68
|
+
elif args.action=='quarantine-restore':receipt=quarantine_restored_deliveries(conn,**scope,workloads_stopped=True)
|
|
69
|
+
else:
|
|
70
|
+
if set(payload)!={'operation_id','payload_sha256','external_id','operator'}:raise ValueError('Supply the independently verified accounting completion receipt fields')
|
|
71
|
+
receipt=reconcile_completed(conn,**scope,**payload)
|
|
72
|
+
print(json.dumps({'action':args.action,'committed':True,'receipt':receipt}))
|
|
73
|
+
|
|
74
|
+
if __name__=='__main__':
|
|
75
|
+
try:main()
|
|
76
|
+
except Exception:
|
|
77
|
+
# Driver exceptions can include connection fields or SQL parameter data.
|
|
78
|
+
# Keep private input details out of terminals and automated logs.
|
|
79
|
+
print('Operator action failed. No success is claimed. Check the private inputs, destination permissions, current schema and workload state.',file=sys.stderr);sys.exit(1)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Shared operator initialization for CLI and fixed native jobs, never HTTP.
|
|
2
|
+
|
|
3
|
+
Validate the whole deployment before opening a connection. Authentication and
|
|
4
|
+
transport belong to the caller's reviewed native credential factory; this module
|
|
5
|
+
neither reads credentials nor chooses endpoints from model/request input.
|
|
6
|
+
"""
|
|
7
|
+
import re
|
|
8
|
+
from workflow_role_bootstrap import canonical_uuid,validate_runtime_bindings,bootstrap_runtime_roles
|
|
9
|
+
from workflow_database import initialize_database,grant_runtime_roles
|
|
10
|
+
from invoice_operations import owned_scope
|
|
11
|
+
|
|
12
|
+
def validate_deployment(deployment):
|
|
13
|
+
if not isinstance(deployment,dict)or set(deployment)!={'provider','deploymentId','accountId','namespace','bindings','tenantId'}:raise ValueError('Use the exact documented deployment identity')
|
|
14
|
+
canonical_uuid(deployment['accountId'])
|
|
15
|
+
if not isinstance(deployment['namespace'],str)or not re.fullmatch(r'[a-zA-Z0-9-]{1,20}',deployment['namespace']):raise ValueError('Use the owned workflow namespace')
|
|
16
|
+
if deployment['provider']!='azure'and deployment['tenantId']is not None:raise ValueError('Only the Azure profile has a native tenant mapping')
|
|
17
|
+
validate_runtime_bindings(provider=deployment['provider'],deployment_id=deployment['deploymentId'],bindings=deployment['bindings'],tenant_id=deployment['tenantId'])
|
|
18
|
+
return {'account_id':deployment['accountId'],'namespace':deployment['namespace']}
|
|
19
|
+
|
|
20
|
+
def initialize_runtime(connect,deployment,*,passwords=None):
|
|
21
|
+
scope=validate_deployment(deployment)
|
|
22
|
+
if deployment['provider']=='ibm'and not callable(passwords):raise ValueError('Use the separate owned secret-store password reader')
|
|
23
|
+
# Native identity mapping and application schema are separate committed
|
|
24
|
+
# phases. A retry admits only exact ownership markers, never rotates existing
|
|
25
|
+
# SQL passwords, and then completes the application phase idempotently.
|
|
26
|
+
with connect('postgres'if deployment['provider']=='azure'else None)as conn:
|
|
27
|
+
if conn.autocommit:raise ValueError('Use an explicit operator transaction')
|
|
28
|
+
if conn.info.user in {item['name']for item in deployment['bindings'].values()}:raise PermissionError('Use the separate operator identity')
|
|
29
|
+
bootstrap_runtime_roles(conn,provider=deployment['provider'],deployment_id=deployment['deploymentId'],bindings=deployment['bindings'],tenant_id=deployment['tenantId'],passwords=passwords)
|
|
30
|
+
with connect(None)as conn:
|
|
31
|
+
if conn.autocommit:raise ValueError('Use an explicit operator transaction')
|
|
32
|
+
# Database-local and shared across deployments: serialize the check and
|
|
33
|
+
# CREATE, rather than allowing competing fresh-schema initializations.
|
|
34
|
+
conn.execute("SELECT pg_advisory_xact_lock(hashtextextended('atmem-workflow-schema-bootstrap-v1',0))")
|
|
35
|
+
if conn.execute("SELECT to_regnamespace('workflow')").fetchone()[0]is None:initialize_database(conn)
|
|
36
|
+
owned_scope(conn,**scope)
|
|
37
|
+
grant_runtime_roles(conn,deployment['bindings']['api']['name'],deployment['bindings']['worker']['name'],native_provider=deployment['provider'])
|
|
38
|
+
conn.execute('INSERT INTO workflow.accounts(id)VALUES(%s)ON CONFLICT DO NOTHING',(scope['account_id'],))
|
|
39
|
+
return {'initialized':True,'schemaVersion':1,'nativeIdentity':'Requires destination-native authentication checks; local success alone is insufficient.'}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Fixed native operator job entrypoint. Not an HTTP server or model tool.
|
|
2
|
+
|
|
3
|
+
The emitted task supplies owned nonsecret deployment metadata and a separate
|
|
4
|
+
operator identity. Credentials are platform secret references, never Terraform
|
|
5
|
+
values. Execution is explicit; preview makes no SDK, secret-file or SQL calls.
|
|
6
|
+
"""
|
|
7
|
+
import argparse
|
|
8
|
+
import base64
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
import http.client
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
import signal
|
|
17
|
+
import sys
|
|
18
|
+
import tempfile
|
|
19
|
+
import time
|
|
20
|
+
sys.path.insert(0,str(Path(__file__).resolve().parent))
|
|
21
|
+
import psycopg
|
|
22
|
+
from invoice_operator_core import validate_deployment,initialize_runtime
|
|
23
|
+
from invoice_maintenance import maintain,monitor
|
|
24
|
+
from invoice_configuration import owned_deployment,native_database,mounted_secret
|
|
25
|
+
from structured_output import _object
|
|
26
|
+
from workflow_connection import DatabaseConnection
|
|
27
|
+
|
|
28
|
+
def deployment_input(environment,provider):
|
|
29
|
+
encoded=environment.get('OPERATOR_DEPLOYMENT_BASE64')
|
|
30
|
+
if not isinstance(encoded,str)or not 1<=len(encoded)<=16000:raise ValueError('Use the emitted bounded operator deployment')
|
|
31
|
+
deployment=json.loads(base64.b64decode(encoded,validate=True),object_pairs_hook=_object)
|
|
32
|
+
validate_deployment(deployment)
|
|
33
|
+
if deployment['provider']!=provider:raise ValueError('Operator provider differs from the bundled deployment')
|
|
34
|
+
return deployment
|
|
35
|
+
|
|
36
|
+
@contextmanager
|
|
37
|
+
def private_environment(provider,environment):
|
|
38
|
+
env=dict(environment)
|
|
39
|
+
if any(key.startswith(('MODEL_','DESTINATION_','PARSER_','DOCUMENT_'))for key in env):raise ValueError('Operator must not receive application/model/action authority')
|
|
40
|
+
if provider not in {'aws','azure','gcp','ibm'}:raise ValueError('Use the owned operator provider')
|
|
41
|
+
if provider not in {'aws','ibm'}and any(key in env for key in ['DATABASE_PASSWORD','DATABASE_PASSWORD_FILE']):raise ValueError('Use native workload database identity')
|
|
42
|
+
if provider!='ibm'and any(key in env for key in ['API_DATABASE_PASSWORD','WORKER_DATABASE_PASSWORD','API_DATABASE_PASSWORD_FILE','WORKER_DATABASE_PASSWORD_FILE']):raise ValueError('Runtime passwords are IBM-only bindings')
|
|
43
|
+
directory=Path(tempfile.mkdtemp(prefix='atmem-private-operator-'));directory.chmod(0o700)
|
|
44
|
+
try:
|
|
45
|
+
for key,maximum in [('DATABASE_PASSWORD',500),('API_DATABASE_PASSWORD',500),('WORKER_DATABASE_PASSWORD',500),('DATABASE_CA_PEM',256000)]:
|
|
46
|
+
if key not in env:continue
|
|
47
|
+
pointer='DATABASE_CA_FILE'if key=='DATABASE_CA_PEM'else key+'_FILE';value=env[key]
|
|
48
|
+
if pointer in env or not isinstance(value,str)or not value or len(value.encode())>maximum or'\0'in value:raise ValueError('Invalid or conflicting native operator secret injection')
|
|
49
|
+
if key=='DATABASE_CA_PEM'and'-----BEGIN CERTIFICATE-----'not in value:raise ValueError('Use the native database CA certificate')
|
|
50
|
+
path=directory/key.lower();descriptor=os.open(path,os.O_CREAT|os.O_EXCL|os.O_WRONLY,0o600)
|
|
51
|
+
with os.fdopen(descriptor,'wb')as stream:stream.write(value.encode())
|
|
52
|
+
env[pointer]=str(path);env.pop(key)
|
|
53
|
+
if environment is os.environ:os.environ.pop(key,None)
|
|
54
|
+
yield env
|
|
55
|
+
finally:shutil.rmtree(directory,ignore_errors=True)
|
|
56
|
+
|
|
57
|
+
def native_connect(provider,env,deployment):
|
|
58
|
+
def connect(database=None):
|
|
59
|
+
settings={**env,**({'DATABASE_NAME':database}if database else{})}
|
|
60
|
+
if provider=='aws':
|
|
61
|
+
host=settings['DATABASE_HOST'];region=settings['REGION']
|
|
62
|
+
if not re.fullmatch(r'[a-z]{2}-[a-z]+-[0-9]',region)or not host.endswith('.'+region+'.rds.amazonaws.com'):raise ValueError('Use the owned regional RDS endpoint')
|
|
63
|
+
factory=DatabaseConnection('aws',host,5432,settings['DATABASE_NAME'],settings['DATABASE_USER'],mounted_secret(settings['DATABASE_PASSWORD_FILE']),ca_file=settings['DATABASE_CA_FILE'],role='migration')
|
|
64
|
+
else:factory=native_database(provider,settings,'migration')
|
|
65
|
+
if factory.parameters['user']in {row['name']for row in deployment['bindings'].values()}:raise PermissionError('Do not run an operator job as API or worker')
|
|
66
|
+
for attempt in range(3):
|
|
67
|
+
try:return psycopg.connect(factory())
|
|
68
|
+
except psycopg.OperationalError as error:
|
|
69
|
+
# Only retry connection establishment before any transaction.
|
|
70
|
+
# Cold proxy certificate/network setup can be transient; SQL
|
|
71
|
+
# permission/authentication/schema failures are never repaired
|
|
72
|
+
# by bypassing TLS, changing identities or replaying statements.
|
|
73
|
+
state=error.sqlstate
|
|
74
|
+
if provider!='gcp'or attempt==2 or(state is not None and not(state.startswith('08')or state=='57P03')):raise
|
|
75
|
+
time.sleep(attempt+1)
|
|
76
|
+
return connect
|
|
77
|
+
|
|
78
|
+
def runtime_passwords(environment,deployment):
|
|
79
|
+
def password(purpose,principal):
|
|
80
|
+
if purpose not in deployment['bindings']or deployment['bindings'][purpose]['principal']!=principal:raise ValueError('Use the exact owned native password binding')
|
|
81
|
+
path=Path(environment[purpose.upper()+'_DATABASE_PASSWORD_FILE']);metadata=path.stat()
|
|
82
|
+
import stat
|
|
83
|
+
if not path.is_absolute()or not stat.S_ISREG(metadata.st_mode)or metadata.st_uid!=os.geteuid()or metadata.st_mode&0o077 or not 24<=metadata.st_size<=500:raise ValueError('Use a private separately supplied native runtime password')
|
|
84
|
+
return path.read_text()
|
|
85
|
+
return password
|
|
86
|
+
|
|
87
|
+
def run_action(action,connect,deployment,*,passwords=None):
|
|
88
|
+
validate_deployment(deployment)
|
|
89
|
+
if action=='initialize':return {'status':'ok',**initialize_runtime(connect,deployment,passwords=passwords)}
|
|
90
|
+
if action=='maintain':return maintain(connect,deployment)
|
|
91
|
+
if action=='monitor':return monitor(connect,deployment)
|
|
92
|
+
raise ValueError('Unsupported operator job action')
|
|
93
|
+
|
|
94
|
+
@contextmanager
|
|
95
|
+
def private_proxy_job(provider,environment):
|
|
96
|
+
mode=environment.get('OPERATOR_PROXY_SHUTDOWN')
|
|
97
|
+
if mode is None:
|
|
98
|
+
yield;return
|
|
99
|
+
if provider!='gcp'or mode!='cloudsql-private-proxy'or environment.get('DATABASE_TRANSPORT')!='cloudsql-private-proxy':raise ValueError('Use the emitted Google job proxy lifecycle')
|
|
100
|
+
try:
|
|
101
|
+
# Stable GA job containers can start concurrently. Wait for the pinned
|
|
102
|
+
# proxy's own startup endpoint rather than depending on beta ordering.
|
|
103
|
+
deadline=time.monotonic()+30
|
|
104
|
+
while time.monotonic()<deadline:
|
|
105
|
+
startup=http.client.HTTPConnection('127.0.0.1',9090,timeout=1)
|
|
106
|
+
try:
|
|
107
|
+
startup.request('GET','/startup');response=startup.getresponse()
|
|
108
|
+
if response.status==200 and len(response.read(2049))<=2048:break
|
|
109
|
+
except (OSError,http.client.HTTPException):pass
|
|
110
|
+
finally:startup.close()
|
|
111
|
+
time.sleep(0.2)
|
|
112
|
+
else:raise TimeoutError('Private SQL proxy did not finish startup')
|
|
113
|
+
yield
|
|
114
|
+
finally:
|
|
115
|
+
# Fixed same-instance loopback endpoint; no configurable URL, redirects,
|
|
116
|
+
# ambient HTTP proxy or outbound credentials. A failed shutdown must not
|
|
117
|
+
# produce an ok receipt while the sidecar keeps the job running.
|
|
118
|
+
connection=http.client.HTTPConnection('127.0.0.1',9091,timeout=2)
|
|
119
|
+
try:
|
|
120
|
+
connection.request('POST','/quitquitquit',body=b'')
|
|
121
|
+
response=connection.getresponse()
|
|
122
|
+
if response.status!=200 or len(response.read(2049))>2048:raise ValueError('Private SQL proxy did not confirm shutdown')
|
|
123
|
+
finally:connection.close()
|
|
124
|
+
|
|
125
|
+
def main(argv=None):
|
|
126
|
+
parser=argparse.ArgumentParser(description=__doc__)
|
|
127
|
+
parser.add_argument('action',choices=['initialize','maintain','monitor']);parser.add_argument('--execute',action='store_true');args=parser.parse_args(argv)
|
|
128
|
+
provider=owned_deployment()['provider']
|
|
129
|
+
if not args.execute:
|
|
130
|
+
deployment_input(os.environ,provider)
|
|
131
|
+
print(json.dumps({'format':'invoice-operator-v1','action':args.action,'status':'preview','willWrite':False}));return 0
|
|
132
|
+
def deadline(*_):raise TimeoutError('Operator execution deadline')
|
|
133
|
+
with private_proxy_job(provider,os.environ):
|
|
134
|
+
deployment=deployment_input(os.environ,provider)
|
|
135
|
+
previous=signal.signal(signal.SIGALRM,deadline);signal.alarm(900)
|
|
136
|
+
try:
|
|
137
|
+
with private_environment(provider,os.environ)as env:
|
|
138
|
+
result=run_action(args.action,native_connect(provider,env,deployment),deployment,passwords=runtime_passwords(env,deployment)if provider=='ibm'else None)
|
|
139
|
+
finally:signal.alarm(0);signal.signal(signal.SIGALRM,previous)
|
|
140
|
+
print(json.dumps({'format':'invoice-operator-v1','action':args.action,**result}))
|
|
141
|
+
return 0 if result['status']=='ok'else 2
|
|
142
|
+
|
|
143
|
+
if __name__=='__main__':
|
|
144
|
+
try:sys.exit(main())
|
|
145
|
+
except Exception:
|
|
146
|
+
# Driver/SDK exceptions can contain sensitive endpoints, SQL or values.
|
|
147
|
+
print(json.dumps({'format':'invoice-operator-v1','status':'failed','code':'operator_job_failed'}));sys.exit(1)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Credentialless one-shot parser protocol; no URLs, cloud calls or model work."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
sys.path.insert(0,str(Path(__file__).resolve().parent))
|
|
7
|
+
from invoice_document import PdfDocument,DocumentError
|
|
8
|
+
|
|
9
|
+
if __name__=='__main__':
|
|
10
|
+
try:
|
|
11
|
+
raw=sys.stdin.buffer.read(10000001)
|
|
12
|
+
document=PdfDocument().extract(raw,hashlib.sha256(raw).hexdigest())
|
|
13
|
+
sys.stdout.write(json.dumps({'status':'ok','document':document},ensure_ascii=False))
|
|
14
|
+
except DocumentError as error:
|
|
15
|
+
sys.stdout.write(json.dumps({'status':'rejected','reason':error.code}));sys.exit(2)
|
|
16
|
+
except Exception:
|
|
17
|
+
sys.stdout.write(json.dumps({'status':'rejected','reason':'extraction_unavailable'}));sys.exit(2)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Parser-only Entra workload authorization; no application/cloud credentials.
|
|
2
|
+
|
|
3
|
+
The issuer/key endpoint, audience and exact worker identities are compiler-owned.
|
|
4
|
+
Staff/delegated tokens and another application in the same tenant grant no access.
|
|
5
|
+
This module does not claim that a valid token proves a native deployment exists.
|
|
6
|
+
"""
|
|
7
|
+
import re
|
|
8
|
+
import hmac
|
|
9
|
+
import jwt
|
|
10
|
+
|
|
11
|
+
UUID=r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
|
|
12
|
+
|
|
13
|
+
class EntraParserVerifier:
|
|
14
|
+
def __init__(self,tenant,audience,worker_client,worker_principal,*,jwks=None):
|
|
15
|
+
if any(not isinstance(value,str)or not re.fullmatch(UUID,value)for value in [tenant,audience,worker_client,worker_principal]):raise ValueError('Use exact owned Entra parser and worker identities')
|
|
16
|
+
self.tenant=tenant;self.audience=audience;self.worker_client=worker_client;self.worker_principal=worker_principal
|
|
17
|
+
self.issuer='https://login.microsoftonline.com/'+tenant+'/v2.0'
|
|
18
|
+
self.jwks=jwks or jwt.PyJWKClient('https://login.microsoftonline.com/'+tenant+'/discovery/v2.0/keys',timeout=2,lifespan=300)
|
|
19
|
+
def __call__(self,token):
|
|
20
|
+
try:
|
|
21
|
+
if not isinstance(token,str)or not 1<=len(token)<=16384:raise ValueError('Invalid workload token')
|
|
22
|
+
header=jwt.get_unverified_header(token)
|
|
23
|
+
if header.get('alg')!='RS256'or not isinstance(header.get('kid'),str)or not 1<=len(header['kid'])<=200:raise ValueError('Invalid workload signing key')
|
|
24
|
+
key=self.jwks.get_signing_key_from_jwt(token).key
|
|
25
|
+
claims=jwt.decode(token,key,algorithms=['RS256'],issuer=self.issuer,audience=self.audience,leeway=30,options={'require':['iss','aud','iat','nbf','exp','tid','oid','azp','ver','roles']})
|
|
26
|
+
if(claims['aud']!=self.audience or claims['tid']!=self.tenant or claims['oid']!=self.worker_principal or claims['azp']!=self.worker_client or claims['ver']!='2.0'
|
|
27
|
+
or'scp'in claims or claims.get('idtyp','app')!='app'or not isinstance(claims['roles'],list)or not 1<=len(claims['roles'])<=100 or any(not isinstance(role,str)for role in claims['roles'])or'Parser.Invoke'not in claims['roles']
|
|
28
|
+
or any(type(claims[name])is not int for name in ['iat','nbf','exp'])or not 0<claims['exp']-claims['iat']<=86700):raise ValueError('Caller is not the owned parser worker')
|
|
29
|
+
return self.worker_principal
|
|
30
|
+
except Exception:raise PermissionError('Authorized parser worker required')from None
|
|
31
|
+
|
|
32
|
+
class SharedParserVerifier:
|
|
33
|
+
"""Private TLS RPC admission only; this token confers no cloud/data authority."""
|
|
34
|
+
def __init__(self,token):
|
|
35
|
+
if not isinstance(token,str)or not re.fullmatch(r'[0-9a-f]{64}',token):raise ValueError('Use a separately provisioned 256-bit parser token')
|
|
36
|
+
self._token=token
|
|
37
|
+
def __call__(self,token):
|
|
38
|
+
if not isinstance(token,str)or not re.fullmatch(r'[0-9a-f]{64}',token)or not hmac.compare_digest(self._token,token):raise PermissionError('Authorized parser worker required')
|
|
39
|
+
return'owned-worker'
|