@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.
Files changed (512) hide show
  1. package/CONSTITUTION.md +144 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +6 -0
  4. package/README.md +31 -2
  5. package/agent-skills/discovery/SKILL.md +48 -0
  6. package/agent-skills/discovery/topics.json +139 -0
  7. package/agent-skills/generation-capabilities.json +760 -0
  8. package/agent-skills/service-lifecycle.json +21 -0
  9. package/agent-skills/specialists/architecture/SKILL.md +15 -0
  10. package/agent-skills/specialists/aws/SKILL.md +17 -0
  11. package/agent-skills/specialists/azure/SKILL.md +7 -0
  12. package/agent-skills/specialists/composition/SKILL.md +17 -0
  13. package/agent-skills/specialists/devops/SKILL.md +10 -0
  14. package/agent-skills/specialists/finops/SKILL.md +7 -0
  15. package/agent-skills/specialists/gcp/SKILL.md +7 -0
  16. package/agent-skills/specialists/ibm/SKILL.md +7 -0
  17. package/agent-skills/specialists/manifest.json +13 -0
  18. package/agent-skills/specialists/pulumi/SKILL.md +7 -0
  19. package/agent-skills/specialists/security/SKILL.md +7 -0
  20. package/agent-skills/specialists/terraform/SKILL.md +14 -0
  21. package/agent-skills/upstream/aws/LICENSE +175 -0
  22. package/agent-skills/upstream/aws/SKILL.md +125 -0
  23. package/agent-skills/upstream/azure/LICENSE +21 -0
  24. package/agent-skills/upstream/azure/SKILL.md +387 -0
  25. package/agent-skills/upstream/gcp/LICENSE +202 -0
  26. package/agent-skills/upstream/gcp/SKILL.md +159 -0
  27. package/agent-skills/upstream/manifest.json +57 -0
  28. package/agent-skills/upstream/pulumi/LICENSE +201 -0
  29. package/agent-skills/upstream/pulumi/SKILL.md +501 -0
  30. package/agent-skills/upstream/terraform/LICENSE +373 -0
  31. package/agent-skills/upstream/terraform/SKILL.md +316 -0
  32. package/agent-skills/workflows/workflows.json +601 -0
  33. package/cli/architect.mjs +3 -0
  34. package/cli/runtime.mjs +115 -0
  35. package/dist/assets/html2canvas.esm-QH1iLAAe.js +22 -0
  36. package/dist/assets/index-BTLHaa3J.css +1 -0
  37. package/dist/assets/index-CQ2C1gKF.js +21338 -0
  38. package/dist/assets/index.es-DTyqIdaO.js +18 -0
  39. package/dist/assets/purify.es-CAi2OJFm.js +3 -0
  40. package/dist/assets/visual-export-Ox0j5xj5.js +173 -0
  41. package/dist/favicon.svg +2 -0
  42. package/dist/fonts/DM-Sans-OFL.txt +93 -0
  43. package/dist/fonts/Manrope-OFL.txt +93 -0
  44. package/dist/fonts/NotoSans-Regular.ttf +0 -0
  45. package/dist/fonts/OFL.txt +94 -0
  46. package/dist/fonts/dm-sans-latin-wght-normal.woff2 +0 -0
  47. package/dist/fonts/manrope-latin-wght-normal.woff2 +0 -0
  48. package/dist/index.html +4 -0
  49. package/dist/licenses/atmem-APACHE-2.0.txt +202 -0
  50. package/dist/licenses/aws-architect-LICENSE.txt +17 -0
  51. package/dist/licenses/aws-mcp-LICENSE.txt +175 -0
  52. package/dist/licenses/aws-mcp-NOTICE.txt +1 -0
  53. package/dist/licenses/cloud-emulation-notices.txt +36619 -0
  54. package/dist/licenses/dependency-notices.txt +6379 -0
  55. package/dist/licenses/emulation-notices.txt +717 -0
  56. package/dist/licenses/github-copilot-sdk-LICENSE.txt +21 -0
  57. package/dist/licenses/memory-dependency-notices.txt +3319 -0
  58. package/dist/licenses/openarchflow-LICENSE.txt +21 -0
  59. package/dist/licenses/python-dependency-notices.txt +11951 -0
  60. package/docs/upstream-lock.json +26 -0
  61. package/knowledge/engineering-practices.json +536 -0
  62. package/package.json +30 -4
  63. package/packaging/uv-release-lock.json +54 -0
  64. package/public/licenses/atmem-APACHE-2.0.txt +202 -0
  65. package/public/licenses/aws-architect-LICENSE.txt +17 -0
  66. package/public/licenses/aws-mcp-LICENSE.txt +175 -0
  67. package/public/licenses/aws-mcp-NOTICE.txt +1 -0
  68. package/public/licenses/cloud-emulation-notices.txt +36619 -0
  69. package/public/licenses/dependency-notices.txt +6379 -0
  70. package/public/licenses/emulation-notices.txt +717 -0
  71. package/public/licenses/github-copilot-sdk-LICENSE.txt +21 -0
  72. package/public/licenses/memory-dependency-notices.txt +3319 -0
  73. package/public/licenses/openarchflow-LICENSE.txt +21 -0
  74. package/public/licenses/python-dependency-notices.txt +11951 -0
  75. package/scripts/terraform-render.mjs +8 -0
  76. package/server/__init__.py +0 -0
  77. package/server/abstraction.py +28 -0
  78. package/server/agent_runtime.py +153 -0
  79. package/server/app.py +257 -0
  80. package/server/architect.py +112 -0
  81. package/server/architecture_activity.py +67 -0
  82. package/server/attachments.py +101 -0
  83. package/server/cloud-emulation-requirements.txt +29 -0
  84. package/server/codex_app_server.py +219 -0
  85. package/server/codex_engineering_backend.py +266 -0
  86. package/server/codex_worker_policy.py +193 -0
  87. package/server/contracts.py +315 -0
  88. package/server/copilot_worker.py +16 -0
  89. package/server/dependency_mirror.py +54 -0
  90. package/server/design_patch.py +43 -0
  91. package/server/emulation-requirements.txt +8 -0
  92. package/server/emulation_adapters.py +20 -0
  93. package/server/emulation_routes.py +98 -0
  94. package/server/emulation_worker.py +351 -0
  95. package/server/engineering_artifacts.py +221 -0
  96. package/server/engineering_assets/application-runtime.json +4372 -0
  97. package/server/engineering_assets/architecture_work.schema.json +79 -0
  98. package/server/engineering_assets/materialize_runtime.py +62 -0
  99. package/server/engineering_assets/prepare_tools.py +96 -0
  100. package/server/engineering_assets/sql_validation.py +59 -0
  101. package/server/engineering_contexts.py +120 -0
  102. package/server/engineering_delivery.py +210 -0
  103. package/server/engineering_finalization.py +210 -0
  104. package/server/engineering_handoff.py +172 -0
  105. package/server/engineering_jobs.py +206 -0
  106. package/server/engineering_knowledge.py +75 -0
  107. package/server/engineering_reconciliation.py +159 -0
  108. package/server/engineering_routes.py +483 -0
  109. package/server/engineering_sessions.py +273 -0
  110. package/server/engineering_tools.py +166 -0
  111. package/server/generation_lint.py +18 -0
  112. package/server/local_providers.py +185 -0
  113. package/server/local_validation.py +236 -0
  114. package/server/memory-requirements.lock +181 -0
  115. package/server/memory-requirements.txt +6 -0
  116. package/server/memory_context.py +58 -0
  117. package/server/memory_routes.py +141 -0
  118. package/server/memory_service.py +306 -0
  119. package/server/memory_worker.py +129 -0
  120. package/server/openai_voice.py +80 -0
  121. package/server/orca_handoff.py +90 -0
  122. package/server/packaged_app.py +9 -0
  123. package/server/pricing.py +190 -0
  124. package/server/process_control_observation.py +31 -0
  125. package/server/prompts.py +136 -0
  126. package/server/public_faq_emulation_worker.py +44 -0
  127. package/server/requirements.lock +1618 -0
  128. package/server/requirements.txt +68 -0
  129. package/server/runtime_dependency_locks.py +27 -0
  130. package/server/runtime_evidence.py +93 -0
  131. package/server/settings.py +14 -0
  132. package/server/skills.py +103 -0
  133. package/server/storage_emulation_worker.py +160 -0
  134. package/server/structured_emulation_worker.py +48 -0
  135. package/server/terraform_routes.py +118 -0
  136. package/server/upstream/__init__.py +0 -0
  137. package/server/upstream/agent/__init__.py +1 -0
  138. package/server/upstream/agent/aws_architect.py +600 -0
  139. package/server/upstream/agent/config/__init__.py +1 -0
  140. package/server/upstream/agent/config/agent_config.py +82 -0
  141. package/server/upstream/agent/config/prompts.py +138 -0
  142. package/server/upstream/agent/rate_limiter.py +62 -0
  143. package/server/upstream/utils/__init__.py +1 -0
  144. package/server/upstream/utils/file_writer.py +43 -0
  145. package/server/validation_applicability.py +85 -0
  146. package/server/validation_jobs.py +252 -0
  147. package/server/validation_workers.py +48 -0
  148. package/server/voice_routes.py +91 -0
  149. package/server/voice_worker.py +26 -0
  150. package/server/windows_snapshot.py +119 -0
  151. package/specs/028-convergence-evaluation/evidence/20261110-ibm-invoice-native/registry-structured/qualification.json +72 -0
  152. package/specs/028-convergence-evaluation/evidence/20261110-invoice-runtime/durable-current/receipt.json +61 -0
  153. package/specs/029-general-purpose-engine/evidence/20261111-runtime-dependencies/sdk/receipt.json +22 -0
  154. package/src/ArchitectureActivity.jsx +54 -0
  155. package/src/ArchitectureWorkPanel.jsx +39 -0
  156. package/src/ArtifactChecks.jsx +49 -0
  157. package/src/BlockRequirements.jsx +13 -0
  158. package/src/BrainActivity.jsx +11 -0
  159. package/src/ChatMessage.jsx +29 -0
  160. package/src/ChatResizeHandle.jsx +6 -0
  161. package/src/CodePreview.jsx +8 -0
  162. package/src/ComponentReview.jsx +31 -0
  163. package/src/DeliveryViews.jsx +59 -0
  164. package/src/EmulationPanel.jsx +44 -0
  165. package/src/EngagementViews.jsx +387 -0
  166. package/src/EngineeringPanel.jsx +151 -0
  167. package/src/FileTree.jsx +7 -0
  168. package/src/FinalChecklist.jsx +15 -0
  169. package/src/HelpNote.jsx +3 -0
  170. package/src/ImplementationDelivery.jsx +91 -0
  171. package/src/JourneyViews.jsx +403 -0
  172. package/src/LocationChoices.jsx +12 -0
  173. package/src/MemoryPanel.jsx +58 -0
  174. package/src/OpenAIVoicePanel.jsx +66 -0
  175. package/src/OutputsView.jsx +71 -0
  176. package/src/ProjectChecks.jsx +44 -0
  177. package/src/TerraformView.jsx +57 -0
  178. package/src/WorkbenchDialogs.jsx +532 -0
  179. package/src/activity-presentation.mjs +28 -0
  180. package/src/ai-qualification-scenarios.mjs +31 -0
  181. package/src/approval-application-files.mjs +32 -0
  182. package/src/approval-applications.mjs +82 -0
  183. package/src/approval-spec-kit.mjs +30 -0
  184. package/src/architecture-activity.css +28 -0
  185. package/src/architecture-activity.mjs +63 -0
  186. package/src/architecture-work.mjs +176 -0
  187. package/src/artifact-work.mjs +103 -0
  188. package/src/async-documents.mjs +191 -0
  189. package/src/atflow.css +1140 -0
  190. package/src/atforma.css +748 -0
  191. package/src/atmem-tokens.css +79 -0
  192. package/src/aws-preflight.mjs +60 -0
  193. package/src/aws-public-faq.mjs +102 -0
  194. package/src/aws-validation-scenarios.mjs +10 -0
  195. package/src/aws-validation.mjs +48 -0
  196. package/src/azure-plan-mocks.mjs +25 -0
  197. package/src/azure-structured.mjs +133 -0
  198. package/src/bedrock-summary.mjs +152 -0
  199. package/src/bounded-rag.mjs +60 -0
  200. package/src/brand.css +9 -0
  201. package/src/canvas-view.mjs +28 -0
  202. package/src/capability-composition.mjs +44 -0
  203. package/src/chat-attachments.mjs +13 -0
  204. package/src/cloud-provider-locks.mjs +9 -0
  205. package/src/cloud-storage-scenarios.mjs +2 -0
  206. package/src/component-review.mjs +55 -0
  207. package/src/composition-spec-kit.mjs +30 -0
  208. package/src/consultation-context.mjs +15 -0
  209. package/src/consultation.mjs +52 -0
  210. package/src/container-contracts.mjs +5 -0
  211. package/src/container-inspection.mjs +75 -0
  212. package/src/container-runtime.mjs +30 -0
  213. package/src/container-security-inspection.mjs +49 -0
  214. package/src/container-terraform.mjs +109 -0
  215. package/src/conversation.mjs +18 -0
  216. package/src/core.mjs +863 -0
  217. package/src/cross-cloud-storage.mjs +100 -0
  218. package/src/delivery-capabilities.mjs +25 -0
  219. package/src/delivery.mjs +58 -0
  220. package/src/deployment-ownership.mjs +10 -0
  221. package/src/deployment-region.mjs +11 -0
  222. package/src/derive.mjs +31 -0
  223. package/src/design-build-flow.mjs +10 -0
  224. package/src/diagram-attention.mjs +24 -0
  225. package/src/discovery.mjs +359 -0
  226. package/src/document.mjs +170 -0
  227. package/src/emulation-feedback.mjs +61 -0
  228. package/src/emulation-guidance.mjs +37 -0
  229. package/src/engagement.mjs +274 -0
  230. package/src/engineering-client.mjs +73 -0
  231. package/src/engineering-defaults.mjs +17 -0
  232. package/src/engineering.css +31 -0
  233. package/src/enterprise-application.mjs +76 -0
  234. package/src/enterprise-aws-host-infrastructure.mjs +62 -0
  235. package/src/enterprise-aws-host-tests.mjs +15 -0
  236. package/src/enterprise-aws-infrastructure-tests.mjs +38 -0
  237. package/src/enterprise-aws-infrastructure.mjs +138 -0
  238. package/src/enterprise-google-infrastructure.mjs +84 -0
  239. package/src/enterprise-google-tests.mjs +25 -0
  240. package/src/estimate.mjs +190 -0
  241. package/src/existing-references.mjs +18 -0
  242. package/src/external-identities.mjs +17 -0
  243. package/src/faq-lambda-build.mjs +28 -0
  244. package/src/final-package.mjs +34 -0
  245. package/src/gcp-structured.mjs +104 -0
  246. package/src/generation-provider.mjs +7 -0
  247. package/src/generation-quality.mjs +69 -0
  248. package/src/handoff.mjs +141 -0
  249. package/src/ibm-registry.mjs +12 -0
  250. package/src/ibm-structured.mjs +103 -0
  251. package/src/identifiers.mjs +15 -0
  252. package/src/implementation-map.mjs +14 -0
  253. package/src/implementation-outcome.mjs +8 -0
  254. package/src/incomplete-terraform.mjs +9 -0
  255. package/src/integrations.ts +137 -0
  256. package/src/interface-contracts.mjs +30 -0
  257. package/src/invoice-application.mjs +66 -0
  258. package/src/invoice-aws-operations.mjs +136 -0
  259. package/src/invoice-aws.mjs +143 -0
  260. package/src/invoice-azure-operations.mjs +147 -0
  261. package/src/invoice-azure.mjs +134 -0
  262. package/src/invoice-builder.mjs +35 -0
  263. package/src/invoice-dependencies.mjs +43 -0
  264. package/src/invoice-gcp-operations.mjs +178 -0
  265. package/src/invoice-gcp.mjs +104 -0
  266. package/src/invoice-ibm-operations.mjs +278 -0
  267. package/src/invoice-ibm.mjs +142 -0
  268. package/src/invoice-operations.mjs +143 -0
  269. package/src/invoice-plan.mjs +45 -0
  270. package/src/invoice-region.mjs +6 -0
  271. package/src/invoice-spec-kit.mjs +61 -0
  272. package/src/invoice-state.mjs +7 -0
  273. package/src/invoice-terraform.mjs +29 -0
  274. package/src/invoice-tests.mjs +122 -0
  275. package/src/journey-workspace.css +106 -0
  276. package/src/journey.mjs +97 -0
  277. package/src/levels.mjs +145 -0
  278. package/src/local-agent.mjs +184 -0
  279. package/src/main.jsx +2481 -0
  280. package/src/model-policy-preflight.mjs +34 -0
  281. package/src/openai-voice.mjs +102 -0
  282. package/src/output-bundle.mjs +13 -0
  283. package/src/ownership-inspection.mjs +28 -0
  284. package/src/pack.mjs +201 -0
  285. package/src/pricing.mjs +85 -0
  286. package/src/production-state.mjs +8 -0
  287. package/src/project-copy.mjs +10 -0
  288. package/src/project-file.mjs +160 -0
  289. package/src/project-links.mjs +3 -0
  290. package/src/protected-state.mjs +43 -0
  291. package/src/public-faq-builder.mjs +19 -0
  292. package/src/public-faq-clouds.mjs +251 -0
  293. package/src/public-faq.mjs +122 -0
  294. package/src/pulumi.mjs +123 -0
  295. package/src/python-container.mjs +25 -0
  296. package/src/python-runtime.mjs +50 -0
  297. package/src/rag-handler.mjs +43 -0
  298. package/src/rag-web-assets.mjs +52 -0
  299. package/src/rag-web.mjs +68 -0
  300. package/src/records.mjs +177 -0
  301. package/src/resources.mjs +163 -0
  302. package/src/review.mjs +252 -0
  303. package/src/runtime/EnterpriseDocument.java +89 -0
  304. package/src/runtime/approved_http_destination.py +72 -0
  305. package/src/runtime/aws-lambda-qualification-lock.json +7 -0
  306. package/src/runtime/aws-python-lock.json +82 -0
  307. package/src/runtime/aws-web-python-lock.json +887 -0
  308. package/src/runtime/aws_json_model.py +42 -0
  309. package/src/runtime/azure-python-lock.json +863 -0
  310. package/src/runtime/azure-sources.json +8 -0
  311. package/src/runtime/azure-test-lock.json +23 -0
  312. package/src/runtime/azure_acceptance.py +192 -0
  313. package/src/runtime/azure_structured.py +153 -0
  314. package/src/runtime/bounded_rag.py +126 -0
  315. package/src/runtime/catalog_admin.py +70 -0
  316. package/src/runtime/cloud-sql-proxy-lock.json +8 -0
  317. package/src/runtime/cloud-sql-proxy-security-review.json +30 -0
  318. package/src/runtime/connector_cli.py +50 -0
  319. package/src/runtime/connector_sync.py +70 -0
  320. package/src/runtime/durable-python-lock.json +247 -0
  321. package/src/runtime/durable_workflow.py +325 -0
  322. package/src/runtime/enterprise-application-manifest.json +110 -0
  323. package/src/runtime/enterprise-application-sources.json +266 -0
  324. package/src/runtime/enterprise-auth-python-lock.json +592 -0
  325. package/src/runtime/enterprise-aws-host-bootstrap-source.json +1 -0
  326. package/src/runtime/enterprise-aws-host-probe-source.json +1 -0
  327. package/src/runtime/enterprise-aws-task-template.json +235 -0
  328. package/src/runtime/enterprise-document-lock.json +18 -0
  329. package/src/runtime/enterprise-document.Dockerfile +15 -0
  330. package/src/runtime/enterprise-runtime-aws-python-lock.json +1115 -0
  331. package/src/runtime/enterprise-runtime-azure-python-lock.json +1106 -0
  332. package/src/runtime/enterprise-runtime-gcp-python-lock.json +1060 -0
  333. package/src/runtime/enterprise-runtime-ibm-python-lock.json +1096 -0
  334. package/src/runtime/enterprise-runtime-python-lock.json +1058 -0
  335. package/src/runtime/enterprise-runtime.Dockerfile +15 -0
  336. package/src/runtime/enterprise-web-app.js +1 -0
  337. package/src/runtime/enterprise-web-entry.js +105 -0
  338. package/src/runtime/enterprise-web-lock.json +29 -0
  339. package/src/runtime/enterprise-web-notices.txt +229 -0
  340. package/src/runtime/enterprise-web.css +4 -0
  341. package/src/runtime/enterprise-web.html +8 -0
  342. package/src/runtime/enterprise_audit.py +91 -0
  343. package/src/runtime/enterprise_audit_schema.sql +25 -0
  344. package/src/runtime/enterprise_auth_schema.sql +67 -0
  345. package/src/runtime/enterprise_auth_store.py +241 -0
  346. package/src/runtime/enterprise_aws_host_bootstrap.py +120 -0
  347. package/src/runtime/enterprise_aws_host_probe.py +105 -0
  348. package/src/runtime/enterprise_aws_hosts.py +136 -0
  349. package/src/runtime/enterprise_aws_jobs.py +300 -0
  350. package/src/runtime/enterprise_aws_objects.py +93 -0
  351. package/src/runtime/enterprise_aws_transfer.py +83 -0
  352. package/src/runtime/enterprise_azure_batch_api.py +128 -0
  353. package/src/runtime/enterprise_azure_host.py +599 -0
  354. package/src/runtime/enterprise_azure_jobs.py +294 -0
  355. package/src/runtime/enterprise_azure_objects.py +234 -0
  356. package/src/runtime/enterprise_azure_pool.py +161 -0
  357. package/src/runtime/enterprise_azure_transfer.py +122 -0
  358. package/src/runtime/enterprise_configuration.py +136 -0
  359. package/src/runtime/enterprise_document.py +116 -0
  360. package/src/runtime/enterprise_document_cli.py +32 -0
  361. package/src/runtime/enterprise_document_contract.py +54 -0
  362. package/src/runtime/enterprise_document_job.py +109 -0
  363. package/src/runtime/enterprise_document_jobs.py +122 -0
  364. package/src/runtime/enterprise_document_jobs_schema.sql +23 -0
  365. package/src/runtime/enterprise_extraction_cache.py +186 -0
  366. package/src/runtime/enterprise_extraction_schema.sql +17 -0
  367. package/src/runtime/enterprise_google_jobs.py +275 -0
  368. package/src/runtime/enterprise_google_transfer.py +85 -0
  369. package/src/runtime/enterprise_http.py +212 -0
  370. package/src/runtime/enterprise_identity.py +52 -0
  371. package/src/runtime/enterprise_knowledge.py +152 -0
  372. package/src/runtime/enterprise_models.py +94 -0
  373. package/src/runtime/enterprise_native.py +181 -0
  374. package/src/runtime/enterprise_oauth.py +132 -0
  375. package/src/runtime/enterprise_operator.py +160 -0
  376. package/src/runtime/enterprise_parser_service.py +136 -0
  377. package/src/runtime/enterprise_parser_transport.py +68 -0
  378. package/src/runtime/enterprise_queries.py +255 -0
  379. package/src/runtime/enterprise_queries_schema.sql +27 -0
  380. package/src/runtime/enterprise_query_service.py +111 -0
  381. package/src/runtime/enterprise_secrets.py +110 -0
  382. package/src/runtime/enterprise_sessions.py +95 -0
  383. package/src/runtime/enterprise_sources.py +260 -0
  384. package/src/runtime/enterprise_web.py +39 -0
  385. package/src/runtime/enterprise_worker.py +68 -0
  386. package/src/runtime/export_contract_acceptance.py +18 -0
  387. package/src/runtime/faq-output-contract.json +40 -0
  388. package/src/runtime/faq-web-app.js +41 -0
  389. package/src/runtime/faq-web.css +1 -0
  390. package/src/runtime/faq-web.html +6 -0
  391. package/src/runtime/gcp-sources.json +9 -0
  392. package/src/runtime/gcp_acceptance.py +110 -0
  393. package/src/runtime/gcp_structured.py +150 -0
  394. package/src/runtime/http_acceptance.py +39 -0
  395. package/src/runtime/iap-web-app.js +36 -0
  396. package/src/runtime/iap-web-source.json +1 -0
  397. package/src/runtime/ibm-python-lock.json +856 -0
  398. package/src/runtime/ibm-sources.json +9 -0
  399. package/src/runtime/ibm_acceptance.py +133 -0
  400. package/src/runtime/ibm_structured.py +113 -0
  401. package/src/runtime/invoice-azapi-provider-lock.json +7 -0
  402. package/src/runtime/invoice-ibm-provider-lock.json +7 -0
  403. package/src/runtime/invoice-parser-lock.json +1199 -0
  404. package/src/runtime/invoice-parser-python-lock.json +56 -0
  405. package/src/runtime/invoice-parser.Dockerfile +190 -0
  406. package/src/runtime/invoice-sources.json +58 -0
  407. package/src/runtime/invoice-test-sources.json +24 -0
  408. package/src/runtime/invoice-web.css +2 -0
  409. package/src/runtime/invoice-web.html +12 -0
  410. package/src/runtime/invoice-web.js +112 -0
  411. package/src/runtime/invoice_configuration.py +17 -0
  412. package/src/runtime/invoice_document.py +95 -0
  413. package/src/runtime/invoice_extraction.py +96 -0
  414. package/src/runtime/invoice_http.py +129 -0
  415. package/src/runtime/invoice_identity.py +5 -0
  416. package/src/runtime/invoice_maintenance.py +40 -0
  417. package/src/runtime/invoice_native.py +77 -0
  418. package/src/runtime/invoice_object_store.py +153 -0
  419. package/src/runtime/invoice_operations.py +108 -0
  420. package/src/runtime/invoice_operator.py +79 -0
  421. package/src/runtime/invoice_operator_core.py +39 -0
  422. package/src/runtime/invoice_operator_job.py +147 -0
  423. package/src/runtime/invoice_parser_cli.py +17 -0
  424. package/src/runtime/invoice_parser_identity.py +39 -0
  425. package/src/runtime/invoice_parser_service.py +89 -0
  426. package/src/runtime/invoice_parser_transport.py +89 -0
  427. package/src/runtime/invoice_processing.py +24 -0
  428. package/src/runtime/invoice_retention.py +16 -0
  429. package/src/runtime/invoice_runtime_secrets.py +43 -0
  430. package/src/runtime/invoice_stage.py +80 -0
  431. package/src/runtime/invoice_web.py +39 -0
  432. package/src/runtime/invoice_worker.py +50 -0
  433. package/src/runtime/lambda_faq.py +5 -0
  434. package/src/runtime/parser-requirements.lock +4 -0
  435. package/src/runtime/policy_review.py +18 -0
  436. package/src/runtime/postgres-qualification-lock.json +21 -0
  437. package/src/runtime/public-faq-sources.json +22 -0
  438. package/src/runtime/public_catalog_admin.py +122 -0
  439. package/src/runtime/public_catalog_store.py +125 -0
  440. package/src/runtime/public_faq.py +144 -0
  441. package/src/runtime/public_faq_app.py +49 -0
  442. package/src/runtime/python-container-lock.json +18 -0
  443. package/src/runtime/rag-sources.json +10 -0
  444. package/src/runtime/rag_acceptance.py +60 -0
  445. package/src/runtime/rag_aws_adapter.py +95 -0
  446. package/src/runtime/rds-ca-source.json +9 -0
  447. package/src/runtime/rds-global-ca.pem +2888 -0
  448. package/src/runtime/state-contract-check-source.json +4 -0
  449. package/src/runtime/state_contract_check.py +338 -0
  450. package/src/runtime/structured-source.json +1 -0
  451. package/src/runtime/structured-web-app.js +71 -0
  452. package/src/runtime/structured-web-source.json +1 -0
  453. package/src/runtime/structured_http.py +105 -0
  454. package/src/runtime/structured_output.py +48 -0
  455. package/src/runtime/web-python-lock.json +802 -0
  456. package/src/runtime/web_user_admin.py +44 -0
  457. package/src/runtime/workflow_application.py +62 -0
  458. package/src/runtime/workflow_bindings.py +128 -0
  459. package/src/runtime/workflow_configuration.py +40 -0
  460. package/src/runtime/workflow_connection.py +105 -0
  461. package/src/runtime/workflow_contract.py +54 -0
  462. package/src/runtime/workflow_database.py +94 -0
  463. package/src/runtime/workflow_errors.py +6 -0
  464. package/src/runtime/workflow_http.py +178 -0
  465. package/src/runtime/workflow_identity.py +69 -0
  466. package/src/runtime/workflow_payload.py +118 -0
  467. package/src/runtime/workflow_process_stage.py +88 -0
  468. package/src/runtime/workflow_profile.py +91 -0
  469. package/src/runtime/workflow_review.py +106 -0
  470. package/src/runtime/workflow_role_bootstrap.py +104 -0
  471. package/src/runtime/workflow_schema.sql +72 -0
  472. package/src/runtime/workflow_worker.py +142 -0
  473. package/src/runtime-settings.mjs +15 -0
  474. package/src/s3-access.mjs +14 -0
  475. package/src/source-contracts.mjs +17 -0
  476. package/src/spec-kit.mjs +186 -0
  477. package/src/state-custody.mjs +32 -0
  478. package/src/state-inspection.mjs +45 -0
  479. package/src/state-spec-kit.mjs +13 -0
  480. package/src/storage-tests.mjs +25 -0
  481. package/src/structured-api.mjs +133 -0
  482. package/src/structured-contract.mjs +19 -0
  483. package/src/structured-operation-policy.mjs +2 -0
  484. package/src/structured-web.mjs +17 -0
  485. package/src/style.css +1 -0
  486. package/src/system-description.mjs +26 -0
  487. package/src/terraform-admission.mjs +66 -0
  488. package/src/terraform-composition.mjs +83 -0
  489. package/src/terraform-expressions.mjs +46 -0
  490. package/src/terraform-validation-coverage.mjs +11 -0
  491. package/src/terraform.mjs +128 -0
  492. package/src/text-statistics.mjs +51 -0
  493. package/src/upstream/openarchflow/format-payload.ts +26 -0
  494. package/src/upstream/openarchflow/import/mermaid.ts +205 -0
  495. package/src/upstream/openarchflow/import/sql-ddl.ts +109 -0
  496. package/src/upstream/openarchflow/layout.ts +54 -0
  497. package/src/upstream/openarchflow/providers/aws.ts +196 -0
  498. package/src/upstream/openarchflow/providers/azure.ts +126 -0
  499. package/src/upstream/openarchflow/providers/cloudnative.ts +71 -0
  500. package/src/upstream/openarchflow/providers/generic.ts +227 -0
  501. package/src/upstream/openarchflow/providers/integration.ts +22 -0
  502. package/src/upstream/openarchflow/providers/types.ts +2 -0
  503. package/src/upstream/openarchflow/templates.ts +937 -0
  504. package/src/upstream-catalog.mjs +10 -0
  505. package/src/useChatScroll.jsx +37 -0
  506. package/src/useEmulation.jsx +36 -0
  507. package/src/useEngineeringStatus.mjs +17 -0
  508. package/src/validation-receipt.mjs +10 -0
  509. package/src/visual-export.mjs +92 -0
  510. package/src/voice-conversation.mjs +117 -0
  511. package/src/work-intake.mjs +24 -0
  512. package/src/workflow-runtime-inventory.mjs +3 -0
