@atmem/architect 0.0.0-stage → 0.2.0-alpha.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (498) hide show
  1. package/CONSTITUTION.md +144 -0
  2. package/README.md +33 -2
  3. package/agent-skills/discovery/SKILL.md +48 -0
  4. package/agent-skills/discovery/topics.json +139 -0
  5. package/agent-skills/generation-capabilities.json +760 -0
  6. package/agent-skills/service-lifecycle.json +21 -0
  7. package/agent-skills/specialists/architecture/SKILL.md +15 -0
  8. package/agent-skills/specialists/aws/SKILL.md +17 -0
  9. package/agent-skills/specialists/azure/SKILL.md +7 -0
  10. package/agent-skills/specialists/composition/SKILL.md +17 -0
  11. package/agent-skills/specialists/devops/SKILL.md +10 -0
  12. package/agent-skills/specialists/finops/SKILL.md +7 -0
  13. package/agent-skills/specialists/gcp/SKILL.md +7 -0
  14. package/agent-skills/specialists/ibm/SKILL.md +7 -0
  15. package/agent-skills/specialists/manifest.json +13 -0
  16. package/agent-skills/specialists/pulumi/SKILL.md +7 -0
  17. package/agent-skills/specialists/security/SKILL.md +7 -0
  18. package/agent-skills/specialists/terraform/SKILL.md +14 -0
  19. package/agent-skills/upstream/aws/LICENSE +175 -0
  20. package/agent-skills/upstream/aws/SKILL.md +125 -0
  21. package/agent-skills/upstream/azure/LICENSE +21 -0
  22. package/agent-skills/upstream/azure/SKILL.md +387 -0
  23. package/agent-skills/upstream/gcp/LICENSE +202 -0
  24. package/agent-skills/upstream/gcp/SKILL.md +159 -0
  25. package/agent-skills/upstream/manifest.json +57 -0
  26. package/agent-skills/upstream/pulumi/LICENSE +201 -0
  27. package/agent-skills/upstream/pulumi/SKILL.md +501 -0
  28. package/agent-skills/upstream/terraform/LICENSE +373 -0
  29. package/agent-skills/upstream/terraform/SKILL.md +316 -0
  30. package/agent-skills/workflows/workflows.json +601 -0
  31. package/cli/architect.mjs +3 -0
  32. package/cli/runtime.mjs +115 -0
  33. package/dist/assets/html2canvas.esm-QH1iLAAe.js +22 -0
  34. package/dist/assets/index-B97MkVMs.css +1 -0
  35. package/dist/assets/index-qtCM2D7O.js +21271 -0
  36. package/dist/assets/index.es-C6C0sneB.js +18 -0
  37. package/dist/assets/purify.es-CAi2OJFm.js +3 -0
  38. package/dist/assets/visual-export-DjbjgQbA.js +173 -0
  39. package/dist/favicon.svg +2 -0
  40. package/dist/fonts/DM-Sans-OFL.txt +93 -0
  41. package/dist/fonts/Manrope-OFL.txt +93 -0
  42. package/dist/fonts/NotoSans-Regular.ttf +0 -0
  43. package/dist/fonts/OFL.txt +94 -0
  44. package/dist/fonts/dm-sans-latin-wght-normal.woff2 +0 -0
  45. package/dist/fonts/manrope-latin-wght-normal.woff2 +0 -0
  46. package/dist/index.html +4 -0
  47. package/dist/licenses/aws-architect-LICENSE.txt +17 -0
  48. package/dist/licenses/aws-mcp-LICENSE.txt +175 -0
  49. package/dist/licenses/aws-mcp-NOTICE.txt +1 -0
  50. package/dist/licenses/cloud-emulation-notices.txt +36619 -0
  51. package/dist/licenses/dependency-notices.txt +6379 -0
  52. package/dist/licenses/emulation-notices.txt +717 -0
  53. package/dist/licenses/github-copilot-sdk-LICENSE.txt +21 -0
  54. package/dist/licenses/memory-dependency-notices.txt +3319 -0
  55. package/dist/licenses/openarchflow-LICENSE.txt +21 -0
  56. package/dist/licenses/python-dependency-notices.txt +11951 -0
  57. package/docs/upstream-lock.json +26 -0
  58. package/knowledge/engineering-practices.json +536 -0
  59. package/package.json +30 -4
  60. package/packaging/uv-release-lock.json +54 -0
  61. package/public/licenses/aws-architect-LICENSE.txt +17 -0
  62. package/public/licenses/aws-mcp-LICENSE.txt +175 -0
  63. package/public/licenses/aws-mcp-NOTICE.txt +1 -0
  64. package/public/licenses/cloud-emulation-notices.txt +36619 -0
  65. package/public/licenses/dependency-notices.txt +6379 -0
  66. package/public/licenses/emulation-notices.txt +717 -0
  67. package/public/licenses/github-copilot-sdk-LICENSE.txt +21 -0
  68. package/public/licenses/memory-dependency-notices.txt +3319 -0
  69. package/public/licenses/openarchflow-LICENSE.txt +21 -0
  70. package/public/licenses/python-dependency-notices.txt +11951 -0
  71. package/scripts/terraform-render.mjs +8 -0
  72. package/server/__init__.py +0 -0
  73. package/server/abstraction.py +28 -0
  74. package/server/agent_runtime.py +153 -0
  75. package/server/app.py +257 -0
  76. package/server/architect.py +112 -0
  77. package/server/architecture_activity.py +67 -0
  78. package/server/attachments.py +101 -0
  79. package/server/cloud-emulation-requirements.txt +29 -0
  80. package/server/codex_app_server.py +219 -0
  81. package/server/codex_engineering_backend.py +266 -0
  82. package/server/codex_worker_policy.py +170 -0
  83. package/server/contracts.py +315 -0
  84. package/server/copilot_worker.py +16 -0
  85. package/server/dependency_mirror.py +54 -0
  86. package/server/design_patch.py +43 -0
  87. package/server/emulation-requirements.txt +8 -0
  88. package/server/emulation_adapters.py +20 -0
  89. package/server/emulation_routes.py +98 -0
  90. package/server/emulation_worker.py +351 -0
  91. package/server/engineering_artifacts.py +215 -0
  92. package/server/engineering_assets/application-runtime.json +4372 -0
  93. package/server/engineering_assets/architecture_work.schema.json +79 -0
  94. package/server/engineering_assets/materialize_runtime.py +62 -0
  95. package/server/engineering_assets/prepare_tools.py +96 -0
  96. package/server/engineering_assets/sql_validation.py +59 -0
  97. package/server/engineering_contexts.py +120 -0
  98. package/server/engineering_delivery.py +210 -0
  99. package/server/engineering_finalization.py +210 -0
  100. package/server/engineering_handoff.py +163 -0
  101. package/server/engineering_jobs.py +206 -0
  102. package/server/engineering_knowledge.py +75 -0
  103. package/server/engineering_reconciliation.py +159 -0
  104. package/server/engineering_routes.py +469 -0
  105. package/server/engineering_sessions.py +273 -0
  106. package/server/engineering_tools.py +166 -0
  107. package/server/generation_lint.py +18 -0
  108. package/server/local_providers.py +181 -0
  109. package/server/local_validation.py +236 -0
  110. package/server/memory-requirements.lock +181 -0
  111. package/server/memory-requirements.txt +6 -0
  112. package/server/memory_routes.py +82 -0
  113. package/server/memory_service.py +273 -0
  114. package/server/memory_worker.py +66 -0
  115. package/server/openai_voice.py +80 -0
  116. package/server/packaged_app.py +9 -0
  117. package/server/pricing.py +84 -0
  118. package/server/process_control_observation.py +31 -0
  119. package/server/prompts.py +123 -0
  120. package/server/public_faq_emulation_worker.py +44 -0
  121. package/server/requirements.lock +1618 -0
  122. package/server/requirements.txt +68 -0
  123. package/server/runtime_dependency_locks.py +27 -0
  124. package/server/runtime_evidence.py +93 -0
  125. package/server/settings.py +14 -0
  126. package/server/skills.py +103 -0
  127. package/server/storage_emulation_worker.py +160 -0
  128. package/server/structured_emulation_worker.py +48 -0
  129. package/server/terraform_routes.py +118 -0
  130. package/server/upstream/__init__.py +0 -0
  131. package/server/upstream/agent/__init__.py +1 -0
  132. package/server/upstream/agent/aws_architect.py +600 -0
  133. package/server/upstream/agent/config/__init__.py +1 -0
  134. package/server/upstream/agent/config/agent_config.py +82 -0
  135. package/server/upstream/agent/config/prompts.py +138 -0
  136. package/server/upstream/agent/rate_limiter.py +62 -0
  137. package/server/upstream/utils/__init__.py +1 -0
  138. package/server/upstream/utils/file_writer.py +43 -0
  139. package/server/validation_applicability.py +85 -0
  140. package/server/validation_jobs.py +252 -0
  141. package/server/validation_workers.py +48 -0
  142. package/server/voice_routes.py +91 -0
  143. package/server/voice_worker.py +26 -0
  144. package/specs/028-convergence-evaluation/evidence/20261110-ibm-invoice-native/registry-structured/qualification.json +72 -0
  145. package/specs/028-convergence-evaluation/evidence/20261110-invoice-runtime/durable-current/receipt.json +61 -0
  146. package/specs/029-general-purpose-engine/evidence/20261111-runtime-dependencies/sdk/receipt.json +22 -0
  147. package/src/ArchitectureActivity.jsx +54 -0
  148. package/src/ArchitectureWorkPanel.jsx +46 -0
  149. package/src/ArtifactChecks.jsx +49 -0
  150. package/src/BlockRequirements.jsx +13 -0
  151. package/src/BrainActivity.jsx +11 -0
  152. package/src/ChatMessage.jsx +19 -0
  153. package/src/ChatResizeHandle.jsx +6 -0
  154. package/src/CodePreview.jsx +8 -0
  155. package/src/ComponentReview.jsx +31 -0
  156. package/src/DeliveryViews.jsx +59 -0
  157. package/src/EmulationPanel.jsx +44 -0
  158. package/src/EngagementViews.jsx +385 -0
  159. package/src/EngineeringPanel.jsx +151 -0
  160. package/src/ImplementationDelivery.jsx +55 -0
  161. package/src/JourneyViews.jsx +400 -0
  162. package/src/MemoryPanel.jsx +46 -0
  163. package/src/OpenAIVoicePanel.jsx +65 -0
  164. package/src/OutputsView.jsx +65 -0
  165. package/src/TerraformView.jsx +57 -0
  166. package/src/WorkbenchDialogs.jsx +532 -0
  167. package/src/activity-presentation.mjs +28 -0
  168. package/src/ai-qualification-scenarios.mjs +31 -0
  169. package/src/approval-application-files.mjs +32 -0
  170. package/src/approval-applications.mjs +82 -0
  171. package/src/approval-spec-kit.mjs +30 -0
  172. package/src/architecture-activity.css +28 -0
  173. package/src/architecture-activity.mjs +63 -0
  174. package/src/architecture-work.mjs +176 -0
  175. package/src/artifact-work.mjs +103 -0
  176. package/src/async-documents.mjs +191 -0
  177. package/src/atflow.css +1140 -0
  178. package/src/atforma.css +748 -0
  179. package/src/atmem-tokens.css +79 -0
  180. package/src/aws-preflight.mjs +60 -0
  181. package/src/aws-public-faq.mjs +102 -0
  182. package/src/aws-validation-scenarios.mjs +10 -0
  183. package/src/aws-validation.mjs +48 -0
  184. package/src/azure-plan-mocks.mjs +25 -0
  185. package/src/azure-structured.mjs +133 -0
  186. package/src/bedrock-summary.mjs +152 -0
  187. package/src/bounded-rag.mjs +60 -0
  188. package/src/brand.css +9 -0
  189. package/src/canvas-view.mjs +28 -0
  190. package/src/capability-composition.mjs +44 -0
  191. package/src/chat-attachments.mjs +13 -0
  192. package/src/cloud-provider-locks.mjs +9 -0
  193. package/src/cloud-storage-scenarios.mjs +2 -0
  194. package/src/component-review.mjs +55 -0
  195. package/src/composition-spec-kit.mjs +30 -0
  196. package/src/consultation-context.mjs +15 -0
  197. package/src/consultation.mjs +52 -0
  198. package/src/container-contracts.mjs +5 -0
  199. package/src/container-inspection.mjs +75 -0
  200. package/src/container-runtime.mjs +30 -0
  201. package/src/container-security-inspection.mjs +49 -0
  202. package/src/container-terraform.mjs +109 -0
  203. package/src/conversation.mjs +17 -0
  204. package/src/core.mjs +863 -0
  205. package/src/cross-cloud-storage.mjs +100 -0
  206. package/src/delivery-capabilities.mjs +25 -0
  207. package/src/delivery.mjs +58 -0
  208. package/src/deployment-ownership.mjs +10 -0
  209. package/src/deployment-region.mjs +11 -0
  210. package/src/derive.mjs +31 -0
  211. package/src/design-build-flow.mjs +10 -0
  212. package/src/diagram-attention.mjs +24 -0
  213. package/src/discovery.mjs +359 -0
  214. package/src/document.mjs +170 -0
  215. package/src/emulation-feedback.mjs +61 -0
  216. package/src/emulation-guidance.mjs +37 -0
  217. package/src/engagement.mjs +274 -0
  218. package/src/engineering-client.mjs +73 -0
  219. package/src/engineering-defaults.mjs +17 -0
  220. package/src/engineering.css +31 -0
  221. package/src/enterprise-application.mjs +76 -0
  222. package/src/enterprise-aws-host-infrastructure.mjs +62 -0
  223. package/src/enterprise-aws-host-tests.mjs +15 -0
  224. package/src/enterprise-aws-infrastructure-tests.mjs +38 -0
  225. package/src/enterprise-aws-infrastructure.mjs +138 -0
  226. package/src/enterprise-google-infrastructure.mjs +84 -0
  227. package/src/enterprise-google-tests.mjs +25 -0
  228. package/src/estimate.mjs +188 -0
  229. package/src/existing-references.mjs +18 -0
  230. package/src/external-identities.mjs +17 -0
  231. package/src/faq-lambda-build.mjs +28 -0
  232. package/src/gcp-structured.mjs +104 -0
  233. package/src/generation-provider.mjs +7 -0
  234. package/src/generation-quality.mjs +69 -0
  235. package/src/handoff.mjs +141 -0
  236. package/src/ibm-registry.mjs +12 -0
  237. package/src/ibm-structured.mjs +103 -0
  238. package/src/identifiers.mjs +15 -0
  239. package/src/implementation-map.mjs +14 -0
  240. package/src/implementation-outcome.mjs +8 -0
  241. package/src/incomplete-terraform.mjs +9 -0
  242. package/src/integrations.ts +137 -0
  243. package/src/interface-contracts.mjs +30 -0
  244. package/src/invoice-application.mjs +66 -0
  245. package/src/invoice-aws-operations.mjs +136 -0
  246. package/src/invoice-aws.mjs +143 -0
  247. package/src/invoice-azure-operations.mjs +147 -0
  248. package/src/invoice-azure.mjs +134 -0
  249. package/src/invoice-builder.mjs +35 -0
  250. package/src/invoice-dependencies.mjs +43 -0
  251. package/src/invoice-gcp-operations.mjs +178 -0
  252. package/src/invoice-gcp.mjs +104 -0
  253. package/src/invoice-ibm-operations.mjs +278 -0
  254. package/src/invoice-ibm.mjs +142 -0
  255. package/src/invoice-operations.mjs +143 -0
  256. package/src/invoice-plan.mjs +45 -0
  257. package/src/invoice-region.mjs +6 -0
  258. package/src/invoice-spec-kit.mjs +61 -0
  259. package/src/invoice-state.mjs +7 -0
  260. package/src/invoice-terraform.mjs +29 -0
  261. package/src/invoice-tests.mjs +122 -0
  262. package/src/journey-workspace.css +72 -0
  263. package/src/journey.mjs +87 -0
  264. package/src/levels.mjs +145 -0
  265. package/src/local-agent.mjs +184 -0
  266. package/src/main.jsx +2458 -0
  267. package/src/model-policy-preflight.mjs +34 -0
  268. package/src/openai-voice.mjs +102 -0
  269. package/src/output-bundle.mjs +11 -0
  270. package/src/ownership-inspection.mjs +28 -0
  271. package/src/pack.mjs +201 -0
  272. package/src/pricing.mjs +31 -0
  273. package/src/production-state.mjs +8 -0
  274. package/src/project-copy.mjs +10 -0
  275. package/src/project-file.mjs +160 -0
  276. package/src/project-links.mjs +3 -0
  277. package/src/protected-state.mjs +43 -0
  278. package/src/public-faq-builder.mjs +19 -0
  279. package/src/public-faq-clouds.mjs +251 -0
  280. package/src/public-faq.mjs +122 -0
  281. package/src/pulumi.mjs +123 -0
  282. package/src/python-container.mjs +25 -0
  283. package/src/python-runtime.mjs +50 -0
  284. package/src/rag-handler.mjs +43 -0
  285. package/src/rag-web-assets.mjs +52 -0
  286. package/src/rag-web.mjs +68 -0
  287. package/src/records.mjs +177 -0
  288. package/src/resources.mjs +163 -0
  289. package/src/review.mjs +252 -0
  290. package/src/runtime/EnterpriseDocument.java +89 -0
  291. package/src/runtime/approved_http_destination.py +72 -0
  292. package/src/runtime/aws-lambda-qualification-lock.json +7 -0
  293. package/src/runtime/aws-python-lock.json +82 -0
  294. package/src/runtime/aws-web-python-lock.json +887 -0
  295. package/src/runtime/aws_json_model.py +42 -0
  296. package/src/runtime/azure-python-lock.json +863 -0
  297. package/src/runtime/azure-sources.json +8 -0
  298. package/src/runtime/azure-test-lock.json +23 -0
  299. package/src/runtime/azure_acceptance.py +192 -0
  300. package/src/runtime/azure_structured.py +153 -0
  301. package/src/runtime/bounded_rag.py +126 -0
  302. package/src/runtime/catalog_admin.py +70 -0
  303. package/src/runtime/cloud-sql-proxy-lock.json +8 -0
  304. package/src/runtime/cloud-sql-proxy-security-review.json +30 -0
  305. package/src/runtime/connector_cli.py +50 -0
  306. package/src/runtime/connector_sync.py +70 -0
  307. package/src/runtime/durable-python-lock.json +247 -0
  308. package/src/runtime/durable_workflow.py +325 -0
  309. package/src/runtime/enterprise-application-manifest.json +110 -0
  310. package/src/runtime/enterprise-application-sources.json +266 -0
  311. package/src/runtime/enterprise-auth-python-lock.json +592 -0
  312. package/src/runtime/enterprise-aws-host-bootstrap-source.json +1 -0
  313. package/src/runtime/enterprise-aws-host-probe-source.json +1 -0
  314. package/src/runtime/enterprise-aws-task-template.json +235 -0
  315. package/src/runtime/enterprise-document-lock.json +18 -0
  316. package/src/runtime/enterprise-document.Dockerfile +15 -0
  317. package/src/runtime/enterprise-runtime-aws-python-lock.json +1115 -0
  318. package/src/runtime/enterprise-runtime-azure-python-lock.json +1106 -0
  319. package/src/runtime/enterprise-runtime-gcp-python-lock.json +1060 -0
  320. package/src/runtime/enterprise-runtime-ibm-python-lock.json +1096 -0
  321. package/src/runtime/enterprise-runtime-python-lock.json +1058 -0
  322. package/src/runtime/enterprise-runtime.Dockerfile +15 -0
  323. package/src/runtime/enterprise-web-app.js +1 -0
  324. package/src/runtime/enterprise-web-entry.js +105 -0
  325. package/src/runtime/enterprise-web-lock.json +29 -0
  326. package/src/runtime/enterprise-web-notices.txt +229 -0
  327. package/src/runtime/enterprise-web.css +4 -0
  328. package/src/runtime/enterprise-web.html +8 -0
  329. package/src/runtime/enterprise_audit.py +91 -0
  330. package/src/runtime/enterprise_audit_schema.sql +25 -0
  331. package/src/runtime/enterprise_auth_schema.sql +67 -0
  332. package/src/runtime/enterprise_auth_store.py +241 -0
  333. package/src/runtime/enterprise_aws_host_bootstrap.py +120 -0
  334. package/src/runtime/enterprise_aws_host_probe.py +105 -0
  335. package/src/runtime/enterprise_aws_hosts.py +136 -0
  336. package/src/runtime/enterprise_aws_jobs.py +300 -0
  337. package/src/runtime/enterprise_aws_objects.py +93 -0
  338. package/src/runtime/enterprise_aws_transfer.py +83 -0
  339. package/src/runtime/enterprise_azure_batch_api.py +128 -0
  340. package/src/runtime/enterprise_azure_host.py +599 -0
  341. package/src/runtime/enterprise_azure_jobs.py +294 -0
  342. package/src/runtime/enterprise_azure_objects.py +234 -0
  343. package/src/runtime/enterprise_azure_pool.py +161 -0
  344. package/src/runtime/enterprise_azure_transfer.py +122 -0
  345. package/src/runtime/enterprise_configuration.py +136 -0
  346. package/src/runtime/enterprise_document.py +116 -0
  347. package/src/runtime/enterprise_document_cli.py +32 -0
  348. package/src/runtime/enterprise_document_contract.py +54 -0
  349. package/src/runtime/enterprise_document_job.py +109 -0
  350. package/src/runtime/enterprise_document_jobs.py +122 -0
  351. package/src/runtime/enterprise_document_jobs_schema.sql +23 -0
  352. package/src/runtime/enterprise_extraction_cache.py +186 -0
  353. package/src/runtime/enterprise_extraction_schema.sql +17 -0
  354. package/src/runtime/enterprise_google_jobs.py +275 -0
  355. package/src/runtime/enterprise_google_transfer.py +85 -0
  356. package/src/runtime/enterprise_http.py +212 -0
  357. package/src/runtime/enterprise_identity.py +52 -0
  358. package/src/runtime/enterprise_knowledge.py +152 -0
  359. package/src/runtime/enterprise_models.py +94 -0
  360. package/src/runtime/enterprise_native.py +181 -0
  361. package/src/runtime/enterprise_oauth.py +132 -0
  362. package/src/runtime/enterprise_operator.py +160 -0
  363. package/src/runtime/enterprise_parser_service.py +136 -0
  364. package/src/runtime/enterprise_parser_transport.py +68 -0
  365. package/src/runtime/enterprise_queries.py +255 -0
  366. package/src/runtime/enterprise_queries_schema.sql +27 -0
  367. package/src/runtime/enterprise_query_service.py +111 -0
  368. package/src/runtime/enterprise_secrets.py +110 -0
  369. package/src/runtime/enterprise_sessions.py +95 -0
  370. package/src/runtime/enterprise_sources.py +260 -0
  371. package/src/runtime/enterprise_web.py +39 -0
  372. package/src/runtime/enterprise_worker.py +68 -0
  373. package/src/runtime/export_contract_acceptance.py +18 -0
  374. package/src/runtime/faq-output-contract.json +40 -0
  375. package/src/runtime/faq-web-app.js +41 -0
  376. package/src/runtime/faq-web.css +1 -0
  377. package/src/runtime/faq-web.html +6 -0
  378. package/src/runtime/gcp-sources.json +9 -0
  379. package/src/runtime/gcp_acceptance.py +110 -0
  380. package/src/runtime/gcp_structured.py +150 -0
  381. package/src/runtime/http_acceptance.py +39 -0
  382. package/src/runtime/iap-web-app.js +36 -0
  383. package/src/runtime/iap-web-source.json +1 -0
  384. package/src/runtime/ibm-python-lock.json +856 -0
  385. package/src/runtime/ibm-sources.json +9 -0
  386. package/src/runtime/ibm_acceptance.py +133 -0
  387. package/src/runtime/ibm_structured.py +113 -0
  388. package/src/runtime/invoice-azapi-provider-lock.json +7 -0
  389. package/src/runtime/invoice-ibm-provider-lock.json +7 -0
  390. package/src/runtime/invoice-parser-lock.json +1199 -0
  391. package/src/runtime/invoice-parser-python-lock.json +56 -0
  392. package/src/runtime/invoice-parser.Dockerfile +190 -0
  393. package/src/runtime/invoice-sources.json +58 -0
  394. package/src/runtime/invoice-test-sources.json +24 -0
  395. package/src/runtime/invoice-web.css +2 -0
  396. package/src/runtime/invoice-web.html +12 -0
  397. package/src/runtime/invoice-web.js +112 -0
  398. package/src/runtime/invoice_configuration.py +17 -0
  399. package/src/runtime/invoice_document.py +95 -0
  400. package/src/runtime/invoice_extraction.py +96 -0
  401. package/src/runtime/invoice_http.py +129 -0
  402. package/src/runtime/invoice_identity.py +5 -0
  403. package/src/runtime/invoice_maintenance.py +40 -0
  404. package/src/runtime/invoice_native.py +77 -0
  405. package/src/runtime/invoice_object_store.py +153 -0
  406. package/src/runtime/invoice_operations.py +108 -0
  407. package/src/runtime/invoice_operator.py +79 -0
  408. package/src/runtime/invoice_operator_core.py +39 -0
  409. package/src/runtime/invoice_operator_job.py +147 -0
  410. package/src/runtime/invoice_parser_cli.py +17 -0
  411. package/src/runtime/invoice_parser_identity.py +39 -0
  412. package/src/runtime/invoice_parser_service.py +89 -0
  413. package/src/runtime/invoice_parser_transport.py +89 -0
  414. package/src/runtime/invoice_processing.py +24 -0
  415. package/src/runtime/invoice_retention.py +16 -0
  416. package/src/runtime/invoice_runtime_secrets.py +43 -0
  417. package/src/runtime/invoice_stage.py +80 -0
  418. package/src/runtime/invoice_web.py +39 -0
  419. package/src/runtime/invoice_worker.py +50 -0
  420. package/src/runtime/lambda_faq.py +5 -0
  421. package/src/runtime/parser-requirements.lock +4 -0
  422. package/src/runtime/policy_review.py +18 -0
  423. package/src/runtime/postgres-qualification-lock.json +21 -0
  424. package/src/runtime/public-faq-sources.json +22 -0
  425. package/src/runtime/public_catalog_admin.py +122 -0
  426. package/src/runtime/public_catalog_store.py +125 -0
  427. package/src/runtime/public_faq.py +144 -0
  428. package/src/runtime/public_faq_app.py +49 -0
  429. package/src/runtime/python-container-lock.json +18 -0
  430. package/src/runtime/rag-sources.json +10 -0
  431. package/src/runtime/rag_acceptance.py +60 -0
  432. package/src/runtime/rag_aws_adapter.py +95 -0
  433. package/src/runtime/rds-ca-source.json +9 -0
  434. package/src/runtime/rds-global-ca.pem +2888 -0
  435. package/src/runtime/state-contract-check-source.json +4 -0
  436. package/src/runtime/state_contract_check.py +338 -0
  437. package/src/runtime/structured-source.json +1 -0
  438. package/src/runtime/structured-web-app.js +71 -0
  439. package/src/runtime/structured-web-source.json +1 -0
  440. package/src/runtime/structured_http.py +105 -0
  441. package/src/runtime/structured_output.py +48 -0
  442. package/src/runtime/web-python-lock.json +802 -0
  443. package/src/runtime/web_user_admin.py +44 -0
  444. package/src/runtime/workflow_application.py +62 -0
  445. package/src/runtime/workflow_bindings.py +128 -0
  446. package/src/runtime/workflow_configuration.py +40 -0
  447. package/src/runtime/workflow_connection.py +105 -0
  448. package/src/runtime/workflow_contract.py +54 -0
  449. package/src/runtime/workflow_database.py +94 -0
  450. package/src/runtime/workflow_errors.py +6 -0
  451. package/src/runtime/workflow_http.py +178 -0
  452. package/src/runtime/workflow_identity.py +69 -0
  453. package/src/runtime/workflow_payload.py +118 -0
  454. package/src/runtime/workflow_process_stage.py +88 -0
  455. package/src/runtime/workflow_profile.py +91 -0
  456. package/src/runtime/workflow_review.py +106 -0
  457. package/src/runtime/workflow_role_bootstrap.py +104 -0
  458. package/src/runtime/workflow_schema.sql +72 -0
  459. package/src/runtime/workflow_worker.py +142 -0
  460. package/src/runtime-settings.mjs +15 -0
  461. package/src/s3-access.mjs +14 -0
  462. package/src/source-contracts.mjs +17 -0
  463. package/src/spec-kit.mjs +186 -0
  464. package/src/state-custody.mjs +32 -0
  465. package/src/state-inspection.mjs +45 -0
  466. package/src/state-spec-kit.mjs +13 -0
  467. package/src/storage-tests.mjs +25 -0
  468. package/src/structured-api.mjs +133 -0
  469. package/src/structured-contract.mjs +19 -0
  470. package/src/structured-operation-policy.mjs +2 -0
  471. package/src/structured-web.mjs +17 -0
  472. package/src/style.css +1 -0
  473. package/src/system-description.mjs +26 -0
  474. package/src/terraform-admission.mjs +66 -0
  475. package/src/terraform-composition.mjs +83 -0
  476. package/src/terraform-expressions.mjs +46 -0
  477. package/src/terraform-validation-coverage.mjs +11 -0
  478. package/src/terraform.mjs +128 -0
  479. package/src/text-statistics.mjs +51 -0
  480. package/src/upstream/openarchflow/format-payload.ts +26 -0
  481. package/src/upstream/openarchflow/import/mermaid.ts +205 -0
  482. package/src/upstream/openarchflow/import/sql-ddl.ts +109 -0
  483. package/src/upstream/openarchflow/layout.ts +54 -0
  484. package/src/upstream/openarchflow/providers/aws.ts +196 -0
  485. package/src/upstream/openarchflow/providers/azure.ts +126 -0
  486. package/src/upstream/openarchflow/providers/cloudnative.ts +71 -0
  487. package/src/upstream/openarchflow/providers/generic.ts +227 -0
  488. package/src/upstream/openarchflow/providers/integration.ts +22 -0
  489. package/src/upstream/openarchflow/providers/types.ts +2 -0
  490. package/src/upstream/openarchflow/templates.ts +937 -0
  491. package/src/upstream-catalog.mjs +10 -0
  492. package/src/useChatScroll.jsx +36 -0
  493. package/src/useEmulation.jsx +36 -0
  494. package/src/validation-receipt.mjs +10 -0
  495. package/src/visual-export.mjs +92 -0
  496. package/src/voice-conversation.mjs +117 -0
  497. package/src/work-intake.mjs +24 -0
  498. package/src/workflow-runtime-inventory.mjs +3 -0
