@atmem/architect 0.0.0-stage → 0.2.0
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/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +31 -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-BTLHaa3J.css +1 -0
- package/dist/assets/index-CQ2C1gKF.js +21338 -0
- package/dist/assets/index.es-DTyqIdaO.js +18 -0
- package/dist/assets/purify.es-CAi2OJFm.js +3 -0
- package/dist/assets/visual-export-Ox0j5xj5.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/atmem-APACHE-2.0.txt +202 -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/atmem-APACHE-2.0.txt +202 -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 +193 -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 +221 -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 +172 -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 +483 -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 +185 -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_context.py +58 -0
- package/server/memory_routes.py +141 -0
- package/server/memory_service.py +306 -0
- package/server/memory_worker.py +129 -0
- package/server/openai_voice.py +80 -0
- package/server/orca_handoff.py +90 -0
- package/server/packaged_app.py +9 -0
- package/server/pricing.py +190 -0
- package/server/process_control_observation.py +31 -0
- package/server/prompts.py +136 -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/server/windows_snapshot.py +119 -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 +39 -0
- package/src/ArtifactChecks.jsx +49 -0
- package/src/BlockRequirements.jsx +13 -0
- package/src/BrainActivity.jsx +11 -0
- package/src/ChatMessage.jsx +29 -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 +387 -0
- package/src/EngineeringPanel.jsx +151 -0
- package/src/FileTree.jsx +7 -0
- package/src/FinalChecklist.jsx +15 -0
- package/src/HelpNote.jsx +3 -0
- package/src/ImplementationDelivery.jsx +91 -0
- package/src/JourneyViews.jsx +403 -0
- package/src/LocationChoices.jsx +12 -0
- package/src/MemoryPanel.jsx +58 -0
- package/src/OpenAIVoicePanel.jsx +66 -0
- package/src/OutputsView.jsx +71 -0
- package/src/ProjectChecks.jsx +44 -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 +18 -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 +190 -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/final-package.mjs +34 -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 +106 -0
- package/src/journey.mjs +97 -0
- package/src/levels.mjs +145 -0
- package/src/local-agent.mjs +184 -0
- package/src/main.jsx +2481 -0
- package/src/model-policy-preflight.mjs +34 -0
- package/src/openai-voice.mjs +102 -0
- package/src/output-bundle.mjs +13 -0
- package/src/ownership-inspection.mjs +28 -0
- package/src/pack.mjs +201 -0
- package/src/pricing.mjs +85 -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 +37 -0
- package/src/useEmulation.jsx +36 -0
- package/src/useEngineeringStatus.mjs +17 -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,351 @@
|
|
|
1
|
+
"""Stateful AWS emulation worker. Only server-generated files enter this process.
|
|
2
|
+
|
|
3
|
+
No Terraform apply, real IAM authorizer or managed-model inference is performed.
|
|
4
|
+
The OS/container boundary remains a hosting requirement, not a Python guarantee.
|
|
5
|
+
"""
|
|
6
|
+
import copy
|
|
7
|
+
import hashlib
|
|
8
|
+
import importlib.util
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import re
|
|
13
|
+
import socket
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
from contextlib import contextmanager
|
|
17
|
+
|
|
18
|
+
ACCOUNT='123456789012'
|
|
19
|
+
REGION='ap-southeast-2'
|
|
20
|
+
LIMITATIONS=[
|
|
21
|
+
'Terraform apply and real AWS account prerequisites are not emulated; run provider checks separately.',
|
|
22
|
+
'IAM enforcement, API Gateway routing/signatures, Cognito sign-in/MFA and CloudFront delivery remain unverified.',
|
|
23
|
+
'Generated Python handlers execute on this host, not the AWS Lambda ARM64 runtime; event polling is driven by the harness.',
|
|
24
|
+
'Bedrock responses and authenticated request envelopes are deterministic fixtures, not model-quality or authentication evidence.',
|
|
25
|
+
'Quotas, billing, latency, service outages and native SharePoint/Confluence APIs require separate qualification.',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
class EmulationFailure(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def require(condition,message):
|
|
33
|
+
if not condition:raise EmulationFailure(message)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@contextmanager
|
|
37
|
+
def deny_network():
|
|
38
|
+
"""Fail closed if an SDK call escapes Moto. No passthrough services or URLs."""
|
|
39
|
+
old_connect,old_connect_ex,old_dns=socket.socket.connect,socket.socket.connect_ex,socket.getaddrinfo
|
|
40
|
+
def denied(*args,**kwargs):raise EmulationFailure('Unexpected network access blocked')
|
|
41
|
+
socket.socket.connect=denied;socket.socket.connect_ex=denied;socket.getaddrinfo=denied
|
|
42
|
+
try:yield
|
|
43
|
+
finally:socket.socket.connect=old_connect;socket.socket.connect_ex=old_connect_ex;socket.getaddrinfo=old_dns
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def load_module(name,file):
|
|
47
|
+
spec=importlib.util.spec_from_file_location(name,file);module=importlib.util.module_from_spec(spec)
|
|
48
|
+
sys.modules[name]=module;spec.loader.exec_module(module);return module
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ScriptedBedrock:
|
|
52
|
+
"""An explicit model fixture with real SDK request-shape validation."""
|
|
53
|
+
def __init__(self,mode='rag'):
|
|
54
|
+
import botocore.session
|
|
55
|
+
self.model=botocore.session.get_session().get_service_model('bedrock-runtime')
|
|
56
|
+
self.mode=mode;self.calls=[];self.failure=None;self.after=None;self.result={}
|
|
57
|
+
def converse(self,**kwargs):
|
|
58
|
+
from botocore.validate import validate_parameters
|
|
59
|
+
from botocore.exceptions import ClientError
|
|
60
|
+
validate_parameters(kwargs,self.model.operation_model('Converse').input_shape)
|
|
61
|
+
self.calls.append(copy.deepcopy(kwargs))
|
|
62
|
+
if self.failure:raise ClientError({'Error':{'Code':self.failure,'Message':'Synthetic fault'}},'Converse')
|
|
63
|
+
if self.mode=='rag':
|
|
64
|
+
payload=json.loads(kwargs['messages'][0]['content'][0]['text']);evidence=payload['evidence']
|
|
65
|
+
text=json.dumps({'answer':evidence[0]['text'],'citations':[evidence[0]['id']]})
|
|
66
|
+
elif self.mode=='structured':text=json.dumps(self.result)
|
|
67
|
+
else:text='Synthetic fixture summary.'
|
|
68
|
+
if self.after:self.after()
|
|
69
|
+
return {'output':{'message':{'role':'assistant','content':[{'text':text}]}},'stopReason':'end_turn'}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Worker:
|
|
73
|
+
def __init__(self,folder):
|
|
74
|
+
self.root=Path(folder);self.tf=json.loads((self.root/'main.tf.json').read_text())
|
|
75
|
+
self.plan=json.loads((self.root/'resource-schedule.json').read_text())
|
|
76
|
+
self.resources=self.tf.get('resource',{});self.refs={};self.checks=[];self.modules={};self.capabilities=[]
|
|
77
|
+
self.variables={k:v['default'] for k,v in self.tf.get('variable',{}).items() if 'default' in v}
|
|
78
|
+
self.variables.update(aws_account_id=ACCOUNT,aws_region=REGION)
|
|
79
|
+
for k in self.tf.get('variable',{}):
|
|
80
|
+
if k.endswith('_model_id'):self.variables[k]='emulated-model'
|
|
81
|
+
self.report={'version':1,'kind':'local-aws-emulation','backend':'moto','status':'running','cloudOperations':False,'terraformApply':'not_run','modelMode':'scripted-fixture','checks':self.checks,'capabilities':self.capabilities,'limitations':LIMITATIONS,'cleanup':'not_run'}
|
|
82
|
+
def check(self,name,fn,evidence='stateful-emulation'):
|
|
83
|
+
try:
|
|
84
|
+
fn();self.checks.append({'name':name,'passed':True,'evidence':evidence})
|
|
85
|
+
except Exception as exc:
|
|
86
|
+
self.checks.append({'name':name,'passed':False,'evidence':evidence,'errorType':type(exc).__name__,'message':str(exc)[:400] if isinstance(exc,EmulationFailure) else 'Generated code or emulator API failed; inspect local diagnostic evidence.'})
|
|
87
|
+
raise
|
|
88
|
+
def resolve(self,value):
|
|
89
|
+
if not isinstance(value,str):return value
|
|
90
|
+
def replace(match):
|
|
91
|
+
expr=match.group(1)
|
|
92
|
+
if expr.startswith('tostring(') and expr.endswith(')'):expr=expr[9:-1]
|
|
93
|
+
if expr.startswith('var.') and expr[4:] in self.variables:return str(self.variables[expr[4:]])
|
|
94
|
+
if expr in self.refs:return str(self.refs[expr])
|
|
95
|
+
raise EmulationFailure('Unsupported generated reference in emulation adapter: '+expr)
|
|
96
|
+
return re.sub(r'\$\{([^{}]+)\}',replace,value)
|
|
97
|
+
def name(self,address):return 'atmem-emu-'+hashlib.sha256(address.encode()).hexdigest()[:16]
|
|
98
|
+
def setup(self):
|
|
99
|
+
import boto3
|
|
100
|
+
self.s3=boto3.client('s3',region_name=REGION);self.sqs=boto3.client('sqs',region_name=REGION);self.db=boto3.client('dynamodb',region_name=REGION)
|
|
101
|
+
for address in self.resources.get('aws_s3_bucket',{}):
|
|
102
|
+
name=self.name(address);self.s3.create_bucket(Bucket=name,CreateBucketConfiguration={'LocationConstraint':REGION})
|
|
103
|
+
self.refs.update({f'aws_s3_bucket.{address}.id':name,f'aws_s3_bucket.{address}.arn':'arn:aws:s3:::'+name})
|
|
104
|
+
if address in self.resources.get('aws_s3_bucket_versioning',{}):self.s3.put_bucket_versioning(Bucket=name,VersioningConfiguration={'Status':self.resources['aws_s3_bucket_versioning'][address]['versioning_configuration']['status']})
|
|
105
|
+
if address in self.resources.get('aws_s3_bucket_server_side_encryption_configuration',{}):self.s3.put_bucket_encryption(Bucket=name,ServerSideEncryptionConfiguration={'Rules':[{'ApplyServerSideEncryptionByDefault':{'SSEAlgorithm':self.resources['aws_s3_bucket_server_side_encryption_configuration'][address]['rule']['apply_server_side_encryption_by_default']['sse_algorithm']}}]})
|
|
106
|
+
if address in self.resources.get('aws_s3_bucket_public_access_block',{}):self.s3.put_public_access_block(Bucket=name,PublicAccessBlockConfiguration={camel:self.resources['aws_s3_bucket_public_access_block'][address][snake] for camel,snake in [('BlockPublicAcls','block_public_acls'),('IgnorePublicAcls','ignore_public_acls'),('BlockPublicPolicy','block_public_policy'),('RestrictPublicBuckets','restrict_public_buckets')]})
|
|
107
|
+
for address,body in self.resources.get('aws_dynamodb_table',{}).items():
|
|
108
|
+
name=self.name(address)
|
|
109
|
+
self.db.create_table(TableName=name,KeySchema=[{'AttributeName':body['hash_key'],'KeyType':'HASH'}],AttributeDefinitions=[{'AttributeName':a['name'],'AttributeType':a['type']} for a in body['attribute']],BillingMode='PAY_PER_REQUEST')
|
|
110
|
+
self.refs[f'aws_dynamodb_table.{address}.name']=name
|
|
111
|
+
for address in self.resources.get('aws_sqs_queue',{}):
|
|
112
|
+
url=self.sqs.create_queue(QueueName=self.name(address))['QueueUrl'];arn=self.sqs.get_queue_attributes(QueueUrl=url,AttributeNames=['QueueArn'])['Attributes']['QueueArn']
|
|
113
|
+
self.refs.update({f'aws_sqs_queue.{address}.url':url,f'aws_sqs_queue.{address}.arn':arn})
|
|
114
|
+
for type_,field in [('aws_apigatewayv2_api','id'),('aws_cognito_user_pool','id'),('aws_cognito_user_pool_client','id')]:
|
|
115
|
+
for address in self.resources.get(type_,{}):self.refs[f'{type_}.{address}.{field}']=self.name(address)
|
|
116
|
+
self.capabilities.extend([{'service':name,'mode':'stateful-service-API'} for name,present in [('S3',self.resources.get('aws_s3_bucket')),('SQS',self.resources.get('aws_sqs_queue')),('DynamoDB',self.resources.get('aws_dynamodb_table'))] if present])
|
|
117
|
+
def module(self,address):
|
|
118
|
+
if address not in self.modules:
|
|
119
|
+
dependency=self.root/'handlers'/address
|
|
120
|
+
if dependency.exists():sys.path.insert(0,str(dependency))
|
|
121
|
+
self.modules[address]=load_module('emulated_'+address,self.root/'handlers'/f'{address}.py')
|
|
122
|
+
fn=self.resources['aws_lambda_function'][address]
|
|
123
|
+
for k in ['SOURCE_BUCKET','RESULTS_TABLE','QUEUE_ARN','EXPECTED_ACCOUNT_ID','EXPECTED_API_ID','CATALOG_TABLE','COGNITO_ISSUER','COGNITO_CLIENT_ID','MAX_OUTPUT_TOKENS']:os.environ.pop(k,None)
|
|
124
|
+
os.environ.update({k:str(self.resolve(v)) for k,v in fn['environment']['variables'].items()})
|
|
125
|
+
require(0<int(os.environ['MAX_INPUT_BYTES'])<=1_000_000,'Input size exceeds bounded emulator support')
|
|
126
|
+
return self.modules[address]
|
|
127
|
+
def storage(self,address):
|
|
128
|
+
name=self.refs[f'aws_s3_bucket.{address}.id'];key='synthetic.txt'
|
|
129
|
+
first=self.s3.put_object(Bucket=name,Key=key,Body=b'first')['VersionId'];second=self.s3.put_object(Bucket=name,Key=key,Body=b'second')['VersionId']
|
|
130
|
+
require(first!=second,'Object versions must differ')
|
|
131
|
+
require(self.s3.get_object(Bucket=name,Key=key,VersionId=first)['Body'].read()==b'first','Prior version recovery failed')
|
|
132
|
+
require(self.s3.get_object(Bucket=name,Key=key)['Body'].read()==b'second','Latest version read failed')
|
|
133
|
+
for version in [first,second]:self.s3.delete_object(Bucket=name,Key=key,VersionId=version)
|
|
134
|
+
def statistics(self,address):
|
|
135
|
+
module=self.module(address)
|
|
136
|
+
result=module.handler({'text':'Hello Sydney\nAWS'},None)
|
|
137
|
+
require(result.get('wordCount')==3 and result.get('characterCount')==16,'Generated counts do not match exact input')
|
|
138
|
+
result=module.handler({'text':'Hi 👋'},None);require(result.get('characterCount')==4,'Unicode code-point count failed')
|
|
139
|
+
for event in [{'text':123},{'text':'x'*(int(os.environ['MAX_INPUT_BYTES'])+1)}]:
|
|
140
|
+
try:module.handler(event,None)
|
|
141
|
+
except ValueError:continue
|
|
142
|
+
raise EmulationFailure('Invalid input was accepted')
|
|
143
|
+
def asynchronous(self,pipeline):
|
|
144
|
+
address=pipeline['worker']['address'];module=self.module(address);module._clients=None
|
|
145
|
+
bucket=os.environ['SOURCE_BUCKET'];table=os.environ['RESULTS_TABLE'];queue=self.refs[f"aws_sqs_queue.{pipeline['queue']['address']}.url"];dlq=self.refs[f"aws_sqs_queue.{pipeline['dlq']['address']}.url"]
|
|
146
|
+
count=int(self.variables[address+'_max_receive_count']);require(1<=count<=20,'Retry count exceeds bounded emulator support')
|
|
147
|
+
redrive=self.resources['aws_sqs_queue'][pipeline['queue']['address']].get('redrive_policy','')
|
|
148
|
+
require(f"deadLetterTargetArn=aws_sqs_queue.{pipeline['dlq']['address']}.arn" in redrive and f'maxReceiveCount=var.{address}_max_receive_count' in redrive,'Generated redrive policy does not match the typed pipeline')
|
|
149
|
+
self.sqs.set_queue_attributes(QueueUrl=queue,Attributes={'RedrivePolicy':json.dumps({'deadLetterTargetArn':self.refs[f"aws_sqs_queue.{pipeline['dlq']['address']}.arn"],'maxReceiveCount':count})})
|
|
150
|
+
notification=self.resources.get('aws_s3_bucket_notification',{}).get(pipeline['bucket']['address'])
|
|
151
|
+
require(notification is not None,'Generated S3 notification configuration is missing')
|
|
152
|
+
mappings=self.resources.get('aws_lambda_event_source_mapping',{})
|
|
153
|
+
require(any(self.resolve(m['event_source_arn'])==os.environ['QUEUE_ARN'] and m.get('function_response_types')==['ReportBatchItemFailures'] for m in mappings.values()),'Generated event source or partial-batch failure contract is missing')
|
|
154
|
+
self.s3.put_bucket_notification_configuration(Bucket=bucket,NotificationConfiguration={'QueueConfigurations':[{'Id':q['id'],'QueueArn':self.resolve(q['queue_arn']),'Events':q['events']} for q in notification['queue']]})
|
|
155
|
+
version=self.s3.put_object(Bucket=bucket,Key='sample.txt',Body=b'Hello Sydney\nAWS')['VersionId']
|
|
156
|
+
bodies=[]
|
|
157
|
+
def consume():
|
|
158
|
+
messages=self.sqs.receive_message(QueueUrl=queue,MaxNumberOfMessages=10,WaitTimeSeconds=0).get('Messages',[])
|
|
159
|
+
for msg in messages:
|
|
160
|
+
try:body=json.loads(msg['Body'])
|
|
161
|
+
except json.JSONDecodeError:body={}
|
|
162
|
+
record={'messageId':msg['MessageId'],'eventSource':'aws:sqs','eventSourceARN':os.environ['QUEUE_ARN'],'body':msg['Body']}
|
|
163
|
+
result=module.handler({'Records':[record]},None)
|
|
164
|
+
if result['batchItemFailures']:self.sqs.change_message_visibility(QueueUrl=queue,ReceiptHandle=msg['ReceiptHandle'],VisibilityTimeout=0)
|
|
165
|
+
else:
|
|
166
|
+
self.sqs.delete_message(QueueUrl=queue,ReceiptHandle=msg['ReceiptHandle'])
|
|
167
|
+
if body.get('Records'):bodies.append(msg['Body'])
|
|
168
|
+
return messages
|
|
169
|
+
consume();items=self.db.scan(TableName=table)['Items'];require(len(items)==1 and items[0]['wordCount']['N']=='3','S3 notification → SQS → generated worker → DynamoDB did not produce the expected result')
|
|
170
|
+
require(items[0]['sourceVersion']['S']==version,'Exact S3 version was not processed')
|
|
171
|
+
require(bool(bodies),'No actual emulated S3 notification was delivered')
|
|
172
|
+
self.sqs.send_message(QueueUrl=queue,MessageBody=bodies[0]);consume();require(len(bodies)==2 and self.db.scan(TableName=table)['Count']==1,'Duplicate delivery was not successfully acknowledged without duplicate results')
|
|
173
|
+
self.sqs.send_message(QueueUrl=queue,MessageBody='invalid json')
|
|
174
|
+
for _ in range(count+2):consume()
|
|
175
|
+
poison=self.sqs.receive_message(QueueUrl=dlq,MaxNumberOfMessages=10).get('Messages',[]);require(len(poison)==1,'Poison message did not reach emulated DLQ')
|
|
176
|
+
self.sqs.send_message(QueueUrl=queue,MessageBody=bodies[0]);consume();require(len(bodies)==3 and self.db.scan(TableName=table)['Count']==1,'Valid replay after poison recovery failed')
|
|
177
|
+
self.capabilities.append({'service':'S3 → SQS → worker','mode':'Moto notification and redrive; harness polls/invokes/acks; visibility advanced explicitly'})
|
|
178
|
+
def summary(self,address):
|
|
179
|
+
module=self.module(address);model=ScriptedBedrock('summary');module._client=model
|
|
180
|
+
require(module.handler({'text':'Synthetic Sydney office.'},None).get('summary')=='Synthetic fixture summary.','Summary response contract failed')
|
|
181
|
+
require(model.calls[-1]['inferenceConfig']['maxTokens']==int(os.environ['MAX_OUTPUT_TOKENS']),'Generated output token bound not used')
|
|
182
|
+
for code,retryable in [('AccessDeniedException',False),('ThrottlingException',True)]:
|
|
183
|
+
model.failure=code;require(module.handler({'text':'Synthetic text.'},None)=={'error':'model_unavailable','retryable':retryable},'Model fault classification failed')
|
|
184
|
+
model.failure=None
|
|
185
|
+
before=len(model.calls)
|
|
186
|
+
try:module.handler({'text':'x'*(int(os.environ['MAX_INPUT_BYTES'])+1)},None)
|
|
187
|
+
except ValueError:pass
|
|
188
|
+
else:raise EmulationFailure('Oversized model input accepted')
|
|
189
|
+
require(len(model.calls)==before,'Oversized input reached model boundary')
|
|
190
|
+
def structured(self,address):
|
|
191
|
+
from botocore.validate import validate_parameters
|
|
192
|
+
import botocore.session
|
|
193
|
+
class PolicyFixture:
|
|
194
|
+
mode='none';logging={};failure=False
|
|
195
|
+
def get_account_data_retention(self):
|
|
196
|
+
if self.failure:raise RuntimeError('Synthetic read denial')
|
|
197
|
+
return {'mode':self.mode}
|
|
198
|
+
def get_model_invocation_logging_configuration(self):return {'loggingConfig':self.logging}
|
|
199
|
+
class IdentityFixture:
|
|
200
|
+
enabled=True
|
|
201
|
+
def admin_get_user(self,**kwargs):
|
|
202
|
+
shape=botocore.session.get_session().get_service_model('cognito-idp').operation_model('AdminGetUser').input_shape
|
|
203
|
+
validate_parameters(kwargs,shape)
|
|
204
|
+
return {'Enabled':self.enabled,'UserStatus':'CONFIRMED','UserAttributes':[{'Name':'sub','Value':'fixture-subject'}]}
|
|
205
|
+
module=self.module(address);model=ScriptedBedrock('structured');policy=PolicyFixture();identity=IdentityFixture()
|
|
206
|
+
module._client=model;module._retention=policy;module._identity=identity
|
|
207
|
+
model.result={f['name']:False if f['type']=='boolean' else 0 if f['type']=='number' else f.get('values',['x'])[0] for f in module.OUTPUT_FIELDS}
|
|
208
|
+
valid=copy.deepcopy(model.result)
|
|
209
|
+
http='COGNITO_USER_POOL_ID' in self.resources['aws_lambda_function'][address]['environment']['variables']
|
|
210
|
+
def event(text):
|
|
211
|
+
if not http:return {'text':text}
|
|
212
|
+
return {'version':'2.0','requestContext':{'apiId':os.environ['EXPECTED_API_ID'],'routeKey':'POST /api/ask','authorizer':{'jwt':{'claims':{'iss':os.environ['COGNITO_ISSUER'],'client_id':os.environ['COGNITO_CLIENT_ID'],'token_use':'access','scope':'atmem/ask','sub':'fixture-subject','username':'fixture-user'}}}},'isBase64Encoded':False,'body':json.dumps({'text':text})}
|
|
213
|
+
def invoke(text):
|
|
214
|
+
result=module.handler(event(text),None)
|
|
215
|
+
return (result['statusCode'],json.loads(result['body'])) if http else (200,result)
|
|
216
|
+
status,result=invoke('Synthetic bounded request')
|
|
217
|
+
require(status==200 and result=={'result':valid,'requiresHumanReview':True},'Structured result failed its declared schema or review contract')
|
|
218
|
+
model.result={'unexpected':'not a declared output'}
|
|
219
|
+
require(invoke('Synthetic input')[1].get('error')=='invalid_model_output','Invalid structured model output was accepted')
|
|
220
|
+
model.result=valid
|
|
221
|
+
before=len(model.calls)
|
|
222
|
+
for mode in ['default','inherit','aws_review',None]:
|
|
223
|
+
policy.mode=mode;require(invoke('Synthetic input')[1].get('error')=='data_retention_unverified','Unverified model retention reached inference')
|
|
224
|
+
policy.mode='none';policy.logging={'textDataDeliveryEnabled':True}
|
|
225
|
+
require(invoke('Synthetic input')[1].get('error')=='data_retention_unverified','Enabled prompt logging reached inference')
|
|
226
|
+
policy.logging={};policy.failure=True
|
|
227
|
+
require(invoke('Synthetic input')[1].get('error')=='data_retention_unverified','Unavailable retention policy reached inference')
|
|
228
|
+
require(len(model.calls)==before,'A failed data policy check sent input to the model')
|
|
229
|
+
policy.failure=False
|
|
230
|
+
if http:
|
|
231
|
+
require(module.handler({},None)['statusCode']==403,'Unverified direct invocation accepted')
|
|
232
|
+
identity.enabled=False;require(invoke('Synthetic input')[0]==403,'Disabled user accepted')
|
|
233
|
+
require(len(model.calls)==before,'Disabled user reached inference')
|
|
234
|
+
identity.enabled=True;model.after=lambda:setattr(identity,'enabled',False)
|
|
235
|
+
require(invoke('Synthetic input')[0]==403,'Revoked user received an in-flight result')
|
|
236
|
+
model.after=None;identity.enabled=True
|
|
237
|
+
before=len(model.calls)
|
|
238
|
+
try:status,result=invoke('x'*(int(os.environ['MAX_INPUT_BYTES'])+1))
|
|
239
|
+
except ValueError:status=400
|
|
240
|
+
require(status==400,'Oversized structured input accepted')
|
|
241
|
+
require(len(model.calls)==before,'Oversized input reached inference')
|
|
242
|
+
self.capabilities.append({'service':'Structured operation','mode':'emitted Python execution, SDK-validated Converse request, scripted model/identity/policy state; real account settings, JWT verification and model accuracy unverified'})
|
|
243
|
+
|
|
244
|
+
def rag(self,address):
|
|
245
|
+
from boto3.dynamodb.types import TypeSerializer
|
|
246
|
+
from botocore.exceptions import ClientError
|
|
247
|
+
module=self.module(address);model=ScriptedBedrock();module._clients=(self.db,model);table=os.environ['CATALOG_TABLE']
|
|
248
|
+
admin=load_module('catalog_admin',self.root/'manage-catalog.py');serializer=TypeSerializer()
|
|
249
|
+
def put(item):self.db.put_item(TableName=table,Item={k:serializer.serialize(v) for k,v in item.items()})
|
|
250
|
+
principal='AROA1234567890123456'
|
|
251
|
+
identity={'recordId':'IDENTITY#'+principal,'enabled':True,'subject':'alice','employeeRegion':'AU'};put(identity)
|
|
252
|
+
event={'version':'2.0','requestContext':{'accountId':ACCOUNT,'apiId':os.environ['EXPECTED_API_ID'],'routeKey':'POST /ask','authorizer':{'iam':{'accountId':ACCOUNT,'callerId':principal+':test','userArn':f'arn:aws:sts::{ACCOUNT}:assumed-role/TestRole/test'}}},'body':json.dumps({'question':'retention policy'})}
|
|
253
|
+
document={'id':'retention','title':'Retention policy','text':'Records retained for 30 days.','allowedSubjects':['alice'],'allowedRegions':['AU']}
|
|
254
|
+
first=admin.put_document(self.db,table,document)
|
|
255
|
+
admin.put_document(self.db,table,{**document,'id':'restricted','title':'RESTRICTED-MARKER','text':'Restricted retention policy SECRET-MARKER.','allowedSubjects':['bob']})
|
|
256
|
+
admin.put_document(self.db,table,{**document,'id':'wrong-region','title':'WRONG-REGION-MARKER','allowedRegions':['US']})
|
|
257
|
+
result=module.handler(event,None);body=json.loads(result['body']);require(result['statusCode']==200 and body['citations'][0]['id']=='retention','Cited retrieval failed')
|
|
258
|
+
require('SECRET-MARKER' not in json.dumps(model.calls) and 'WRONG-REGION-MARKER' not in json.dumps(model.calls),'Unauthorized evidence reached the model boundary')
|
|
259
|
+
updated=admin.put_document(self.db,table,{**document,'text':'Records retained for 45 days.'},first['revision'])
|
|
260
|
+
require('45 days' in module.handler(event,None)['body'],'Updated document not reflected')
|
|
261
|
+
try:admin.put_document(self.db,table,document,first['revision'])
|
|
262
|
+
except ClientError as error:require(error.response['Error']['Code']=='ConditionalCheckFailedException','Unexpected service error during stale-revision test')
|
|
263
|
+
else:raise EmulationFailure('Stale document revision overwrote current data')
|
|
264
|
+
# Fault injection changes actual emulated state during the generated model call.
|
|
265
|
+
model.after=lambda:put({**identity,'enabled':False})
|
|
266
|
+
require(module.handler(event,None)['statusCode']==403,'Mid-generation identity revocation did not suppress delivery')
|
|
267
|
+
model.after=None;put(identity)
|
|
268
|
+
forged={**event,'body':json.dumps({'question':'retention','subject':'bob'})};require(module.handler(forged,None)['statusCode']==400,'Body identity spoof accepted')
|
|
269
|
+
admin.delete_document(self.db,table,'retention',updated['revision']);before=len(model.calls)
|
|
270
|
+
body=json.loads(module.handler(event,None)['body']);require(body.get('abstained') is True and len(model.calls)==before,'Deleted/restricted evidence caused model inference')
|
|
271
|
+
put({**identity,'enabled':False});require(module.handler(event,None)['statusCode']==403,'Disabled identity accepted')
|
|
272
|
+
put(identity)
|
|
273
|
+
if 'COGNITO_ISSUER' in os.environ:
|
|
274
|
+
subject='11111111-1111-1111-1111-111111111111';put({**identity,'recordId':'IDENTITY#COGNITO#'+subject})
|
|
275
|
+
browser=copy.deepcopy(event);browser['requestContext']['routeKey']='POST /api/ask';browser['requestContext']['authorizer']={'jwt':{'claims':{'sub':subject,'iss':os.environ['COGNITO_ISSUER'],'client_id':os.environ['COGNITO_CLIENT_ID'],'token_use':'access','scope':'atmem/ask'}}}
|
|
276
|
+
require(module.handler(browser,None)['statusCode']==200,'Browser claims-to-directory adapter failed')
|
|
277
|
+
browser['requestContext']['authorizer']['jwt']['claims']['client_id']='wrong';require(module.handler(browser,None)['statusCode']==403,'Wrong browser client accepted')
|
|
278
|
+
self.capabilities.append({'service':'Browser authorization adapter','mode':'synthetic claims only; no JWT signature, OAuth or MFA emulation'})
|
|
279
|
+
self.rag_context=(module,event,model)
|
|
280
|
+
return table,admin
|
|
281
|
+
def connectors(self,table):
|
|
282
|
+
sys.path.insert(0,str(self.root/'connectors'))
|
|
283
|
+
sync=load_module('connector_sync',self.root/'connectors/connector_sync.py');cli=load_module('connector_cli',self.root/'connectors/connector_cli.py')
|
|
284
|
+
store=cli.Store(self.db,table);checkpoint=cli.Checkpoint(self.root/'connector-checkpoint.json')
|
|
285
|
+
doc={'sourceId':'one','sourceVersion':'1','title':'Source policy','text':'Synthetic source policy.','allowedSubjects':['alice'],'allowedRegions':['AU']}
|
|
286
|
+
snapshot={'snapshotId':'1','complete':True,'permissionsComplete':True,'documents':[doc]}
|
|
287
|
+
sync.synchronize(snapshot,'fixture',store,checkpoint);saved=checkpoint.load('fixture');key=next(iter(saved['documents']));require(store.get(key)['text']==doc['text'],'Connector did not write into emulated DynamoDB')
|
|
288
|
+
module,event,model=self.rag_context
|
|
289
|
+
event={**event,'body':json.dumps({'question':'source policy'})}
|
|
290
|
+
require(key in module.handler(event,None)['body'],'Synchronized source did not reach generated retrieval')
|
|
291
|
+
self.db.update_item(TableName=table,Key={'recordId':{'S':'DOCUMENT#'+key}},UpdateExpression='SET authorizationExpiresAt = :expired',ExpressionAttributeValues={':expired':{'N':str(int(time.time())-1)}})
|
|
292
|
+
before=len(model.calls);require(json.loads(module.handler(event,None)['body']).get('abstained') is True and len(model.calls)==before,'Expired source permission lease reached model boundary')
|
|
293
|
+
try:sync.synchronize({**snapshot,'permissionsComplete':False},'fixture',store,checkpoint)
|
|
294
|
+
except ValueError:pass
|
|
295
|
+
else:raise EmulationFailure('Incomplete source permissions accepted')
|
|
296
|
+
# Fail after a write but before final checkpoint, then resume from the journal.
|
|
297
|
+
original=store.put
|
|
298
|
+
def interrupted(item,expected):original(item,expected);raise OSError('Synthetic interruption')
|
|
299
|
+
store.put=interrupted
|
|
300
|
+
try:sync.synchronize({**snapshot,'snapshotId':'2','documents':[{**doc,'sourceVersion':'2','text':'Updated policy.'}]},'fixture',store,checkpoint)
|
|
301
|
+
except OSError:pass
|
|
302
|
+
else:raise EmulationFailure('Connector fault injection did not interrupt')
|
|
303
|
+
store.put=original
|
|
304
|
+
sync.synchronize({**snapshot,'snapshotId':'2','documents':[{**doc,'sourceVersion':'2','text':'Updated policy.','allowedSubjects':['bob']}]},'fixture',store,checkpoint)
|
|
305
|
+
require(store.get(key)['allowedSubjects']==['bob'],'Connector ACL recovery failed')
|
|
306
|
+
require(not checkpoint.load('fixture').get('pending'),'Recovered connector journal retained pending writes')
|
|
307
|
+
sync.synchronize({**snapshot,'snapshotId':'3','documents':[]},'fixture',store,checkpoint);require(store.get(key) is None,'Source deletion was not propagated')
|
|
308
|
+
self.capabilities.append({'service':'Source connectors','mode':'normalized snapshot fixture + emitted sync/journal code + stateful DynamoDB; vendor HTTP APIs unverified'})
|
|
309
|
+
def run(self):
|
|
310
|
+
import moto
|
|
311
|
+
from moto import mock_aws
|
|
312
|
+
sys.path.insert(0,str(Path(__file__).resolve().parent))
|
|
313
|
+
from emulation_adapters import s3_version_notifications, PATCH
|
|
314
|
+
self.report['emulatorPatches']=[PATCH]
|
|
315
|
+
self.report['backendVersion']=moto.__version__
|
|
316
|
+
stateful={'aws_s3_bucket','aws_s3_bucket_versioning','aws_s3_bucket_server_side_encryption_configuration','aws_s3_bucket_public_access_block','aws_s3_bucket_notification','aws_sqs_queue','aws_dynamodb_table'}
|
|
317
|
+
self.report['resourceCoverage']=[{'type':name,'count':len(items),'mode':'stateful-api-adapter' if name in stateful else 'host-executed-code' if name=='aws_lambda_function' else 'not-emulated'} for name,items in self.resources.items() if name!='terraform_data']
|
|
318
|
+
require(not self.tf.get('locals',{}).get('unfinished_work'),'Incomplete architecture blocks emulation')
|
|
319
|
+
require(len(self.plan['rows'])<=40,'Architecture exceeds local emulation limit')
|
|
320
|
+
os.environ.update(AWS_ACCESS_KEY_ID='testing',AWS_SECRET_ACCESS_KEY='testing',AWS_DEFAULT_REGION=REGION,AWS_EC2_METADATA_DISABLED='true')
|
|
321
|
+
with mock_aws(config={'core':{'mock_credentials':True,'service_whitelist':['s3','sqs','dynamodb'],'passthrough':{'urls':[],'services':[]}},'lambda':{'use_docker':False}}):
|
|
322
|
+
with deny_network(), s3_version_notifications():
|
|
323
|
+
self.check('Stateful emulator resource setup',self.setup)
|
|
324
|
+
for row in self.plan['rows']:
|
|
325
|
+
if row.get('foundation'):self.check('Versioned storage and prior-version recovery: '+row['address'],lambda r=row:self.storage(r['address']))
|
|
326
|
+
if row.get('compute'):self.check('Generated statistics handler and input bounds: '+row['address'],lambda r=row:self.statistics(r['address']))
|
|
327
|
+
for pipeline in self.plan['asyncPlan']['pipelines']:self.check('Generated async pipeline, duplicate safety, DLQ and replay',lambda p=pipeline:self.asynchronous(p))
|
|
328
|
+
for address,fn in self.resources.get('aws_lambda_function',{}).items():
|
|
329
|
+
env=fn['environment']['variables']
|
|
330
|
+
if 'CATALOG_TABLE' in env:
|
|
331
|
+
self.check('Generated retrieval, conditional writes, ACLs, updates, deletion and revocation',lambda a=address:self.rag(a))
|
|
332
|
+
if self.plan['sourceContractPlan']['sources']:self.check('Generated source synchronization, interruption recovery, ACLs and deletes',lambda:self.connectors(os.environ['CATALOG_TABLE']))
|
|
333
|
+
elif any(r.get('address')==address and r.get('structured') for r in self.plan['rows']):self.check('Generated structured operation, authorization, revocation and fail-closed data policy',lambda a=address:self.structured(a),evidence='host-runtime-with-scripted-policy-identity-model-fixtures')
|
|
334
|
+
elif 'MODEL_ARN' in env:self.check('Generated summary handler, token bounds and simulated model faults',lambda a=address:self.summary(a))
|
|
335
|
+
require(set(self.resources.get('aws_lambda_function',{}))==set(self.modules),'Some generated functions lack an emulator scenario')
|
|
336
|
+
require(len(self.checks)>1,'No supported executable scenario in this design')
|
|
337
|
+
self.report.update(status='passed',cleanup='passed',message='Supported local emulation checks passed. Review the unverified boundaries before deployment.')
|
|
338
|
+
return self.report
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def main():
|
|
342
|
+
worker=Worker(sys.argv[1])
|
|
343
|
+
try:result=worker.run()
|
|
344
|
+
except Exception as exc:
|
|
345
|
+
worker.report.update(status='failed',cleanup='process_exit',message='Emulation failed; no live cloud operations were attempted.',failureType=type(exc).__name__)
|
|
346
|
+
if not worker.checks:worker.checks.append({'name':'Emulation eligibility','passed':False,'message':str(exc)[:400] if isinstance(exc,EmulationFailure) else 'Worker initialization failed.'})
|
|
347
|
+
result=worker.report
|
|
348
|
+
print(json.dumps(result))
|
|
349
|
+
raise SystemExit(0 if result['status']=='passed' else 1)
|
|
350
|
+
|
|
351
|
+
if __name__=='__main__':main()
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Immutable local artifact adapter; snapshots never execute generated code.
|
|
2
|
+
|
|
3
|
+
Capture uses directory-relative, no-follow opens and rejects symlinks/special files.
|
|
4
|
+
Runtime validation is separate: neither capture nor agent completion proves quality.
|
|
5
|
+
"""
|
|
6
|
+
import hashlib
|
|
7
|
+
import io
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path,PurePosixPath
|
|
11
|
+
import re
|
|
12
|
+
import stat
|
|
13
|
+
import uuid
|
|
14
|
+
import zipfile
|
|
15
|
+
|
|
16
|
+
from .engineering_sessions import SessionError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EngineeringArtifacts:
|
|
20
|
+
TABLE = 'engineering_artifacts'
|
|
21
|
+
KIND = 'delivery'
|
|
22
|
+
EXCLUDED = {'.worker-tmp', '.tools', '.git', '.terraform', '__pycache__'}
|
|
23
|
+
|
|
24
|
+
def __init__(self, store, *, max_bytes=64_000_000, max_files=2000):
|
|
25
|
+
self.store, self.db = store, store.db
|
|
26
|
+
self.max_bytes, self.max_files = max_bytes, max_files
|
|
27
|
+
self.db.execute(f'''CREATE TABLE IF NOT EXISTS {self.TABLE} (
|
|
28
|
+
id TEXT PRIMARY KEY, session TEXT NOT NULL, run TEXT NOT NULL,
|
|
29
|
+
manifest TEXT NOT NULL, archive BLOB NOT NULL)''')
|
|
30
|
+
self.db.commit()
|
|
31
|
+
|
|
32
|
+
def _require_state(self,row):
|
|
33
|
+
if row['state']!='idle' or row['last_status'] is None:
|
|
34
|
+
raise SessionError('Artifact capture requires a settled engineering run')
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def _state_identity(row):
|
|
38
|
+
return tuple(row[key] for key in ['state','run','turn','last_status'])
|
|
39
|
+
|
|
40
|
+
def _persist(self,manifest,data):
|
|
41
|
+
if self.KIND=='delivery':
|
|
42
|
+
# Reopening/capturing unchanged settled bytes must keep the artifact
|
|
43
|
+
# identity that owns its automatic check reports. Never carry evidence
|
|
44
|
+
# across a run, changed pins, exclusions, metadata or bytes.
|
|
45
|
+
identity={key:value for key,value in manifest.items() if key!='id'}
|
|
46
|
+
for raw in self.db.execute(f'SELECT id,manifest FROM {self.TABLE} WHERE session=? AND run=? ORDER BY rowid',(manifest['sessionId'],manifest['runId'])):
|
|
47
|
+
previous=json.loads(raw['manifest'])
|
|
48
|
+
if {key:value for key,value in previous.items() if key!='id'}==identity:
|
|
49
|
+
stored=self.db.execute(f'SELECT archive FROM {self.TABLE} WHERE id=?',(raw['id'],)).fetchone()
|
|
50
|
+
if stored is None or bytes(stored['archive'])!=data:
|
|
51
|
+
raise SessionError('Stored package bytes do not match their capture receipt')
|
|
52
|
+
return previous
|
|
53
|
+
with self.db:
|
|
54
|
+
self.db.execute(f'INSERT INTO {self.TABLE} VALUES (?,?,?,?,?)',
|
|
55
|
+
(manifest['id'],manifest['sessionId'],manifest['runId'],json.dumps(manifest),data))
|
|
56
|
+
return manifest
|
|
57
|
+
|
|
58
|
+
def capture(self, scope, session, workspace, *, expected_inputs=None):
|
|
59
|
+
if expected_inputs is not None:
|
|
60
|
+
if not isinstance(expected_inputs,dict) or any(not isinstance(name,str) or not PurePosixPath(name).parts or PurePosixPath(name).is_absolute() or '..' in PurePosixPath(name).parts or '\\' in name or not isinstance(digest,str) or not re.fullmatch(r'[a-f0-9]{64}',digest) for name,digest in expected_inputs.items()):
|
|
61
|
+
raise ValueError('Pinned inputs require relative names and actual SHA-256 digests')
|
|
62
|
+
before = self.store.get(scope, session)
|
|
63
|
+
self._require_state(before)
|
|
64
|
+
if os.name == 'nt':
|
|
65
|
+
from .windows_snapshot import snapshot
|
|
66
|
+
data,files,exclusions=snapshot(workspace,excluded=self.EXCLUDED,max_bytes=self.max_bytes,max_files=self.max_files)
|
|
67
|
+
return self._complete_capture(scope,session,before,expected_inputs,data,files,exclusions)
|
|
68
|
+
if os.name != 'posix' or not hasattr(os, 'O_NOFOLLOW'):
|
|
69
|
+
raise SessionError('This platform has no qualified artifact capture adapter')
|
|
70
|
+
output, files, exclusions = io.BytesIO(), [], []
|
|
71
|
+
total = 0
|
|
72
|
+
observed_stats={}
|
|
73
|
+
def identity(info):return tuple(getattr(info,key) for key in ['st_dev','st_ino','st_mode','st_nlink','st_size','st_mtime_ns','st_ctime_ns'])
|
|
74
|
+
flags = os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK
|
|
75
|
+
def walk(directory, prefix, archive):
|
|
76
|
+
nonlocal total
|
|
77
|
+
observed_stats[prefix]=identity(os.fstat(directory))
|
|
78
|
+
names = sorted(os.listdir(directory))
|
|
79
|
+
for name in names:
|
|
80
|
+
relative = f'{prefix}/{name}' if prefix else name
|
|
81
|
+
if any(ord(c) < 32 for c in name) or '\\' in name or name in {'.', '..'}:
|
|
82
|
+
raise SessionError('Artifact contains an unsupported filename')
|
|
83
|
+
if name in self.EXCLUDED:
|
|
84
|
+
exclusions.append(relative)
|
|
85
|
+
continue
|
|
86
|
+
descriptor = os.open(name, flags, dir_fd=directory)
|
|
87
|
+
try:
|
|
88
|
+
first = os.fstat(descriptor)
|
|
89
|
+
observed_stats[relative]=identity(first)
|
|
90
|
+
if stat.S_ISDIR(first.st_mode):
|
|
91
|
+
# Count directories too: huge empty trees must not bypass bounds.
|
|
92
|
+
files.append({'path': relative+'/', 'kind': 'directory'})
|
|
93
|
+
if len(files) > self.max_files:
|
|
94
|
+
raise SessionError('Artifact entry limit exceeded; no partial package was saved')
|
|
95
|
+
info=zipfile.ZipInfo(relative+'/');info.external_attr=(stat.S_IFDIR|0o755)<<16
|
|
96
|
+
archive.writestr(info,b'')
|
|
97
|
+
walk(descriptor, relative, archive)
|
|
98
|
+
elif stat.S_ISREG(first.st_mode):
|
|
99
|
+
if first.st_nlink != 1 or first.st_size + total > self.max_bytes:
|
|
100
|
+
raise SessionError('Artifact is linked or exceeds the byte limit; no partial package was saved')
|
|
101
|
+
chunks, size = [], 0
|
|
102
|
+
while True:
|
|
103
|
+
chunk = os.read(descriptor, min(1_000_000, self.max_bytes-total-size+1))
|
|
104
|
+
if not chunk: break
|
|
105
|
+
chunks.append(chunk)
|
|
106
|
+
size += len(chunk)
|
|
107
|
+
if total+size > self.max_bytes:
|
|
108
|
+
raise SessionError('Artifact byte limit exceeded; no partial package was saved')
|
|
109
|
+
final = os.fstat(descriptor)
|
|
110
|
+
if (first.st_size,first.st_mtime_ns,first.st_ctime_ns) != (final.st_size,final.st_mtime_ns,final.st_ctime_ns) or size != first.st_size:
|
|
111
|
+
raise SessionError('Artifact changed during capture; retry after work settles')
|
|
112
|
+
content = b''.join(chunks)
|
|
113
|
+
files.append({'path': relative, 'kind': 'file', 'bytes': size,
|
|
114
|
+
'sha256': hashlib.sha256(content).hexdigest()})
|
|
115
|
+
if len(files) > self.max_files:
|
|
116
|
+
raise SessionError('Artifact entry limit exceeded; no partial package was saved')
|
|
117
|
+
info = zipfile.ZipInfo(relative)
|
|
118
|
+
info.compress_type = zipfile.ZIP_DEFLATED
|
|
119
|
+
info.external_attr = (stat.S_IFREG | (0o755 if first.st_mode & 0o111 else 0o644)) << 16
|
|
120
|
+
archive.writestr(info, content)
|
|
121
|
+
total += size
|
|
122
|
+
else:
|
|
123
|
+
raise SessionError('Artifact contains a special file; no package was saved')
|
|
124
|
+
finally:
|
|
125
|
+
os.close(descriptor)
|
|
126
|
+
try:
|
|
127
|
+
root = os.open(Path(workspace), flags | os.O_DIRECTORY)
|
|
128
|
+
try:
|
|
129
|
+
with zipfile.ZipFile(output, 'w', compression=zipfile.ZIP_DEFLATED) as archive:
|
|
130
|
+
walk(root, '', archive)
|
|
131
|
+
# Recheck the entire observed tree: an earlier file or directory
|
|
132
|
+
# may change after its individual read while another is captured.
|
|
133
|
+
audited=set()
|
|
134
|
+
def audit(directory,prefix):
|
|
135
|
+
if identity(os.fstat(directory))!=observed_stats.get(prefix):raise SessionError('Source changed during snapshot; no partial evidence was saved')
|
|
136
|
+
audited.add(prefix)
|
|
137
|
+
for name in sorted(os.listdir(directory)):
|
|
138
|
+
if name in self.EXCLUDED:continue
|
|
139
|
+
relative=f'{prefix}/{name}' if prefix else name
|
|
140
|
+
descriptor=os.open(name,flags,dir_fd=directory)
|
|
141
|
+
try:
|
|
142
|
+
info=os.fstat(descriptor)
|
|
143
|
+
if identity(info)!=observed_stats.get(relative):raise SessionError('Source changed during snapshot; no partial evidence was saved')
|
|
144
|
+
audited.add(relative)
|
|
145
|
+
if stat.S_ISDIR(info.st_mode):audit(descriptor,relative)
|
|
146
|
+
finally:os.close(descriptor)
|
|
147
|
+
audit(root,'')
|
|
148
|
+
if audited!=set(observed_stats):raise SessionError('Source entries changed during snapshot; no partial evidence was saved')
|
|
149
|
+
finally:
|
|
150
|
+
os.close(root)
|
|
151
|
+
except OSError:
|
|
152
|
+
raise SessionError('Artifact contains an unavailable or unsafe entry; no package was saved') from None
|
|
153
|
+
return self._complete_capture(scope,session,before,expected_inputs,output.getvalue(),files,exclusions)
|
|
154
|
+
|
|
155
|
+
def _complete_capture(self,scope,session,before,expected_inputs,data,files,exclusions):
|
|
156
|
+
after = self.store.get(scope, session)
|
|
157
|
+
self._require_state(after)
|
|
158
|
+
if self._state_identity(after)!=self._state_identity(before):
|
|
159
|
+
raise SessionError('Engineering run changed during capture; no package was saved')
|
|
160
|
+
pinned = expected_inputs or {}
|
|
161
|
+
observed = {entry['path']:entry.get('sha256') for entry in files}
|
|
162
|
+
if any(observed.get(path)!=digest for path,digest in pinned.items()):
|
|
163
|
+
raise SessionError('Pinned project evidence or reviewed instructions changed; restore the original inputs before capture')
|
|
164
|
+
ident = str(uuid.uuid4())
|
|
165
|
+
manifest = {'id': ident, 'sessionId': session, 'runId': before['run'],
|
|
166
|
+
'model': before['model'], 'effort': before['effort'],
|
|
167
|
+
'kind':self.KIND,'turnStatus': 'in_progress' if self.KIND=='working_checkpoint' else before['last_status'], 'sha256': hashlib.sha256(data).hexdigest(),
|
|
168
|
+
'bytes': len(data), 'files': files, 'excluded': exclusions,
|
|
169
|
+
'pinnedInputs': pinned,
|
|
170
|
+
'validation': 'not_established_by_artifact_capture'}
|
|
171
|
+
return self._persist(manifest,data)
|
|
172
|
+
|
|
173
|
+
def get(self, scope, session, artifact):
|
|
174
|
+
self.store.get(scope, session)
|
|
175
|
+
row = self.db.execute(f'SELECT manifest,archive FROM {self.TABLE} WHERE id=? AND session=?',
|
|
176
|
+
(artifact,session)).fetchone()
|
|
177
|
+
if row is None:
|
|
178
|
+
raise SessionError('Artifact is unavailable for this project and session')
|
|
179
|
+
return json.loads(row['manifest']), bytes(row['archive'])
|
|
180
|
+
|
|
181
|
+
def manifests(self,scope,session):
|
|
182
|
+
self.store.get(scope,session)
|
|
183
|
+
rows=self.db.execute(f'SELECT manifest FROM {self.TABLE} WHERE session=? ORDER BY rowid DESC LIMIT 20',(session,)).fetchall()
|
|
184
|
+
return [json.loads(row['manifest']) for row in rows]
|
|
185
|
+
|
|
186
|
+
def contains(self,scope,session,artifact):
|
|
187
|
+
self.store.get(scope,session)
|
|
188
|
+
return self.db.execute(f'SELECT 1 FROM {self.TABLE} WHERE id=? AND session=?',(artifact,session)).fetchone() is not None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class WorkingCheckpoints(EngineeringArtifacts):
|
|
192
|
+
"""Internal check inputs only; never listed/downloaded as delivery artifacts."""
|
|
193
|
+
TABLE='engineering_checkpoints'
|
|
194
|
+
KIND='working_checkpoint'
|
|
195
|
+
|
|
196
|
+
def _require_state(self,row):
|
|
197
|
+
if row['state']!='running' or not row['run'] or not row['turn']:
|
|
198
|
+
raise SessionError('A working checkpoint requires a confirmed active engineering turn')
|
|
199
|
+
|
|
200
|
+
def capture(self,scope,session,workspace,*,expected_inputs=None):
|
|
201
|
+
if not expected_inputs:raise SessionError('Working checkpoints require pinned original engineering inputs')
|
|
202
|
+
return super().capture(scope,session,workspace,expected_inputs=expected_inputs)
|
|
203
|
+
|
|
204
|
+
def _persist(self,manifest,data):
|
|
205
|
+
rows=self.db.execute(f'SELECT manifest FROM {self.TABLE} WHERE session=? AND run=?',(manifest['sessionId'],manifest['runId'])).fetchall()
|
|
206
|
+
previous=[json.loads(row['manifest']) for row in rows]
|
|
207
|
+
for row in previous:
|
|
208
|
+
if row['sha256']==manifest['sha256']:return row
|
|
209
|
+
if len(previous)>=16 or sum(row['bytes'] for row in previous)+len(data)>128_000_000:
|
|
210
|
+
raise SessionError('This run reached its bounded working-checkpoint storage limit')
|
|
211
|
+
return super()._persist(manifest,data)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class CheckSources:
|
|
215
|
+
"""Validator-side resolver; delivery HTTP endpoints keep the delivery store."""
|
|
216
|
+
def __init__(self,*sources):self.sources=sources
|
|
217
|
+
def contains(self,scope,session,artifact):return any(source.contains(scope,session,artifact) for source in self.sources)
|
|
218
|
+
def get(self,scope,session,artifact):
|
|
219
|
+
for source in self.sources:
|
|
220
|
+
if source.contains(scope,session,artifact):return source.get(scope,session,artifact)
|
|
221
|
+
raise SessionError('Check source is unavailable for this project and session')
|