@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,294 @@
|
|
|
1
|
+
"""Owned Azure Batch document request contract, not an admitted native executor.
|
|
2
|
+
|
|
3
|
+
Host preparation/release helpers, private pool/IAM provisioning, durable recovery
|
|
4
|
+
and actual node-agent checks must be composed before enabling this provider in
|
|
5
|
+
enterprise_worker. Schema-valid requests alone do not prove a running sandbox.
|
|
6
|
+
"""
|
|
7
|
+
import hashlib
|
|
8
|
+
import copy
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import shlex
|
|
12
|
+
|
|
13
|
+
API_VERSION = '2026-10-01'
|
|
14
|
+
FORMAT = 'enterprise-azure-document-job-v1'
|
|
15
|
+
_UUID = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
|
|
16
|
+
_HASH = r'[0-9a-f]{64}'
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def configuration(value):
|
|
20
|
+
fields = {'format', 'subscriptionId', 'region', 'resourceGroup', 'batchAccount',
|
|
21
|
+
'poolId', 'storageAccount', 'container', 'workerClientId',
|
|
22
|
+
'transferClientId', 'parserImage', 'transferImage', 'hostHelperSha256'}
|
|
23
|
+
if not isinstance(value, dict) or set(value) != fields or value['format'] != FORMAT:
|
|
24
|
+
raise ValueError('Use the exact owned Azure document-job configuration')
|
|
25
|
+
patterns = {'subscriptionId': _UUID, 'region': r'[a-z][a-z0-9]{1,30}',
|
|
26
|
+
'resourceGroup': r'[A-Za-z0-9_()-](?:[A-Za-z0-9_().-]{0,88}[A-Za-z0-9_()-])?',
|
|
27
|
+
'batchAccount': r'[a-z0-9]{3,24}', 'poolId': r'document-[a-z0-9-]{1,54}',
|
|
28
|
+
'storageAccount': r'[a-z0-9]{3,24}',
|
|
29
|
+
'container': r'[a-z0-9]+(?:-[a-z0-9]+)*',
|
|
30
|
+
'workerClientId': _UUID, 'transferClientId': _UUID,
|
|
31
|
+
'hostHelperSha256': _HASH}
|
|
32
|
+
for name, pattern in patterns.items():
|
|
33
|
+
if not isinstance(value[name], str) or not re.fullmatch(pattern, value[name]):
|
|
34
|
+
raise ValueError('Invalid owned Azure job destination or identity')
|
|
35
|
+
if not 3 <= len(value['container']) <= 63:
|
|
36
|
+
raise ValueError('Use the owned temporary container')
|
|
37
|
+
if value['workerClientId'] == value['transferClientId']:
|
|
38
|
+
raise ValueError('Transfer hosts must not inherit the question worker identity')
|
|
39
|
+
registry = None
|
|
40
|
+
for name in ['parserImage', 'transferImage']:
|
|
41
|
+
if not isinstance(value[name], str) or not re.fullmatch(
|
|
42
|
+
r'[a-z0-9]{5,50}\.azurecr\.io/[a-z0-9]+(?:[._/-][a-z0-9]+)*@sha256:' + _HASH, value[name]):
|
|
43
|
+
raise ValueError('Use immutable owned Azure Container Registry artifacts')
|
|
44
|
+
current = value[name].split('/', 1)[0]
|
|
45
|
+
if registry is not None and registry != current:
|
|
46
|
+
raise ValueError('Use the same owned artifact registry')
|
|
47
|
+
registry = current
|
|
48
|
+
if value['parserImage'] == value['transferImage']:
|
|
49
|
+
raise ValueError('Keep parser and trusted transfer artifacts separate')
|
|
50
|
+
# Registry/account names alone cannot prove subscription or regional ownership.
|
|
51
|
+
# The future native preflight must check ARM identity/location, not infer them.
|
|
52
|
+
return dict(value)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def identity(value):
|
|
56
|
+
if not isinstance(value, str) or not re.fullmatch(_HASH, value):
|
|
57
|
+
raise ValueError('Use the exact opaque per-file identity')
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def transfer_metadata(config, metadata, etag):
|
|
62
|
+
owned = configuration(config)
|
|
63
|
+
if (not isinstance(metadata, dict) or set(metadata) != {'format', 'requestId', 'sha256', 'mediaType'}
|
|
64
|
+
or metadata['format'] != 'enterprise-document-job-v1'
|
|
65
|
+
or not isinstance(metadata['sha256'], str) or not re.fullmatch(_HASH, metadata['sha256'])):
|
|
66
|
+
raise ValueError('Use the exact byte-bound document request')
|
|
67
|
+
identity(metadata['requestId'])
|
|
68
|
+
from enterprise_document_contract import MEDIA_TYPES, media_type
|
|
69
|
+
if (media_type(metadata['mediaType']) != metadata['mediaType'] or
|
|
70
|
+
metadata['mediaType'] not in MEDIA_TYPES | {'application/octet-stream'}):
|
|
71
|
+
raise ValueError('Use a supported canonical document MIME')
|
|
72
|
+
# Azure Blob ETags are conditional tokens; never use one as a content digest.
|
|
73
|
+
if not isinstance(etag, str) or not re.fullmatch(r'"0x[0-9A-Fa-f]{1,32}"', etag):
|
|
74
|
+
raise ValueError('Use the acknowledged native Azure input ETag')
|
|
75
|
+
return {'format': 'enterprise-azure-document-transfer-v1',
|
|
76
|
+
'subscriptionId': owned['subscriptionId'], 'region': owned['region'],
|
|
77
|
+
'resourceGroup': owned['resourceGroup'], 'storageAccount': owned['storageAccount'],
|
|
78
|
+
'container': owned['container'], 'transferClientId': owned['transferClientId'],
|
|
79
|
+
'request': dict(metadata), 'inputETag': etag}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def volume_names(identifier):
|
|
83
|
+
# IDs are 64 hex characters, also satisfying Batch's maximum job ID length.
|
|
84
|
+
token = identity(identifier)
|
|
85
|
+
return ('atmem-document-' + token + '-input', 'atmem-document-' + token + '-output')
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _host_command(owned, mode):
|
|
89
|
+
if mode not in {'prepare', 'release'}:
|
|
90
|
+
raise ValueError('Use the fixed trusted host operation')
|
|
91
|
+
digest = owned['hostHelperSha256']
|
|
92
|
+
path = '/opt/atmem/document-host/' + digest + '/enterprise_azure_host.py'
|
|
93
|
+
# No shell/path expansion, application-package default version, URL fetching,
|
|
94
|
+
# inherited PYTHONPATH or unverified host script. The root-owned helper tree
|
|
95
|
+
# and its parents are separately required by pool provisioning/preflight.
|
|
96
|
+
code = (
|
|
97
|
+
'import os,stat,hashlib,sys;'
|
|
98
|
+
'f=os.open(' + repr(path) + ',os.O_RDONLY|os.O_NOFOLLOW);'
|
|
99
|
+
's=os.fstat(f);'
|
|
100
|
+
'assert stat.S_ISREG(s.st_mode) and s.st_uid==0 and not s.st_mode&0o022 and s.st_size<=262144;'
|
|
101
|
+
'b=os.read(f,262145);os.close(f);'
|
|
102
|
+
'assert len(b)==s.st_size and hashlib.sha256(b).hexdigest()==' + repr(digest) + ';'
|
|
103
|
+
'sys.argv=[' + repr(path) + ',' + repr(mode) + '];'
|
|
104
|
+
'exec(compile(b,' + repr(path) + ',"exec"),{"__name__":"__main__","__file__":' + repr(path) + '})'
|
|
105
|
+
)
|
|
106
|
+
return '/usr/bin/python3 -I -c ' + shlex.quote(code)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def job_spec(config, metadata, etag):
|
|
110
|
+
owned = configuration(config)
|
|
111
|
+
transfer = transfer_metadata(owned, metadata, etag)
|
|
112
|
+
encoded = json.dumps(transfer, separators=(',', ':'), allow_nan=False)
|
|
113
|
+
# Never use commonEnvironmentSettings: those are inherited by the parser.
|
|
114
|
+
environment = [{'name': 'DOCUMENT_JOB', 'value': encoded},
|
|
115
|
+
{'name': 'DOCUMENT_TRANSFER_IMAGE', 'value': owned['transferImage']}]
|
|
116
|
+
admin = {'autoUser': {'scope': 'task', 'elevationLevel': 'admin'}}
|
|
117
|
+
return {'id': metadata['requestId'], 'poolInfo': {'poolId': owned['poolId']},
|
|
118
|
+
'constraints': {'maxWallClockTime': 'PT8M', 'maxTaskRetryCount': 0},
|
|
119
|
+
# Keep active until its single task is observed. terminatejob here
|
|
120
|
+
# would complete an empty job before the task can be submitted.
|
|
121
|
+
'onAllTasksComplete': 'noaction',
|
|
122
|
+
'jobPreparationTask': {'id': 'prepare', 'commandLine': _host_command(owned, 'prepare'),
|
|
123
|
+
'waitForSuccess': True, 'rerunOnNodeRebootAfterSuccess': True,
|
|
124
|
+
'userIdentity': admin, 'environmentSettings': environment,
|
|
125
|
+
'constraints': {'maxWallClockTime': 'PT1M', 'maxTaskRetryCount': 0, 'retentionTime': 'PT0S'}},
|
|
126
|
+
'jobReleaseTask': {'id': 'release', 'commandLine': _host_command(owned, 'release'),
|
|
127
|
+
'userIdentity': {'autoUser': dict(admin['autoUser'])},
|
|
128
|
+
'environmentSettings': [dict(item) for item in environment],
|
|
129
|
+
'maxWallClockTime': 'PT1M', 'retentionTime': 'PT0S'},
|
|
130
|
+
'metadata': [{'name': 'atmem-owner', 'value': fingerprint(owned)},
|
|
131
|
+
{'name': 'atmem-parser-image', 'value': owned['parserImage']},
|
|
132
|
+
{'name': 'atmem-helper-sha256', 'value': owned['hostHelperSha256']},
|
|
133
|
+
{'name': 'atmem-worker-client', 'value': owned['workerClientId']}]}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def task_spec(config, identifier):
|
|
137
|
+
owned = configuration(config)
|
|
138
|
+
source, target = volume_names(identifier)
|
|
139
|
+
options = ['--rm', '--network=none', '--read-only', '--cap-drop=ALL',
|
|
140
|
+
'--security-opt=no-new-privileges:true', '--pids-limit=128',
|
|
141
|
+
'--memory=2g', '--memory-swap=2g', '--cpus=2', '--log-driver=none',
|
|
142
|
+
'--label=atmem.document.owner=' + cleanup_owner(owned),
|
|
143
|
+
'--label=atmem.document.request=' + identity(identifier),
|
|
144
|
+
'--label=atmem.document.policy=' + fingerprint(owned),
|
|
145
|
+
'--label=atmem.document.stage=parser',
|
|
146
|
+
'--tmpfs=/tmp:rw,noexec,nosuid,nodev,size=128m',
|
|
147
|
+
'--mount=type=volume,source=' + source + ',target=/input,readonly',
|
|
148
|
+
'--mount=type=volume,source=' + target + ',target=/output']
|
|
149
|
+
return {'id': 'document', 'commandLine': 'python -I /app/enterprise_document_cli.py --job',
|
|
150
|
+
'containerSettings': {'imageName': owned['parserImage'],
|
|
151
|
+
'containerRunOptions': ' '.join(options),
|
|
152
|
+
'containerHostBatchBindMounts': [], 'workingDirectory': 'containerImageDefault'},
|
|
153
|
+
# Batch maps a native non-admin task UID. Do not assume the image's
|
|
154
|
+
# 10001 UID or give the untrusted parser admin to bypass permissions.
|
|
155
|
+
'userIdentity': {'autoUser': {'scope': 'task', 'elevationLevel': 'nonadmin'}},
|
|
156
|
+
'constraints': {'maxWallClockTime': 'PT2M', 'maxTaskRetryCount': 0, 'retentionTime': 'PT0S'}}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def fingerprint(config):
|
|
160
|
+
return hashlib.sha256(json.dumps(configuration(config), sort_keys=True, separators=(',', ':')).encode()).hexdigest()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def cleanup_owner(config):
|
|
164
|
+
checked = configuration(config)
|
|
165
|
+
keys = ['subscriptionId', 'region', 'resourceGroup', 'batchAccount', 'poolId',
|
|
166
|
+
'storageAccount', 'container', 'transferClientId']
|
|
167
|
+
return hashlib.sha256(json.dumps({**{key: checked[key] for key in keys},
|
|
168
|
+
'registry': checked['parserImage'].split('/')[0]}, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def verify_job(config, metadata, etag, reply):
|
|
172
|
+
"""Reject unowned/replaced jobs before adopting them during recovery."""
|
|
173
|
+
expected = job_spec(config, metadata, etag)
|
|
174
|
+
if not isinstance(reply, dict) or not typed_equal(job_observation(expected), job_observation({key: reply.get(key) for key in expected})):
|
|
175
|
+
raise ValueError('Azure document job ownership or execution contract changed')
|
|
176
|
+
if any(key in reply for key in ['jobManagerTask', 'commonEnvironmentSettings', 'networkConfiguration']):
|
|
177
|
+
raise ValueError('Unexpected Azure document job authority')
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def verify_task(config, identifier, reply):
|
|
182
|
+
expected = task_spec(config, identifier)
|
|
183
|
+
if not isinstance(reply, dict) or any(not typed_equal(value, reply.get(key)) for key, value in expected.items()):
|
|
184
|
+
raise ValueError('Azure document parser isolation contract changed')
|
|
185
|
+
if any(key in reply for key in ['environmentSettings', 'resourceFiles', 'outputFiles',
|
|
186
|
+
'applicationPackageReferences', 'multiInstanceSettings', 'dependsOn']):
|
|
187
|
+
raise ValueError('Unexpected Azure parser input or authority')
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def typed_equal(expected, observed):
|
|
192
|
+
if type(expected) is not type(observed): return False
|
|
193
|
+
if isinstance(expected, dict):
|
|
194
|
+
return set(expected) == set(observed) and all(typed_equal(value, observed[key]) for key, value in expected.items())
|
|
195
|
+
if isinstance(expected, list):
|
|
196
|
+
return len(expected) == len(observed) and all(typed_equal(left, right) for left, right in zip(expected, observed))
|
|
197
|
+
return expected == observed
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def job_observation(value):
|
|
201
|
+
"""Only documented name/value arrays are unordered; no fields are dropped."""
|
|
202
|
+
value = copy.deepcopy(value)
|
|
203
|
+
arrays = [value.get('metadata')]
|
|
204
|
+
for key in ['jobPreparationTask', 'jobReleaseTask']:
|
|
205
|
+
task = value.get(key)
|
|
206
|
+
if isinstance(task, dict): arrays.append(task.get('environmentSettings'))
|
|
207
|
+
for items in arrays:
|
|
208
|
+
if not isinstance(items, list): raise ValueError('Use exact native name/value settings')
|
|
209
|
+
names = set()
|
|
210
|
+
for item in items:
|
|
211
|
+
if (not isinstance(item, dict) or set(item) != {'name', 'value'} or
|
|
212
|
+
not isinstance(item['name'], str) or not isinstance(item['value'], str) or item['name'] in names):
|
|
213
|
+
raise ValueError('Ambiguous native name/value settings')
|
|
214
|
+
names.add(item['name'])
|
|
215
|
+
items.sort(key=lambda entry: entry['name'])
|
|
216
|
+
return value
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def recover_job(config, identifier, reply):
|
|
220
|
+
"""Recover only an exact owned historical job, without executing its text.
|
|
221
|
+
|
|
222
|
+
The SQL ledger stores opaque IDs, not source metadata or arbitrary cleanup
|
|
223
|
+
endpoints. The native job supplies its bounded original byte/ETag/artifact
|
|
224
|
+
binding; the stable destination and full immutable shape are checked here.
|
|
225
|
+
This result is for observation/cleanup, never permission to launch old code.
|
|
226
|
+
"""
|
|
227
|
+
current = configuration(config)
|
|
228
|
+
identity(identifier)
|
|
229
|
+
if not isinstance(reply, dict) or reply.get('id') != identifier:
|
|
230
|
+
raise ValueError('Native job identity changed')
|
|
231
|
+
def settings(value, names):
|
|
232
|
+
if not isinstance(value, list) or len(value) != len(names):
|
|
233
|
+
raise ValueError('Use exact original job metadata')
|
|
234
|
+
result = {}
|
|
235
|
+
for entry in value:
|
|
236
|
+
if (not isinstance(entry, dict) or set(entry) != {'name', 'value'} or
|
|
237
|
+
not isinstance(entry['name'], str) or entry['name'] not in names or entry['name'] in result or not isinstance(entry['value'], str)):
|
|
238
|
+
raise ValueError('Ambiguous original job metadata')
|
|
239
|
+
result[entry['name']] = entry['value']
|
|
240
|
+
return result
|
|
241
|
+
metadata = settings(reply.get('metadata'), {'atmem-owner', 'atmem-parser-image',
|
|
242
|
+
'atmem-helper-sha256', 'atmem-worker-client'})
|
|
243
|
+
prepare = reply.get('jobPreparationTask')
|
|
244
|
+
if not isinstance(prepare, dict): raise ValueError('Original preparation contract is required')
|
|
245
|
+
environment = settings(prepare.get('environmentSettings'), {'DOCUMENT_JOB', 'DOCUMENT_TRANSFER_IMAGE'})
|
|
246
|
+
historical = configuration({**current, 'parserImage': metadata['atmem-parser-image'],
|
|
247
|
+
'transferImage': environment['DOCUMENT_TRANSFER_IMAGE'], 'hostHelperSha256': metadata['atmem-helper-sha256'],
|
|
248
|
+
'workerClientId': metadata['atmem-worker-client']})
|
|
249
|
+
if cleanup_owner(historical) != cleanup_owner(current):
|
|
250
|
+
raise ValueError('Do not adopt a different cleanup destination or artifact registry')
|
|
251
|
+
if not 0 < len(environment['DOCUMENT_JOB'].encode()) <= 4096:
|
|
252
|
+
raise ValueError('Original transfer metadata exceeds the bound')
|
|
253
|
+
from enterprise_document_job import unique
|
|
254
|
+
value = json.loads(environment['DOCUMENT_JOB'], object_pairs_hook=unique)
|
|
255
|
+
if not isinstance(value, dict) or not isinstance(value.get('request'), dict) or value['request'].get('requestId') != identifier:
|
|
256
|
+
raise ValueError('Original file identity changed')
|
|
257
|
+
expected = transfer_metadata(historical, value['request'], value.get('inputETag'))
|
|
258
|
+
if value != expected:
|
|
259
|
+
raise ValueError('Original transfer destination changed')
|
|
260
|
+
verify_job(historical, expected['request'], expected['inputETag'], reply)
|
|
261
|
+
return historical, expected
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def terminal_writer_nodes(config, identifier, job, task, status):
|
|
265
|
+
"""Seal the complete native preparation-node set only after job completion.
|
|
266
|
+
|
|
267
|
+
A preflight snapshot or one task's current node omits earlier retry nodes.
|
|
268
|
+
This function observes provider metadata; it does not certify local cleanup
|
|
269
|
+
or erase storage. Every returned node still requires its own acknowledgement.
|
|
270
|
+
"""
|
|
271
|
+
historical, transfer = recover_job(config, identifier, job)
|
|
272
|
+
if job.get('state') != 'completed': raise ValueError('Native work has not completed termination/release')
|
|
273
|
+
if (not isinstance(status, dict) or status.get('odata.nextLink') or status.get('@odata.nextLink') or
|
|
274
|
+
not isinstance(status.get('value'), list) or len(status['value']) > 100):
|
|
275
|
+
raise ValueError('Observe the complete native preparation/release history')
|
|
276
|
+
nodes = set()
|
|
277
|
+
for row in status['value']:
|
|
278
|
+
if (not isinstance(row, dict) or row.get('poolId') != historical['poolId'] or
|
|
279
|
+
not isinstance(row.get('nodeId'), str) or not re.fullmatch(r'[A-Za-z0-9_-]{1,128}', row['nodeId']) or
|
|
280
|
+
row['nodeId'] in nodes or not isinstance(row.get('jobPreparationTaskExecutionInfo'), dict) or
|
|
281
|
+
row['jobPreparationTaskExecutionInfo'].get('state') != 'completed'):
|
|
282
|
+
raise ValueError('Native preparation history is incomplete or foreign')
|
|
283
|
+
release = row.get('jobReleaseTaskExecutionInfo')
|
|
284
|
+
if release is not None and (not isinstance(release, dict) or release.get('state') != 'completed'):
|
|
285
|
+
raise ValueError('Native release is still active or unknown')
|
|
286
|
+
nodes.add(row['nodeId'])
|
|
287
|
+
if task is not None:
|
|
288
|
+
verify_task(historical, identifier, task)
|
|
289
|
+
if task.get('state') != 'completed': raise ValueError('Native parser task is not terminal')
|
|
290
|
+
assigned = task.get('nodeInfo')
|
|
291
|
+
if assigned is not None and (not isinstance(assigned, dict) or assigned.get('poolId') != historical['poolId'] or
|
|
292
|
+
assigned.get('nodeId') not in nodes):
|
|
293
|
+
raise ValueError('Native task assignment is missing from complete preparation history')
|
|
294
|
+
return historical, transfer, frozenset(nodes)
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Bounded, native-MI Azure temporary blobs; never a source URL connector.
|
|
2
|
+
|
|
3
|
+
ARM preflight is observational, not an administrative lock. Provisioning must
|
|
4
|
+
restrict changes to this dedicated no-history account/container. Lost nodes and
|
|
5
|
+
cloud erasure require separate controller/recovery evidence.
|
|
6
|
+
"""
|
|
7
|
+
import base64
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from email.utils import format_datetime
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
import time
|
|
14
|
+
from urllib.parse import quote
|
|
15
|
+
import requests
|
|
16
|
+
from enterprise_document_contract import MAX_INPUT
|
|
17
|
+
from enterprise_document_job import MAX_ENVELOPE, unique
|
|
18
|
+
from enterprise_sources import SourceUnavailable
|
|
19
|
+
|
|
20
|
+
ARM_VERSION = '2025-06-01'
|
|
21
|
+
BLOB_VERSION = '2025-05-05'
|
|
22
|
+
_UUID = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def object_key(identifier, side, node_id=None):
|
|
26
|
+
if not isinstance(identifier, str) or not re.fullmatch('[0-9a-f]{64}', identifier) or side not in {'input', 'result', 'cleanup'}:
|
|
27
|
+
raise ValueError('Use the owned per-file object identity')
|
|
28
|
+
if side == 'cleanup':
|
|
29
|
+
if not isinstance(node_id, str) or not re.fullmatch(r'[A-Za-z0-9_-]{1,128}', node_id):
|
|
30
|
+
raise ValueError('Use the exact native cleanup node identity')
|
|
31
|
+
return 'document-jobs/' + identifier + '/cleanup/' + node_id
|
|
32
|
+
if node_id is not None: raise ValueError('Only cleanup receipts have a node identity')
|
|
33
|
+
return 'document-jobs/' + identifier + '/' + side
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def etag(value):
|
|
37
|
+
if not isinstance(value, str) or not re.fullmatch(r'"0x[0-9A-Fa-f]{1,32}"', value):
|
|
38
|
+
raise SourceUnavailable('document_object_unavailable')
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AzureDocumentObjects:
|
|
43
|
+
def __init__(self, *, subscription, resource_group, region, account, container,
|
|
44
|
+
client_id, session=None, credential=None):
|
|
45
|
+
for value, pattern in [(subscription, _UUID), (client_id, _UUID),
|
|
46
|
+
(resource_group, r'[A-Za-z0-9_()-](?:[A-Za-z0-9_().-]{0,88}[A-Za-z0-9_()-])?'),
|
|
47
|
+
(region, r'[a-z][a-z0-9]{1,30}'), (account, r'[a-z0-9]{3,24}'),
|
|
48
|
+
(container, r'[a-z0-9]+(?:-[a-z0-9]+)*')]:
|
|
49
|
+
if not isinstance(value, str) or not re.fullmatch(pattern, value):
|
|
50
|
+
raise ValueError('Use the owned Azure temporary object destination')
|
|
51
|
+
if not 3 <= len(container) <= 63:
|
|
52
|
+
raise ValueError('Use the owned Azure temporary container')
|
|
53
|
+
self.region, self.account, self.container, self.client_id = region, account, container, client_id
|
|
54
|
+
self.account_id = ('/subscriptions/' + subscription + '/resourceGroups/' + resource_group +
|
|
55
|
+
'/providers/Microsoft.Storage/storageAccounts/' + account)
|
|
56
|
+
self.service_id = self.account_id + '/blobServices/default'
|
|
57
|
+
self.container_id = self.service_id + '/containers/' + container
|
|
58
|
+
self.base = 'https://' + account + '.blob.core.windows.net/' + container + '/'
|
|
59
|
+
self.session = session or requests.Session()
|
|
60
|
+
self.session.trust_env = False
|
|
61
|
+
self.credential = credential
|
|
62
|
+
|
|
63
|
+
def _credential(self):
|
|
64
|
+
if self.credential is None:
|
|
65
|
+
from azure.identity import ManagedIdentityCredential
|
|
66
|
+
self.credential = ManagedIdentityCredential(client_id=self.client_id, retry_total=0,
|
|
67
|
+
connection_timeout=1, read_timeout=2)
|
|
68
|
+
return self.credential
|
|
69
|
+
|
|
70
|
+
def _send(self, method, url, *, arm=False, headers=None, raw=None, limit=32768, missing=False):
|
|
71
|
+
# The private entry point still enforces destination ownership. Never
|
|
72
|
+
# follow redirects or use developer proxy/netrc credentials.
|
|
73
|
+
if method not in {'GET', 'PUT', 'DELETE'}:
|
|
74
|
+
raise ValueError('Use the fixed owned object operations')
|
|
75
|
+
if arm:
|
|
76
|
+
if method != 'GET' or url not in {'https://management.azure.com' + value for value in
|
|
77
|
+
[self.account_id, self.service_id, self.container_id]}:
|
|
78
|
+
raise ValueError('Use only the fixed owned ARM observations')
|
|
79
|
+
elif not url.startswith(self.base + 'document-jobs/') or not re.fullmatch(
|
|
80
|
+
re.escape(self.base) + r'document-jobs/[0-9a-f]{64}/(?:input|result|cleanup/[A-Za-z0-9_-]{1,128})', url):
|
|
81
|
+
raise ValueError('Use only the fixed owned blob object')
|
|
82
|
+
try:
|
|
83
|
+
scope = 'https://management.azure.com/.default' if arm else 'https://storage.azure.com/.default'
|
|
84
|
+
token = self._credential().get_token(scope).token
|
|
85
|
+
if not isinstance(token, str) or not 8 <= len(token) <= 20000 or any(ord(c) < 33 or ord(c) > 126 for c in token):
|
|
86
|
+
raise ValueError()
|
|
87
|
+
request_headers = {'Authorization': 'Bearer ' + token, 'Accept-Encoding': 'identity'}
|
|
88
|
+
if not arm:
|
|
89
|
+
request_headers.update({'x-ms-version': BLOB_VERSION,
|
|
90
|
+
'x-ms-date': format_datetime(datetime.now(timezone.utc), usegmt=True)})
|
|
91
|
+
request_headers.update(headers or {})
|
|
92
|
+
started = time.monotonic()
|
|
93
|
+
with self.session.request(method, url, headers=request_headers, data=raw,
|
|
94
|
+
params={'api-version': ARM_VERSION} if arm else None,
|
|
95
|
+
stream=True, allow_redirects=False, timeout=(2, 8)) as response:
|
|
96
|
+
if missing and response.status_code == 404:
|
|
97
|
+
return None
|
|
98
|
+
expected = {'GET': {200}, 'PUT': {201}, 'DELETE': {202}}
|
|
99
|
+
if response.status_code not in expected[method]:
|
|
100
|
+
raise ValueError()
|
|
101
|
+
received = {key.lower(): value for key, value in response.headers.items()}
|
|
102
|
+
if received.get('content-encoding') not in {None, 'identity'}:
|
|
103
|
+
raise ValueError()
|
|
104
|
+
data = bytearray()
|
|
105
|
+
for chunk in response.iter_content(16384):
|
|
106
|
+
data.extend(chunk)
|
|
107
|
+
if len(data) > limit or time.monotonic() - started > 15:
|
|
108
|
+
raise ValueError()
|
|
109
|
+
return bytes(data), received
|
|
110
|
+
except Exception:
|
|
111
|
+
raise SourceUnavailable('document_object_unavailable') from None
|
|
112
|
+
|
|
113
|
+
def _arm(self, identifier):
|
|
114
|
+
raw, _ = self._send('GET', 'https://management.azure.com' + identifier, arm=True, limit=262144)
|
|
115
|
+
try:
|
|
116
|
+
value = json.loads(raw, object_pairs_hook=unique)
|
|
117
|
+
if not isinstance(value, dict) or not isinstance(value.get('id'), str) or value['id'].lower() != identifier.lower():
|
|
118
|
+
raise ValueError()
|
|
119
|
+
return value
|
|
120
|
+
except Exception:
|
|
121
|
+
raise SourceUnavailable('document_object_policy_unavailable') from None
|
|
122
|
+
|
|
123
|
+
def preflight(self):
|
|
124
|
+
"""Before any data operation, observe placement and no retained history."""
|
|
125
|
+
try:
|
|
126
|
+
account = self._arm(self.account_id)
|
|
127
|
+
properties = account['properties']
|
|
128
|
+
if (account.get('location') != self.region or account.get('kind') != 'StorageV2' or
|
|
129
|
+
account.get('sku', {}).get('name') not in {'Standard_LRS', 'Standard_ZRS'} or
|
|
130
|
+
properties.get('primaryLocation') != self.region or properties.get('secondaryLocation') is not None or
|
|
131
|
+
properties.get('supportsHttpsTrafficOnly') is not True or properties.get('minimumTlsVersion') != 'TLS1_2' or
|
|
132
|
+
properties.get('allowSharedKeyAccess') is not False or properties.get('allowBlobPublicAccess') is not False or
|
|
133
|
+
properties.get('publicNetworkAccess') != 'Disabled' or
|
|
134
|
+
properties.get('encryption', {}).get('services', {}).get('blob', {}).get('enabled') is not True or
|
|
135
|
+
properties.get('immutableStorageWithVersioning', {}).get('enabled') is True):
|
|
136
|
+
raise ValueError()
|
|
137
|
+
service = self._arm(self.service_id)['properties']
|
|
138
|
+
if service.get('isVersioningEnabled') is not False:
|
|
139
|
+
raise ValueError()
|
|
140
|
+
for name in ['deleteRetentionPolicy', 'containerDeleteRetentionPolicy', 'restorePolicy', 'changeFeed']:
|
|
141
|
+
if service.get(name, {}).get('enabled') is not False:
|
|
142
|
+
raise ValueError()
|
|
143
|
+
container = self._arm(self.container_id)['properties']
|
|
144
|
+
if (container.get('publicAccess') != 'None' or container.get('hasImmutabilityPolicy') is not False or
|
|
145
|
+
container.get('hasLegalHold') is not False or
|
|
146
|
+
container.get('immutableStorageWithVersioning', {}).get('enabled') is True):
|
|
147
|
+
raise ValueError()
|
|
148
|
+
except Exception:
|
|
149
|
+
raise SourceUnavailable('document_object_policy_unavailable') from None
|
|
150
|
+
|
|
151
|
+
def url(self, identifier, side, node_id=None):
|
|
152
|
+
return self.base + quote(object_key(identifier, side, node_id), safe='/')
|
|
153
|
+
|
|
154
|
+
def put(self, identifier, side, raw):
|
|
155
|
+
if side == 'cleanup': raise ValueError('Only the root publisher creates cleanup acknowledgements')
|
|
156
|
+
url = self.url(identifier, side)
|
|
157
|
+
maximum = MAX_INPUT if side == 'input' else MAX_ENVELOPE
|
|
158
|
+
if not isinstance(raw, bytes) or not 0 < len(raw) <= maximum:
|
|
159
|
+
raise ValueError('Use bounded owned document bytes')
|
|
160
|
+
self.preflight()
|
|
161
|
+
_, headers = self._send('PUT', url, raw=raw, headers={'If-None-Match': '*',
|
|
162
|
+
'Content-Type': 'application/octet-stream', 'Content-Length': str(len(raw)),
|
|
163
|
+
'Content-MD5': base64.b64encode(hashlib.md5(raw, usedforsecurity=False).digest()).decode(),
|
|
164
|
+
'x-ms-blob-type': 'BlockBlob'}, limit=4096)
|
|
165
|
+
if headers.get('x-ms-request-server-encrypted') != 'true' or headers.get('x-ms-version-id') is not None:
|
|
166
|
+
raise SourceUnavailable('document_object_unavailable')
|
|
167
|
+
return etag(headers.get('etag'))
|
|
168
|
+
|
|
169
|
+
def load(self, identifier, side, condition=None, *, allow_missing=False, node_id=None):
|
|
170
|
+
if allow_missing and side != 'cleanup': raise ValueError('Only cleanup receipt lookup is optional')
|
|
171
|
+
url = self.url(identifier, side, node_id)
|
|
172
|
+
headers = {} if condition is None else {'If-Match': etag(condition)}
|
|
173
|
+
self.preflight()
|
|
174
|
+
maximum = MAX_INPUT if side == 'input' else 1024 if side == 'cleanup' else MAX_ENVELOPE
|
|
175
|
+
value = self._send('GET', url, headers=headers, limit=maximum, missing=allow_missing)
|
|
176
|
+
if value is None: return None
|
|
177
|
+
raw, observed = value
|
|
178
|
+
try:
|
|
179
|
+
if (observed.get('x-ms-server-encrypted') != 'true' or observed.get('x-ms-version-id') is not None or
|
|
180
|
+
observed.get('x-ms-blob-type') != 'BlockBlob' or
|
|
181
|
+
not isinstance(observed.get('content-length'), str) or
|
|
182
|
+
not re.fullmatch('[1-9][0-9]{0,9}', observed['content-length']) or
|
|
183
|
+
int(observed['content-length']) != len(raw) or (condition is not None and observed.get('etag') != condition)):
|
|
184
|
+
raise ValueError()
|
|
185
|
+
etag(observed.get('etag'))
|
|
186
|
+
return raw
|
|
187
|
+
except Exception:
|
|
188
|
+
raise SourceUnavailable('document_object_unavailable') from None
|
|
189
|
+
|
|
190
|
+
def delete(self, identifier, side):
|
|
191
|
+
if side == 'cleanup': raise ValueError('Cleanup metadata is retired by its dedicated lifecycle policy')
|
|
192
|
+
url = self.url(identifier, side)
|
|
193
|
+
self.preflight()
|
|
194
|
+
try:
|
|
195
|
+
value = self._send('DELETE', url, headers={'x-ms-delete-snapshots': 'include'}, missing=True, limit=4096)
|
|
196
|
+
if value is not None and value[1].get('x-ms-version-id') is not None:
|
|
197
|
+
raise ValueError()
|
|
198
|
+
except Exception:
|
|
199
|
+
raise SourceUnavailable('document_cleanup_unavailable') from None
|
|
200
|
+
|
|
201
|
+
def cleanup_acknowledged(self, identifier, owner, node_id):
|
|
202
|
+
if not isinstance(owner, str) or not re.fullmatch('[0-9a-f]{64}', owner):
|
|
203
|
+
raise ValueError('Use the exact owned cleanup destination fingerprint')
|
|
204
|
+
raw = self.load(identifier, 'cleanup', allow_missing=True, node_id=node_id)
|
|
205
|
+
if raw is None: return False
|
|
206
|
+
try:
|
|
207
|
+
value = json.loads(raw, object_pairs_hook=unique)
|
|
208
|
+
if (not isinstance(value, dict) or value.get('cleaned') is not True or value != {
|
|
209
|
+
'format': 'enterprise-azure-document-cleanup-v2', 'requestId': identifier,
|
|
210
|
+
'owner': owner, 'nodeId': node_id, 'cleaned': True}):
|
|
211
|
+
raise ValueError()
|
|
212
|
+
return True
|
|
213
|
+
except Exception:
|
|
214
|
+
raise SourceUnavailable('document_cleanup_receipt_unavailable') from None
|
|
215
|
+
|
|
216
|
+
def cleanup_acknowledged_all(self, identifier, owner, node_ids):
|
|
217
|
+
"""Caller must obtain this complete closed set from terminal native work.
|
|
218
|
+
|
|
219
|
+
A partial/preflight node list is not a settlement proof. Empty/duplicate
|
|
220
|
+
sets cannot accidentally certify cleanup; unscheduled jobs require a
|
|
221
|
+
separate verified no-writer lifecycle in the controller.
|
|
222
|
+
"""
|
|
223
|
+
if (not isinstance(node_ids, (set, frozenset, list, tuple)) or not 1 <= len(node_ids) <= 100 or
|
|
224
|
+
any(not isinstance(node, str) or not re.fullmatch(r'[A-Za-z0-9_-]{1,128}', node) for node in node_ids) or
|
|
225
|
+
len(set(node_ids)) != len(node_ids)):
|
|
226
|
+
raise ValueError('Use the complete unique native writer-node set')
|
|
227
|
+
return all(self.cleanup_acknowledged(identifier, owner, node) for node in sorted(node_ids))
|
|
228
|
+
|
|
229
|
+
def close(self):
|
|
230
|
+
try:
|
|
231
|
+
self.session.close()
|
|
232
|
+
finally:
|
|
233
|
+
if self.credential is not None:
|
|
234
|
+
self.credential.close()
|