@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,599 @@
1
+ """Root-owned Batch prepare/release/reaper; fixed local Docker Engine only.
2
+
3
+ Standalone standard-library helper. No document bytes pass through this process.
4
+ The trusted transfer artifact alone reads Blob/input/result bytes. This helper is
5
+ not a cloud provisioning contract or evidence that a Batch node is configured.
6
+ """
7
+ from contextlib import contextmanager
8
+ import base64
9
+ from datetime import datetime, timezone
10
+ from email.utils import format_datetime
11
+ import fcntl
12
+ import hashlib
13
+ import http.client
14
+ import json
15
+ import os
16
+ from pathlib import Path
17
+ import re
18
+ import socket
19
+ import stat
20
+ import sys
21
+ import time
22
+ from urllib.parse import quote, urlencode
23
+ import uuid
24
+
25
+ API = '/v1.43'
26
+ MAX_JSON = 1048576
27
+ HASH = r'[0-9a-f]{64}'
28
+ UUID = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
29
+ STATE_FIELDS = {'format', 'requestId', 'nonce', 'owner', 'policy', 'helperSha256',
30
+ 'parserImage', 'transferImage', 'transfer', 'createdAt', 'expiresAt', 'phase', 'published', 'receiptPublished', 'nodeId'}
31
+
32
+
33
+ def unique(pairs):
34
+ value = {}
35
+ for key, item in pairs:
36
+ if key in value: raise ValueError('Ambiguous owned metadata')
37
+ value[key] = item
38
+ return value
39
+
40
+
41
+ def encoded(value):
42
+ return json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False).encode()
43
+
44
+
45
+ def digest(value):
46
+ return hashlib.sha256(encoded(value)).hexdigest()
47
+
48
+
49
+ def match(value, pattern):
50
+ return isinstance(value, str) and re.fullmatch(pattern, value) is not None
51
+
52
+
53
+ def node_identity(value):
54
+ if not match(value, r'[A-Za-z0-9_-]{1,128}'): raise ValueError('Use the exact native node identity')
55
+ return value
56
+
57
+
58
+ def image(value):
59
+ if not match(value, r'[a-z0-9]{5,50}\.azurecr\.io/[a-z0-9]+(?:[._/-][a-z0-9]+)*@sha256:' + HASH):
60
+ raise ValueError('Use an immutable owned ACR artifact')
61
+ return value
62
+
63
+
64
+ def policy(value):
65
+ fields = {'format', 'subscriptionId', 'region', 'resourceGroup', 'batchAccount', 'poolId',
66
+ 'storageAccount', 'container', 'workerClientId', 'transferClientId',
67
+ 'parserImage', 'transferImage', 'hostHelperSha256'}
68
+ if not isinstance(value, dict) or set(value) != fields or value['format'] != 'enterprise-azure-document-job-v1':
69
+ raise ValueError('Use the exact root-owned host policy')
70
+ patterns = {'subscriptionId': UUID, 'region': r'[a-z][a-z0-9]{1,30}',
71
+ 'resourceGroup': r'[A-Za-z0-9_()-](?:[A-Za-z0-9_().-]{0,88}[A-Za-z0-9_()-])?',
72
+ 'batchAccount': r'[a-z0-9]{3,24}', 'poolId': r'document-[a-z0-9-]{1,54}',
73
+ 'storageAccount': r'[a-z0-9]{3,24}', 'container': r'[a-z0-9]+(?:-[a-z0-9]+)*',
74
+ 'workerClientId': UUID, 'transferClientId': UUID, 'hostHelperSha256': HASH}
75
+ if any(not match(value[key], pattern) for key, pattern in patterns.items()) or not 3 <= len(value['container']) <= 63:
76
+ raise ValueError('Invalid owned host destination')
77
+ if value['workerClientId'] == value['transferClientId']:
78
+ raise ValueError('Worker identity must not be on the transfer node')
79
+ for key in ['parserImage', 'transferImage']: image(value[key])
80
+ if (value['parserImage'] == value['transferImage'] or
81
+ value['parserImage'].split('/')[0] != value['transferImage'].split('/')[0]):
82
+ raise ValueError('Use distinct artifacts from the same owned registry')
83
+ return dict(value)
84
+
85
+
86
+ def owner(value):
87
+ checked = policy(value)
88
+ keys = ['subscriptionId', 'region', 'resourceGroup', 'batchAccount', 'poolId',
89
+ 'storageAccount', 'container', 'transferClientId']
90
+ return digest({**{key: checked[key] for key in keys}, 'registry': checked['parserImage'].split('/')[0]})
91
+
92
+
93
+ def volumes(identifier):
94
+ if not match(identifier, HASH): raise ValueError('Invalid owned job identity')
95
+ return ['atmem-document-' + identifier + '-' + side for side in ['input', 'output']]
96
+
97
+
98
+ def transfer(value, config):
99
+ keys = {'format', 'subscriptionId', 'region', 'resourceGroup', 'storageAccount',
100
+ 'container', 'transferClientId', 'request', 'inputETag'}
101
+ if not isinstance(value, dict) or set(value) != keys or value['format'] != 'enterprise-azure-document-transfer-v1':
102
+ raise ValueError('Use exact transfer metadata')
103
+ for key in ['subscriptionId', 'region', 'resourceGroup', 'storageAccount', 'container', 'transferClientId']:
104
+ if value[key] != config[key]: raise ValueError('Transfer destination changed')
105
+ request = value['request']
106
+ if (not isinstance(request, dict) or set(request) != {'format', 'requestId', 'sha256', 'mediaType'} or
107
+ request['format'] != 'enterprise-document-job-v1' or not match(request['requestId'], HASH) or
108
+ not match(request['sha256'], HASH) or not match(request['mediaType'], r'[a-z0-9.+-]+/[a-z0-9.+-]+') or
109
+ len(request['mediaType']) > 200 or not match(value['inputETag'], r'"0x[0-9A-Fa-f]{1,32}"')):
110
+ raise ValueError('Invalid byte-bound transfer metadata')
111
+ # Full supported-MIME validation remains in the independently versioned
112
+ # transfer artifact; this host never opens or parses the file.
113
+ if len(encoded(value)) > 4096: raise ValueError('Transfer metadata exceeds host limit')
114
+ return json.loads(encoded(value), object_pairs_hook=unique)
115
+
116
+
117
+ def protected_directory(path, *, uid=0):
118
+ """Walk every absolute ancestor without following symlinks, including /var."""
119
+ path = Path(path)
120
+ if not path.is_absolute() or '..' in path.parts: raise ValueError('Use an absolute protected directory')
121
+ fd = os.open('/', os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
122
+ try:
123
+ for part in path.parts[1:]:
124
+ child = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=fd)
125
+ os.close(fd); fd = child
126
+ info = os.fstat(fd)
127
+ if info.st_uid != uid or stat.S_IMODE(info.st_mode) & 0o022:
128
+ raise ValueError('Host directory is not protected by its owner')
129
+ return fd
130
+ except BaseException:
131
+ os.close(fd)
132
+ raise
133
+
134
+
135
+ def owned_file(directory, name, *, uid=0, limit=16384):
136
+ fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=directory)
137
+ with os.fdopen(fd, 'rb') as stream:
138
+ info = os.fstat(stream.fileno())
139
+ if (not stat.S_ISREG(info.st_mode) or info.st_uid != uid or info.st_nlink != 1 or
140
+ stat.S_IMODE(info.st_mode) & 0o022 or not 0 < info.st_size <= limit):
141
+ raise ValueError('Invalid protected host metadata file')
142
+ raw = stream.read(limit + 1)
143
+ if len(raw) != info.st_size: raise ValueError('Host metadata changed')
144
+ return raw
145
+
146
+
147
+ class Store:
148
+ def __init__(self, directory, *, uid=0):
149
+ info = os.fstat(directory)
150
+ if not stat.S_ISDIR(info.st_mode) or info.st_uid != uid or stat.S_IMODE(info.st_mode) != 0o700:
151
+ raise ValueError('Use the private root-owned state directory')
152
+ self.fd, self.uid = os.dup(directory), uid
153
+
154
+ def close(self): os.close(self.fd)
155
+
156
+ @contextmanager
157
+ def lock(self, identifier):
158
+ volumes(identifier)
159
+ fd = os.open(identifier + '.lock', os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW | os.O_NONBLOCK, 0o600, dir_fd=self.fd)
160
+ try:
161
+ info = os.fstat(fd)
162
+ if not stat.S_ISREG(info.st_mode) or info.st_uid != self.uid or info.st_nlink != 1 or stat.S_IMODE(info.st_mode) != 0o600:
163
+ raise ValueError('Invalid owned state lock')
164
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
165
+ yield
166
+ finally: os.close(fd)
167
+
168
+ def read(self, identifier):
169
+ volumes(identifier)
170
+ try: raw = owned_file(self.fd, identifier + '.json', uid=self.uid)
171
+ except FileNotFoundError: return None
172
+ value = json.loads(raw, object_pairs_hook=unique)
173
+ if not isinstance(value, dict): raise ValueError('Invalid owned state')
174
+ return value
175
+
176
+ def write(self, state, *, create=False):
177
+ name = volumes(state['requestId']) and state['requestId'] + '.json'
178
+ raw = encoded(state)
179
+ if len(raw) > 16384: raise ValueError('State exceeds owned bound')
180
+ temporary = name if create else '.write-' + uuid.uuid4().hex
181
+ fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600, dir_fd=self.fd)
182
+ try:
183
+ with os.fdopen(fd, 'wb') as stream:
184
+ stream.write(raw); stream.flush(); os.fsync(stream.fileno())
185
+ if not create: os.replace(temporary, name, src_dir_fd=self.fd, dst_dir_fd=self.fd)
186
+ os.fsync(self.fd)
187
+ finally:
188
+ if not create:
189
+ try: os.unlink(temporary, dir_fd=self.fd)
190
+ except FileNotFoundError: pass
191
+
192
+ def identifiers(self):
193
+ # State contains metadata only. Tombstones deliberately prevent adoption
194
+ # of a retried/replayed Batch job ID; no age-based source erasure claim.
195
+ return sorted(name[:-5] for name in os.listdir(self.fd) if re.fullmatch(HASH + r'\.json', name))
196
+
197
+
198
+ class UnixConnection(http.client.HTTPConnection):
199
+ def __init__(self, path):
200
+ super().__init__('localhost', timeout=3)
201
+ self.path = path
202
+ def connect(self):
203
+ self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
204
+ self.sock.settimeout(self.timeout)
205
+ self.sock.connect(self.path)
206
+
207
+
208
+ class Engine:
209
+ def __init__(self, path='/var/run/docker.sock'):
210
+ if not isinstance(path, str) or not path.startswith('/') or not stat.S_ISSOCK(os.stat(path).st_mode):
211
+ raise ValueError('Use the fixed local Docker Engine socket')
212
+ self.path = path
213
+
214
+ def call(self, method, path, body=None, *, statuses=(200,), missing=False, timeout=10):
215
+ if method not in {'GET', 'POST', 'DELETE'} or not path.startswith(API + '/'):
216
+ raise ValueError('Use only the owned native Engine API')
217
+ connection = UnixConnection(self.path)
218
+ started = time.monotonic()
219
+ try:
220
+ raw = None if body is None else encoded(body)
221
+ connection.request(method, path, body=raw, headers={} if raw is None else {'Content-Type': 'application/json'})
222
+ response = connection.getresponse()
223
+ if missing and response.status == 404: return None
224
+ if response.status not in statuses: raise ValueError('Owned Engine operation failed')
225
+ chunks = bytearray()
226
+ while True:
227
+ piece = response.read(16384)
228
+ if not piece: break
229
+ chunks.extend(piece)
230
+ if len(chunks) > MAX_JSON or time.monotonic() - started > timeout:
231
+ raise ValueError('Owned Engine response exceeded bounds')
232
+ if not chunks: return {}
233
+ return json.loads(chunks, object_pairs_hook=unique)
234
+ finally: connection.close()
235
+
236
+
237
+ def cleanup_receipt(state):
238
+ if state.get('phase') != 'cleaned' or not match(state.get('requestId'), HASH) or not match(state.get('owner'), HASH):
239
+ raise ValueError('Only a completed owned cleanup can be acknowledged')
240
+ return {'format': 'enterprise-azure-document-cleanup-v2', 'requestId': state['requestId'],
241
+ 'owner': state['owner'], 'nodeId': node_identity(state.get('nodeId')), 'cleaned': True}
242
+
243
+
244
+ class RootReceipts:
245
+ """Only metadata leaves this root process; no document or result reader.
246
+
247
+ A create-only receipt remains until the generated store's metadata lifecycle
248
+ expires it. This makes a lost acknowledgement repeatable without recreating
249
+ document bytes. VM MI is a node boundary, not a per-process security boundary.
250
+ """
251
+ def __init__(self, config, node_id, *, connection=None):
252
+ self.config = policy(config)
253
+ self.node_id = node_identity(node_id)
254
+ self.connection = connection
255
+
256
+ def send(self, method, host, path, *, headers=None, data=None):
257
+ connection = (self.connection(method, host, path) if self.connection is not None else
258
+ (http.client.HTTPConnection(host, timeout=3) if host == '169.254.169.254' else
259
+ http.client.HTTPSConnection(host, timeout=3)))
260
+ try:
261
+ connection.request(method, path, body=data, headers={'Accept-Encoding': 'identity', **(headers or {})})
262
+ response = connection.getresponse()
263
+ observed = {}
264
+ for key, value in response.getheaders():
265
+ if key.lower() in observed: raise ValueError()
266
+ observed[key.lower()] = value
267
+ if observed.get('content-encoding') not in {None, 'identity'}: raise ValueError()
268
+ started = time.monotonic(); raw = bytearray()
269
+ while True:
270
+ chunk = response.read(4096)
271
+ if not chunk: break
272
+ raw.extend(chunk)
273
+ if len(raw) > 32768 or time.monotonic() - started > 8: raise ValueError()
274
+ return response.status, observed, bytes(raw)
275
+ finally: connection.close()
276
+
277
+ def publish(self, receipt):
278
+ if not isinstance(receipt, dict) or receipt.get('cleaned') is not True:
279
+ raise ValueError('Use the exact metadata-only owned cleanup receipt')
280
+ expected = {'format': 'enterprise-azure-document-cleanup-v2', 'requestId': receipt.get('requestId'),
281
+ 'owner': owner(self.config), 'nodeId': self.node_id, 'cleaned': True}
282
+ if receipt != expected or not match(receipt.get('requestId'), HASH):
283
+ raise ValueError('Use the exact metadata-only owned cleanup receipt')
284
+ try:
285
+ path = '/metadata/identity/oauth2/token?' + urlencode({'api-version': '2018-02-01',
286
+ 'resource': 'https://storage.azure.com/', 'client_id': self.config['transferClientId']})
287
+ status, _, raw = self.send('GET', '169.254.169.254', path, headers={'Metadata': 'true'})
288
+ if status != 200: raise ValueError()
289
+ access = json.loads(raw, object_pairs_hook=unique)
290
+ token = access.get('access_token')
291
+ if (access.get('token_type') != 'Bearer' or access.get('resource') != 'https://storage.azure.com/' or
292
+ not match(access.get('expires_on'), '[1-9][0-9]{0,11}') or int(access['expires_on']) < time.time() + 30 or
293
+ not isinstance(token, str) or not 8 <= len(token) <= 20000 or any(ord(c) < 33 or ord(c) > 126 for c in token)):
294
+ raise ValueError()
295
+ host = self.config['storageAccount'] + '.blob.core.windows.net'
296
+ path = '/' + self.config['container'] + '/document-jobs/' + receipt['requestId'] + '/cleanup/' + self.node_id
297
+ raw = encoded(receipt)
298
+ common = {'Authorization': 'Bearer ' + token, 'x-ms-version': '2025-05-05',
299
+ 'x-ms-date': format_datetime(datetime.now(timezone.utc), usegmt=True)}
300
+ status, headers, _ = self.send('PUT', host, path, data=raw, headers={**common,
301
+ 'If-None-Match': '*', 'x-ms-blob-type': 'BlockBlob', 'Content-Type': 'application/json',
302
+ 'Content-Length': str(len(raw)),
303
+ 'Content-MD5': base64.b64encode(hashlib.md5(raw, usedforsecurity=False).digest()).decode()})
304
+ if status == 201:
305
+ if (headers.get('x-ms-request-server-encrypted') != 'true' or headers.get('x-ms-version-id') is not None or
306
+ not match(headers.get('etag'), r'"0x[0-9A-Fa-f]{1,32}"')):
307
+ raise ValueError()
308
+ return
309
+ if status != 412: raise ValueError()
310
+ # A lost PUT response may have committed the exact acknowledgement.
311
+ # Never overwrite or adopt a different owner's receipt.
312
+ status, headers, observed = self.send('GET', host, path, headers=common)
313
+ if (status != 200 or headers.get('x-ms-server-encrypted') != 'true' or
314
+ headers.get('x-ms-blob-type') != 'BlockBlob' or headers.get('x-ms-version-id') is not None or
315
+ headers.get('content-length') != str(len(observed)) or observed != raw):
316
+ raise ValueError()
317
+ except Exception:
318
+ raise ValueError('Owned cleanup acknowledgement remains pending') from None
319
+
320
+
321
+ class Host:
322
+ def __init__(self, config, store, engine, *, node_id, receipts, clock=time.time, monotonic=time.monotonic, sleep=time.sleep):
323
+ self.config, self.store, self.engine = policy(config), store, engine
324
+ self.node_id = node_identity(node_id)
325
+ self.clock, self.monotonic, self.sleep = clock, monotonic, sleep
326
+ if not callable(getattr(receipts, 'publish', None)): raise ValueError('Use an owned cleanup receipt publisher')
327
+ self.receipts = receipts
328
+ self.owner = owner(self.config)
329
+
330
+ def check_state(self, state):
331
+ if isinstance(state, dict) and state.get('format') == 'enterprise-azure-document-host-v1' and set(state) == STATE_FIELDS - {'receiptPublished', 'nodeId'}:
332
+ state.update(format='enterprise-azure-document-host-v2', receiptPublished=False)
333
+ if isinstance(state, dict) and state.get('format') == 'enterprise-azure-document-host-v2' and set(state) == STATE_FIELDS - {'nodeId'}:
334
+ # A legacy global receipt did not settle every possible retry node.
335
+ # Reinspect this local node and publish its own scoped receipt.
336
+ state.update(format='enterprise-azure-document-host-v3', nodeId=self.node_id, receiptPublished=False)
337
+ if (not isinstance(state, dict) or set(state) != STATE_FIELDS or state['format'] != 'enterprise-azure-document-host-v3' or
338
+ state['nodeId'] != self.node_id or
339
+ not match(state['requestId'], HASH) or not match(state['nonce'], '[0-9a-f]{32}') or
340
+ state['owner'] != self.owner or not match(state['policy'], HASH) or not match(state['helperSha256'], HASH) or
341
+ type(state['createdAt']) is not int or type(state['expiresAt']) is not int or
342
+ state['expiresAt'] - state['createdAt'] != 600 or type(state['published']) is not bool or type(state['receiptPublished']) is not bool or
343
+ (state['receiptPublished'] and state['phase'] != 'cleaned') or
344
+ state['phase'] not in {'preparing', 'ready', 'releasing', 'cleanup_failed', 'cleaned'}):
345
+ raise ValueError('Unowned or invalid recovery state')
346
+ for key in ['parserImage', 'transferImage']:
347
+ if image(state[key]).split('/')[0] != self.config['parserImage'].split('/')[0]:
348
+ raise ValueError('Recovery artifact registry changed')
349
+ transfer(state['transfer'], self.config)
350
+ if state['transfer']['request']['requestId'] != state['requestId']:
351
+ raise ValueError('Recovery request identity changed')
352
+ return state
353
+
354
+ def labels(self, state):
355
+ return {'atmem.document.owner': state['owner'], 'atmem.document.request': state['requestId'],
356
+ 'atmem.document.policy': state['policy']}
357
+
358
+ def volume_labels(self, state, side):
359
+ return {**self.labels(state), 'atmem.document.nonce': state['nonce'], 'atmem.document.side': side}
360
+
361
+ def volume(self, state, side, *, missing=False):
362
+ name = volumes(state['requestId'])[0 if side == 'input' else 1]
363
+ info = self.engine.call('GET', API + '/volumes/' + quote(name, safe=''), missing=missing)
364
+ if info is None: return None
365
+ if (info.get('Name') != name or info.get('Driver') != 'local' or (info.get('Options') is not None and info.get('Options') != {}) or
366
+ info.get('Labels') != self.volume_labels(state, side)):
367
+ raise ValueError('Do not adopt or delete an unowned volume')
368
+ return info
369
+
370
+ def preflight(self):
371
+ info = self.engine.call('GET', API + '/info')
372
+ if (info.get('OSType') != 'linux' or info.get('LiveRestoreEnabled') is not False or
373
+ info.get('LoggingDriver') != 'none' or
374
+ not any(item.startswith('name=seccomp,profile=builtin') for item in info.get('SecurityOptions', []))):
375
+ raise ValueError('Use the reviewed dedicated no-log/non-live-restore Engine')
376
+ for key in ['parserImage', 'transferImage']:
377
+ info = self.engine.call('GET', API + '/images/' + quote(self.config[key], safe='') + '/json')
378
+ if (self.config[key] not in info.get('RepoDigests', []) or info.get('Os') != 'linux' or
379
+ info.get('Architecture') != 'amd64' or info.get('Config', {}).get('User') != '10001:10001'):
380
+ raise ValueError('Owned immutable artifact is not prefetched')
381
+
382
+ def container(self, state, identifier):
383
+ if not match(identifier, HASH): raise ValueError('Invalid native container ID')
384
+ info = self.engine.call('GET', API + '/containers/' + identifier + '/json', missing=True)
385
+ if info is None: return None
386
+ config, host = info['Config'], info['HostConfig']
387
+ labels = config.get('Labels') or {}
388
+ stage = labels.get('atmem.document.stage')
389
+ if stage not in {'parser', 'download', 'upload'} or any(labels.get(k) != v for k, v in self.labels(state).items()):
390
+ raise ValueError('Foreign container uses an owned volume')
391
+ expected_image = state['parserImage'] if stage == 'parser' else state['transferImage']
392
+ if config.get('Image') != expected_image:
393
+ raise ValueError('Container artifact ownership changed')
394
+ if ((stage == 'parser' and not match(config.get('User'), r'[1-9][0-9]{0,9}(?::[0-9]{1,10})?')) or
395
+ (stage != 'parser' and config.get('User') != '0:0')):
396
+ raise ValueError('Container execution user changed')
397
+ if stage != 'parser' and labels.get('atmem.document.nonce') != state['nonce']:
398
+ raise ValueError('Transfer container ownership changed')
399
+ mounts = info.get('Mounts', [])
400
+ if len(mounts) != 2: raise ValueError('Unexpected host or data mount')
401
+ for side, name in zip(['input', 'output'], volumes(state['requestId'])):
402
+ entries = [m for m in mounts if m.get('Destination') == '/' + side]
403
+ writable = stage == 'download' or stage == 'parser' and side == 'output'
404
+ if len(entries) != 1 or entries[0].get('Type') != 'volume' or entries[0].get('Name') != name or entries[0].get('RW') is not writable:
405
+ raise ValueError('Per-file mount ownership changed')
406
+ if (host.get('NetworkMode') != ('none' if stage == 'parser' else 'host') or
407
+ host.get('ReadonlyRootfs') is not True or host.get('Privileged') is not False or
408
+ host.get('CapDrop') != ['ALL'] or
409
+ (host.get('CapAdd') or []) != (['DAC_READ_SEARCH'] if stage == 'upload' else []) or
410
+ host.get('PidMode') not in {'', None} or host.get('IpcMode') not in {'private', ''} or
411
+ host.get('Devices') or host.get('LogConfig', {}).get('Type') != 'none' or
412
+ 'no-new-privileges:true' not in (host.get('SecurityOpt') or [])):
413
+ raise ValueError('Container isolation changed; refuse unowned cleanup')
414
+ return info
415
+
416
+ def containers(self, state):
417
+ identifiers = set()
418
+ for name in volumes(state['requestId']):
419
+ filters = json.dumps({'volume': [name]}, separators=(',', ':'))
420
+ result = self.engine.call('GET', API + '/containers/json?' + urlencode({'all': '1', 'filters': filters}))
421
+ if not isinstance(result, list) or len(result) > 32: raise ValueError('Unexpected owned volume consumers')
422
+ for item in result:
423
+ if not match(item.get('Id'), HASH): raise ValueError('Invalid native consumer identity')
424
+ identifiers.add(item['Id'])
425
+ # Validate every consumer before deleting any container or volume.
426
+ return [info for identifier in sorted(identifiers) if (info := self.container(state, identifier)) is not None]
427
+
428
+ def cleanup(self, state):
429
+ self.check_state(state)
430
+ for side in ['input', 'output']: self.volume(state, side, missing=True)
431
+ for info in self.containers(state):
432
+ self.engine.call('DELETE', API + '/containers/' + info['Id'] + '?force=true', statuses=(204,), missing=True)
433
+ for side in ['output', 'input']:
434
+ found = self.volume(state, side, missing=True)
435
+ if found is not None:
436
+ self.engine.call('DELETE', API + '/volumes/' + quote(found['Name'], safe=''), statuses=(204,), missing=True)
437
+ state['phase'] = 'cleaned'
438
+ self.store.write(state)
439
+ if not state['receiptPublished']:
440
+ self.receipts.publish(cleanup_receipt(state))
441
+ state['receiptPublished'] = True
442
+ self.store.write(state)
443
+
444
+ def run_transfer(self, state, stage):
445
+ if stage not in {'download', 'upload'}: raise ValueError('Use a trusted transfer stage')
446
+ labels = {**self.labels(state), 'atmem.document.nonce': state['nonce'], 'atmem.document.stage': stage}
447
+ mounts = [{'Type': 'volume', 'Source': name, 'Target': '/' + side, 'ReadOnly': stage == 'upload'}
448
+ for side, name in zip(['input', 'output'], volumes(state['requestId']))]
449
+ body = {'Image': state['transferImage'], 'User': '0:0',
450
+ 'Cmd': ['python', '-I', '/app/enterprise_azure_transfer.py', stage],
451
+ 'Env': ['DOCUMENT_JOB=' + encoded(state['transfer']).decode()], 'Labels': labels,
452
+ 'HostConfig': {'NetworkMode': 'host', 'ReadonlyRootfs': True, 'Privileged': False,
453
+ 'CapDrop': ['ALL'], 'CapAdd': ['DAC_READ_SEARCH'] if stage == 'upload' else [],
454
+ 'SecurityOpt': ['no-new-privileges:true'], 'LogConfig': {'Type': 'none', 'Config': {}},
455
+ 'PidsLimit': 64, 'Memory': 268435456, 'MemorySwap': 268435456, 'NanoCpus': 1000000000,
456
+ 'Mounts': mounts, 'Tmpfs': {'/tmp': 'rw,noexec,nosuid,nodev,size=32m'}, 'RestartPolicy': {'Name': 'no'}}}
457
+ name = 'atmem-document-' + state['requestId'] + '-' + stage
458
+ reply = self.engine.call('POST', API + '/containers/create?' + urlencode({'name': name, 'platform': 'linux/amd64'}), body, statuses=(201,))
459
+ identifier = reply.get('Id')
460
+ if not match(identifier, HASH): raise ValueError('Invalid native transfer acknowledgement')
461
+ self.container(state, identifier)
462
+ self.engine.call('POST', API + '/containers/' + identifier + '/start', statuses=(204,))
463
+ deadline = self.monotonic() + 35
464
+ while True:
465
+ observed = self.container(state, identifier)
466
+ if observed is None: raise ValueError('Transfer process disappeared')
467
+ if not observed['State']['Running']:
468
+ if observed['State'].get('ExitCode') != 0 or observed['State'].get('OOMKilled'):
469
+ raise ValueError('Trusted transfer failed')
470
+ self.engine.call('DELETE', API + '/containers/' + identifier, statuses=(204,))
471
+ return
472
+ if self.monotonic() >= deadline: raise ValueError('Trusted transfer exceeded deadline')
473
+ self.sleep(.1)
474
+
475
+ def prepare(self, value):
476
+ value = transfer(value, self.config)
477
+ identifier = value['request']['requestId']
478
+ with self.store.lock(identifier):
479
+ if self.store.read(identifier) is not None: raise ValueError('Do not replay an existing per-file job')
480
+ self.preflight()
481
+ now = int(self.clock())
482
+ state = {'format': 'enterprise-azure-document-host-v3', 'requestId': identifier, 'nodeId': self.node_id, 'nonce': uuid.uuid4().hex,
483
+ 'owner': self.owner, 'policy': digest(self.config), 'helperSha256': self.config['hostHelperSha256'],
484
+ 'parserImage': self.config['parserImage'], 'transferImage': self.config['transferImage'], 'transfer': value,
485
+ 'createdAt': now, 'expiresAt': now + 600, 'phase': 'preparing', 'published': False, 'receiptPublished': False}
486
+ # Durable intent exists before the first volume/container creation.
487
+ self.store.write(state, create=True)
488
+ try:
489
+ for side, name in zip(['input', 'output'], volumes(identifier)):
490
+ if self.engine.call('GET', API + '/volumes/' + quote(name, safe=''), missing=True) is not None:
491
+ raise ValueError('Do not adopt an existing volume')
492
+ self.engine.call('POST', API + '/volumes/create', {'Name': name, 'Driver': 'local',
493
+ 'Labels': self.volume_labels(state, side)}, statuses=(201,))
494
+ self.volume(state, side)
495
+ self.run_transfer(state, 'download')
496
+ state['phase'] = 'ready'
497
+ self.store.write(state)
498
+ except BaseException:
499
+ try: self.cleanup(state)
500
+ except Exception:
501
+ if state['phase'] != 'cleaned': state['phase'] = 'cleanup_failed'
502
+ self.store.write(state)
503
+ raise
504
+
505
+ def release(self, value, transfer_image):
506
+ value = transfer(value, self.config)
507
+ identifier = value['request']['requestId']
508
+ with self.store.lock(identifier):
509
+ state = self.store.read(identifier)
510
+ if state is None: raise ValueError('No owned per-file preparation')
511
+ self.check_state(state)
512
+ if state['transfer'] != value or state['transferImage'] != transfer_image:
513
+ raise ValueError('Release must match the original per-file owner')
514
+ if state['phase'] == 'cleaned':
515
+ if not state['receiptPublished']: self.cleanup(state)
516
+ if not state['published']: raise ValueError('Failed job was cleaned without publication')
517
+ return
518
+ succeeded = False
519
+ try:
520
+ if state['phase'] != 'ready': raise ValueError('Preparation never completed')
521
+ if any(info['State']['Running'] for info in self.containers(state)):
522
+ raise ValueError('A parser or transfer still owns the file')
523
+ state['phase'] = 'releasing'; self.store.write(state)
524
+ self.run_transfer(state, 'upload')
525
+ state['published'] = True; self.store.write(state)
526
+ succeeded = True
527
+ finally:
528
+ try: self.cleanup(state)
529
+ except Exception:
530
+ if state['phase'] != 'cleaned': state['phase'] = 'cleanup_failed'
531
+ self.store.write(state)
532
+ raise
533
+ if not succeeded: raise ValueError('Document publication did not complete')
534
+
535
+ def reap(self):
536
+ failures = []
537
+ for identifier in self.store.identifiers():
538
+ try:
539
+ with self.store.lock(identifier):
540
+ state = self.check_state(self.store.read(identifier))
541
+ if ((state['phase'] == 'cleaned' and not state['receiptPublished']) or
542
+ state['phase'] != 'cleaned' and state['expiresAt'] <= int(self.clock())):
543
+ self.cleanup(state)
544
+ except BlockingIOError:
545
+ continue # An active trusted prepare/release holds this lock.
546
+ except Exception:
547
+ failures.append(identifier)
548
+ if failures: raise ValueError('Owned document cleanup remains pending')
549
+
550
+
551
+ def main():
552
+ store = None
553
+ try:
554
+ if os.geteuid() != 0 or len(sys.argv) != 2 or sys.argv[1] not in {'prepare', 'release', 'reap'}:
555
+ raise ValueError('Use the fixed root-owned host command')
556
+ directory = protected_directory('/etc/atmem')
557
+ try:
558
+ config = policy(json.loads(owned_file(directory, 'document-host.json'), object_pairs_hook=unique))
559
+ node = json.loads(owned_file(directory, 'document-host-node.json'), object_pairs_hook=unique)
560
+ if not isinstance(node, dict) or set(node) != {'format', 'nodeId'} or node['format'] != 'enterprise-azure-document-node-v1':
561
+ raise ValueError('Use the protected instance-specific native node identity')
562
+ node_id = node_identity(node['nodeId'])
563
+ finally: os.close(directory)
564
+ directory = protected_directory('/var/lib/atmem/document-jobs')
565
+ try: store = Store(directory)
566
+ finally: os.close(directory)
567
+ host = Host(config, store, Engine(), node_id=node_id, receipts=RootReceipts(config, node_id))
568
+ mode = sys.argv[1]
569
+ # The root service may invoke reap directly, without the inline Batch
570
+ # bootstrap. Verify this installed standalone helper in both paths.
571
+ helper_dir = protected_directory(str(Path(__file__).parent))
572
+ try: helper_sha = hashlib.sha256(owned_file(helper_dir, Path(__file__).name, limit=262144)).hexdigest()
573
+ finally: os.close(helper_dir)
574
+ if mode in {'prepare', 'reap'} and helper_sha != config['hostHelperSha256']:
575
+ raise ValueError('Installed helper differs from the protected pool policy')
576
+ if mode == 'reap': host.reap(); return
577
+ value = os.environ.get('DOCUMENT_JOB', '')
578
+ if not 0 < len(value.encode()) <= 4096: raise ValueError('Use the owned per-file metadata')
579
+ value = transfer(json.loads(value, object_pairs_hook=unique), config)
580
+ if (os.environ.get('AZ_BATCH_JOB_ID') != value['request']['requestId'] or
581
+ os.environ.get('AZ_BATCH_POOL_ID') != config['poolId'] or
582
+ os.environ.get('AZ_BATCH_ACCOUNT_NAME') != config['batchAccount'] or os.environ.get('AZ_BATCH_NODE_ID') != node_id):
583
+ raise ValueError('Native Batch owner changed')
584
+ if mode == 'prepare':
585
+ if os.environ.get('DOCUMENT_TRANSFER_IMAGE') != config['transferImage']:
586
+ raise ValueError('Transfer artifact differs from the protected pool policy')
587
+ host.prepare(value)
588
+ else:
589
+ existing = store.read(value['request']['requestId'])
590
+ if existing is None or existing.get('helperSha256') != helper_sha:
591
+ raise ValueError('Release helper differs from the original per-file owner')
592
+ host.release(value, os.environ.get('DOCUMENT_TRANSFER_IMAGE'))
593
+ except Exception:
594
+ raise SystemExit('Owned Azure document host operation could not complete; recovery remains pending.') from None
595
+ finally:
596
+ if store is not None: store.close()
597
+
598
+
599
+ if __name__ == '__main__': main()