@@ -0,0 +1,160 @@
1
+ """Owned SQL bootstrap/enrollment/revocation; never an HTTP user operation.
2
+
3
+ Existing company/source IDs come from the approved directory/IT handoff. Do not
4
+ self-activate a source mapping by matching email strings or unverified JWT claims.
5
+ No cloud identity, database, role, replacement key or source registration is created.
6
+ """
7
+ from pathlib import Path
8
+ import sys
9
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
10
+ import hashlib
11
+ import json
12
+ import os
13
+ from psycopg.rows import dict_row
14
+ import psycopg
15
+ from enterprise_configuration import deployment,read_deployment,native_database,session_domain
16
+ from enterprise_auth_store import initialize_auth_store,grant_auth_runtime
17
+ from enterprise_audit import initialize_audit,grant_audit_writer
18
+ from enterprise_extraction_cache import initialize_extraction_cache,grant_extraction_runtime
19
+ from enterprise_queries import initialize_query_store,grant_query_runtime
20
+ from enterprise_document_jobs import initialize_document_jobs,grant_document_jobs_runtime
21
+ from enterprise_secrets import SecretFiles
22
+ from public_faq import object_pairs
23
+
24
+
25
+ def initialize(conn,api_role,worker_role):
26
+ if conn.autocommit or not isinstance(api_role,str)or not isinstance(worker_role,str)or api_role==worker_role:
27
+ raise ValueError('Use an operator transaction and separate existing API/worker SQL identities')
28
+ with conn.transaction():
29
+ existing=conn.execute("SELECT count(*)FROM pg_namespace WHERE nspname IN('enterprise_auth','enterprise_queries','enterprise_extractions','enterprise_audit','enterprise_document_jobs')").fetchone()[0]
30
+ if existing:raise ValueError('Private schemas already exist; no adoption or implicit migration is permitted')
31
+ initialize_auth_store(conn);initialize_query_store(conn);initialize_extraction_cache(conn);initialize_audit(conn);initialize_document_jobs(conn)
32
+ grant_auth_runtime(conn,api_role);grant_query_runtime(conn,api_role);grant_audit_writer(conn,api_role)
33
+ grant_auth_runtime(conn,worker_role);grant_query_runtime(conn,worker_role,worker=True)
34
+ grant_extraction_runtime(conn,worker_role);grant_audit_writer(conn,worker_role);grant_document_jobs_runtime(conn,worker_role)
35
+
36
+
37
+ def checked_identity(metadata,subject):
38
+ owned=deployment(metadata)
39
+ if not isinstance(subject,str)or not 1<=len(subject.encode('utf-8'))<=255 or any(ord(c)<32 for c in subject):
40
+ raise ValueError('Supply the existing verified company API subject from the approved directory handoff')
41
+ key=hashlib.sha256((owned['company']['issuer']+'\0'+subject).encode()).hexdigest()
42
+ return owned,key
43
+
44
+
45
+ def require_schema(cursor):
46
+ for schema,filename in [('enterprise_auth','enterprise_auth_schema.sql'),('enterprise_queries','enterprise_queries_schema.sql')]:
47
+ observed=cursor.execute('SELECT version,source_sha256 FROM '+schema+'.schema_version').fetchone()
48
+ expected=hashlib.sha256(Path(__file__).with_name(filename).read_bytes()).hexdigest()
49
+ if observed!={'version':1,'source_sha256':expected}:raise ValueError('Operator schema differs; use the reviewed lifecycle')
50
+
51
+
52
+ def journal(cursor,account,key,action,active,alias=None,version=None,registration_version=None,domain=None):
53
+ cursor.execute('INSERT INTO enterprise_auth.administration_events(account_id,principal_key,action,active,source_alias,mapping_version,registration_version,registration_domain)VALUES(%s,%s,%s,%s,%s,%s,%s,%s)',(account,key,action,active,alias,version,registration_version,domain))
54
+
55
+
56
+ def cancel_requests(cursor,account,key=None):
57
+ suffix=' AND principal_key=%s'if key is not None else''
58
+ rows=cursor.execute("UPDATE enterprise_queries.requests SET state='cancelled',question_ciphertext=NULL,result_ciphertext=NULL,lease_token=NULL,lease_until=NULL,error_code=NULL,updated_at=clock_timestamp()WHERE account_id=%s AND state IN('queued','running','ready')"+suffix+' RETURNING id,principal_key,attempt',(account,key)if key is not None else(account,)).fetchall()
59
+ for row in rows:cursor.execute("INSERT INTO enterprise_queries.events(query_id,account_id,principal_key,event,attempt)VALUES(%s,%s,%s,'cancelled',%s)",(row['id'],account,row['principal_key'],row['attempt']))
60
+ return rows
61
+
62
+
63
+ def activate(conn,metadata):
64
+ owned=deployment(metadata);account=owned['accountId'];domain=session_domain(owned)
65
+ if conn.autocommit:raise ValueError('Use an operator transaction')
66
+ with conn.transaction(),conn.cursor(row_factory=dict_row)as cursor:
67
+ require_schema(cursor)
68
+ cursor.execute('SELECT pg_advisory_xact_lock(hashtextextended(%s,0))',('enterprise-company:'+account,))
69
+ row=cursor.execute('SELECT session_domain,version FROM enterprise_auth.company_bindings WHERE account_id=%s FOR UPDATE',(account,)).fetchone()
70
+ if row and row['session_domain']==domain:return {'changed':False,'registrationVersion':row['version']}
71
+ version=row['version']+1 if row else 1
72
+ cursor.execute('INSERT INTO enterprise_auth.company_bindings(account_id,session_domain,version)VALUES(%s,%s,%s)ON CONFLICT(account_id)DO UPDATE SET session_domain=EXCLUDED.session_domain,version=EXCLUDED.version,actor=current_user,updated_at=clock_timestamp()',(account,domain,version))
73
+ cursor.execute('UPDATE enterprise_auth.sessions SET revoked_at=clock_timestamp()WHERE account_id=%s AND revoked_at IS NULL',(account,))
74
+ cursor.execute('DELETE FROM enterprise_auth.attempts WHERE account_id=%s',(account,))
75
+ cancel_requests(cursor,account)
76
+ journal(cursor,account,None,'company_registration_updated',True,registration_version=version,domain=domain)
77
+ return {'changed':True,'registrationVersion':version}
78
+
79
+
80
+ def require_company(cursor,owned):
81
+ row=cursor.execute('SELECT session_domain FROM enterprise_auth.company_bindings WHERE account_id=%s FOR SHARE',(owned['accountId'],)).fetchone()
82
+ if not row or row['session_domain']!=session_domain(owned):raise ValueError('Activate the owned company registration before changing directory membership')
83
+
84
+
85
+ def reset_source(cursor,account,key,source):
86
+ cursor.execute("UPDATE enterprise_auth.connections SET credential_ciphertext=NULL,status='revoked',revision=revision+1,updated_at=clock_timestamp()WHERE account_id=%s AND principal_key=%s AND source_id=%s",(account,key,source))
87
+ cursor.execute('DELETE FROM enterprise_auth.attempts WHERE account_id=%s AND principal_key=%s AND source_id=%s',(account,key,source))
88
+
89
+
90
+ def enroll(conn,metadata,subject,mappings):
91
+ owned,key=checked_identity(metadata,subject)
92
+ if conn.autocommit or not isinstance(mappings,dict)or set(mappings)!={'sharepoint','confluence'}or any(not isinstance(v,str)or not 1<=len(v.encode('utf-8'))<=255 or any(ord(c)<32 for c in v)for v in mappings.values()):
93
+ raise ValueError('Supply both approved native source-account mappings in an operator transaction')
94
+ account=owned['accountId'];sources={'sharepoint':'sharepoint:'+owned['sources']['sharepoint']['driveId'],'confluence':'confluence:'+owned['sources']['confluence']['cloudId']}
95
+ with conn.transaction(),conn.cursor(row_factory=dict_row)as cursor:
96
+ require_schema(cursor)
97
+ require_company(cursor,owned)
98
+ cursor.execute('SELECT pg_advisory_xact_lock(hashtextextended(%s,0))',('enterprise-provision:'+account+':'+key,))
99
+ current=cursor.execute('SELECT issuer,subject,active FROM enterprise_auth.memberships WHERE account_id=%s AND principal_key=%s FOR UPDATE',(account,key)).fetchone()
100
+ if current and(current['issuer']!=owned['company']['issuer']or current['subject']!=subject):raise ValueError('Existing company identity binding differs')
101
+ if not current or not current['active']:
102
+ cursor.execute('INSERT INTO enterprise_auth.memberships(account_id,principal_key,issuer,subject,active)VALUES(%s,%s,%s,%s,true)ON CONFLICT(account_id,principal_key)DO UPDATE SET active=true',(account,key,owned['company']['issuer'],subject))
103
+ journal(cursor,account,key,'membership_enabled',True)
104
+ changed=0
105
+ for alias,source in sources.items():
106
+ old=cursor.execute('SELECT source_subject,version,active FROM enterprise_auth.source_mappings WHERE account_id=%s AND principal_key=%s AND source_id=%s FOR UPDATE',(account,key,source)).fetchone()
107
+ if old and old['active']and old['source_subject']==mappings[alias]:continue
108
+ version=old['version']+1 if old else 1
109
+ cursor.execute('INSERT INTO enterprise_auth.source_mappings(account_id,principal_key,source_id,source_subject,version,active)VALUES(%s,%s,%s,%s,%s,true)ON CONFLICT(account_id,principal_key,source_id)DO UPDATE SET source_subject=EXCLUDED.source_subject,version=EXCLUDED.version,active=true',(account,key,source,mappings[alias],version))
110
+ reset_source(cursor,account,key,source)
111
+ journal(cursor,account,key,'mapping_updated',True,alias,version);changed+=1
112
+ return {'principalKey':key,'active':True,'mappingsChanged':changed}
113
+
114
+
115
+ def revoke(conn,metadata,subject):
116
+ owned,key=checked_identity(metadata,subject)
117
+ if conn.autocommit:raise ValueError('Use an operator transaction')
118
+ account=owned['accountId']
119
+ with conn.transaction(),conn.cursor(row_factory=dict_row)as cursor:
120
+ require_schema(cursor)
121
+ require_company(cursor,owned)
122
+ cursor.execute('SELECT pg_advisory_xact_lock(hashtextextended(%s,0))',('enterprise-provision:'+account+':'+key,))
123
+ member=cursor.execute('SELECT active FROM enterprise_auth.memberships WHERE account_id=%s AND principal_key=%s FOR UPDATE',(account,key)).fetchone()
124
+ if not member:return {'principalKey':key,'active':False,'changed':False}
125
+ cursor.execute('UPDATE enterprise_auth.memberships SET active=false WHERE account_id=%s AND principal_key=%s',(account,key))
126
+ mappings=cursor.execute('UPDATE enterprise_auth.source_mappings SET active=false,version=version+1 WHERE account_id=%s AND principal_key=%s AND active RETURNING source_id,version',(account,key)).fetchall()
127
+ sources={'sharepoint:'+owned['sources']['sharepoint']['driveId']:'sharepoint','confluence:'+owned['sources']['confluence']['cloudId']:'confluence'}
128
+ for row in mappings:
129
+ reset_source(cursor,account,key,row['source_id'])
130
+ if row['source_id']in sources:journal(cursor,account,key,'mapping_updated',False,sources[row['source_id']],row['version'])
131
+ # Also erase any older/inactive registration's remaining credential.
132
+ cursor.execute("UPDATE enterprise_auth.connections SET credential_ciphertext=NULL,status='revoked',revision=revision+1,updated_at=clock_timestamp()WHERE account_id=%s AND principal_key=%s AND(status<>'revoked'OR credential_ciphertext IS NOT NULL)",(account,key))
133
+ cursor.execute('UPDATE enterprise_auth.sessions SET revoked_at=clock_timestamp()WHERE account_id=%s AND principal_key=%s AND revoked_at IS NULL',(account,key))
134
+ cursor.execute('DELETE FROM enterprise_auth.attempts WHERE account_id=%s AND principal_key=%s',(account,key))
135
+ requests=cancel_requests(cursor,account,key)
136
+ if member['active']:journal(cursor,account,key,'membership_disabled',False)
137
+ return {'principalKey':key,'active':False,'changed':bool(member['active']or mappings or requests)}
138
+
139
+
140
+ def main():
141
+ if sys.argv[1:]:raise SystemExit('Supply the owned operator command as private JSON on stdin')
142
+ raw=sys.stdin.buffer.read(32769)
143
+ if len(raw)>32768:raise ValueError('Operator command exceeds its boundary')
144
+ command=json.loads(raw,object_pairs_hook=object_pairs)
145
+ if not isinstance(command,dict):raise ValueError('Use an owned operator command')
146
+ mode=command.get('operation')
147
+ expected={'initialize':{'operation','apiRole','workerRole'},'activate':{'operation'},'enroll':{'operation','subject','mappings'},'revoke':{'operation','subject'}}
148
+ if mode not in expected or set(command)!=expected[mode]:raise ValueError('Invalid owned operator command')
149
+ owned=read_deployment()
150
+ with SecretFiles(owned['provider'],os.environ,'operator')as secrets:
151
+ database=native_database(owned['provider'],secrets.env,'migration',secrets)
152
+ with psycopg.connect(database(),connect_timeout=5)as conn,conn.transaction():
153
+ if mode=='initialize':initialize(conn,command['apiRole'],command['workerRole']);activate(conn,owned);result={'initialized':True}
154
+ elif mode=='activate':result=activate(conn,owned)
155
+ elif mode=='enroll':result=enroll(conn,owned,command['subject'],command['mappings'])
156
+ else:result=revoke(conn,owned,command['subject'])
157
+ print(json.dumps(result))
158
+
159
+
160
+ if __name__=='__main__':main()
@@ -0,0 +1,136 @@
1
+ """Private byte-only company-document RPC; credentialless parser workload.
2
+
3
+ Network admission, TLS and egress isolation are mandatory infrastructure controls.
4
+ The service accepts no URL, model input, parser option or company/source identity.
5
+ """
6
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ import socket
11
+ import sys
12
+ import threading
13
+ import time
14
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
15
+ from enterprise_document import ExtractionError, MAX_INPUT, media_type
16
+ from enterprise_document_cli import installed_parser
17
+
18
+
19
+ class DocumentServer(ThreadingHTTPServer):
20
+ daemon_threads = True
21
+ request_queue_size = 16
22
+
23
+ def __init__(self, address, parser, authorize):
24
+ if not callable(getattr(parser, 'extract', None)) or not callable(authorize):
25
+ raise ValueError('Supply the owned parser and explicit caller admission adapter')
26
+ self.parser, self.authorize = parser, authorize
27
+ self.slot = threading.BoundedSemaphore(1)
28
+ super().__init__(address, DocumentHandler)
29
+
30
+
31
+ class DocumentHandler(BaseHTTPRequestHandler):
32
+ protocol_version = 'HTTP/1.0'
33
+
34
+ def log_message(self, *args):
35
+ pass
36
+
37
+ def setup(self):
38
+ super().setup()
39
+ self.connection.settimeout(5)
40
+
41
+ def reply(self, status, value):
42
+ body = json.dumps(value, ensure_ascii=False, allow_nan=False).encode()
43
+ self.send_response(status)
44
+ for key, value in [('Content-Type', 'application/json'), ('Content-Length', str(len(body))),
45
+ ('Cache-Control', 'no-store'), ('X-Content-Type-Options', 'nosniff')]:
46
+ self.send_header(key, value)
47
+ self.end_headers()
48
+ self.wfile.write(body)
49
+
50
+ def do_GET(self):
51
+ self.reply(200, {'status': 'running'}) if self.path == '/healthz' else self.reply(404, {'status': 'rejected'})
52
+
53
+ def do_POST(self):
54
+ if self.path != '/extract':
55
+ self.reply(404, {'status': 'rejected'})
56
+ return
57
+ try:
58
+ self.server.authorize(self.headers.get_all('Authorization') or [])
59
+ except Exception:
60
+ self.reply(403, {'status': 'rejected', 'reason': 'access_denied'})
61
+ return
62
+ lengths = self.headers.get_all('Content-Length') or []
63
+ types = self.headers.get_all('Content-Type') or []
64
+ if self.headers.get('Transfer-Encoding') or len(lengths) != 1 or not lengths[0].isascii() or not lengths[0].isdigit():
65
+ self.reply(411, {'status': 'rejected', 'reason': 'invalid_request'})
66
+ return
67
+ if len(lengths[0]) > 10 or not 1 <= int(lengths[0]) <= MAX_INPUT:
68
+ self.reply(413, {'status': 'rejected', 'reason': 'document_too_large'})
69
+ return
70
+ try:
71
+ if len(types) != 1:
72
+ raise ExtractionError('invalid_document')
73
+ declared = media_type(types[0])
74
+ except ExtractionError:
75
+ self.reply(415, {'status': 'rejected', 'reason': 'invalid_document'})
76
+ return
77
+ if not self.server.slot.acquire(blocking=False):
78
+ self.reply(429, {'status': 'rejected', 'reason': 'busy'})
79
+ return
80
+ try:
81
+ remaining, body, deadline = int(lengths[0]), bytearray(), time.monotonic() + 15
82
+ while remaining:
83
+ left = deadline - time.monotonic()
84
+ if left <= 0:
85
+ raise TimeoutError()
86
+ self.connection.settimeout(min(5, left))
87
+ chunk = self.rfile.read1(min(remaining, 65536))
88
+ if not chunk:
89
+ raise ValueError()
90
+ body.extend(chunk)
91
+ remaining -= len(chunk)
92
+ self.connection.settimeout(5)
93
+ document = self.server.parser.extract(bytes(body), declared)
94
+ self.reply(200, {'status': 'ok', 'document': document})
95
+ except ExtractionError as failure:
96
+ self.reply(422, {'status': 'rejected', 'reason': failure.code})
97
+ except (TimeoutError, socket.timeout):
98
+ self.reply(408, {'status': 'rejected', 'reason': 'upload_timeout'})
99
+ except ValueError:
100
+ self.reply(400, {'status': 'rejected', 'reason': 'invalid_request'})
101
+ except Exception:
102
+ self.reply(503, {'status': 'rejected', 'reason': 'extraction_unavailable'})
103
+ finally:
104
+ self.server.slot.release()
105
+
106
+
107
+ def main():
108
+ forbidden = ['DATABASE_PASSWORD', 'DATABASE_PASSWORD_FILE', 'DATABASE_HOST', 'DATABASE_CONNECTION_NAME',
109
+ 'DESTINATION_TOKEN_FILE', 'DESTINATION_ENDPOINT', 'GOOGLE_APPLICATION_CREDENTIALS', 'AWS_ACCESS_KEY_ID',
110
+ 'AWS_SECRET_ACCESS_KEY', 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI', 'AWS_CONTAINER_CREDENTIALS_FULL_URI',
111
+ 'IDENTITY_ENDPOINT', 'IDENTITY_HEADER', 'MSI_ENDPOINT', 'MSI_SECRET', 'MSAL_CLIENT_SECRET', 'CONFLUENCE_CLIENT_SECRET']
112
+ if any(os.environ.get(key) for key in forbidden) or Path('/var/run/secrets/codeengine.cloud.ibm.com/compute-resource-token/token').exists():
113
+ raise ValueError('Do not supply source, database, model or workload credentials to the parser')
114
+ mode = os.environ.get('PARSER_AUTH_MODE', '')
115
+ if mode == 'shared-token':
116
+ from invoice_parser_identity import SharedParserVerifier
117
+ verify = SharedParserVerifier(os.environ.pop('PARSER_SHARED_TOKEN', ''))
118
+ def authorize(headers):
119
+ if len(headers) != 1 or not headers[0].startswith('Bearer '):
120
+ raise PermissionError()
121
+ verify(headers[0][7:])
122
+ elif mode == 'private-ingress':
123
+ # Explicitly selected only when the native hosting boundary authenticates
124
+ # and restricts callers. An arbitrary request header is never that proof.
125
+ def authorize(headers):
126
+ return None
127
+ else:
128
+ raise ValueError('Select explicit owned parser admission')
129
+ port = int(os.environ.get('PORT', '8080'))
130
+ if not 1024 <= port <= 65535:
131
+ raise ValueError('Use an owned nonprivileged parser port')
132
+ DocumentServer(('0.0.0.0', port), installed_parser(), authorize).serve_forever(poll_interval=.2)
133
+
134
+
135
+ if __name__ == '__main__':
136
+ main()
@@ -0,0 +1,68 @@
1
+ """Application-side fixed private HTTPS document parser adapter."""
2
+ import json
3
+ import time
4
+ from urllib.parse import urlsplit
5
+ import requests
6
+ from enterprise_document_contract import MAX_INPUT, document_result, media_type
7
+ from enterprise_sources import SourceUnavailable
8
+
9
+
10
+ def unique_object(pairs):
11
+ result = {}
12
+ for key, value in pairs:
13
+ if key in result:
14
+ raise ValueError('Duplicate response field')
15
+ result[key] = value
16
+ return result
17
+
18
+
19
+ class DocumentClient:
20
+ def __init__(self, endpoint, authorization, *, session=None):
21
+ try:
22
+ target = urlsplit(endpoint)
23
+ if (not isinstance(endpoint, str) or len(endpoint) > 2000 or target.scheme != 'https'
24
+ or not target.hostname or target.path != '/extract' or target.query or target.fragment
25
+ or target.username or target.password or (target.port is not None and not 1 <= target.port <= 65535)
26
+ or any(ord(c) < 33 or ord(c) > 126 for c in endpoint)):
27
+ raise ValueError()
28
+ except (TypeError, ValueError):
29
+ raise ValueError('Supply the fixed private HTTPS /extract endpoint') from None
30
+ if authorization is not None and not callable(authorization):
31
+ raise ValueError('Supply an owned workload admission adapter')
32
+ self.endpoint, self.authorization = endpoint, authorization
33
+ # No shared session mutation during parallel source extraction.
34
+ self.session = session
35
+
36
+ def __call__(self, raw, declared):
37
+ if not isinstance(raw, bytes) or not 0 < len(raw) <= MAX_INPUT:
38
+ raise SourceUnavailable('source_document_unsupported')
39
+ session = self.session or requests.Session()
40
+ session.trust_env = False
41
+ try:
42
+ headers = {'Content-Type': media_type(declared)}
43
+ if self.authorization is not None:
44
+ token = self.authorization()
45
+ if not isinstance(token, str) or not token.startswith('Bearer ') or not 8 <= len(token) <= 20000 or any(ord(c) < 33 or ord(c) > 126 for c in token[7:]):
46
+ raise ValueError('Invalid caller admission')
47
+ headers['Authorization'] = token
48
+ deadline = time.monotonic() + 125
49
+ with session.post(self.endpoint, data=raw, headers=headers, timeout=(2, 115), allow_redirects=False, stream=True) as response:
50
+ if response.status_code != 200:
51
+ raise SourceUnavailable('document_extraction_unavailable')
52
+ content = bytearray()
53
+ for chunk in response.iter_content(1):
54
+ content.extend(chunk)
55
+ if len(content) > 1201000 or time.monotonic() > deadline:
56
+ raise ValueError('Unconfirmed extraction boundary')
57
+ value = json.loads(content, object_pairs_hook=unique_object,
58
+ parse_constant=lambda _: (_ for _ in ()).throw(ValueError('Nonfinite output')))
59
+ if not isinstance(value, dict) or set(value) != {'status', 'document'} or value['status'] != 'ok':
60
+ raise ValueError('Unconfirmed extraction result')
61
+ return document_result(value['document'], raw, declared)['text']
62
+ except SourceUnavailable:
63
+ raise
64
+ except Exception:
65
+ raise SourceUnavailable('document_extraction_unavailable') from None
66
+ finally:
67
+ if self.session is None:
68
+ session.close()
@@ -0,0 +1,255 @@
1
+ """Encrypted durable question jobs; PostgreSQL/Procrastinate, no private queue args.
2
+
3
+ Raw browser cookies, JWTs, source credentials, questions and answers never enter
4
+ queue arguments. Domain leases independently fence duplicate/stale workers. A
5
+ ready result is only internal evidence until current native sources reauthorize it.
6
+ """
7
+ from contextlib import contextmanager
8
+ from dataclasses import dataclass,field
9
+ import hashlib
10
+ import json
11
+ from pathlib import Path
12
+ import re
13
+ import uuid
14
+ import procrastinate
15
+ from procrastinate.schema import SchemaManager
16
+ from psycopg import sql
17
+ from enterprise_identity import CompanyPrincipal
18
+ from enterprise_sessions import CompanySessions
19
+ from enterprise_sources import query_text,SourceDenied,SourceUnavailable
20
+
21
+ TASK='enterprise.answer.v1'
22
+ QUEUE='enterprise-answer'
23
+
24
+ def task_identity(account_id):
25
+ if not isinstance(account_id,str)or not re.fullmatch('[A-Za-z0-9_-]{1,128}',account_id):raise ValueError('Supply the owned company account')
26
+ suffix=hashlib.sha256(account_id.encode()).hexdigest()[:16]
27
+ return TASK+'.'+suffix,QUEUE+'-'+suffix
28
+
29
+
30
+ class QueryMissing(LookupError):pass
31
+ class QueryConflict(ValueError):pass
32
+ class QueryLeaseLost(SourceUnavailable):pass
33
+
34
+
35
+ def identifier(value):
36
+ if not isinstance(value,str)or str(uuid.UUID(value))!=value:raise ValueError('Use a canonical request identity')
37
+ return value
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class QueryLease:
42
+ id: str
43
+ token: str=field(repr=False)
44
+ principal: CompanyPrincipal=field(repr=False)
45
+ session_id: str=field(repr=False)
46
+ question: str=field(repr=False)
47
+ attempt: int
48
+
49
+
50
+ class EnterpriseQueryStore:
51
+ def __init__(self,auth,task,account_id,*,lease_seconds=120,ttl_seconds=900,session_domain=None):
52
+ name,queue=task_identity(account_id)
53
+ if task.name!=name or task.queue!=queue or getattr(task,'queueing_lock',None)is not None:raise ValueError('Use the owned query task without a queueing lock')
54
+ if type(lease_seconds)is not int or not 15<=lease_seconds<=600 or type(ttl_seconds)is not int or not lease_seconds<=ttl_seconds<=3600:raise ValueError('Invalid owned query deadlines')
55
+ self.auth=auth;self.task=task;self.account_id=account_id;self.lease_seconds=lease_seconds;self.ttl_seconds=ttl_seconds;self.sessions=CompanySessions(auth,domain=session_domain)
56
+
57
+ @contextmanager
58
+ def transaction(self):
59
+ with self.auth.transaction()as cur:
60
+ cur.execute('SET LOCAL search_path=pg_catalog,enterprise_queries,pg_temp')
61
+ yield cur
62
+
63
+ def context(self,row,purpose):
64
+ result=['knowledge-query',purpose,row['account_id'],row['principal_key'],row['session_sha256'],str(row['id'])]
65
+ if purpose=='answer':result.append(row['attempt'])
66
+ return result
67
+
68
+ def _principal(self,cur,session_id):
69
+ if not isinstance(session_id,str)or not re.fullmatch('[0-9a-f]{64}',session_id):raise SourceDenied('company_identity_required')
70
+ row=cur.execute('SELECT s.account_id,s.principal_key,s.nonce_ciphertext,s.csrf_sha256,extract(epoch FROM s.expires_at)::bigint AS expires_at,m.issuer,m.subject FROM enterprise_auth.sessions s JOIN enterprise_auth.memberships m USING(account_id,principal_key)WHERE s.session_sha256=%s AND s.revoked_at IS NULL AND s.expires_at>clock_timestamp()',(session_id,)).fetchone()
71
+ if not row:raise SourceDenied('company_identity_required')
72
+ principal=CompanyPrincipal(row['account_id'],row['issuer'],row['subject'],row['expires_at'])
73
+ if principal.key!=row['principal_key']or principal.account_id!=self.account_id:raise SourceDenied('company_identity_required')
74
+ self.auth._authorize(cur,principal)
75
+ self.sessions.private(row,session_id)
76
+ return principal
77
+
78
+ def _owner(self,cur,session):
79
+ principal=self._principal(cur,session.id)
80
+ if principal.account_id!=session.principal.account_id or principal.key!=session.principal.key:raise SourceDenied('company_identity_required')
81
+ return principal
82
+
83
+ def _row(self,cur,identity):
84
+ identifier(identity)
85
+ return cur.execute('SELECT *,expires_at>clock_timestamp()AS current,lease_until>clock_timestamp()AS leased FROM enterprise_queries.requests WHERE id=%s AND account_id=%s FOR UPDATE',(identity,self.account_id)).fetchone()
86
+
87
+ def _event(self,cur,row,event):
88
+ cur.execute('INSERT INTO enterprise_queries.events(query_id,account_id,principal_key,event,attempt)VALUES(%s,%s,%s,%s,%s)',(row['id'],row['account_id'],row['principal_key'],event,row['attempt']))
89
+
90
+ def _end(self,cur,row,state,error=None):
91
+ cur.execute('UPDATE enterprise_queries.requests SET state=%s,error_code=%s,question_ciphertext=NULL,result_ciphertext=NULL,lease_token=NULL,lease_until=NULL,updated_at=clock_timestamp()WHERE id=%s',(state,error,row['id']))
92
+ self._event(cur,row,state)
93
+
94
+ def _enqueue(self,cur,identity,*,delay=0):
95
+ # No execution lock: domain leases fence duplicate notifications and allow
96
+ # a replacement to recover an expired lease while an old worker winds down.
97
+ scheduled=None
98
+ if delay:
99
+ scheduled=cur.execute('SELECT clock_timestamp()+make_interval(secs=>%s)AS at',(delay,)).fetchone()['at']
100
+ self.task.configure(connection=cur.connection,schedule_at=scheduled).defer(query_id=str(identity))
101
+
102
+ def submit(self,session,question,request_key):
103
+ question=query_text(question);identifier(request_key)
104
+ with self.transaction()as cur:
105
+ principal=self._owner(cur,session)
106
+ digest=self.auth.box.fingerprint(['question',principal.account_id,principal.key,session.id,request_key],question)
107
+ cur.execute('SELECT pg_advisory_xact_lock(hashtextextended(%s,0))',('knowledge-query:'+principal.account_id+':'+principal.key,))
108
+ existing=cur.execute('SELECT * FROM enterprise_queries.requests WHERE account_id=%s AND principal_key=%s AND session_sha256=%s AND request_key=%s FOR UPDATE',(principal.account_id,principal.key,session.id,request_key)).fetchone()
109
+ if existing:
110
+ if existing['question_mac']!=digest:raise QueryConflict('Request identity already refers to another question')
111
+ return str(existing['id'])
112
+ count=cur.execute("SELECT count(*)AS n FROM enterprise_queries.requests WHERE account_id=%s AND principal_key=%s AND state IN('queued','running')AND expires_at>clock_timestamp()",(principal.account_id,principal.key)).fetchone()['n']
113
+ if count>=3:raise SourceUnavailable('query_capacity_reached')
114
+ row={'id':str(uuid.uuid4()),'account_id':principal.account_id,'principal_key':principal.key,'session_sha256':session.id,'attempt':0}
115
+ encrypted=self.auth.box.seal(self.context(row,'question'),{'question':question})
116
+ cur.execute("INSERT INTO enterprise_queries.requests(id,account_id,principal_key,session_sha256,request_key,question_mac,question_ciphertext,state,expires_at)VALUES(%s,%s,%s,%s,%s,%s,%s,'queued',least(to_timestamp(%s),clock_timestamp()+make_interval(secs=>%s)))",(row['id'],principal.account_id,principal.key,session.id,request_key,digest,encrypted,principal.expires_at,self.ttl_seconds))
117
+ self._enqueue(cur,row['id']);self._event(cur,row,'submitted')
118
+ return row['id']
119
+
120
+ def get(self,session,identity):
121
+ with self.transaction()as cur:
122
+ principal=self._owner(cur,session);row=self._row(cur,identity)
123
+ if not row or row['account_id']!=principal.account_id or row['principal_key']!=principal.key or row['session_sha256']!=session.id:raise QueryMissing('Question unavailable')
124
+ if not row['current']and row['state']!='expired':
125
+ self._end(cur,row,'expired');row.update(state='expired',question_ciphertext=None,result_ciphertext=None)
126
+ return row
127
+
128
+ def cancel(self,session,identity):
129
+ with self.transaction()as cur:
130
+ principal=self._owner(cur,session);row=self._row(cur,identity)
131
+ if not row or row['account_id']!=principal.account_id or row['principal_key']!=principal.key or row['session_sha256']!=session.id:raise QueryMissing('Question unavailable')
132
+ if row['state']not in {'cancelled','expired'}:self._end(cur,row,'cancelled')
133
+
134
+ def pending(self,session):
135
+ with self.transaction()as cur:
136
+ principal=self._owner(cur,session)
137
+ rows=cur.execute("SELECT * FROM enterprise_queries.requests WHERE account_id=%s AND principal_key=%s AND session_sha256=%s AND state IN('queued','running','ready')AND expires_at>clock_timestamp()ORDER BY created_at DESC LIMIT 3",(principal.account_id,principal.key,session.id)).fetchall()
138
+ return [{'id':str(row['id']),'status':row['state'],'question':self.auth.box.open(self.context(row,'question'),row['question_ciphertext'])['question']}for row in reversed(rows)]
139
+
140
+ def claim(self,identity):
141
+ with self.transaction()as cur:
142
+ row=self._row(cur,identity)
143
+ if not row:return None
144
+ if not row['current']:
145
+ if row['state']!='expired':self._end(cur,row,'expired')
146
+ return None
147
+ if row['state']not in {'queued','running'}or(row['state']=='running'and row['leased']):return None
148
+ try:principal=self._principal(cur,row['session_sha256'])
149
+ except SourceDenied:
150
+ self._end(cur,row,'cancelled');return None
151
+ if principal.account_id!=row['account_id']or principal.key!=row['principal_key']:self._end(cur,row,'cancelled');return None
152
+ if row['attempt']>=3:self._end(cur,row,'failed','unavailable');return None
153
+ question=self.auth.box.open(self.context(row,'question'),row['question_ciphertext'])['question'];query_text(question)
154
+ if self.auth.box.fingerprint(['question',row['account_id'],row['principal_key'],row['session_sha256'],str(row['request_key'])],question)!=row['question_mac']:raise SourceDenied('query_binding_invalid')
155
+ token=str(uuid.uuid4());row['attempt']+=1
156
+ cur.execute("UPDATE enterprise_queries.requests SET state='running',attempt=%s,lease_token=%s,lease_until=least(expires_at,clock_timestamp()+make_interval(secs=>%s)),result_ciphertext=NULL,error_code=NULL,updated_at=clock_timestamp()WHERE id=%s",(row['attempt'],token,self.lease_seconds,identity))
157
+ self._event(cur,row,'claimed')
158
+ return QueryLease(identity,token,principal,row['session_sha256'],question,row['attempt'])
159
+
160
+ def _lease(self,cur,lease):
161
+ row=self._row(cur,lease.id)
162
+ if not row or row['state']!='running'or not row['current']or not row['leased']or str(row['lease_token'])!=lease.token or row['attempt']!=lease.attempt:raise QueryLeaseLost('Question worker lease changed')
163
+ principal=self._principal(cur,row['session_sha256'])
164
+ if principal.account_id!=lease.principal.account_id or principal.key!=lease.principal.key:raise SourceDenied('company_identity_required')
165
+ return row
166
+
167
+ def guard(self,lease,*,renew=False):
168
+ with self.transaction()as cur:
169
+ row=self._lease(cur,lease)
170
+ if renew:cur.execute('UPDATE enterprise_queries.requests SET lease_until=least(expires_at,clock_timestamp()+make_interval(secs=>%s))WHERE id=%s',(self.lease_seconds,row['id']))
171
+
172
+ def complete(self,lease,receipt):
173
+ if not isinstance(receipt,dict)or receipt.get('question')!=lease.question or len(json.dumps(receipt,ensure_ascii=False,allow_nan=False).encode())>500000:raise ValueError('Invalid owned answer receipt')
174
+ with self.transaction()as cur:
175
+ row=self._lease(cur,lease);cipher=self.auth.box.seal(self.context(row,'answer'),receipt)
176
+ cur.execute("UPDATE enterprise_queries.requests SET state='ready',result_ciphertext=%s,lease_token=NULL,lease_until=NULL,updated_at=clock_timestamp()WHERE id=%s",(cipher,row['id']));self._event(cur,row,'ready')
177
+
178
+ def failed(self,lease,code,*,retry=False):
179
+ if code not in {'source','setup','signin','unavailable','answer'}:raise ValueError('Use an owned public failure code')
180
+ with self.transaction()as cur:
181
+ row=self._row(cur,lease.id)
182
+ if not row or row['state']!='running'or str(row['lease_token'])!=lease.token or row['attempt']!=lease.attempt:return
183
+ if retry and row['current']and row['attempt']<3:
184
+ cur.execute("UPDATE enterprise_queries.requests SET state='queued',lease_token=NULL,lease_until=NULL,error_code=NULL,updated_at=clock_timestamp()WHERE id=%s",(row['id'],))
185
+ self._enqueue(cur,row['id'],delay=5*row['attempt']);self._event(cur,row,'retry_pending')
186
+ else:self._end(cur,row,'failed',code)
187
+
188
+ def open_result(self,row):
189
+ if row['state']!='ready':raise QueryConflict('Question is not ready')
190
+ return self.auth.box.open(self.context(row,'answer'),row['result_ciphertext'])
191
+
192
+ def released(self,session,identity,attempt):
193
+ with self.transaction()as cur:
194
+ principal=self._owner(cur,session);row=self._row(cur,identity)
195
+ if not row or row['account_id']!=principal.account_id or row['principal_key']!=principal.key or row['session_sha256']!=session.id:raise QueryMissing('Question unavailable')
196
+ if not row['current']or row['state']!='ready'or row['attempt']!=attempt:raise QueryLeaseLost('Question changed before release')
197
+ self._event(cur,row,'released')
198
+
199
+ def requeue(self,session,identity,attempt):
200
+ with self.transaction()as cur:
201
+ principal=self._owner(cur,session);row=self._row(cur,identity)
202
+ if not row or row['account_id']!=principal.account_id or row['principal_key']!=principal.key or row['session_sha256']!=session.id:raise QueryMissing('Question unavailable')
203
+ if row['state']!='ready'or row['attempt']!=attempt:return
204
+ if not row['current']or row['attempt']>=3:self._end(cur,row,'failed','answer');return
205
+ cur.execute("UPDATE enterprise_queries.requests SET state='queued',result_ciphertext=NULL,error_code=NULL,updated_at=clock_timestamp()WHERE id=%s",(identity,));self._enqueue(cur,identity);self._event(cur,row,'retry_pending')
206
+
207
+ def recover(self,limit=50):
208
+ if type(limit)is not int or not 1<=limit<=100:raise ValueError('Use a bounded recovery batch')
209
+ with self.transaction()as cur:
210
+ rows=cur.execute("SELECT r.*,expires_at>clock_timestamp()AS current FROM enterprise_queries.requests r WHERE account_id=%s AND((state!='expired'AND expires_at<clock_timestamp())OR(state='running'AND lease_until<clock_timestamp())OR(state='queued'AND NOT EXISTS(SELECT 1 FROM enterprise_queries.procrastinate_jobs j WHERE j.task_name=%s AND j.args->>'query_id'=r.id::text AND j.status IN('todo','doing'))))ORDER BY updated_at FOR UPDATE OF r SKIP LOCKED LIMIT %s",(self.account_id,self.task.name,limit)).fetchall()
211
+ for row in rows:
212
+ if not row['current']:self._end(cur,row,'expired')
213
+ elif row['attempt']>=3:self._end(cur,row,'failed','unavailable')
214
+ else:
215
+ cur.execute("UPDATE enterprise_queries.requests SET state='queued',lease_token=NULL,lease_until=NULL,result_ciphertext=NULL,updated_at=clock_timestamp()WHERE id=%s",(row['id'],));self._enqueue(cur,row['id']);self._event(cur,row,'retry_pending')
216
+ return len(rows)
217
+
218
+
219
+ def producer_queries(auth,dsn,account_id,*,session_domain=None):
220
+ app=procrastinate.App(connector=procrastinate.SyncPsycopgConnector(conninfo=dsn,kwargs={'connect_timeout':5,'options':'-c search_path=pg_catalog,enterprise_queries,pg_temp'}))
221
+ def worker_only(query_id):raise RuntimeError('Run the owned dedicated query worker')
222
+ name,queue=task_identity(account_id)
223
+ task=app.task(name=name,queue=queue)(worker_only)
224
+ return EnterpriseQueryStore(auth,task,account_id,session_domain=session_domain)
225
+
226
+
227
+ def initialize_query_store(conn):
228
+ if procrastinate.__version__!='3.10.0' or conn.autocommit:raise ValueError('Use the reviewed queue version and an operator transaction')
229
+ body=Path(__file__).with_name('enterprise_queries_schema.sql').read_bytes()
230
+ conn.execute('CREATE SCHEMA enterprise_queries');conn.execute('SET LOCAL search_path=enterprise_queries,pg_catalog')
231
+ conn.execute(SchemaManager.get_schema());conn.execute(body.decode())
232
+ conn.execute('INSERT INTO enterprise_queries.schema_version VALUES(1,%s,%s)',(hashlib.sha256(body).hexdigest(),procrastinate.__version__))
233
+ for objects in ['TABLES','SEQUENCES','FUNCTIONS']:conn.execute('REVOKE ALL ON ALL '+objects+' IN SCHEMA enterprise_queries FROM PUBLIC')
234
+ conn.execute('REVOKE ALL ON SCHEMA enterprise_queries FROM PUBLIC')
235
+
236
+
237
+ def grant_query_runtime(conn,role,*,worker=False):
238
+ if conn.autocommit or not isinstance(role,str)or not re.fullmatch('[A-Za-z0-9_.@-]{1,63}',role)or type(worker)is not bool:raise ValueError('Use an owned runtime role and operator transaction')
239
+ expected=hashlib.sha256(Path(__file__).with_name('enterprise_queries_schema.sql').read_bytes()).hexdigest()
240
+ if conn.execute('SELECT version,source_sha256,queue_version FROM enterprise_queries.schema_version').fetchone()!=(1,expected,'3.10.0'):raise ValueError('Review the exact fresh query schema before granting access')
241
+ flags=conn.execute('SELECT rolsuper,rolcreatedb,rolcreaterole,rolreplication,rolbypassrls,rolcanlogin FROM pg_roles WHERE rolname=%s',(role,)).fetchone()
242
+ if not flags or any(flags[:5])or not flags[5]or conn.execute("SELECT has_schema_privilege(%s,'enterprise_queries','CREATE')",(role,)).fetchone()[0]:raise ValueError('Use a nonadministrative role that does not own the query schema')
243
+ for table in ['memberships','source_mappings','company_bindings','schema_version']:
244
+ if conn.execute('SELECT has_table_privilege(%s,%s,%s)',(role,'enterprise_auth.'+table,'INSERT,UPDATE,DELETE,TRUNCATE,TRIGGER,REFERENCES')).fetchone()[0]:raise ValueError('Question runtime must not inherit authority-table write privileges')
245
+ target=sql.Identifier(role)
246
+ conn.execute(sql.SQL('GRANT USAGE ON SCHEMA enterprise_queries TO {}').format(target))
247
+ conn.execute(sql.SQL('GRANT SELECT ON enterprise_queries.schema_version TO {}').format(target))
248
+ conn.execute(sql.SQL('GRANT SELECT,INSERT,UPDATE ON enterprise_queries.requests TO {}').format(target))
249
+ conn.execute(sql.SQL('GRANT INSERT ON enterprise_queries.events TO {}').format(target))
250
+ conn.execute(sql.SQL('GRANT USAGE,SELECT ON ALL SEQUENCES IN SCHEMA enterprise_queries TO {}').format(target))
251
+ conn.execute(sql.SQL('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA enterprise_queries TO {}').format(target))
252
+ conn.execute(sql.SQL('GRANT SELECT,INSERT ON enterprise_queries.procrastinate_jobs,enterprise_queries.procrastinate_events TO {}').format(target))
253
+ if worker:
254
+ for table in ['procrastinate_jobs','procrastinate_events','procrastinate_workers','procrastinate_periodic_defers']:
255
+ conn.execute(sql.SQL('GRANT SELECT,INSERT,UPDATE,DELETE ON enterprise_queries.{} TO {}').format(sql.Identifier(table),target))