@@ -0,0 +1,143 @@
1
+ import {invoiceBuilder,v,r,l}from'./invoice-builder.mjs';
2
+ import {awsInvoiceOperations}from'./invoice-aws-operations.mjs';
3
+ import {CLOUD_PROVIDER_LOCKS}from'./cloud-provider-locks.mjs';
4
+
5
+ // Isolated adapter under qualification. Not registered as a complete invoice
6
+ // capability until application, operations and whole-export checks pass.
7
+ export function awsInvoiceTerraform(project,row,environment='staging'){
8
+ if(row.provider!=='aws')throw Error('AWS invoice adapter cannot substitute another provider');
9
+ const b=invoiceBuilder(project,row,environment),{input,add,region}=b,n=row.address;
10
+ const account=input('account_id','Expected commercial AWS account, using an approved operator identity.','123456789012','^[0-9]{12}$');
11
+ const zone=input('public_zone_id','Existing owned public Route53 zone. Referenced, never recreated or imported.','Z0123456789EXAMPLE','^[A-Z0-9]{5,40}$');
12
+ const model=input('model_id','Reviewed direct regional Bedrock foundation model ID. Verify regional availability, quotas, terms and retention-none policy.','anthropic.reviewed-model-v1:0','^[a-z0-9][a-z0-9.-]*:[0-9]+$');
13
+ const endpoint=input('accounting_endpoint','Existing accounting HTTPS adapter endpoint; no vendor or replacement system is provisioned.','https://accounting.example.test/actions','^https://[A-Za-z0-9.-]+(:[0-9]{1,5})?(/[^?#@ ]*)?$');
14
+ const scope=input('accounting_scope','Reviewed opaque destination account/scope, bound into every approval.','owned-account','^[A-Za-z0-9_.:/-]{1,200}$');
15
+ const seed=b.seed;
16
+ for(const key of ['runtime_image','parser_image']){
17
+ input(key,'Published, reviewed immutable image in the emitted ECR repository. Empty during foundation setup.',key==='runtime_image'?'123456789012.dkr.ecr.ap-southeast-2.amazonaws.com/reviewed-runtime@sha256:'+'a'.repeat(64):'123456789012.dkr.ecr.ap-southeast-2.amazonaws.com/reviewed-parser@sha256:'+'b'.repeat(64));
18
+ b.variable[key].default='';b.variable[key].validation={condition:'${!('+(key==='runtime_image'?'var.applications_enabled || var.operations_enabled':'var.applications_enabled')+') || (can(regex("@sha256:[0-9a-f]{64}$",var.'+key+')) && !endswith(var.'+key+',"@sha256:'+ '0'.repeat(64)+'"))}',error_message:'Application release requires a reviewed immutable image digest.'};
19
+ b.testExpressions[key]='format("123456789012.dkr.ecr.'+(row.region||'ap-southeast-2')+'.amazonaws.com/invoice-%s-'+(key==='runtime_image'?'runtime':'parser')+'@sha256:'+(key==='runtime_image'?'a':'b').repeat(64)+'",substr(sha256('+JSON.stringify(seed)+'),0,12))';
20
+ b.resource.terraform_data.review_gate.lifecycle.precondition.push({condition:'${!('+(key==='runtime_image'?'var.applications_enabled || var.operations_enabled':'var.applications_enabled')+') || startswith(var.'+key+',format("%s.dkr.ecr.%s.amazonaws.com/%s-'+(key==='runtime_image'?'runtime':'parser')+'@sha256:",var.account_id,var.region,local.name))}',error_message:'Use the package-owned regional ECR repository; a digest from another repository is not an admitted release.'});
21
+ }
22
+ b.variable.replay_review_base64={type:'string',default:'',description:'Scoped expiring operator receipt for the real endpoint idempotency contract. Empty disables automatic replay of uncertain deliveries.'};b.examples.replay_review_base64='';
23
+ b.data.aws_caller_identity={destination:{}};b.data.aws_availability_zones={destination:{state:'available',filter:{name:'zone-type',values:['availability-zone']}}};b.data.aws_route53_zone={destination:{zone_id:zone,private_zone:false}};
24
+ b.resource.terraform_data.review_gate.lifecycle.precondition.push({condition:'${data.aws_caller_identity.destination.account_id == var.account_id && !data.aws_route53_zone.destination.private_zone && length(data.aws_availability_zones.destination.names) >= 2}',error_message:'Use the reviewed account, owned public DNS zone and at least two regional availability zones.'});
25
+ Object.assign(b.locals,{api_hostname:'${format("%s.%s",local.name,trimsuffix(data.aws_route53_zone.destination.name,"."))}',parser_zone:'${format("%s.internal.%s",local.name,trimsuffix(data.aws_route53_zone.destination.name,"."))}',parser_hostname:'${format("parse.%s",local.parser_zone)}',public_origin:'${format("https://%s",local.api_hostname)}',parser_endpoint:'${format("https://%s/extract",local.parser_hostname)}',destination_contract:{id:'normalized-action-v1',endpointSha256:'${sha256(var.accounting_endpoint)}',scope,mappingVersion:1},destination_contract_base64:'${base64encode(jsonencode(local.destination_contract))}'});
26
+ add('aws_vpc',n,{cidr_block:'10.82.0.0/16',enable_dns_support:true,enable_dns_hostnames:true,tags:{Name:l('name')}});
27
+ add('aws_internet_gateway',n,{vpc_id:r('aws_vpc',n,'id')});
28
+ for(let i=0;i<2;i++){
29
+ add('aws_subnet',n+'_public_'+i,{vpc_id:r('aws_vpc',n,'id'),availability_zone:'${data.aws_availability_zones.destination.names['+i+']}',cidr_block:'10.82.'+i+'.0/24',map_public_ip_on_launch:false});
30
+ add('aws_subnet',n+'_private_'+i,{vpc_id:r('aws_vpc',n,'id'),availability_zone:'${data.aws_availability_zones.destination.names['+i+']}',cidr_block:'10.82.'+(16+i)+'.0/24',map_public_ip_on_launch:false});
31
+ }
32
+ add('aws_eip',n,{domain:'vpc'});add('aws_nat_gateway',n,{allocation_id:r('aws_eip',n,'id'),subnet_id:r('aws_subnet',n+'_public_0','id'),depends_on:['aws_internet_gateway.'+n]});
33
+ add('aws_route_table',n+'_public',{vpc_id:r('aws_vpc',n,'id')});
34
+ add('aws_route',n+'_public',{route_table_id:r('aws_route_table',n+'_public','id'),destination_cidr_block:'0.0.0.0/0',gateway_id:r('aws_internet_gateway',n,'id')});
35
+ add('aws_route_table',n+'_private',{vpc_id:r('aws_vpc',n,'id')});
36
+ add('aws_route',n+'_private',{route_table_id:r('aws_route_table',n+'_private','id'),destination_cidr_block:'0.0.0.0/0',nat_gateway_id:r('aws_nat_gateway',n,'id')});
37
+ const publicSubnets=[],privateSubnets=[];
38
+ for(let i=0;i<2;i++){publicSubnets.push(r('aws_subnet',n+'_public_'+i,'id'));privateSubnets.push(r('aws_subnet',n+'_private_'+i,'id'));for(const kind of ['public','private'])add('aws_route_table_association',n+'_'+kind+'_'+i,{subnet_id:r('aws_subnet',n+'_'+kind+'_'+i,'id'),route_table_id:r('aws_route_table',n+'_'+kind,'id')});}
39
+ for(const purpose of ['api_lb','api','worker','parser_lb','parser','database'])add('aws_security_group',n+'_'+purpose,{name_prefix:l('name')+'-'+purpose+'-',description:'Owned invoice '+purpose+' boundary',vpc_id:r('aws_vpc',n,'id')});
40
+ const ingress=(name,target,port,source)=>add('aws_vpc_security_group_ingress_rule',n+'_'+name,{security_group_id:r('aws_security_group',n+'_'+target,'id'),ip_protocol:'tcp',from_port:port,to_port:port,...(source==='public'?{cidr_ipv4:'0.0.0.0/0'}:{referenced_security_group_id:r('aws_security_group',n+'_'+source,'id')})});
41
+ const egress=(name,source,port,target)=>add('aws_vpc_security_group_egress_rule',n+'_'+name,{security_group_id:r('aws_security_group',n+'_'+source,'id'),ip_protocol:'tcp',from_port:port,to_port:port,...(target==='internet'?{cidr_ipv4:'0.0.0.0/0'}:{referenced_security_group_id:r('aws_security_group',n+'_'+target,'id')})});
42
+ ingress('public_https','api_lb',443,'public');ingress('api_from_lb','api',8080,'api_lb');egress('lb_to_api','api_lb',8080,'api');
43
+ ingress('parser_from_worker','parser_lb',443,'worker');ingress('parser_from_lb','parser',8080,'parser_lb');egress('lb_to_parser','parser_lb',8080,'parser');
44
+ for(const purpose of ['api','worker']){ingress('database_from_'+purpose,'database',5432,purpose);egress(purpose+'_database',purpose,5432,'database');egress(purpose+'_https',purpose,443,'internet');}
45
+ add('aws_db_subnet_group',n,{name:l('name'),subnet_ids:privateSubnets});
46
+ add('aws_db_parameter_group',n,{name:l('name'),family:'postgres17',parameter:[{name:'rds.force_ssl',value:'1'},{name:'log_statement',value:'none'},{name:'log_min_error_statement',value:'panic'},{name:'log_parameter_max_length_on_error',value:'0'}]});
47
+ add('aws_db_instance',n,{identifier:l('name'),engine:'postgres',engine_version:'17.11',instance_class:'db.t4g.small',allocated_storage:20,max_allocated_storage:100,storage_type:'gp3',storage_encrypted:true,db_name:'invoice',username:'invoice_operator',manage_master_user_password:true,iam_database_authentication_enabled:true,port:5432,publicly_accessible:false,multi_az:true,db_subnet_group_name:r('aws_db_subnet_group',n,'name'),vpc_security_group_ids:[r('aws_security_group',n+'_database','id')],parameter_group_name:r('aws_db_parameter_group',n,'name'),backup_retention_period:7,copy_tags_to_snapshot:true,deletion_protection:true,skip_final_snapshot:false,final_snapshot_identifier:l('name')+'-final',delete_automated_backups:false,auto_minor_version_upgrade:true,allow_major_version_upgrade:false,ca_cert_identifier:'rds-ca-rsa2048-g1',lifecycle:{prevent_destroy:true}});
48
+ add('aws_s3_bucket',n,{bucket:account+'-'+l('name')+'-invoices',force_destroy:false,lifecycle:{prevent_destroy:true}});const bucket=r('aws_s3_bucket',n,'id'),bucketArn=r('aws_s3_bucket',n,'arn'),objects=bucketArn+'/accounts/'+l('account_uuid')+'/invoices/*';
49
+ add('aws_s3_bucket_public_access_block',n,{bucket,block_public_acls:true,block_public_policy:true,ignore_public_acls:true,restrict_public_buckets:true});add('aws_s3_bucket_ownership_controls',n,{bucket,rule:{object_ownership:'BucketOwnerEnforced'}});add('aws_s3_bucket_server_side_encryption_configuration',n,{bucket,rule:{apply_server_side_encryption_by_default:{sse_algorithm:'AES256'}}});add('aws_s3_bucket_versioning',n,{bucket,versioning_configuration:{status:'Enabled'}});
50
+ add('aws_s3_bucket_lifecycle_configuration',n,{bucket,rule:[{id:'invoice-retention',status:'Enabled',filter:{prefix:'accounts/'},expiration:{days:90},noncurrent_version_expiration:{noncurrent_days:1},abort_incomplete_multipart_upload:{days_after_initiation:1}}],depends_on:['aws_s3_bucket_versioning.'+n]});
51
+ add('aws_s3_bucket_policy',n,{bucket,policy:JSON.stringify({Version:'2012-10-17',Statement:[{Sid:'TLSOnly',Effect:'Deny',Principal:'*',Action:'s3:*',Resource:[bucketArn,bucketArn+'/*'],Condition:{Bool:{'aws:SecureTransport':'false','aws:PrincipalIsAWSService':'false'}}}]})});
52
+ add('aws_secretsmanager_secret',n+'_destination',{name:l('name')+'/accounting-token',recovery_window_in_days:30,lifecycle:{prevent_destroy:true}});
53
+ add('aws_cognito_user_pool',n,{name:l('name'),username_attributes:['email'],auto_verified_attributes:['email'],admin_create_user_config:{allow_admin_create_user_only:true},mfa_configuration:'ON',software_token_mfa_configuration:{enabled:true},password_policy:{minimum_length:14,require_lowercase:true,require_uppercase:true,require_numbers:true,require_symbols:true,temporary_password_validity_days:3},account_recovery_setting:{recovery_mechanism:{name:'admin_only',priority:1}},lifecycle:{prevent_destroy:true}});
54
+ add('aws_cognito_resource_server',n,{identifier:l('name'),name:'Invoice processing',user_pool_id:r('aws_cognito_user_pool',n,'id'),scope:{scope_name:'process',scope_description:'Signed-in invoice workspace access; approval roles are checked independently in SQL.'}});
55
+ add('aws_cognito_user_pool_client',n,{name:l('name')+'-browser',user_pool_id:r('aws_cognito_user_pool',n,'id'),generate_secret:false,allowed_oauth_flows_user_pool_client:true,allowed_oauth_flows:['code'],allowed_oauth_scopes:['openid',l('name')+'/process'],callback_urls:[l('public_origin')+'/'],logout_urls:[l('public_origin')+'/'],supported_identity_providers:['COGNITO'],prevent_user_existence_errors:'ENABLED',enable_token_revocation:true,access_token_validity:30,id_token_validity:30,refresh_token_validity:1,token_validity_units:{access_token:'minutes',id_token:'minutes',refresh_token:'days'},depends_on:['aws_cognito_resource_server.'+n]});
56
+ add('aws_cognito_user_pool_domain',n,{domain:account+'-'+l('name'),user_pool_id:r('aws_cognito_user_pool',n,'id')});
57
+ for(const purpose of ['runtime','parser'])add('aws_ecr_repository',n+'_'+purpose,{name:l('name')+'-'+purpose,image_tag_mutability:'IMMUTABLE',image_scanning_configuration:{scan_on_push:true},encryption_configuration:{encryption_type:'AES256'},force_delete:false,lifecycle:{prevent_destroy:true}});
58
+ const trust=JSON.stringify({Version:'2012-10-17',Statement:[{Effect:'Allow',Principal:{Service:'ecs-tasks.amazonaws.com'},Action:'sts:AssumeRole',Condition:{StringEquals:{'aws:SourceAccount':account},ArnLike:{'aws:SourceArn':'arn:aws:ecs:'+region+':'+account+':*'}}}]});
59
+ for(const purpose of ['api','worker','parser']){
60
+ add('aws_cloudwatch_log_group',n+'_'+purpose,{name:'/ecs/'+l('name')+'/'+purpose,retention_in_days:7});add('aws_iam_role',n+'_'+purpose+'_execution',{name:l('name')+'-'+purpose+'-execution',assume_role_policy:trust});
61
+ const repo=r('aws_ecr_repository',n+'_'+(purpose==='parser'?'parser':'runtime'),'arn');
62
+ add('aws_iam_role_policy',n+'_'+purpose+'_execution',{role:r('aws_iam_role',n+'_'+purpose+'_execution','id'),policy:JSON.stringify({Version:'2012-10-17',Statement:[{Effect:'Allow',Action:['ecr:GetAuthorizationToken'],Resource:'*'},{Effect:'Allow',Action:['ecr:BatchGetImage','ecr:GetDownloadUrlForLayer','ecr:BatchCheckLayerAvailability'],Resource:repo},{Effect:'Allow',Action:['logs:CreateLogStream','logs:PutLogEvents'],Resource:r('aws_cloudwatch_log_group',n+'_'+purpose,'arn')+':*'},...(purpose==='worker'?[{Effect:'Allow',Action:['secretsmanager:GetSecretValue'],Resource:r('aws_secretsmanager_secret',n+'_destination','arn')}]:[])]})});
63
+ if(purpose!=='parser'){
64
+ add('aws_iam_role',n+'_'+purpose,{name:l('name')+'-'+purpose,assume_role_policy:trust});
65
+ add('aws_iam_role_policy',n+'_'+purpose,{role:r('aws_iam_role',n+'_'+purpose,'id'),policy:JSON.stringify({Version:'2012-10-17',Statement:[{Effect:'Allow',Action:purpose==='api'?['s3:PutObject','s3:GetObject','s3:GetObjectVersion']:['s3:GetObject','s3:GetObjectVersion'],Resource:objects},{Effect:'Allow',Action:['rds-db:connect'],Resource:'arn:aws:rds-db:'+region+':'+account+':dbuser:'+r('aws_db_instance',n,'resource_id')+'/'+l(purpose+'_db_user')},...(purpose==='worker'?[{Effect:'Allow',Action:['bedrock:InvokeModel'],Resource:'arn:aws:bedrock:'+region+'::foundation-model/'+model},{Effect:'Allow',Action:['bedrock:GetAccountDataRetention','bedrock:GetModelInvocationLoggingConfiguration'],Resource:'*',Condition:{StringEquals:{'aws:RequestedRegion':region}}}]:[])]})});
66
+ }
67
+ }
68
+ add('aws_acm_certificate',n,{domain_name:l('api_hostname'),subject_alternative_names:[l('parser_hostname')],validation_method:'DNS',lifecycle:{create_before_destroy:true}});
69
+ add('aws_route53_record',n+'_certificate',{for_each:{api:l('api_hostname'),parser:l('parser_hostname')},zone_id:zone,...Object.fromEntries([['name','resource_record_name'],['type','resource_record_type']].map(([key,attribute])=>[key,'${one([for dvo in aws_acm_certificate.'+n+'.domain_validation_options : dvo.'+attribute+' if dvo.domain_name == each.value])}'])),records:['${one([for dvo in aws_acm_certificate.'+n+'.domain_validation_options : dvo.resource_record_value if dvo.domain_name == each.value])}'],ttl:60,allow_overwrite:false});
70
+ add('aws_acm_certificate_validation',n,{certificate_arn:r('aws_acm_certificate',n,'arn'),validation_record_fqdns:'${[for record in aws_route53_record.'+n+'_certificate : record.fqdn]}'});
71
+ add('aws_route53_zone',n+'_parser',{name:l('parser_zone'),vpc:{vpc_id:r('aws_vpc',n,'id')}});
72
+ for(const purpose of ['api','parser']){
73
+ add('aws_lb',n+'_'+purpose,{name:l('name')+'-'+purpose,internal:purpose==='parser',load_balancer_type:'application',security_groups:[r('aws_security_group',n+'_'+purpose+'_lb','id')],subnets:purpose==='api'?publicSubnets:privateSubnets,idle_timeout:150,drop_invalid_header_fields:true,enable_deletion_protection:true});
74
+ add('aws_lb_target_group',n+'_'+purpose,{name:l('name')+'-'+purpose,port:8080,protocol:'HTTP',target_type:'ip',vpc_id:r('aws_vpc',n,'id'),deregistration_delay:120,health_check:{enabled:true,path:'/healthz',protocol:'HTTP',port:'traffic-port',matcher:'200',interval:30,timeout:5,healthy_threshold:2,unhealthy_threshold:3}});
75
+ add('aws_lb_listener',n+'_'+purpose,{load_balancer_arn:r('aws_lb',n+'_'+purpose,'arn'),port:443,protocol:'HTTPS',ssl_policy:'ELBSecurityPolicy-TLS13-1-2-2021-06',certificate_arn:r('aws_acm_certificate_validation',n,'certificate_arn'),default_action:{type:'forward',target_group_arn:r('aws_lb_target_group',n+'_'+purpose,'arn')}});
76
+ add('aws_route53_record',n+'_'+purpose,{zone_id:purpose==='api'?zone:r('aws_route53_zone',n+'_parser','zone_id'),name:l(purpose==='api'?'api_hostname':'parser_hostname'),type:'A',alias:{name:r('aws_lb',n+'_'+purpose,'dns_name'),zone_id:r('aws_lb',n+'_'+purpose,'zone_id'),evaluate_target_health:true}});
77
+ }
78
+ add('aws_ecs_cluster',n,{name:l('name'),setting:{name:'containerInsights',value:'enabled'}});
79
+ const common={REGION:region,ACCOUNT_ID:l('account_uuid'),WORKFLOW_NAMESPACE:'invoice',DATABASE_NAME:'invoice',DATABASE_HOST:r('aws_db_instance',n,'address'),DATABASE_CA_FILE:'/app/rds-global-ca.pem',DOCUMENT_BUCKET:bucket,AWS_ACCOUNT_ID:account,DESTINATION_CONTRACT_BASE64:l('destination_contract_base64')};
80
+ for(const purpose of ['api','worker','parser']){
81
+ const image=purpose==='parser'?v('parser_image'):v('runtime_image'),settings=purpose==='parser'?{PORT:'8080'}:purpose==='api'?{...common,DATABASE_USER:l('api_db_user'),PUBLIC_ORIGIN:l('public_origin'),POOL_ID:r('aws_cognito_user_pool',n,'id'),CLIENT_ID:r('aws_cognito_user_pool_client',n,'id'),AUTH_ORIGIN:'https://'+r('aws_cognito_user_pool_domain',n,'domain')+'.auth.'+region+'.amazoncognito.com',AUTH_SCOPE:l('name')+'/process'}:{...common,DATABASE_USER:l('worker_db_user'),PARSER_ENDPOINT:l('parser_endpoint'),MODEL_ID:model,MAX_OUTPUT_TOKENS:'2048',DESTINATION_ENDPOINT:endpoint,DESTINATION_SCOPE:scope,DESTINATION_REPLAY_REVIEW_BASE64:v('replay_review_base64')};
82
+ const container={name:purpose,image,essential:true,readonlyRootFilesystem:true,portMappings:[{containerPort:8080,protocol:'tcp'}],environment:Object.entries(settings).map(([name,value])=>({name,value})),mountPoints:[{sourceVolume:'runtime-tmp',containerPath:'/tmp',readOnly:false}],dependsOn:[{containerName:'prepare-tmp',condition:'SUCCESS'}],logConfiguration:{logDriver:'awslogs',options:{'awslogs-group':r('aws_cloudwatch_log_group',n+'_'+purpose,'name'),'awslogs-region':region,'awslogs-stream-prefix':purpose}},...(purpose==='api'?{}:{command:['python','-I','/app/'+(purpose==='worker'?'invoice_worker.py':'invoice_parser_service.py')]}),...(purpose==='worker'?{secrets:[{name:'DESTINATION_TOKEN',valueFrom:r('aws_secretsmanager_secret',n+'_destination','arn')}]}:{})};
83
+ const init={name:'prepare-tmp',image,essential:false,user:'0:0',readonlyRootFilesystem:true,command:['python','-I','-c','import os;os.chmod("/runtime-tmp",0o1777)'],mountPoints:[{sourceVolume:'runtime-tmp',containerPath:'/runtime-tmp',readOnly:false}]};
84
+ add('aws_ecs_task_definition',n+'_'+purpose,{count:'${var.applications_enabled ? 1 : 0}',family:l('name')+'-'+purpose,network_mode:'awsvpc',requires_compatibilities:['FARGATE'],cpu:purpose==='parser'?'1024':'512',memory:purpose==='parser'?'2048':'1024',execution_role_arn:r('aws_iam_role',n+'_'+purpose+'_execution','arn'),...(purpose!=='parser'?{task_role_arn:r('aws_iam_role',n+'_'+purpose,'arn')}:{}),volume:{name:'runtime-tmp'},runtime_platform:{operating_system_family:'LINUX',cpu_architecture:'X86_64'},container_definitions:JSON.stringify([init,container]),depends_on:['aws_iam_role_policy.'+n+'_'+purpose+'_execution',...(purpose!=='parser'?['aws_iam_role_policy.'+n+'_'+purpose]:[])]});
85
+ add('aws_ecs_service',n+'_'+purpose,{count:'${var.applications_enabled ? 1 : 0}',name:l('name')+'-'+purpose,cluster:r('aws_ecs_cluster',n,'id'),task_definition:r('aws_ecs_task_definition',n+'_'+purpose+'[0]','arn'),desired_count:1,launch_type:'FARGATE',platform_version:'1.4.0',deployment_circuit_breaker:{enable:true,rollback:true},network_configuration:{subnets:privateSubnets,security_groups:[r('aws_security_group',n+'_'+purpose,'id')],assign_public_ip:false},...(purpose!=='worker'?{load_balancer:{target_group_arn:r('aws_lb_target_group',n+'_'+purpose,'arn'),container_name:purpose,container_port:8080},health_check_grace_period_seconds:90}:{}),depends_on:['aws_lb_listener.'+n+'_api','aws_lb_listener.'+n+'_parser','aws_route_table_association.'+n+'_private_0','aws_route_table_association.'+n+'_private_1']});
86
+ }
87
+ b.output.application_url={value:l('public_origin')};b.output.runtime_repository={value:r('aws_ecr_repository',n+'_runtime','repository_url')};b.output.parser_repository={value:r('aws_ecr_repository',n+'_parser','repository_url')};b.output.account_identity={value:l('account_uuid')};
88
+ b.output.operator_deployment={value:{provider:'aws',deploymentId:l('account_uuid'),accountId:l('account_uuid'),namespace:'invoice',tenantId:null,bindings:Object.fromEntries(['api','worker'].map(purpose=>[purpose,{name:l(purpose+'_db_user'),principal:r('aws_iam_role',n+'_'+purpose,'arn')}]))}};
89
+ awsInvoiceOperations(b,n,{account,region,privateSubnets,trust});
90
+ b.output.database_bootstrap={value:{host:r('aws_db_instance',n,'address'),database:'invoice',adminSecretArn:r('aws_db_instance',n,'master_user_secret[0].secret_arn'),apiRole:l('api_db_user'),workerRole:l('worker_db_user'),apiPrincipal:r('aws_iam_role',n+'_api','arn'),workerPrincipal:r('aws_iam_role',n+'_worker','arn'),destinationSecretArn:r('aws_secretsmanager_secret',n+'_destination','arn')}};
91
+ b.assertions.push(`run "private_database_and_parser" {\n command = plan\n assert {\n condition = aws_db_instance.${n}.publicly_accessible == false && aws_db_instance.${n}.iam_database_authentication_enabled && aws_db_instance.${n}.storage_encrypted && aws_lb.${n}_parser.internal\n error_message = "Database and parser must retain their private, encrypted and IAM boundaries."\n }\n assert {\n condition = aws_ecs_task_definition.${n}_parser[0].task_role_arn == null\n error_message = "Parser must not inherit application data/model authority."\n }\n}\nrun "foundation_only" {\n command = plan\n variables {\n applications_enabled = false\n operations_enabled = false\n maintenance_enabled = false\n }\n assert {\n condition = length(aws_ecs_service.${n}_api) == 0 && length(aws_ecs_service.${n}_worker) == 0 && length(aws_ecs_service.${n}_parser) == 0\n error_message = "Bootstrap and publication must precede application release."\n }\n}`);
92
+ b.assertions.push(`run "foreign_image_rejected" {\n command = plan\n variables { runtime_image = "123456789012.dkr.ecr.ap-southeast-2.amazonaws.com/foreign@sha256:${'a'.repeat(64)}" }\n expect_failures = [terraform_data.review_gate]\n}\nrun "mutable_image_rejected" {\n command = plan\n variables { runtime_image = "unreviewed:latest" }\n expect_failures = [var.runtime_image]\n}\nrun "foreign_account_rejected" {\n command = plan\n variables {\n account_id = "999999999999"\n applications_enabled = false\n operations_enabled = false\n maintenance_enabled = false\n }\n expect_failures = [terraform_data.review_gate]\n}\n`);
93
+ const mock='mock_provider "aws" {\n override_during = plan\n mock_resource "aws_iam_role" { defaults = { arn="arn:aws:iam::123456789012:role/owned-fixture" } }\n override_resource {\n target = aws_iam_role.'+n+'_api\n values = { arn="arn:aws:iam::123456789012:role/owned-api-fixture" }\n }\n override_resource {\n target = aws_iam_role.'+n+'_worker\n values = { arn="arn:aws:iam::123456789012:role/owned-worker-fixture" }\n }\n mock_resource "aws_s3_bucket" { defaults = { id="fixture-invoices",arn="arn:aws:s3:::fixture-invoices" } }\n mock_resource "aws_secretsmanager_secret" { defaults = { arn="arn:aws:secretsmanager:ap-southeast-2:123456789012:secret:owned-destination-fixture" } }\n mock_resource "aws_db_instance" { defaults = { address = "fixture.abcdef.ap-southeast-2.rds.amazonaws.com", master_user_secret = [{secret_arn="arn:aws:secretsmanager:ap-southeast-2:123456789012:secret:owned-operator-fixture",kms_key_id="owned-native-key",secret_status="active"}] } }\n mock_resource "aws_cognito_user_pool" { defaults = { id = "ap-southeast-2_Fixture" } }\n mock_resource "aws_cognito_user_pool_client" { defaults = { id = "fixtureclient123" } }\n mock_data "aws_caller_identity" { defaults = { account_id = "123456789012" } }\n mock_data "aws_availability_zones" { defaults = { names = ["ap-southeast-2a","ap-southeast-2b"] } }\n mock_data "aws_route53_zone" { defaults = { name = "example.test.",private_zone = false } }\n}\n';
94
+ return b.finish({aws:{region,allowed_account_ids:[account]}},{aws:{source:'hashicorp/aws',version:'6.72.0'}},CLOUD_PROVIDER_LOCKS.aws,mock,AWS_INVOICE_GUIDE);
95
+ }
96
+
97
+ export const AWS_INVOICE_GUIDE=`# AWS invoice workflow infrastructure
98
+
99
+ New private VPC/subnets, regional NAT, IAM-authenticated Multi-AZ PostgreSQL 17,
100
+ private versioned invoice bucket, invited/MFA Cognito users, separate API/worker
101
+ roles, a role-free isolated parser task, ECR repositories and regional TLS ingress.
102
+ The public Route53 zone and external accounting system remain existing references.
103
+
104
+ Run foundation first with applications_enabled=false. Build/scan/publish the exact
105
+ emitted runtime/parser images only after authorization. Bootstrap SQL logins/grants,
106
+ schema using the emitted fixed operator initialize task, then admit verified staff
107
+ memberships with the separate operator CLI; populate the
108
+ accounting secret outside Terraform state/source. Then review a plan enabling the
109
+ application release with operations_enabled=true and maintenance_enabled=true.
110
+ Native platform secret injection becomes a private temporary
111
+ file; credential rotation requires an explicit workload rollout. Never put secret
112
+ values in tfvars, source, build context or command arguments.
113
+
114
+ Parser ingress is private HTTPS restricted to the worker security group. TLS ends
115
+ at its internal regional ALB; the target HTTP hop is inside restricted private
116
+ security groups. The parser has no task IAM role, data/model grant, secret injection
117
+ or outbound security-group rule. A short trusted root init container prepares only
118
+ the ephemeral temporary volume; processing containers retain nonroot image users.
119
+ No Docker daemon is installed in the worker. Verify actual platform isolation and
120
+ effective inherited IAM in an authorized staging environment before production.
121
+
122
+ The storage lifecycle expires current originals after 90 days and noncurrent
123
+ versions after one additional day; physical provider deletion is asynchronous.
124
+ Application payload access expires independently of physical cleanup. The emitted
125
+ OPERATIONS.md and invoice_operator.py implement scoped audit cleanup, restored
126
+ delivery quarantine and explicit completion reconciliation. Fixed Fargate operator
127
+ tasks, daily expired-only cleanup and hourly count-only monitoring are emitted;
128
+ review their existing same-account SNS alert route and verify its subscribed
129
+ notification/publish policy before release. Scheduler delivery failures and stopped
130
+ operator failures reach a private encrypted queue; missing successful receipts also
131
+ alarm. No accounting/model/object/task IAM role is given to an operator container.
132
+ The platform execution role can fetch only the managed RDS admin secret password,
133
+ owned image and logs. Bootstrap is manual and never scheduled. Native operating
134
+ checks and alert delivery remain destination acceptance steps. Seven-day encrypted database backups may
135
+ contain older data; review the stated backup/retention policy before deployment.
136
+ No static/mock check establishes an exact deletion deadline or cloud restore.
137
+
138
+ The single NAT, ALBs, managed database and continuously running tasks have fixed
139
+ cost drivers. Review actual regional rates, service/model availability and quotas;
140
+ no invented quote, price cap, throughput/latency SLA or deployment claim is made.
141
+ Infrastructure emission remains under qualification until the complete project,
142
+ operations, native account inputs and original full business brief are checked.
143
+ `;
@@ -0,0 +1,147 @@
1
+ import{v,r,l}from'./invoice-builder.mjs';
2
+
3
+ // Separate trusted operator jobs. Job start permission is privileged because the
4
+ // native API accepts execution-template overrides; never grant it to the product.
5
+ export function azureInvoiceOperations(b,n,{rg,rgId,region,subscription,identity}){
6
+ const{add,input}=b;
7
+ for(const key of ['operations_enabled','maintenance_enabled']){b.variable[key]={type:'bool',default:false,description:key==='operations_enabled'?'Create manual fixed operator jobs after reviewed immutable image publication.':'After successful bootstrap/membership and log-query verification, schedule daily retention/hourly monitor and enable alerts.'};b.examples[key]=false;b.testExpressions[key]='true';}
8
+ const actionGroup=input('operator_action_group_id','Existing reviewed same-subscription Azure Monitor action group with a verified on-call route; referenced without modifying it.','/subscriptions/'+b.examples.subscription_id+'/resourceGroups/reviewed-operations/providers/Microsoft.Insights/actionGroups/on-call','^/subscriptions/[0-9a-f-]{36}/resourceGroups/[^/]+/providers/Microsoft\\.Insights/actionGroups/[^/]+$');
9
+ b.resource.terraform_data.review_gate.lifecycle.precondition.push({condition:'${!var.maintenance_enabled || var.operations_enabled}',error_message:'Maintenance requires its separate owned operator jobs.'},{condition:'${!var.applications_enabled || (var.operations_enabled && var.maintenance_enabled)}',error_message:'Complete owned SQL/membership bootstrap and operating-log acceptance before application release.'},{condition:'${startswith(lower(var.operator_action_group_id),format("/subscriptions/%s/",var.subscription_id))}',error_message:'Use the reviewed existing notification group in this subscription.'});
10
+ b.locals.operator_deployment=b.output.operator_deployment.value;
11
+ const environment=r('azurerm_container_app_environment',n,'id'),workspace=r('azurerm_log_analytics_workspace',n,'id');
12
+ add('azurerm_log_analytics_workspace',n,{name:l('name')+'-operations',resource_group_name:rg,location:region,sku:'PerGB2018',retention_in_days:30,lifecycle:{prevent_destroy:true}});
13
+ // Azure Monitor routing avoids the legacy HTTP Data Collector/_CL path. The
14
+ // destination alone is insufficient: both log categories are explicitly routed.
15
+ b.resource.azurerm_container_app_environment[n].logs_destination='azure-monitor';
16
+ add('azurerm_monitor_diagnostic_setting',n+'_environment',{name:'invoice-operating-logs',target_resource_id:environment,log_analytics_workspace_id:workspace,log_analytics_destination_type:'Dedicated',enabled_log:[{category:'ContainerAppConsoleLogs'},{category:'ContainerAppSystemLogs'}]});
17
+ const settings={DATABASE_NAME:'invoice',DATABASE_HOST:r('azurerm_postgresql_flexible_server',n,'fqdn'),DATABASE_USER:l('name')+'-operator',DATABASE_CA_FILE:'/usr/local/lib/python3.13/site-packages/certifi/cacert.pem',WORKLOAD_IDENTITY_CLIENT_ID:identity('operator','client_id'),OPERATOR_DEPLOYMENT_BASE64:'${base64encode(jsonencode(local.operator_deployment))}'};
18
+ const common={replicaTimeout:900,replicaRetryLimit:0,registries:[{server:r('azurerm_container_registry',n,'login_server'),identity:identity('pull','id')}],identitySettings:[{identity:identity('pull','id'),lifecycle:'None'},{identity:identity('operator','id'),lifecycle:'Main'}]};
19
+ for(const action of ['initialize','maintain','monitor']){
20
+ const manual={...common,triggerType:'Manual',manualTriggerConfig:{parallelism:1,replicaCompletionCount:1}};
21
+ const scheduled={...common,triggerType:'Schedule',scheduleTriggerConfig:{cronExpression:action==='maintain'?'17 2 * * *':'7 * * * *',parallelism:1,replicaCompletionCount:1}};
22
+ // Decode one of two complete configurations, omitting the inactive trigger
23
+ // rather than sending optional ARM objects as explicit null values.
24
+ const configuration=action==='initialize'?manual:'${jsondecode(var.maintenance_enabled ? jsonencode('+JSON.stringify(scheduled)+') : jsonencode('+JSON.stringify(manual)+'))}';
25
+ add('azapi_resource',n+'_operator_'+action,{count:'${var.operations_enabled ? 1 : 0}',type:'Microsoft.App/jobs@2025-07-01',name:l('name')+'-'+action,parent_id:rgId,location:region,identity:{type:'UserAssigned',identity_ids:[identity('pull','id'),identity('operator','id')]},body:{properties:{environmentId:environment,workloadProfileName:'Consumption',configuration,template:{containers:[{name:'operator',image:v('runtime_image'),command:['python','-I','/app/invoice_operator_job.py',action,'--execute'],resources:{cpu:0.5,memory:'1Gi'},env:Object.entries(settings).map(([name,value])=>({name,value}))}]}}},schema_validation_enabled:true,response_export_values:['properties.configuration.triggerType'],depends_on:['azurerm_role_assignment.'+n+'_pull','azurerm_postgresql_flexible_server_active_directory_administrator.'+n,'azurerm_postgresql_flexible_server_database.'+n,'azurerm_monitor_diagnostic_setting.'+n+'_environment']});
26
+ const prefix='${local.name}-'+action;
27
+ const owned=`| where EnvironmentName == '${l('name')}'\n| where JobName == '${prefix}' or JobName startswith '${prefix}-' or ContainerGroupName startswith '${prefix}-'`;
28
+ if(action!=='initialize'){
29
+ const hours=action==='maintain'?48:2;
30
+ const query=`ContainerAppConsoleLogs\n| where TimeGenerated > ago(${hours}h)\n${owned}\n| where ContainerName == 'operator'\n| extend receipt = parse_json(Log)\n| where receipt.format == 'invoice-operator-v1' and receipt.action == '${action}' and receipt.status == 'ok'\n| summarize SuccessfulRuns=count()\n| where SuccessfulRuns < 1`;
31
+ add('azurerm_monitor_scheduled_query_rules_alert_v2',n+'_'+action+'_missing',{name:l('name')+'-'+action+'-missing',resource_group_name:rg,location:region,scopes:[workspace],severity:2,evaluation_frequency:'PT1H',window_duration:action==='maintain'?'P2D':'PT2H',enabled:v('maintenance_enabled'),skip_query_validation:'${!var.maintenance_enabled}',auto_mitigation_enabled:true,description:'Missing count-only success receipt. Initial manual bootstrap/log ingestion precedes enabling scheduled operating jobs; verify notification delivery.',criteria:{query,time_aggregation_method:'Count',operator:'GreaterThan',threshold:0,failing_periods:{number_of_evaluation_periods:1,minimum_failing_periods_to_trigger_alert:1}},action:{action_groups:[actionGroup]},depends_on:['azurerm_monitor_diagnostic_setting.'+n+'_environment']});
32
+ b.files['monitoring/'+action+'-missing.kql']=query+'\n';
33
+ }
34
+ const consoleQuery=`ContainerAppConsoleLogs\n| where TimeGenerated > ago(1h)\n${owned}\n| where ContainerName == 'operator'\n| extend receipt=parse_json(Log)\n| where receipt.format == 'invoice-operator-v1' and receipt.status in ('failed','attention','backlog')\n| project TimeGenerated`;
35
+ // System logs use ReplicaName (not console ContainerGroupName). Include image
36
+ // pull/scheduling warnings so failures before Python startup are not invisible.
37
+ const systemOwned=`| where EnvironmentName == '${l('name')}'\n| where JobName == '${prefix}' or JobName startswith '${prefix}-' or ReplicaName startswith '${prefix}-'`;
38
+ const query=`union (\n${consoleQuery}\n), (\nContainerAppSystemLogs\n| where TimeGenerated > ago(1h)\n${systemOwned}\n| where Type =~ 'Warning' or Type =~ 'Error'\n| project TimeGenerated\n)\n| summarize Failures=count()\n| where Failures > 0`;
39
+ add('azurerm_monitor_scheduled_query_rules_alert_v2',n+'_'+action+'_failed',{name:l('name')+'-'+action+'-failed',resource_group_name:rg,location:region,scopes:[workspace],severity:2,evaluation_frequency:'PT5M',window_duration:'PT1H',enabled:v('operations_enabled'),skip_query_validation:'${!var.maintenance_enabled}',auto_mitigation_enabled:true,description:'Fixed operator failure/attention or native platform warning/error. Inspect trusted operator execution history; never replay uncertain accounting effects.',criteria:{query,time_aggregation_method:'Count',operator:'GreaterThan',threshold:0,failing_periods:{number_of_evaluation_periods:1,minimum_failing_periods_to_trigger_alert:1}},action:{action_groups:[actionGroup]},depends_on:['azurerm_monitor_diagnostic_setting.'+n+'_environment']});
40
+ b.files['monitoring/'+action+'-failed.kql']=query+'\n';
41
+ }
42
+ b.output.operator_jobs={value:{resourceGroup:rg,initialize:l('name')+'-initialize',maintain:l('name')+'-maintain',monitor:l('name')+'-monitor',workspace:workspace,actionGroup:actionGroup,operatorPrincipal:identity('operator','principal_id'),operatorClientId:identity('operator','client_id'),permissionBoundary:'Only a trusted destination operator may start jobs. Native start accepts execution-template overrides; no start/RBAC permission is given to API or worker.'}};
43
+ b.output.operator_start_urls={value:Object.fromEntries(['initialize','maintain','monitor'].map(action=>[action,rgId+'/providers/Microsoft.App/jobs/'+l('name')+'-'+action+'/start?api-version=2025-07-01']))};
44
+ b.output.operator_bootstrap_url={value:rgId+'/providers/Microsoft.App/jobs/'+l('name')+'-initialize/start?api-version=2025-07-01',description:'Use only after the reviewed operations-enabled resources have actually been applied. A URL is not proof the job exists.'};
45
+ b.files['OPERATING_JOBS.md']=AZURE_OPERATING_JOBS;
46
+ b.assertions.push(`run "manual_operator_before_applications" {
47
+ command = plan
48
+ variables {
49
+ applications_enabled = false
50
+ maintenance_enabled = false
51
+ staff_subjects = []
52
+ }
53
+ assert {
54
+ condition = azapi_resource.${n}_operator_initialize[0].body.properties.configuration.triggerType == "Manual" && azapi_resource.${n}_operator_maintain[0].body.properties.configuration.triggerType == "Manual" && length(azapi_resource.${n}_api) == 0
55
+ error_message = "Manual SQL bootstrap/log qualification must precede scheduled operations and application release."
56
+ }
57
+ }
58
+ run "scheduled_jobs_and_log_validation" {
59
+ command = plan
60
+ assert {
61
+ condition = azapi_resource.${n}_operator_initialize[0].body.properties.configuration.triggerType == "Manual" && azapi_resource.${n}_operator_maintain[0].body.properties.configuration.triggerType == "Schedule" && azapi_resource.${n}_operator_monitor[0].body.properties.configuration.replicaTimeout == 900 && !azurerm_monitor_scheduled_query_rules_alert_v2.${n}_monitor_missing.skip_query_validation && azurerm_container_app_environment.${n}.logs_destination == "azure-monitor"
62
+ error_message = "Application release must include scheduled operations and mandatory current-table query validation."
63
+ }
64
+ }
65
+ run "operating_jobs_required" {
66
+ command = plan
67
+ variables {
68
+ operations_enabled = false
69
+ maintenance_enabled = false
70
+ }
71
+ expect_failures = [terraform_data.review_gate]
72
+ }
73
+ run "operator_mutable_image_rejected" {
74
+ command = plan
75
+ variables {
76
+ applications_enabled = false
77
+ maintenance_enabled = false
78
+ runtime_image = "unreviewed:latest"
79
+ }
80
+ expect_failures = [var.runtime_image]
81
+ }
82
+ `);
83
+ }
84
+
85
+ const AZURE_OPERATING_JOBS=`# Azure invoice operating jobs
86
+
87
+ Obtain authorization before destination writes, publication, apply or job start.
88
+ The emitted native jobs are fixed trusted operator tools, not API/model endpoints.
89
+ Azure job start accepts an execution template that can override image/command/env.
90
+ Treat start permission as access to the job's available managed identity. Never
91
+ grant job start/contributor/operator or listSecrets rights to the product, end users,
92
+ worker, parser or an untrusted automation identity. This package assigns none.
93
+
94
+ Foundation: applications_enabled=false, operations_enabled=false,
95
+ maintenance_enabled=false. Review regional availability, model/version, identity,
96
+ costs and existing operator_action_group_id. No existing action group or its routes
97
+ are adopted or modified. Verify its actual notification recipients and delivery.
98
+ Azure Monitor and resource-specific log tables are used; diagnostic routing includes
99
+ both console and system logs. Logs retain 30 days, separate from 90-day invoices.
100
+ Provider-managed state must remain private/encrypted; do not publish workspace keys.
101
+
102
+ After authorized build/scan/publication of the immutable runtime image to owned ACR,
103
+ set operations_enabled=true with applications_enabled/maintenance_enabled=false.
104
+ All three jobs are Manual. The pull identity has lifecycle None; only the separate
105
+ PostgreSQL Entra administrator identity has lifecycle Main. There is no password,
106
+ accounting secret, model, object access or HTTP ingress on these jobs. The exact
107
+ operator identity and login bind token acquisition to the managed database.
108
+
109
+ Start initialize as the trusted destination operator:
110
+
111
+ az rest --method post --url "$(terraform output -raw operator_bootstrap_url)" --body '{}'
112
+
113
+ The shared core maps owned API/worker principals in postgres, then initializes
114
+ the application schema/grants/account in invoice. Check completed execution status,
115
+ exit code and initialized:true log receipt; start acceptance alone proves neither.
116
+ Admit verified staff subjects/roles using the separate membership CLI/runbook.
117
+ Do not infer reviewer privileges from Entra app access. Keep accounting secret
118
+ population separate from this SQL-only operator identity.
119
+
120
+ The nonsecret operator_start_urls output gives the exact remaining start endpoints;
121
+ call them with an empty JSON body as above. Do not override image/command/template.
122
+ Start maintain and monitor manually and check count-only receipts and both native
123
+ log tables. Query files are in monitoring/. Validate their ownership filters against
124
+ actual JobName/replica fields at the destination. Alerts are initially unvalidated
125
+ while fresh tables do not yet exist. Enabling maintenance_enabled makes ARM query
126
+ validation mandatory; a missing/incorrect table/query must block release, not be
127
+ silently skipped. Log ingestion/diagnostic activation can be delayed. Verify actual
128
+ queries and an action-group test notification before enabling scheduled release.
129
+
130
+ Release with operations_enabled=true, maintenance_enabled=true and
131
+ applications_enabled=true. Bootstrap stays Manual. Maintain runs 02:17 UTC daily;
132
+ monitor at minute 7 hourly. Completion/parallelism is one, native retry is zero and
133
+ replica timeout is 900 seconds. The shared process deadline and SQL statement/lock
134
+ bounds apply as well. Scheduling does not prove native IAM/SQL/network/alerts worked.
135
+
136
+ Daily expired-only SQL cleanup is bounded to twenty 500-record transactions and
137
+ refuses live execution jobs/leases. New/young operations survive; original-object
138
+ expiry is a separate asynchronous lifecycle. Monitoring is a read-only count-only
139
+ transaction. Two hours of missing monitor receipts and 48 hours of missing cleanup
140
+ receipts alert, along with failure/attention/backlog and platform warnings/errors.
141
+ These are explicit operating defaults, not promised availability or a client SLA.
142
+ No KQL engine or Azure Monitor ingestion/notification execution is proved by local
143
+ provider schemas/mocks. Retain actual destination acceptance before production.
144
+
145
+ Restore/quarantine/reconciliation still require an isolated paused destination.
146
+ Never use daily maintenance to reset or resend unknown accounting side effects.
147
+ `;
@@ -0,0 +1,134 @@
1
+ import {invoiceBuilder,v,r,l,UUID}from'./invoice-builder.mjs';
2
+ import {CLOUD_PROVIDER_LOCKS}from'./cloud-provider-locks.mjs';
3
+ import {azureInvoiceOperations}from'./invoice-azure-operations.mjs';
4
+ import azapi from'./runtime/invoice-azapi-provider-lock.json'with{type:'json'};
5
+
6
+ // Native composition under qualification; not a registered complete capability.
7
+ export function azureInvoiceTerraform(project,row,environment='staging'){
8
+ if(row.provider!=='azure')throw Error('Azure invoice compilation cannot substitute another cloud');
9
+ const b=invoiceBuilder(project,row,environment),{input,add,region}=b,n=row.address;
10
+ const seed=b.seed;
11
+ const subscription=input('subscription_id','Expected reviewed Azure subscription.','11111111-1111-1111-1111-111111111111',UUID);
12
+ const tenant=input('tenant_id','Expected company Entra tenant, bound in both control-plane and application identities.','22222222-2222-2222-2222-222222222222',UUID);
13
+ const endpoint=input('accounting_endpoint','Existing reviewed accounting HTTPS adapter; never recreated.','https://accounting.example.test/actions','^https://[A-Za-z0-9.-]+(:[0-9]{1,5})?(/[^?#@ ]*)?$');
14
+ const scope=input('accounting_scope','Existing accounting scope bound into approvals.','owned-team','^[A-Za-z0-9_.:/-]{1,200}$');
15
+ const model=input('model_name','Reviewed regional Azure OpenAI structured-output model; confirm current availability and terms.','REVIEW_MODEL');
16
+ const modelVersion=input('model_version','Explicit supported regional model version; no silent upgrade.','REVIEW_VERSION');
17
+ for(const purpose of ['runtime','parser']){
18
+ const key=purpose+'_image';input(key,'Reviewed immutable image published in the emitted private ACR. Empty during foundation.','reviewedregistry.azurecr.io/invoice/'+purpose+'@sha256:'+'a'.repeat(64));
19
+ b.variable[key].default='';b.variable[key].validation={condition:'${!('+(purpose==='runtime'?'var.applications_enabled || var.operations_enabled':'var.applications_enabled')+') || can(regex("@sha256:[0-9a-f]{64}$",var.'+key+'))}',error_message:'Application release requires an immutable reviewed image digest.'};
20
+ }
21
+ b.variable.staff_subjects={type:'set(string)',default:[],validation:{condition:'${alltrue([for subject in var.staff_subjects : can(regex('+JSON.stringify(UUID)+',subject))])}',error_message:'Supply verified native employee object IDs.'},description:'Explicit existing staff object IDs assigned application access. SQL memberships independently assign reviewer/user authority.'};
22
+ b.examples.staff_subjects=['55555555-5555-5555-5555-555555555555'];
23
+ b.variable.replay_review_base64={type:'string',default:'',description:'Scoped expiring accounting idempotency review; empty disables uncertain replay.'};b.examples.replay_review_base64='';
24
+ b.data.azurerm_client_config={operator:{}};
25
+ b.resource.terraform_data.review_gate.lifecycle.precondition.push({condition:'${data.azurerm_client_config.operator.subscription_id == var.subscription_id && data.azurerm_client_config.operator.tenant_id == var.tenant_id && (!var.applications_enabled || length(var.staff_subjects)>0)}',error_message:'Use the reviewed subscription/tenant and explicitly admitted staff identities.'});
26
+ Object.assign(b.locals,{storage_name:'${format("%sdocs",replace(local.name,"-",""))}',registry_name:'${format("%sacr",replace(local.name,"-",""))}',public_origin:'${format("https://%s-api.%s",local.name,azurerm_container_app_environment.'+n+'.default_domain)}',parser_endpoint:'${format("https://%s-parser.internal.%s/extract",local.name,azurerm_container_app_environment.'+n+'.default_domain)}',destination_contract:{id:'normalized-action-v1',endpointSha256:'${sha256(var.accounting_endpoint)}',scope,mappingVersion:1},destination_contract_base64:'${base64encode(jsonencode(local.destination_contract))}'});
27
+ add('azurerm_resource_group',n,{name:l('name'),location:region});const rg=r('azurerm_resource_group',n,'name'),rgId=r('azurerm_resource_group',n,'id');
28
+ add('azurerm_virtual_network',n,{name:l('name'),resource_group_name:rg,location:region,address_space:['10.85.0.0/16']});
29
+ for(const[purpose,cidr]of Object.entries({apps:'10.85.0.0/23',database:'10.85.4.0/24',endpoints:'10.85.5.0/24'}))add('azurerm_subnet',n+'_'+purpose,{name:purpose,resource_group_name:rg,virtual_network_name:r('azurerm_virtual_network',n,'name'),address_prefixes:[cidr],...(purpose==='database'?{delegation:{name:'postgres',service_delegation:{name:'Microsoft.DBforPostgreSQL/flexibleServers',actions:['Microsoft.Network/virtualNetworks/subnets/join/action']}}}:{})});
30
+ add('azurerm_public_ip',n,{name:l('name')+'-egress',resource_group_name:rg,location:region,allocation_method:'Static',sku:'Standard'});
31
+ add('azurerm_nat_gateway',n,{name:l('name'),resource_group_name:rg,location:region,sku_name:'Standard'});
32
+ add('azurerm_nat_gateway_public_ip_association',n,{nat_gateway_id:r('azurerm_nat_gateway',n,'id'),public_ip_address_id:r('azurerm_public_ip',n,'id')});
33
+ add('azurerm_subnet_nat_gateway_association',n,{subnet_id:r('azurerm_subnet',n+'_apps','id'),nat_gateway_id:r('azurerm_nat_gateway',n,'id')});
34
+ const dns={database:'private.postgres.database.azure.com',blob:'privatelink.blob.core.windows.net',model:'privatelink.openai.azure.com',vault:'privatelink.vaultcore.azure.net'};
35
+ for(const[purpose,name]of Object.entries(dns)){
36
+ add('azurerm_private_dns_zone',n+'_'+purpose,{name,resource_group_name:rg});
37
+ add('azurerm_private_dns_zone_virtual_network_link',n+'_'+purpose,{name:purpose,resource_group_name:rg,private_dns_zone_name:r('azurerm_private_dns_zone',n+'_'+purpose,'name'),virtual_network_id:r('azurerm_virtual_network',n,'id'),registration_enabled:false});
38
+ }
39
+ for(const purpose of ['api','worker','pull','operator'])add('azurerm_user_assigned_identity',n+'_'+purpose,{name:l('name')+'-'+purpose,resource_group_name:rg,location:region});
40
+ const identity=(purpose,attribute)=>r('azurerm_user_assigned_identity',n+'_'+purpose,attribute);
41
+ add('azurerm_postgresql_flexible_server',n,{name:l('name'),resource_group_name:rg,location:region,version:'17',sku_name:'GP_Standard_D2ds_v5',storage_mb:32768,auto_grow_enabled:true,backup_retention_days:7,geo_redundant_backup_enabled:false,delegated_subnet_id:r('azurerm_subnet',n+'_database','id'),private_dns_zone_id:r('azurerm_private_dns_zone',n+'_database','id'),public_network_access_enabled:false,authentication:{active_directory_auth_enabled:true,password_auth_enabled:false,tenant_id:tenant},high_availability:{mode:'ZoneRedundant'},lifecycle:{prevent_destroy:true},depends_on:['azurerm_private_dns_zone_virtual_network_link.'+n+'_database']});
42
+ add('azurerm_postgresql_flexible_server_database',n,{name:'invoice',server_id:r('azurerm_postgresql_flexible_server',n,'id'),charset:'UTF8',collation:'en_US.utf8',lifecycle:{prevent_destroy:true}});
43
+ add('azurerm_postgresql_flexible_server_active_directory_administrator',n,{server_name:r('azurerm_postgresql_flexible_server',n,'name'),resource_group_name:rg,tenant_id:tenant,object_id:identity('operator','principal_id'),principal_name:l('name')+'-operator',principal_type:'ServicePrincipal'});
44
+ for(const[name,value]of Object.entries({require_secure_transport:'ON',log_statement:'none',log_min_error_statement:'panic',log_parameter_max_length_on_error:'0'}))add('azurerm_postgresql_flexible_server_configuration',n+'_'+name,{name,server_id:r('azurerm_postgresql_flexible_server',n,'id'),value});
45
+ add('azurerm_storage_account',n,{name:l('storage_name'),resource_group_name:rg,location:region,account_tier:'Standard',account_replication_type:'ZRS',min_tls_version:'TLS1_2',shared_access_key_enabled:false,allow_nested_items_to_be_public:false,public_network_access_enabled:false,default_to_oauth_authentication:true,blob_properties:{versioning_enabled:true},lifecycle:{prevent_destroy:true}});
46
+ add('azurerm_storage_container',n,{name:'invoices',storage_account_id:r('azurerm_storage_account',n,'id'),container_access_type:'private',lifecycle:{prevent_destroy:true}});
47
+ add('azurerm_storage_management_policy',n,{storage_account_id:r('azurerm_storage_account',n,'id'),rule:[{name:'invoice-retention',enabled:true,filters:{prefix_match:['invoices/accounts/'],blob_types:['blockBlob']},actions:{base_blob:{delete_after_days_since_modification_greater_than:90},version:{delete_after_days_since_creation:1}}}]});
48
+ add('azurerm_container_registry',n,{name:l('registry_name'),resource_group_name:rg,location:region,sku:'Standard',admin_enabled:false,anonymous_pull_enabled:false,lifecycle:{prevent_destroy:true}});
49
+ add('azurerm_key_vault',n,{name:l('name'),resource_group_name:rg,location:region,tenant_id:tenant,sku_name:'standard',rbac_authorization_enabled:true,purge_protection_enabled:true,soft_delete_retention_days:90,public_network_access_enabled:false,lifecycle:{prevent_destroy:true}});
50
+ add('azurerm_cognitive_account',n,{name:l('name')+'-model',resource_group_name:rg,location:region,kind:'OpenAI',sku_name:'S0',custom_subdomain_name:l('name')+'-model',local_auth_enabled:false,public_network_access_enabled:false,lifecycle:{prevent_destroy:true}});
51
+ add('azurerm_cognitive_deployment',n,{name:'invoice-extraction',cognitive_account_id:r('azurerm_cognitive_account',n,'id'),version_upgrade_option:'NoAutoUpgrade',model:{format:'OpenAI',name:model,version:modelVersion},sku:{name:'Standard',capacity:1}});
52
+ for(const[purpose,type,group]of [['blob','azurerm_storage_account','blob'],['model','azurerm_cognitive_account','account'],['vault','azurerm_key_vault','vault']])add('azurerm_private_endpoint',n+'_'+purpose,{name:l('name')+'-'+purpose,resource_group_name:rg,location:region,subnet_id:r('azurerm_subnet',n+'_endpoints','id'),private_service_connection:{name:purpose,private_connection_resource_id:r(type,n,'id'),subresource_names:[group],is_manual_connection:false},private_dns_zone_group:{name:'owned',private_dns_zone_ids:[r('azurerm_private_dns_zone',n+'_'+purpose,'id')]}});
53
+ const role=(suffix,scope,role_definition_name,principal_id)=>add('azurerm_role_assignment',n+'_'+suffix,{scope,role_definition_name,principal_id,skip_service_principal_aad_check:true});
54
+ role('pull',r('azurerm_container_registry',n,'id'),'AcrPull',identity('pull','principal_id'));
55
+ role('model',r('azurerm_cognitive_account',n,'id'),'Cognitive Services OpenAI User',identity('worker','principal_id'));role('model_policy',r('azurerm_cognitive_account',n,'id'),'Reader',identity('worker','principal_id'));
56
+ // This owned vault is dedicated to the one accounting credential. Vault scope
57
+ // permits metadata/bootstrap before the separately populated secret exists.
58
+ role('destination',r('azurerm_key_vault',n,'id'),'Key Vault Secrets User',identity('worker','principal_id'));
59
+ role('operator_secrets',r('azurerm_key_vault',n,'id'),'Key Vault Secrets Officer',r('data.azurerm_client_config','operator','object_id'));
60
+ add('azurerm_role_definition',n+'_documents',{name:l('name')+'-upload-read',scope:rgId,assignable_scopes:[rgId],description:'Private invoice upload/read without object deletion or administrative account access.',permissions:{actions:[],not_actions:[],data_actions:['Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read','Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write'],not_data_actions:[]}});
61
+ add('azurerm_role_assignment',n+'_upload',{scope:r('azurerm_storage_container',n,'id'),role_definition_id:r('azurerm_role_definition',n+'_documents','role_definition_resource_id'),principal_id:identity('api','principal_id'),skip_service_principal_aad_check:true});
62
+ role('read_documents',r('azurerm_storage_container',n,'id'),'Storage Blob Data Reader',identity('worker','principal_id'));
63
+ add('azurerm_container_app_environment',n,{name:l('name'),resource_group_name:rg,location:region,infrastructure_subnet_id:r('azurerm_subnet',n+'_apps','id'),internal_load_balancer_enabled:false,workload_profile:{name:'Consumption',workload_profile_type:'Consumption'},depends_on:['azurerm_subnet_nat_gateway_association.'+n,'azurerm_nat_gateway_public_ip_association.'+n]});
64
+ for(const suffix of ['', '_spa','_parser'])add('azuread_application_registration',n+suffix,{display_name:l('name')+(suffix||'-api'),sign_in_audience:'AzureADMyOrg',requested_access_token_version:2,implicit_access_token_issuance_enabled:false,implicit_id_token_issuance_enabled:false});
65
+ const app=(suffix,attribute)=>r('azuread_application_registration',n+suffix,attribute),apiClient=app('','client_id'),spaClient=app('_spa','client_id'),parserClient=app('_parser','client_id');
66
+ const uuid=purpose=>'${uuidv5("url",format("%s:%s:'+purpose+'",var.tenant_id,local.name))}';
67
+ for(const suffix of ['','_parser'])add('azuread_application_identifier_uri',n+suffix,{application_id:app(suffix,'id'),identifier_uri:'api://'+app(suffix,'client_id')});
68
+ add('azuread_application_permission_scope',n,{application_id:app('','id'),scope_id:uuid('user-scope'),value:'access_as_user',type:'Admin',admin_consent_display_name:'Use the invoice workspace',admin_consent_description:'Submit and review invoices under independently managed staff membership.'});
69
+ add('azuread_application_app_role',n,{application_id:app('','id'),role_id:uuid('workspace-role'),allowed_member_types:['User'],display_name:'Invoice staff',description:'Use the invoice application; SQL authorizes individual actions.',value:'WorkspaceUser'});
70
+ add('azuread_application_app_role',n+'_parser',{application_id:app('_parser','id'),role_id:uuid('parser-role'),allowed_member_types:['Application'],display_name:'Parser worker',description:'Invoke the private PDF parser as the exact owned worker.',value:'Parser.Invoke'});
71
+ for(const suffix of ['', '_spa','_parser'])add('azuread_service_principal',n+suffix,{client_id:app(suffix,'client_id'),app_role_assignment_required:suffix!=='_spa',depends_on:suffix==='_spa'?[]:['azuread_application_app_role.'+n+suffix]});
72
+ add('azuread_app_role_assignment',n+'_staff',{for_each:'${var.staff_subjects}',app_role_id:uuid('workspace-role'),principal_object_id:'${each.value}',resource_object_id:r('azuread_service_principal',n,'object_id')});
73
+ add('azuread_app_role_assignment',n+'_parser',{app_role_id:uuid('parser-role'),principal_object_id:identity('worker','principal_id'),resource_object_id:r('azuread_service_principal',n+'_parser','object_id')});
74
+ add('azuread_application_api_access',n,{application_id:app('_spa','id'),api_client_id:apiClient,scope_ids:[uuid('user-scope')],depends_on:['azuread_application_permission_scope.'+n]});
75
+ add('azuread_application_pre_authorized',n,{application_id:app('','id'),authorized_client_id:spaClient,permission_ids:[uuid('user-scope')],depends_on:['azuread_application_permission_scope.'+n]});
76
+ add('azuread_application_redirect_uris',n,{application_id:app('_spa','id'),type:'SPA',redirect_uris:[l('public_origin')+'/']});
77
+ const common={REGION:region,ACCOUNT_ID:l('account_uuid'),WORKFLOW_NAMESPACE:'invoice',DATABASE_NAME:'invoice',DATABASE_HOST:r('azurerm_postgresql_flexible_server',n,'fqdn'),DATABASE_CA_FILE:'/usr/local/lib/python3.13/site-packages/certifi/cacert.pem',DOCUMENT_ACCOUNT:l('storage_name'),DOCUMENT_BUCKET:'invoices',DESTINATION_CONTRACT_BASE64:l('destination_contract_base64')};
78
+ for(const purpose of ['api','worker','parser']){
79
+ const image=v(purpose==='parser'?'parser_image':'runtime_image');
80
+ const settings=purpose==='parser'?{PORT:'8080',PARSER_AUTH_MODE:'entra-workload',PARSER_TENANT_ID:tenant,PARSER_AUDIENCE:parserClient,PARSER_WORKER_CLIENT_ID:identity('worker','client_id'),PARSER_WORKER_PRINCIPAL_ID:identity('worker','principal_id')}:purpose==='api'?{...common,DATABASE_USER:l('api_db_user'),WORKLOAD_IDENTITY_CLIENT_ID:identity('api','client_id'),TENANT_ID:tenant,API_CLIENT_ID:apiClient,SPA_CLIENT_ID:spaClient,PUBLIC_ORIGIN:l('public_origin')}:{...common,DATABASE_USER:l('worker_db_user'),WORKLOAD_IDENTITY_CLIENT_ID:identity('worker','client_id'),MANAGED_IDENTITY_CLIENT_ID:identity('worker','client_id'),PARSER_ENDPOINT:l('parser_endpoint'),PARSER_AUDIENCE:parserClient,MODEL_RESOURCE_ID:r('azurerm_cognitive_account',n,'id'),MODEL_ENDPOINT:r('azurerm_cognitive_account',n,'endpoint'),MODEL_DEPLOYMENT:r('azurerm_cognitive_deployment',n,'name'),MAX_OUTPUT_TOKENS:'2048',DESTINATION_ENDPOINT:endpoint,DESTINATION_SCOPE:scope,DESTINATION_REPLAY_REVIEW_BASE64:v('replay_review_base64')};
81
+ const pull=identity('pull','id'),workload=purpose==='parser'?null:identity(purpose,'id');
82
+ add('azapi_resource',n+'_'+purpose,{count:'${var.applications_enabled ? 1 : 0}',type:'Microsoft.App/containerApps@2025-07-01',name:l('name')+'-'+purpose,parent_id:rgId,location:region,identity:{type:'UserAssigned',identity_ids:[pull,...(workload?[workload]:[])]},body:{properties:{environmentId:r('azurerm_container_app_environment',n,'id'),workloadProfileName:'Consumption',configuration:{activeRevisionsMode:'Single',registries:[{server:r('azurerm_container_registry',n,'login_server'),identity:pull}],identitySettings:[{identity:pull,lifecycle:'None'},...(workload?[{identity:workload,lifecycle:'Main'}]:[])],...(purpose==='worker'?{secrets:[{name:'accounting-token',keyVaultUrl:r('azurerm_key_vault',n,'vault_uri')+'secrets/accounting-token',identity:workload}]}:{}),...(purpose!=='worker'?{ingress:{external:purpose==='api',targetPort:8080,transport:'http',allowInsecure:false,traffic:[{latestRevision:true,weight:100}]}}:{})},template:{scale:{minReplicas:1,maxReplicas:1},containers:[{name:purpose,image,resources:{cpu:purpose==='parser'?1:0.5,memory:purpose==='parser'?'2Gi':'1Gi'},...(purpose==='api'?{}:{command:['python','-I','/app/'+(purpose==='worker'?'invoice_worker.py':'invoice_parser_service.py')]}),env:[...Object.entries(settings).map(([name,value])=>({name,value})),...(purpose==='worker'?[{name:'DESTINATION_TOKEN',secretRef:'accounting-token'}]:[])],probes:[{type:'Liveness',httpGet:{path:'/healthz',port:8080,scheme:'HTTP'},initialDelaySeconds:15,periodSeconds:30,failureThreshold:3}]}]}}},response_export_values:['properties.configuration.ingress.fqdn'],schema_validation_enabled:true,depends_on:['azurerm_role_assignment.'+n+'_pull','azurerm_role_assignment.'+n+'_model','azurerm_role_assignment.'+n+'_model_policy','azurerm_role_assignment.'+n+'_destination','azuread_app_role_assignment.'+n+'_parser','azurerm_private_endpoint.'+n+'_blob','azurerm_private_endpoint.'+n+'_model','azurerm_private_endpoint.'+n+'_vault']});
83
+ b.resource.terraform_data.review_gate.lifecycle.precondition.push({condition:'${!('+(purpose==='parser'?'var.applications_enabled':'var.applications_enabled || var.operations_enabled')+') || startswith(var.'+(purpose==='parser'?'parser_image':'runtime_image')+',format("%s.azurecr.io/invoice/'+(purpose==='parser'?'parser':'runtime')+'@sha256:",local.registry_name))}',error_message:'Use the package-owned private regional registry and immutable image.'});
84
+ }
85
+ b.output.application_url={value:l('public_origin')};b.output.runtime_registry={value:r('azurerm_container_registry',n,'login_server')};
86
+ b.output.operator_deployment={value:{provider:'azure',deploymentId:l('account_uuid'),accountId:l('account_uuid'),namespace:'invoice',tenantId:tenant,bindings:Object.fromEntries(['api','worker'].map(purpose=>[purpose,{name:l(purpose+'_db_user'),principal:identity(purpose,'principal_id')}]))}};
87
+ azureInvoiceOperations(b,n,{rg,rgId,region,subscription,identity});
88
+ b.output.database_bootstrap={value:{host:r('azurerm_postgresql_flexible_server',n,'fqdn'),database:'invoice',operatorClientId:identity('operator','client_id'),operatorLogin:l('name')+'-operator',nativeRoleMappingDatabase:'postgres'}};
89
+ b.assertions.push(`run "private_data_and_parser" {\n command = plan\n assert {\n condition = !azurerm_postgresql_flexible_server.${n}.public_network_access_enabled && !azurerm_postgresql_flexible_server.${n}.authentication[0].password_auth_enabled && !azurerm_storage_account.${n}.public_network_access_enabled && !azurerm_cognitive_account.${n}.local_auth_enabled\n error_message = "Private data and keyless database/model boundaries are required."\n }\n assert {\n condition = azapi_resource.${n}_parser[0].body.properties.configuration.identitySettings[0].lifecycle == "None" && azapi_resource.${n}_parser[0].body.properties.configuration.ingress.external == false\n error_message = "Parser image-pull identity must not be available in the private runtime."\n }\n}\nrun "foundation_only" {\n command = plan\n variables {\n applications_enabled = false\n operations_enabled = false\n maintenance_enabled = false\n }\n assert {\n condition = length(azapi_resource.${n}_api) == 0 && length(azapi_resource.${n}_worker) == 0 && length(azapi_resource.${n}_parser) == 0\n error_message = "Release must follow bootstrap, secret population and immutable publication."\n }\n}`);
90
+ b.assertions.push(`run "separate_workloads_and_regional_model" {\n command = plan\n assert {\n condition = azurerm_user_assigned_identity.${n}_api.client_id != azurerm_user_assigned_identity.${n}_worker.client_id && azurerm_cognitive_deployment.${n}.sku[0].name == "Standard" && azurerm_cognitive_deployment.${n}.version_upgrade_option == "NoAutoUpgrade"\n error_message = "Separate identities and an explicit regional model version are required."\n }\n assert {\n condition = one([for entry in azapi_resource.${n}_parser[0].body.properties.template.containers[0].env : entry.value if entry.name == "PARSER_AUTH_MODE"]) == "entra-workload" && azuread_application_app_role.${n}_parser.value == "Parser.Invoke"\n error_message = "Private ingress alone does not authorize the exact worker."\n }\n}\nrun "mutable_image_rejected" {\n command = plan\n variables { runtime_image = "unreviewed:latest" }\n expect_failures = [var.runtime_image]\n}\nrun "foreign_registry_rejected" {\n command = plan\n variables { runtime_image = "foreign.azurecr.io/invoice/runtime@sha256:${'a'.repeat(64)}" }\n expect_failures = [terraform_data.review_gate]\n}\nrun "unadmitted_staff_rejected" {\n command = plan\n variables { staff_subjects = [] }\n expect_failures = [terraform_data.review_gate]\n}\n`);
91
+ const mock=azureInvoiceMocks(n,b.examples,seed);
92
+ const imageExample=purpose=>'format("%sacr.azurecr.io/invoice/'+purpose+'@sha256:'+'a'.repeat(64)+'",replace(format("invoice-%s",substr(sha256('+JSON.stringify(seed)+'),0,12)),"-",""))';
93
+ b.testExpressions.runtime_image=imageExample('runtime');b.testExpressions.parser_image=imageExample('parser');
94
+ return b.finish({azurerm:{features:{},subscription_id:subscription,tenant_id:tenant,resource_provider_registrations:'none',storage_use_azuread:true},azuread:{tenant_id:tenant},azapi:{subscription_id:subscription,tenant_id:tenant,skip_provider_registration:true,enable_preflight:false}},{azurerm:{source:'hashicorp/azurerm',version:'4.81.0'},azuread:{source:'hashicorp/azuread',version:'3.10.0'},azapi:{source:azapi.source,version:azapi.version}},CLOUD_PROVIDER_LOCKS.azure+'\n'+CLOUD_PROVIDER_LOCKS.azuread+'\n'+azapi.lock,mock,AZURE_INVOICE_GUIDE);
95
+ }
96
+
97
+ function azureInvoiceMocks(n,examples,seed){
98
+ const subscription=examples.subscription_id,tenant=examples.tenant_id,base='/subscriptions/'+subscription+'/resourceGroups/invoice-fixture',guid=i=>'00000000-0000-4000-8000-'+String(i).padStart(12,'0');
99
+ let mock='mock_provider "azurerm" {\n override_during = plan\n mock_data "azurerm_client_config" { defaults = { subscription_id="'+subscription+'",tenant_id="'+tenant+'",object_id="'+guid(1)+'" } }\n}\nmock_provider "azuread" { override_during = plan }\nmock_provider "azapi" { override_during = plan }\n';
100
+ const override=(target,values)=>{mock+='override_resource {\n override_during = plan\n target = '+target+'\n values = '+JSON.stringify(values)+'\n}\n';};
101
+ override('azurerm_resource_group.'+n,{id:base});override('azurerm_virtual_network.'+n,{id:base+'/providers/Microsoft.Network/virtualNetworks/fixture'});
102
+ for(const purpose of ['apps','database','endpoints'])override('azurerm_subnet.'+n+'_'+purpose,{id:base+'/providers/Microsoft.Network/virtualNetworks/fixture/subnets/'+purpose});
103
+ for(const[purpose,i]of ['api','worker','pull','operator'].map((x,i)=>[x,i]))override('azurerm_user_assigned_identity.'+n+'_'+purpose,{id:base+'/providers/Microsoft.ManagedIdentity/userAssignedIdentities/'+purpose,client_id:guid(100+i),principal_id:guid(200+i)});
104
+ for(const purpose of ['database','blob','model','vault'])override('azurerm_private_dns_zone.'+n+'_'+purpose,{id:base+'/providers/Microsoft.Network/privateDnsZones/'+purpose+'.example.test'});
105
+ override('azurerm_postgresql_flexible_server.'+n,{id:base+'/providers/Microsoft.DBforPostgreSQL/flexibleServers/invoice-fixture',fqdn:'invoice-fixture.postgres.database.azure.com'});
106
+ const storage=base+'/providers/Microsoft.Storage/storageAccounts/invoicefixture';override('azurerm_storage_account.'+n,{id:storage});override('azurerm_storage_container.'+n,{id:storage+'/blobServices/default/containers/invoices'});
107
+ override('azurerm_cognitive_account.'+n,{id:base+'/providers/Microsoft.CognitiveServices/accounts/invoice-fixture',endpoint:'https://invoice-fixture.openai.azure.com/'});override('azurerm_key_vault.'+n,{id:base+'/providers/Microsoft.KeyVault/vaults/invoice-fixture',vault_uri:'https://invoice-fixture.vault.azure.net/'});
108
+ override('azurerm_log_analytics_workspace.'+n,{id:base+'/providers/Microsoft.OperationalInsights/workspaces/invoice-fixture'});
109
+ override('azurerm_container_app_environment.'+n,{id:base+'/providers/Microsoft.App/managedEnvironments/invoice-fixture',default_domain:'invoice-fixture.australiaeast.azurecontainerapps.io'});
110
+ mock+='override_resource {\n override_during = plan\n target = azurerm_container_registry.'+n+'\n values = {\n id = '+JSON.stringify(base+'/providers/Microsoft.ContainerRegistry/registries/fixture')+'\n login_server = format("invoice%sacr.azurecr.io",substr(sha256('+JSON.stringify(seed)+'),0,12))\n }\n}\n';
111
+ override('azurerm_role_definition.'+n+'_documents',{role_definition_resource_id:base+'/providers/Microsoft.Authorization/roleDefinitions/'+guid(50)});
112
+ for(const[suffix,i]of [['',1],['_spa',2],['_parser',3]]){override('azuread_application_registration.'+n+suffix,{id:'/applications/'+guid(300+i),client_id:guid(400+i)});override('azuread_service_principal.'+n+suffix,{object_id:guid(500+i)});}
113
+ return mock;
114
+ }
115
+
116
+ const AZURE_INVOICE_GUIDE=`# Azure invoice workflow (under qualification)
117
+
118
+ Separate Container Apps API, worker and private authenticated parser; private
119
+ Entra-only PostgreSQL, versioned private objects, private regional model, native
120
+ secret references and explicit staff/SQL authority. The parser's ACR pull identity
121
+ uses lifecycle None; only the worker's exact app-only Parser.Invoke token is admitted.
122
+ Native cloud isolation/authentication still require authorized destination checks.
123
+
124
+ Foundation precedes native SQL identity/schema/membership bootstrap, accounting
125
+ secret population and exact image publication. Never put secrets in Terraform
126
+ variables or source. See OPERATIONS.md. Application images and direct staff role
127
+ assignments do not imply SQL reviewer membership. No existing users, accounting
128
+ system or external website are recreated. Review regional model/version availability,
129
+ modified monitoring approval, quotas, fixed costs, private operator connectivity,
130
+ state, monitoring and managed restore. OPERATING_JOBS.md supplies native manual
131
+ bootstrap and phased daily/hourly jobs, diagnostic routing and query alerts. Job
132
+ start authority is privileged and remains outside the product. Native operations
133
+ and whole-export qualification remain in progress; this is not a complete invoice case.
134
+ `;
@@ -0,0 +1,35 @@
1
+ import {invoiceApplicationFiles}from'./invoice-application.mjs';
2
+ import {existingReferenceFiles}from'./existing-references.mjs';
3
+ import {INVOICE_OPERATIONS}from'./invoice-operations.mjs';
4
+ import{INVOICE_REGION_PATTERNS,validInvoiceRegion}from'./invoice-region.mjs';
5
+
6
+ export const v=name=>'${var.'+name+'}';
7
+ export const r=(type,name,attribute)=>'${'+type+'.'+name+'.'+attribute+'}';
8
+ export const l=name=>'${local.'+name+'}';
9
+ export const UUID='^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$';
10
+
11
+ export function invoiceBuilder(project,row,environment='staging'){
12
+ if(!project||typeof project.id!=='string'||!project.id||!row||!/^block_[0-9a-f]+$/.test(row.address)||!['aws','azure','gcp','ibm'].includes(row.provider))throw Error('Use a stable owned invoice project, address and provider');
13
+ if(!['staging','production','qualification'].includes(environment))throw Error('Use an explicit supported invoice environment');
14
+ const seed='https://atmem.ai/workspaces/'+encodeURIComponent(project.id)+'/'+row.address+'/'+environment;
15
+ const variable={review_approved:{type:'bool',default:false},applications_enabled:{type:'bool',default:false,description:'Enable only after native SQL identity/schema/membership bootstrap, secret population and immutable image publication are reviewed.'}};
16
+ const examples={review_approved:false,applications_enabled:false},resource={},data={},output={},assertions=[],testExpressions={},files=invoiceApplicationFiles(row.provider,row.address);
17
+ files['OPERATIONS.md']=INVOICE_OPERATIONS;
18
+ const locals={name:'${format("invoice-%s",substr(sha256('+JSON.stringify(seed)+'),0,12))}',account_uuid:new RegExp(UUID).test(project.id)?project.id:'${uuidv5("url",'+JSON.stringify(seed)+')}',api_db_user:'${format("invoice_api_%s",substr(sha256('+JSON.stringify(seed)+'),0,12))}',worker_db_user:'${format("invoice_worker_%s",substr(sha256('+JSON.stringify(seed)+'),0,12))}'};
19
+ const input=(name,description,example,pattern)=>{
20
+ if(variable[name])throw Error('Duplicate invoice input '+name);
21
+ variable[name]={type:'string',nullable:false,description,validation:{condition:pattern?'${can(regex('+JSON.stringify(pattern)+',var.'+name+'))}':'${length(trimspace(var.'+name+')) > 0}',error_message:'Supply the reviewed '+name+' input.'}};examples[name]=example;return v(name);
22
+ };
23
+ if(row.region&&!validInvoiceRegion(row.provider,row.region))throw Error('Preserve and resolve the explicit invoice region as a canonical provider region before compilation.');
24
+ const region=input('region','Reviewed single deployment/data region; verify current service/model availability and quotas.',row.region||{aws:'ap-southeast-2',azure:'australiaeast',gcp:'australia-southeast1',ibm:'au-syd'}[row.provider],INVOICE_REGION_PATTERNS[row.provider]);
25
+ if(row.provider==='aws')variable.region.validation.condition=variable.region.validation.condition.slice(0,-1)+' && !startswith(var.region,"cn-")}';
26
+ if(row.region){variable.region.default=row.region;variable.region.validation={condition:'${var.region == '+JSON.stringify(row.region)+'}',error_message:'Placement is bound to this design. Update and regenerate the design before changing region.'};}
27
+ resource.terraform_data={review_gate:{input:{design_revision:project.revision,environment,provider:row.provider},lifecycle:{precondition:[{condition:'${var.review_approved}',error_message:'Review the actual source, placement, identities, existing accounting contract, costs, operations and destination account before any apply.'}]}}};
28
+ const add=(type,name,body)=>((resource[type]||={})[name]={...body,depends_on:['terraform_data.review_gate',...(body.depends_on||[])]});
29
+ function finish(providerConfig,requiredProviders,lock,mock,guide){
30
+ const values=Object.entries({...examples,review_approved:true,applications_enabled:true}).map(([key,value])=>key+' = '+(testExpressions[key]||JSON.stringify(value))).join('\n');
31
+ const config={terraform:{required_version:'>= 1.17.1, < 2.0.0',required_providers:requiredProviders},provider:providerConfig,variable,locals,data,resource,output};
32
+ return{...files,...existingReferenceFiles(project),'.terraform.lock.hcl':lock,'main.tf.json':JSON.stringify(config,null,2)+'\n','deployment.tfvars.json.example':JSON.stringify(examples,null,2)+'\n','.gitignore':'.terraform/\n*.tfstate*\n*.tfplan\n*.tfvars\n*.tfvars.json\n!deployment.tfvars.json.example\n.local/\n.venv/\nbuild/\n__pycache__/\n','tests/invoice.tftest.hcl':mock+'\nvariables {\n'+values+'\n}\n'+assertions.join('\n')+'\nrun "review_required" {\n command = plan\n variables { review_approved = false }\n expect_failures = [terraform_data.review_gate]\n}\n','README.md':guide,'invoice-deployment-boundary.json':JSON.stringify({provider:row.provider,sourceComponent:row.id,namespace:'invoice',accountIdentity:'local.account_uuid',applicationRelease:'Explicit foundation/bootstrap/application phases; no automatic cloud operations.',externalAccounting:'Preserved external dependency with a normalized, independently testable action/receipt contract; actual endpoint/credentials/idempotency acceptance remain destination inputs.',qualification:'Compiler emission alone is not full project acceptance or deployment evidence.'},null,2)+'\n'};
33
+ }
34
+ return{variable,examples,resource,data,locals,output,assertions,testExpressions,files,input,add,finish,region,seed};
35
+ }