@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
package/src/records.mjs
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Project records beside the diagram: requirements, assumptions, open questions,
|
|
2
|
+
// and decisions with supersession history. See specs/002-project-model.
|
|
3
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
4
|
+
|
|
5
|
+
export const REQUIREMENT_KINDS = ["functional", "nonfunctional"];
|
|
6
|
+
export const CATEGORIES = [
|
|
7
|
+
"security",
|
|
8
|
+
"reliability",
|
|
9
|
+
"availability",
|
|
10
|
+
"scalability",
|
|
11
|
+
"performance",
|
|
12
|
+
"maintainability",
|
|
13
|
+
"cost",
|
|
14
|
+
"compliance",
|
|
15
|
+
"other",
|
|
16
|
+
];
|
|
17
|
+
export const PRIORITIES = ["must", "should", "could"];
|
|
18
|
+
export const REQUIREMENT_STATUSES = ["open", "met", "at-risk", "not-met"];
|
|
19
|
+
export const SOURCES = ["user", "agent", "import"];
|
|
20
|
+
export const OPS = [">=", "<=", "=="];
|
|
21
|
+
|
|
22
|
+
const text = (value, max = 2000) => {const result=String(value??"");if(result.length>max)throw Error(`A project record exceeds ${max.toLocaleString('en')} characters; split it into linked records. No content was truncated.`);return result;};
|
|
23
|
+
const pick = (value, allowed, fallback) => (allowed.includes(value) ? value : fallback);
|
|
24
|
+
const links = (value) => {if(Array.isArray(value)&&value.length>50)throw Error("A record links more than 50 components; no links were truncated.");return Array.isArray(value)?value.map(id=>text(id,100)).filter(Boolean):[];};
|
|
25
|
+
const today = () => new Date().toLocaleDateString("en-CA");
|
|
26
|
+
const day = (value) => (/^\d{4}-\d{2}-\d{2}$/.test(value) ? value : today());
|
|
27
|
+
const uid = (prefix) => `${prefix}-${crypto.randomUUID().slice(0, 8)}`;
|
|
28
|
+
|
|
29
|
+
// A measure needs a numeric target when compared with >= or <=.
|
|
30
|
+
export function measureProblem(measure) {
|
|
31
|
+
if (!measure) return null;
|
|
32
|
+
if (!text(measure.metric).trim()) return "Name the metric.";
|
|
33
|
+
if (!OPS.includes(measure.op)) return "Choose >=, <= or ==.";
|
|
34
|
+
if (measure.op !== "==" && (typeof measure.target !== "number" || !Number.isFinite(measure.target)))
|
|
35
|
+
return "Use a number for >= and <= targets.";
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function cleanMeasure(m) {
|
|
40
|
+
if (!m || typeof m !== "object") return null;
|
|
41
|
+
const target =
|
|
42
|
+
typeof m.target === "number" && Number.isFinite(m.target)
|
|
43
|
+
? m.target
|
|
44
|
+
: typeof m.target === "boolean"
|
|
45
|
+
? m.target
|
|
46
|
+
: text(m.target, 100);
|
|
47
|
+
const measure = { metric: text(m.metric, 150), op: m.op, target, unit: text(m.unit, 40) };
|
|
48
|
+
return measureProblem(measure) ? null : measure;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function cleanRequirement(r) {
|
|
52
|
+
const measure = cleanMeasure(r?.measure);
|
|
53
|
+
return {
|
|
54
|
+
id: text(r?.id || uid("req"), 60),
|
|
55
|
+
kind: pick(r?.kind, REQUIREMENT_KINDS, "functional"),
|
|
56
|
+
category: pick(r?.category, CATEGORIES, "other"),
|
|
57
|
+
statement: text(r?.statement, 1000),
|
|
58
|
+
...(r?.rationale ? {rationale:text(r.rationale,1000)} : {}),
|
|
59
|
+
...(r?.basis ? {basis:text(r.basis,1000)} : {}),
|
|
60
|
+
priority: pick(r?.priority, PRIORITIES, "should"),
|
|
61
|
+
status: pick(r?.status, REQUIREMENT_STATUSES, "open"),
|
|
62
|
+
source: pick(r?.source, SOURCES, "user"),
|
|
63
|
+
links: links(r?.links),
|
|
64
|
+
...(measure ? { measure } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function cleanAssumption(a) {
|
|
69
|
+
return {
|
|
70
|
+
id: text(a?.id || uid("asm"), 60),
|
|
71
|
+
statement: text(a?.statement, 1000),
|
|
72
|
+
source: pick(a?.source, SOURCES, "user"),
|
|
73
|
+
owner: text(a?.owner, 120),
|
|
74
|
+
links: links(a?.links),
|
|
75
|
+
madeAt: day(a?.madeAt),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function cleanQuestion(q) {
|
|
80
|
+
return {
|
|
81
|
+
id: text(q?.id || uid("q"), 60),
|
|
82
|
+
question: text(q?.question, 1000),
|
|
83
|
+
owner: text(q?.owner, 120),
|
|
84
|
+
status: q?.status === "answered" ? "answered" : "open",
|
|
85
|
+
answer: text(q?.answer, 2000),
|
|
86
|
+
links: links(q?.links),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const newRequirement = (fields = {}) => cleanRequirement({ kind: "nonfunctional", category: "performance", ...fields });
|
|
91
|
+
export const newAssumption = (fields = {}) => cleanAssumption(fields);
|
|
92
|
+
export const newQuestion = (fields = {}) => cleanQuestion(fields);
|
|
93
|
+
|
|
94
|
+
// Records that point at components no longer on the canvas.
|
|
95
|
+
export function missingLinks(project, record) {
|
|
96
|
+
const ids = new Set(project.nodes.map((n) => n.id));
|
|
97
|
+
return record.links.filter((id) => !ids.has(id));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function linkedTo(project, componentId) {
|
|
101
|
+
const has = (r) => r.links.includes(componentId);
|
|
102
|
+
return {
|
|
103
|
+
needs: (project.needs || []).filter(has),
|
|
104
|
+
assumptions: (project.assumptions || []).filter(has),
|
|
105
|
+
questions: (project.openQuestions || []).filter(has),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---- Decisions ----
|
|
110
|
+
|
|
111
|
+
export const slugSubject = (title) => {
|
|
112
|
+
const full = String(title ?? "");
|
|
113
|
+
const slug = full
|
|
114
|
+
.toLowerCase()
|
|
115
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
116
|
+
.replace(/^-|-$/g, "") || "general";
|
|
117
|
+
if (slug.length <= 80) return slug;
|
|
118
|
+
// This is a derived lookup key, not the decision title. Bind the suffix to
|
|
119
|
+
// the whole title so proposals sharing a long prefix keep distinct histories.
|
|
120
|
+
const digest = Array.from(sha256(new TextEncoder().encode(full)), byte =>
|
|
121
|
+
byte.toString(16).padStart(2, "0")).join("").slice(0, 16);
|
|
122
|
+
return `${slug.slice(0, 63).replace(/-+$/, "")}-${digest}`;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
function nextDecisionId(decisions) {
|
|
126
|
+
const max = Math.max(0, ...decisions.map((d) => Number(/^adr-(\d+)$/.exec(d.id)?.[1] || 0)));
|
|
127
|
+
return `adr-${String(max + 1).padStart(3, "0")}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Revise a decision: a new record becomes current, the old one is superseded and kept.
|
|
131
|
+
export function reviseDecision(project, id, changes, reason, revision = project.revision) {
|
|
132
|
+
const decisions = project.decisions || [];
|
|
133
|
+
const old = decisions.find((d) => d.id === id);
|
|
134
|
+
if (!old) throw Error("Decision not found.");
|
|
135
|
+
if (old.status === "Superseded") throw Error("This decision is already superseded; revise the current one.");
|
|
136
|
+
if (!text(reason).trim()) throw Error("Give a reason for the change.");
|
|
137
|
+
const next = {
|
|
138
|
+
...old,
|
|
139
|
+
...changes,
|
|
140
|
+
id: nextDecisionId(decisions),
|
|
141
|
+
subject: old.subject,
|
|
142
|
+
status: "Accepted",
|
|
143
|
+
date: today(),
|
|
144
|
+
revision,
|
|
145
|
+
supersedes: old.id,
|
|
146
|
+
supersededBy: null,
|
|
147
|
+
reason: text(reason),
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
...project,
|
|
151
|
+
decisions: [
|
|
152
|
+
...decisions.map((d) => (d.id === old.id ? { ...d, status: "Superseded", supersededBy: next.id } : d)),
|
|
153
|
+
next,
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function currentDecision(decisions = [], subject) {
|
|
159
|
+
const live = decisions.filter((d) => d.subject === subject && d.status !== "Superseded");
|
|
160
|
+
return live.at(-1) || null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Oldest first, ending with the current decision.
|
|
164
|
+
export function decisionHistory(decisions = [], subject) {
|
|
165
|
+
const current = currentDecision(decisions, subject);
|
|
166
|
+
if (!current) return [];
|
|
167
|
+
const byId = new Map(decisions.map((d) => [d.id, d]));
|
|
168
|
+
const chain = [current];
|
|
169
|
+
const seen = new Set([current.id]);
|
|
170
|
+
let at = current;
|
|
171
|
+
while (at.supersedes && byId.has(at.supersedes) && !seen.has(at.supersedes)) {
|
|
172
|
+
at = byId.get(at.supersedes);
|
|
173
|
+
seen.add(at.id);
|
|
174
|
+
chain.unshift(at);
|
|
175
|
+
}
|
|
176
|
+
return chain;
|
|
177
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {approvalProfiles} from './approval-applications.mjs';
|
|
2
|
+
import {declaredRegion} from './deployment-region.mjs';
|
|
3
|
+
import {containerRuntime} from './container-runtime.mjs';
|
|
4
|
+
import {resourceAddress,assertDistinctIdentifiers} from './identifiers.mjs';
|
|
5
|
+
import {capabilityCompositionPlan} from './delivery-capabilities.mjs';
|
|
6
|
+
import {hasInvoice,invoiceResourcePlan}from'./invoice-plan.mjs';
|
|
7
|
+
import {publicFaqPlan} from './public-faq.mjs';
|
|
8
|
+
import {ibmStructuredPlan} from './ibm-structured.mjs';
|
|
9
|
+
import {structuredApiPlan} from './structured-api.mjs';
|
|
10
|
+
import {azureStructuredPlan} from './azure-structured.mjs';
|
|
11
|
+
import {gcpStructuredPlan} from './gcp-structured.mjs';
|
|
12
|
+
import {existingDeployment} from './deployment-ownership.mjs';
|
|
13
|
+
import {externalIdentity,validateExternalReferences} from './external-identities.mjs';
|
|
14
|
+
import {hasContainers,containerContract} from './container-contracts.mjs';
|
|
15
|
+
import {existingReference} from './existing-references.mjs';
|
|
16
|
+
import {hasCrossCloud,crossCloudPlan} from './cross-cloud-storage.mjs';
|
|
17
|
+
import {webPlan} from './rag-web.mjs';
|
|
18
|
+
import {sourceContractPlan} from './source-contracts.mjs';
|
|
19
|
+
import {ragPlan} from './bounded-rag.mjs';
|
|
20
|
+
import {summaryPlan} from './bedrock-summary.mjs';
|
|
21
|
+
import { asyncPipelinePlan } from './async-documents.mjs';
|
|
22
|
+
import { runtimeSettings } from "./runtime-settings.mjs";
|
|
23
|
+
// A schedule of the diagram's intent, not discovered cloud inventory.
|
|
24
|
+
import { storageAccessPlan } from "./s3-access.mjs";
|
|
25
|
+
import { roleOf } from "./review.mjs";
|
|
26
|
+
|
|
27
|
+
const SERVICES = [
|
|
28
|
+
[/\bcloudfront\b/i, "Amazon CloudFront", ["aws_cloudfront_distribution"], "Requests, transfer and private static assets"],
|
|
29
|
+
[/\bcognito\b/i, "Amazon Cognito", ["aws_cognito_user_pool"], "Active users and authentication features"],
|
|
30
|
+
[/\bapi\s*gateway\b/i, "Amazon API Gateway", ["aws_apigatewayv2_api"], "API requests, transfer and access logs"],
|
|
31
|
+
[/\bs3\b/i, "Amazon S3", ["aws_s3_bucket", "aws_s3_bucket_public_access_block", "aws_s3_bucket_server_side_encryption_configuration", "aws_s3_bucket_versioning", "aws_s3_bucket_ownership_controls", "aws_s3_bucket_policy"], "Storage, requests, retrieval and transfer"],
|
|
32
|
+
[/bedrock.*guard|guard.*bedrock/i, "Amazon Bedrock Guardrails", ["aws_bedrock_guardrail"], "Enabled policy checks and text units; confirm each policy's charge"],
|
|
33
|
+
[/bedrock.*knowledge|knowledge.*bedrock/i, "Amazon Bedrock Knowledge Bases", ["aws_bedrockagent_knowledge_base", "aws_iam_role"], "Embeddings, ingestion, vector store and retrieval; select the backing store"],
|
|
34
|
+
[/\bbedrock\b/i, "Amazon Bedrock", [], "Model-specific input/output tokens; guardrails, embeddings and knowledge bases separately"],
|
|
35
|
+
[/\blambda\b/i, "AWS Lambda", ["aws_lambda_function", "aws_iam_role"], "Requests, duration, memory and logs"],
|
|
36
|
+
[/\bopensearch\b/i, "Amazon OpenSearch", [], "Deployment mode, indexing/search capacity and storage"],
|
|
37
|
+
[/\bapp\s*runner\b/i, "AWS App Runner", ["aws_apprunner_service"], "Provisioned and active compute, builds, logs and transfer"],
|
|
38
|
+
[/\b(ecs|fargate)\b/i, "Amazon ECS", ["aws_ecs_service", "aws_ecs_task_definition"], "Compute, load balancing, networking, images and logs"],
|
|
39
|
+
[/\bdynamodb\b/i, "Amazon DynamoDB", ["aws_dynamodb_table"], "Read/write requests, storage and backup"],
|
|
40
|
+
[/\bsqs\b/i, "Amazon SQS", ["aws_sqs_queue"], "Requests, payload size and transfer"],
|
|
41
|
+
[/\bsagemaker\b/i, "Amazon SageMaker AI", [], "Training, serving instances, processing and storage"],
|
|
42
|
+
[/\b(iot|kinesis)\b/i, "AWS ingestion service", [], "Messages, connections, processing and retention"],
|
|
43
|
+
[/\bcloudtrail\b/i, "AWS CloudTrail", ["aws_cloudtrail"], "Management/data events, delivery, retention and query costs"],
|
|
44
|
+
[/\bcloudwatch\b/i, "Amazon CloudWatch", ["aws_cloudwatch_log_group"], "Logs, metrics, alarms and retention"],
|
|
45
|
+
[/\biam\b/i, "AWS IAM", ["aws_iam_policy"], "Review associated services; IAM policy alone is not an application guardrail"],
|
|
46
|
+
];
|
|
47
|
+
const CANDIDATES = { data: "Storage or existing data connector", ingest: "Ingestion / processing service", index: "Search / vector index", llm: "Model API and model selection", guard: "Guardrails and access policies", human: "Approval workflow", app: "Application runtime and API", output: "User interface / delivery integration", model: "Model training or serving", monitor: "Monitoring and alerting" };
|
|
48
|
+
|
|
49
|
+
// Explicit selections are authoritative; labels are only candidates, never a way to
|
|
50
|
+
// override an unsupported or conflicting selection. Generic names retain open modes.
|
|
51
|
+
export function resolveService(data) {
|
|
52
|
+
if(data.implementation==='approval-workflow')return null;
|
|
53
|
+
if (data.provider !== "aws") return null;
|
|
54
|
+
const value = String(data.service || data.label || "");
|
|
55
|
+
if (/bedrock/i.test(value) && /\b(model|inference|invoke)\b/i.test(value) && /(guardrail|knowledge)/i.test(value) && /\bwith\b|[+&]/i.test(value)) return { ambiguous: true, selected: !!data.service };
|
|
56
|
+
let matches = SERVICES.filter(([re]) => re.test(value));
|
|
57
|
+
if (/bedrock.*(guard|knowledge)|(guard|knowledge).*bedrock/i.test(value)) matches = matches.filter(([,name]) => name !== "Amazon Bedrock");
|
|
58
|
+
if (matches.length !== 1) return { ambiguous: matches.length > 1, selected: !!data.service };
|
|
59
|
+
let [, name, resources, costs] = matches[0];
|
|
60
|
+
if (name === "Amazon OpenSearch") {
|
|
61
|
+
if (/serverless/i.test(value)) { name = "Amazon OpenSearch Serverless"; resources = ["aws_opensearchserverless_collection"]; }
|
|
62
|
+
else if (/\b(domain|provisioned|managed cluster)\b/i.test(value)) { name = "Amazon OpenSearch domain"; resources = ["aws_opensearch_domain"]; }
|
|
63
|
+
}
|
|
64
|
+
return { name, resources, costs, selected: !!data.service };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function resourcePlan(project) {
|
|
68
|
+
approvalProfiles(project);
|
|
69
|
+
validateExternalReferences(project);
|
|
70
|
+
assertDistinctIdentifiers(project.nodes.map(n=>n.id));
|
|
71
|
+
return capabilityCompositionPlan(project,standaloneResourcePlan)||standaloneResourcePlan(project);
|
|
72
|
+
}
|
|
73
|
+
function standaloneResourcePlan(project) {
|
|
74
|
+
validateExternalReferences(project);
|
|
75
|
+
if(hasInvoice(project))return invoiceResourcePlan(project);
|
|
76
|
+
if(hasCrossCloud(project))return crossCloudPlan(project);
|
|
77
|
+
const nodes = project.nodes.filter((n) => n.data.kind !== "note");
|
|
78
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
79
|
+
const rows = nodes.map((n) => {
|
|
80
|
+
const match = resolveService(n.data);
|
|
81
|
+
const existing = existingDeployment(n);
|
|
82
|
+
const foundation = !existing && n.data.provider === "aws" && match?.name === "Amazon S3";
|
|
83
|
+
const compute=!existing && n.data.provider==="aws" && match?.name==="AWS Lambda" && n.data.implementation==="text-statistics";
|
|
84
|
+
// Encode the complete ID: stable on reorder/rename, valid Terraform identifier, no collisions.
|
|
85
|
+
const address = resourceAddress(n.id);
|
|
86
|
+
return { id: n.id, address, label: n.data.label, provider: n.data.provider,
|
|
87
|
+
service: match?.name || n.data.service || CANDIDATES[roleOf(n)] || "Service to be selected",
|
|
88
|
+
sourceExisting:n.data.origin === "existing", selection: match?.ambiguous ? "Ambiguous service names" : match?.name ? (match.selected ? "Explicit service field; deployment review required" : "Named in diagram") : "Needs service confirmation", existing,
|
|
89
|
+
region: n.data.config?.region || "To be selected", resources: match?.resources || [],
|
|
90
|
+
costDrivers: match?.costs || "Confirm service, capacity and billable dimensions",
|
|
91
|
+
dependencies: project.edges.filter((e) => e.target === n.id && ids.has(e.source)).map((e) => e.source),
|
|
92
|
+
terraform: existing ? "Existing — reference/import plan needed" : foundation ? "S3 foundation generated" : "Implementation needed",
|
|
93
|
+
foundation, compute, api:!existing&&match?.name==="Amazon API Gateway"&&n.data.implementation==="iam-rag-api", structured:n.data.implementation==="bedrock-structured", summary:!existing&&n.data.provider==="aws"&&match?.name==="AWS Lambda"&&(n.data.implementation==="bedrock-summary"||(n.data.implementation==="bedrock-structured"&&n.data.config?.outputContract)), settings: runtimeSettings(n.data.config),
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
const asyncPlan=asyncPipelinePlan(project,rows);
|
|
97
|
+
const modelPlan=summaryPlan(project,rows);
|
|
98
|
+
const retrievalPlan=ragPlan(project,rows);
|
|
99
|
+
const structuredPlan=structuredApiPlan(project,rows,modelPlan);
|
|
100
|
+
const connectorPlan=sourceContractPlan(project,rows,retrievalPlan);
|
|
101
|
+
const browserPlan=webPlan(project,rows,{entries:[...retrievalPlan.entries,...structuredPlan.entries]});
|
|
102
|
+
const accessPlan=storageAccessPlan(project,rows);
|
|
103
|
+
const azurePlan=azureStructuredPlan(project,rows);
|
|
104
|
+
const googlePlan=gcpStructuredPlan(project,rows);
|
|
105
|
+
const ibmPlan=ibmStructuredPlan(project,rows);
|
|
106
|
+
const faqPlan=publicFaqPlan(project,rows);
|
|
107
|
+
for(const row of rows){
|
|
108
|
+
row.reference=accessPlan.bindings.some(b=>b.id===row.id);
|
|
109
|
+
row.externalReference=row.existing && row.provider==="tools" && nodes.find(n=>n.id===row.id)?.data.binding==="web-browser";
|
|
110
|
+
row.existingServiceReference=existingReference(nodes.find(n=>n.id===row.id),externalIdentity(project,row.id));
|
|
111
|
+
row.containerContract=containerContract(nodes.find(n=>n.id===row.id));
|
|
112
|
+
row.implemented=faqPlan.editorIds.includes(row.id)||faqPlan.componentIds.includes(row.id)||ibmPlan.componentIds.includes(row.id)||googlePlan.componentIds.includes(row.id)||azurePlan.componentIds.includes(row.id)||(structuredPlan.entries.some(e=>e.api.id===row.id)&&browserPlan.entries.some(e=>e.api.id===row.id))||!!row.containerContract||!!row.existingServiceReference||row.externalReference||row.foundation||row.compute||row.reference||browserPlan.componentIds.includes(row.id)||connectorPlan.sources.some(s=>s.id===row.id)||retrievalPlan.componentIds.includes(row.id)||modelPlan.modelIds.includes(row.id)||modelPlan.entries.some(e=>e.worker.id===row.id)||asyncPlan.componentIds.includes(row.id);
|
|
113
|
+
if(faqPlan.componentIds.includes(row.id)){row.costDrivers='Compute and HTTP requests, model input/output tokens, catalog read/write/versioned storage, logs and transfer; verify current regional service/model prices and quotas. Capacity limits are not invoice caps.';row.resources={aws:['aws_lambda_function','aws_apigatewayv2_api','aws_s3_bucket','aws_iam_role'],azure:['azurerm_linux_web_app','azurerm_storage_account','azurerm_storage_container'],gcp:['google_cloud_run_v2_service','google_storage_bucket','google_service_account'],ibm:['ibm_code_engine_app','ibm_cos_bucket','ibm_iam_trusted_profile']}[row.provider];row.terraform='Public-only cited FAQ source, browser, private catalog, separate editor/runtime roles and regional model transport generated; destination behavior unverified';}
|
|
114
|
+
if(azurePlan.componentIds.includes(row.id)){row.resources=['azurerm_linux_web_app','azurerm_service_plan','azurerm_cognitive_account','azurerm_cognitive_deployment','azuread_application_registration'];row.terraform='Composed Azure structured application source, browser, Entra authorization and regional model deployment generated; destination behavior unverified';}
|
|
115
|
+
if(ibmPlan.componentIds.includes(row.id)){row.resources=['ibm_code_engine_app','ibm_resource_instance','ibm_appid_application','ibm_iam_trusted_profile','ibm_iam_trusted_profile_link'];row.terraform='Composed IBM structured application source, browser, App ID authorization and keyless regional model transport generated; destination behavior unverified';}
|
|
116
|
+
if(googlePlan.componentIds.includes(row.id)){row.resources=['google_cloud_run_v2_service','google_service_account','google_project_iam_custom_role','google_iap_web_cloud_run_service_iam_member'];row.terraform='Composed Google structured application source, browser, IAP identity and regional model transport generated; destination behavior unverified';}
|
|
117
|
+
if(asyncPlan.componentIds.includes(row.id))row.terraform="Typed asynchronous pipeline implementation generated";
|
|
118
|
+
if(modelPlan.modelIds.includes(row.id))row.terraform="Existing regional foundation model — required model ID input generated";
|
|
119
|
+
if(modelPlan.entries.some(e=>e.worker.id===row.id))row.terraform=row.structured?"Schema-checked structured generation handler, scoped model permission and caller binding generated":"Bedrock summary handler, scoped model permission and caller binding generated";
|
|
120
|
+
if(retrievalPlan.componentIds.includes(row.id))row.terraform="Bounded IAM-authenticated RAG implementation generated";
|
|
121
|
+
if(connectorPlan.sources.some(s=>s.id===row.id))row.terraform="Local source-contract runner generated; native vendor integration unverified";
|
|
122
|
+
if(browserPlan.componentIds.includes(row.id))row.terraform="Private web hosting and Cognito authorization-code/PKCE sign-in generated";
|
|
123
|
+
if(row.containerContract){row.service=row.containerContract.service;row.resources=[row.containerContract.type];row.terraform="Native private container footprint generated; supplied image and application behavior require validation";}
|
|
124
|
+
if(row.compute){row.terraform="Text-statistics function, execution role, logs, version and alias generated";row.resources=["aws_lambda_function","aws_iam_role","aws_iam_role_policy","aws_cloudwatch_log_group","aws_lambda_alias","aws_lambda_permission"];}
|
|
125
|
+
if(faqPlan.editorIds.includes(row.id)){row.resources=[];row.terraform='Existing public catalog editor — provider-native role/group input and separate conditional editor CLI generated; actual access unverified';}
|
|
126
|
+
if(row.externalReference)row.terraform="External browser — no infrastructure to provision; connection behavior still requires validation";
|
|
127
|
+
if(row.existingServiceReference){row.resources=[];row.terraform="Existing service reference — preserved without provisioning or import; behavior and connections remain unverified";}
|
|
128
|
+
if(row.reference){row.terraform="Existing IAM principal — required ARN input generated";row.service="Existing AWS IAM principal";row.selection="Explicit principal binding";}
|
|
129
|
+
}
|
|
130
|
+
if(hasContainers(project))for(const row of rows)if(!row.existing&&!row.containerContract){row.implemented=false;row.terraform="Container compiler composition does not implement this required component";}
|
|
131
|
+
if(faqPlan.entries.length)for(const row of rows)if(!row.existing&&!faqPlan.componentIds.includes(row.id)){row.implemented=false;row.terraform='Public FAQ composition does not yet implement this additional required component';}
|
|
132
|
+
const connections = project.edges.filter((e) => ids.has(e.source) && ids.has(e.target)).map((e) => ({ id: e.id, source: e.source, target: e.target, data: e.label || "data", generated: (faqPlan.edgeIds.includes(e.id)||ibmPlan.edgeIds.includes(e.id)||googlePlan.edgeIds.includes(e.id)||azurePlan.edgeIds.includes(e.id)||accessPlan.connections.some(c=>c.id===e.id)||asyncPlan.edgeIds.includes(e.id)||modelPlan.edgeIds.includes(e.id)||retrievalPlan.edgeIds.includes(e.id)||connectorPlan.connections.some(c=>c.id===e.id)||(browserPlan.edgeIds.includes(e.id)||structuredPlan.entries.some(x=>browserPlan.entries.some(w=>w.api.id===x.api.id)&&x.edges.some(y=>y.id===e.id)))), status: (faqPlan.edgeIds.includes(e.id)||ibmPlan.edgeIds.includes(e.id)||googlePlan.edgeIds.includes(e.id)||azurePlan.edgeIds.includes(e.id)||accessPlan.connections.some(c=>c.id===e.id)||asyncPlan.edgeIds.includes(e.id)||modelPlan.edgeIds.includes(e.id)||retrievalPlan.edgeIds.includes(e.id)||connectorPlan.connections.some(c=>c.id===e.id)||(browserPlan.edgeIds.includes(e.id)||structuredPlan.entries.some(x=>browserPlan.entries.some(w=>w.api.id===x.api.id)&&x.edges.some(y=>y.id===e.id)))) ? "Scoped access policy generated; account and runtime verification required" : "Application integration and permissions not implemented" }));
|
|
133
|
+
if(faqPlan.entries.length)for(const connection of connections)if(!faqPlan.edgeIds.includes(connection.id)){connection.generated=false;connection.status='Additional required integration is not implemented by this public FAQ composition';}
|
|
134
|
+
const missingStorageAccess=rows.filter(r=>(r.foundation||r.compute||r.summary||r.api)&&!accessPlan.connections.some(e=>e.target===r.id)&&!browserPlan.entries.some(e=>e.api.id===r.id||e.worker.id===r.id));
|
|
135
|
+
const containerSettingGaps=rows.filter(r=>r.containerContract).flatMap(row=>[...containerRuntime(nodes.find(n=>n.id===row.id)).gaps,...declaredRegion(nodes.find(n=>n.id===row.id)).gaps].map(g=>({id:`container-setting-${row.address}-${g.key}`,componentIds:[row.id],message:`${row.label}: explicit ${g.key}=${JSON.stringify(g.value)} ${g.reason}.`})));
|
|
136
|
+
const gaps = [
|
|
137
|
+
...containerSettingGaps.map(g=>g.message),
|
|
138
|
+
...missingStorageAccess.map(r=>`${r.label}: explicit caller access contract is missing.`),
|
|
139
|
+
...rows.filter((r) => !r.implemented).map((r) => `${r.label}: ${r.terraform}.`),
|
|
140
|
+
...connections.filter(e=>!e.generated).map((e) => `Wire ${e.source} → ${e.target}: implement data flow, access policy and acceptance test.`),
|
|
141
|
+
...(!rows.length ? ["No resources in the design yet."] : []),
|
|
142
|
+
];
|
|
143
|
+
const encode=value=>Array.from(new TextEncoder().encode(value),b=>b.toString(16).padStart(2,"0")).join("");
|
|
144
|
+
const gapDetails=[
|
|
145
|
+
...containerSettingGaps,
|
|
146
|
+
...missingStorageAccess.map(r=>({id:`access-${r.address}`,componentIds:[r.id],taskId:`TASK-RESOURCE-${encode(r.id)}`,message:`${r.label}: explicit caller access contract is missing.`})),
|
|
147
|
+
...rows.filter(r=>!r.implemented).map(r=>({id:`resource-${r.address}`,componentIds:[r.id],taskId:`TASK-RESOURCE-${encode(r.id)}`,message:`${r.label}: ${r.terraform}.`})),
|
|
148
|
+
...connections.filter(e=>!e.generated).map(e=>({id:`connection-${encode(e.id)}`,edgeIds:[e.id],taskId:`TASK-INTEGRATION-${encode(e.id)}`,message:`Wire ${e.source} → ${e.target}: implement data flow, access policy and acceptance test.`})),
|
|
149
|
+
...(!rows.length?[{id:'architecture-empty',taskId:'TASK-SCOPE',message:'No resources in the design yet.'}]:[]),
|
|
150
|
+
];
|
|
151
|
+
return { revision: project.revision, rows, connections, accessPlan, asyncPlan, publicFaqPlan:faqPlan, azureStructuredPlan:azurePlan, gcpStructuredPlan:googlePlan, ibmStructuredPlan:ibmPlan, summaryPlan:modelPlan, structuredApiPlan:structuredPlan, ragPlan:retrievalPlan, sourceContractPlan:connectorPlan, webPlan:browserPlan, gaps, gapDetails, scope: "Diagram-derived resource schedule; service choices and resource types require engineering review.", sources: [
|
|
152
|
+
"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket",
|
|
153
|
+
"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/bedrock_guardrail",
|
|
154
|
+
"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/bedrockagent_knowledge_base",
|
|
155
|
+
"https://aws.amazon.com/bedrock/pricing/",
|
|
156
|
+
] };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function resourceMarkdown(project) {
|
|
160
|
+
const plan = resourcePlan(project);
|
|
161
|
+
const c = (v) => String(v ?? "").replaceAll("|", "\\|").replaceAll("\n", " ");
|
|
162
|
+
return `## Cloud resource schedule\n\n${plan.scope}\n\n| Block ID | Component | Provider | Service | Region | Ownership | Terraform coverage |\n|---|---|---|---|---|---|---|\n${plan.rows.map((r) => `| ${c(r.id)} | ${c(r.label)} | ${r.provider} | ${c(r.service)} | ${c(r.region)} | ${r.existing ? "Existing" : "Proposed"} | ${r.terraform} |`).join("\n")}\n\n### Resources and cost dimensions\n\n${plan.rows.map((r) => `- **${c(r.label)}** (${c(r.id)}): ${r.resources.join(", ") || "Resource types to confirm"}. ${r.costDrivers}. Depends on: ${r.dependencies.join(", ") || "none drawn"}.`).join("\n")}\n\n### Engineering work remaining\n\n${plan.gaps.map((g) => `- ${g}`).join("\n") || "Generated configuration remains unverified. Validate every delivery unit, interface, destination input, cost and security boundary before applying."}\n\nReferences (resource semantics; not retrieved prices):\n${plan.sources.map((s) => `- ${s}`).join("\n")}\n`;
|
|
163
|
+
}
|
package/src/review.mjs
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// Architecture review: deterministic, provider-neutral rules over the project model.
|
|
2
|
+
// See specs/003-architecture-review. Each finding names its rule, so it can be traced.
|
|
3
|
+
|
|
4
|
+
export const RULES_VERSION = "1.0.0";
|
|
5
|
+
export const ATTRIBUTES = [
|
|
6
|
+
["security", "Security & controls"],
|
|
7
|
+
["reliability", "Reliability"],
|
|
8
|
+
["availability", "Availability"],
|
|
9
|
+
["scalability", "Scalability"],
|
|
10
|
+
["performance", "Performance"],
|
|
11
|
+
["maintainability", "Maintainability"],
|
|
12
|
+
["cost", "Cost"],
|
|
13
|
+
];
|
|
14
|
+
export const SEVERITIES = ["blocker", "major", "minor", "info"];
|
|
15
|
+
|
|
16
|
+
// What a block does, whether runnable (kind) or sketched (role).
|
|
17
|
+
export function roleOf(n) {
|
|
18
|
+
const { kind, role } = n.data;
|
|
19
|
+
if (role) return role;
|
|
20
|
+
return (
|
|
21
|
+
{ source: "data", stream: "data", history: "data", features: "ingest", train: "model", registry: "model", predict: "model", monitor: "monitor", agent: "llm", guard: "guard", tool: "app", output: "output" }[kind] || "other"
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
const isHuman = (n) => roleOf(n) === "human" || /\b(approv|human|review queue|person)\b/i.test(n.data.label);
|
|
25
|
+
const isGuard = (n) => roleOf(n) === "guard" || isHuman(n);
|
|
26
|
+
|
|
27
|
+
function downstream(p, id, seen = new Set()) {
|
|
28
|
+
for (const e of p.edges)
|
|
29
|
+
if (e.source === id && !seen.has(e.target)) {
|
|
30
|
+
seen.add(e.target);
|
|
31
|
+
downstream(p, e.target, seen);
|
|
32
|
+
}
|
|
33
|
+
return seen;
|
|
34
|
+
}
|
|
35
|
+
// Paths from `from` to any node matching `to` that avoid nodes matching `via`.
|
|
36
|
+
function reachesAvoiding(p, from, to, via) {
|
|
37
|
+
const stack = [from],
|
|
38
|
+
seen = new Set([from]);
|
|
39
|
+
while (stack.length) {
|
|
40
|
+
const id = stack.pop();
|
|
41
|
+
for (const e of p.edges.filter((x) => x.source === id)) {
|
|
42
|
+
const n = p.nodes.find((x) => x.id === e.target);
|
|
43
|
+
if (!n || seen.has(n.id) || via(n)) continue;
|
|
44
|
+
if (to(n)) return true;
|
|
45
|
+
seen.add(n.id);
|
|
46
|
+
stack.push(n.id);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const hasNeed = (p, category, re) =>
|
|
53
|
+
(p.needs || []).some((r) => r.category === category && (!re || re.test(`${r.statement} ${r.measure?.metric || ""}`)));
|
|
54
|
+
const fact = (p, name) => [...(p.facts || [])].reverse().find((f) => f.name === name);
|
|
55
|
+
|
|
56
|
+
const RULES = [
|
|
57
|
+
{
|
|
58
|
+
id: "SEC-001",
|
|
59
|
+
attribute: "security",
|
|
60
|
+
severity: "blocker",
|
|
61
|
+
check(p) {
|
|
62
|
+
const llms = p.nodes.filter((n) => roleOf(n) === "llm");
|
|
63
|
+
const exits = (n) => ["app", "output"].includes(roleOf(n));
|
|
64
|
+
const open = llms.filter((n) => reachesAvoiding(p, n.id, exits, isGuard));
|
|
65
|
+
return open.map((n) => ({
|
|
66
|
+
statement: `${n.data.label} can reach users or systems without a guardrail or approval step.`,
|
|
67
|
+
rationale: "Generated content can be wrong, unsafe or leak data; it needs a check before it leaves the system.",
|
|
68
|
+
action: "Define and represent output validation or an approval control where needed; specify failure behavior and test it. Verify source authorization before retrieval and citation grounding separately: a guardrail block alone proves neither.",
|
|
69
|
+
components: [n.id],
|
|
70
|
+
}));
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "SEC-002",
|
|
75
|
+
attribute: "security",
|
|
76
|
+
severity: "major",
|
|
77
|
+
check(p) {
|
|
78
|
+
const sensitive = p.nodes.filter((n) => n.data.config?.classification === "Confidential");
|
|
79
|
+
const flagged = fact(p, "data.sensitive")?.value === true;
|
|
80
|
+
if (!sensitive.length && !flagged) return [];
|
|
81
|
+
if (hasNeed(p, "compliance", /stay|resid|region|locat/i)) return [];
|
|
82
|
+
return [
|
|
83
|
+
{
|
|
84
|
+
statement: "Sensitive data is involved but where it must be stored is not stated.",
|
|
85
|
+
rationale: "Data residency rules decide which regions and services are allowed.",
|
|
86
|
+
action: "Add a compliance requirement such as “data stays in the EU”, and set regions on data blocks.",
|
|
87
|
+
components: sensitive.map((n) => n.id),
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "SEC-003",
|
|
94
|
+
attribute: "security",
|
|
95
|
+
severity: "major",
|
|
96
|
+
check(p) {
|
|
97
|
+
const actors = p.nodes.filter((n) => ["llm", "model"].includes(roleOf(n)));
|
|
98
|
+
// Systems that receive actions (integrations), not the interface people read.
|
|
99
|
+
const apps = p.nodes.filter((n) => roleOf(n) === "app" && (n.data.kind === "tool" || /integration/i.test(n.data.category || "")));
|
|
100
|
+
const unchecked = apps.filter((a) => actors.some((m) => downstream(p, m.id).has(a.id) && reachesAvoiding(p, m.id, (x) => x.id === a.id, isHuman)));
|
|
101
|
+
if (!unchecked.length || hasNeed(p, "security", /person|human|approv/i)) return [];
|
|
102
|
+
return [
|
|
103
|
+
{
|
|
104
|
+
statement: "Automated results reach other systems with no person approving them.",
|
|
105
|
+
rationale: "Actions taken on model output (orders, emails, records) need clear human accountability.",
|
|
106
|
+
action: "Add an approval step, or record a requirement that states which actions need approval.",
|
|
107
|
+
components: unchecked.map((n) => n.id),
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "REL-001",
|
|
114
|
+
attribute: "reliability",
|
|
115
|
+
severity: "major",
|
|
116
|
+
check(p) {
|
|
117
|
+
const models = p.nodes.filter((n) => ["llm", "model"].includes(roleOf(n)));
|
|
118
|
+
if (!models.length || p.nodes.some((n) => roleOf(n) === "monitor")) return [];
|
|
119
|
+
return [
|
|
120
|
+
{
|
|
121
|
+
statement: "No monitoring for model quality or drift.",
|
|
122
|
+
rationale: "Model answers and predictions degrade quietly; someone needs to see it happen.",
|
|
123
|
+
action: "Add a monitoring block (quality, drift, cost) and decide who is alerted.",
|
|
124
|
+
components: models.map((n) => n.id),
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "REL-002",
|
|
131
|
+
attribute: "reliability",
|
|
132
|
+
severity: "minor",
|
|
133
|
+
check(p) {
|
|
134
|
+
const apps = p.nodes.filter((n) => roleOf(n) === "app" && n.data.origin === "existing");
|
|
135
|
+
return apps.length
|
|
136
|
+
? [
|
|
137
|
+
{
|
|
138
|
+
statement: "The design depends on existing systems; their failure behaviour is not described.",
|
|
139
|
+
rationale: "If an integration is down, requests may be lost or duplicated.",
|
|
140
|
+
action: "State what happens when each existing system is unavailable (retry, queue, fallback).",
|
|
141
|
+
components: apps.map((n) => n.id),
|
|
142
|
+
},
|
|
143
|
+
]
|
|
144
|
+
: [];
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "AVL-001",
|
|
149
|
+
attribute: "availability",
|
|
150
|
+
severity: "minor",
|
|
151
|
+
check: (p) =>
|
|
152
|
+
p.nodes.length && !hasNeed(p, "availability")
|
|
153
|
+
? [{ statement: "No availability target.", rationale: "Without one, nobody can say whether a single region or a single endpoint is enough.", action: "Add a requirement such as “available 99.5% of the time” or “business hours only”.", components: [] }]
|
|
154
|
+
: [],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: "PRF-001",
|
|
158
|
+
attribute: "performance",
|
|
159
|
+
severity: "minor",
|
|
160
|
+
check: (p) =>
|
|
161
|
+
p.nodes.some((n) => ["llm", "model"].includes(roleOf(n))) && !hasNeed(p, "performance")
|
|
162
|
+
? [{ statement: "No response-time target.", rationale: "Model size, retrieval depth and region all trade speed against cost.", action: "Add a requirement such as “responds within 5 seconds”.", components: [] }]
|
|
163
|
+
: [],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: "SCL-001",
|
|
167
|
+
attribute: "scalability",
|
|
168
|
+
severity: "info",
|
|
169
|
+
check: (p) =>
|
|
170
|
+
p.nodes.length && !fact(p, "requests.per_month") && !fact(p, "users")
|
|
171
|
+
? [{ statement: "Expected usage is unknown, so scale can't be assessed.", rationale: "Usage drives capacity, limits and cost.", action: "Tell us in the chat roughly how many people use it and how often.", components: [] }]
|
|
172
|
+
: [],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "MNT-001",
|
|
176
|
+
attribute: "maintainability",
|
|
177
|
+
severity: "minor",
|
|
178
|
+
check: (p) => {
|
|
179
|
+
const loose = (p.needs || []).filter((r) => r.kind === "nonfunctional" && !r.measure);
|
|
180
|
+
return loose.length
|
|
181
|
+
? [{ statement: `${loose.length} quality requirement${loose.length > 1 ? "s have" : " has"} no measurable target.`, rationale: "A target makes it testable and lets the review say met or not met.", action: `Add a metric and target to: ${loose.map((r) => `“${r.statement}”`).join(", ")}.`, components: [] }]
|
|
182
|
+
: [];
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: "MNT-002",
|
|
187
|
+
attribute: "maintainability",
|
|
188
|
+
severity: "minor",
|
|
189
|
+
check: (p) => {
|
|
190
|
+
const unowned = (p.decisions || []).filter((d) => d.status === "Accepted" && !d.owner?.trim());
|
|
191
|
+
return unowned.length
|
|
192
|
+
? [{ statement: `${unowned.length} accepted decision${unowned.length > 1 ? "s have" : " has"} no owner.`, rationale: "Someone must be accountable for each choice before build.", action: "Assign an owner in Design → Decisions.", components: [] }]
|
|
193
|
+
: [];
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "CST-001",
|
|
198
|
+
attribute: "cost",
|
|
199
|
+
severity: "info",
|
|
200
|
+
check: (p) =>
|
|
201
|
+
p.nodes.length && !fact(p, "budget.per_month") && !hasNeed(p, "cost")
|
|
202
|
+
? [{ statement: "No budget stated.", rationale: "Without one, the estimate can't flag an option as too expensive.", action: "Mention a monthly running budget, or a range, in the chat.", components: [] }]
|
|
203
|
+
: [],
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "DSN-001",
|
|
207
|
+
attribute: "maintainability",
|
|
208
|
+
severity: "info",
|
|
209
|
+
check: (p) => {
|
|
210
|
+
const sketch = p.nodes.filter((n) => n.data.kind === "resource");
|
|
211
|
+
return sketch.length
|
|
212
|
+
? [{ statement: `${sketch.length} block${sketch.length > 1 ? "s are" : " is"} sketch-only and can't be test-run yet.`, rationale: "Sketch blocks describe intent; runnable fixtures exist only for some workflows.", action: "Keep designing; assign a runnable behaviour in block detail when one exists.", components: sketch.map((n) => n.id) }]
|
|
213
|
+
: [];
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
const keyOf = (rule, components) => `${rule}:${[...components].sort().join(",")}`;
|
|
219
|
+
|
|
220
|
+
// Findings are recomputed from current data; accepted risks persist by rule + components.
|
|
221
|
+
export function review(full) {
|
|
222
|
+
const notes = new Set(full.nodes.filter((n) => n.data.kind === "note").map((n) => n.id));
|
|
223
|
+
const project = { ...full, nodes: full.nodes.filter((n) => !notes.has(n.id)), edges: full.edges.filter((e) => !notes.has(e.source) && !notes.has(e.target)) };
|
|
224
|
+
const accepted = project.reviewAccepted || {};
|
|
225
|
+
return RULES.flatMap((rule) =>
|
|
226
|
+
rule.check(project).map((f, i) => {
|
|
227
|
+
const key = keyOf(rule.id, f.components);
|
|
228
|
+
return {
|
|
229
|
+
id: `${rule.id}-${i + 1}`,
|
|
230
|
+
key,
|
|
231
|
+
rule: rule.id,
|
|
232
|
+
version: RULES_VERSION,
|
|
233
|
+
attribute: rule.attribute,
|
|
234
|
+
severity: rule.severity,
|
|
235
|
+
...f,
|
|
236
|
+
source: `AtMem AI Architect rule ${rule.id} v${RULES_VERSION}`,
|
|
237
|
+
status: accepted[key] ? "accepted" : "open",
|
|
238
|
+
...(accepted[key] ? { acceptance: accepted[key] } : {}),
|
|
239
|
+
};
|
|
240
|
+
}),
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function acceptRisk(project, finding, reason, owner) {
|
|
245
|
+
if (!String(reason || "").trim() || !String(owner || "").trim()) throw Error("Accepting a risk needs a reason and an owner.");
|
|
246
|
+
return {
|
|
247
|
+
...project,
|
|
248
|
+
reviewAccepted: { ...(project.reviewAccepted || {}), [finding.key]: { reason, owner, date: new Date().toLocaleDateString("en-CA") } },
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export const blockers = (findings) => findings.filter((f) => f.severity === "blocker" && f.status === "open");
|